Unreviewed. Fix individual benchmark description urls to go to in-depth.html instead...
[WebKit-https.git] / Websites / bugs.webkit.org / editflagtypes.cgi
1 #!/usr/bin/perl -T
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 #
6 # This Source Code Form is "Incompatible With Secondary Licenses", as
7 # defined by the Mozilla Public License, v. 2.0.
8
9 use 5.10.1;
10 use strict;
11 use warnings;
12
13 use lib qw(. lib);
14
15 use Bugzilla;
16 use Bugzilla::Constants;
17 use Bugzilla::Flag;
18 use Bugzilla::FlagType;
19 use Bugzilla::Group;
20 use Bugzilla::Util;
21 use Bugzilla::Error;
22 use Bugzilla::Product;
23 use Bugzilla::Token;
24
25 # Make sure the user is logged in and has the right privileges.
26 my $user = Bugzilla->login(LOGIN_REQUIRED);
27 my $cgi = Bugzilla->cgi;
28 my $template = Bugzilla->template;
29
30 print $cgi->header();
31
32 $user->in_group('editcomponents')
33   || scalar(@{$user->get_products_by_permission('editcomponents')})
34   || ThrowUserError("auth_failure", {group  => "editcomponents",
35                                      action => "edit",
36                                      object => "flagtypes"});
37
38 # We need this everywhere.
39 my $vars = get_products_and_components();
40 my @products = @{$vars->{products}};
41
42 my $action = $cgi->param('action') || 'list';
43 my $token  = $cgi->param('token');
44 my $prod_name = $cgi->param('product');
45 my $comp_name = $cgi->param('component');
46 my $flag_id = $cgi->param('id');
47
48 my ($product, $component);
49
50 if ($prod_name) {
51     # Make sure the user is allowed to view this product name.
52     # Users with global editcomponents privs can see all product names.
53     ($product) = grep { lc($_->name) eq lc($prod_name) } @products;
54     $product || ThrowUserError('product_access_denied', { name => $prod_name });
55 }
56
57 if ($comp_name) {
58     $product || ThrowUserError('flag_type_component_without_product');
59     ($component) = grep { lc($_->name) eq lc($comp_name) } @{$product->components};
60     $component || ThrowUserError('product_unknown_component', { product => $product->name,
61                                                                 comp => $comp_name });
62 }
63
64 # If 'categoryAction' is set, it has priority over 'action'.
65 if (my ($category_action) = grep { $_ =~ /^categoryAction-(?:\w+)$/ } $cgi->param()) {
66     $category_action =~ s/^categoryAction-//;
67
68     my @inclusions = $cgi->param('inclusions');
69     my @exclusions = $cgi->param('exclusions');
70     my @categories;
71     if ($category_action =~ /^(in|ex)clude$/) {
72         if (!$user->in_group('editcomponents') && !$product) {
73             # The user can only add the flag type to products they can administrate.
74             foreach my $prod (@products) {
75                 push(@categories, $prod->id . ':0')
76             }
77         }
78         else {
79             my $category = ($product ? $product->id : 0) . ':' .
80                            ($component ? $component->id : 0);
81             push(@categories, $category);
82         }
83     }
84
85     if ($category_action eq 'include') {
86         foreach my $category (@categories) {
87             push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
88         }
89     }
90     elsif ($category_action eq 'exclude') {
91         foreach my $category (@categories) {
92             push(@exclusions, $category) unless grep($_ eq $category, @exclusions);
93         }
94     }
95     elsif ($category_action eq 'removeInclusion') {
96         my @inclusion_to_remove = $cgi->param('inclusion_to_remove');
97         foreach my $remove (@inclusion_to_remove) {
98             @inclusions = grep { $_ ne $remove } @inclusions;
99         }
100     }
101     elsif ($category_action eq 'removeExclusion') {
102         my @exclusion_to_remove = $cgi->param('exclusion_to_remove');
103         foreach my $remove (@exclusion_to_remove) {
104             @exclusions = grep { $_ ne $remove } @exclusions;
105         }
106     }
107
108     $vars->{'groups'} = get_settable_groups();
109     $vars->{'action'} = $action;
110
111     my $type = {};
112     $type->{$_} = $cgi->param($_) foreach $cgi->param();
113     # Make sure boolean fields are defined, else they fall back to 1.
114     foreach my $boolean (qw(is_active is_requestable is_requesteeble is_multiplicable)) {
115         $type->{$boolean} ||= 0;
116     }
117
118     # That's what I call a big hack. The template expects to see a group object.
119     $type->{'grant_group'} = {};
120     $type->{'grant_group'}->{'name'} = $cgi->param('grant_group');
121     $type->{'request_group'} = {};
122     $type->{'request_group'}->{'name'} = $cgi->param('request_group');
123
124     $vars->{'inclusions'} = clusion_array_to_hash(\@inclusions, \@products);
125     $vars->{'exclusions'} = clusion_array_to_hash(\@exclusions, \@products);
126
127     $vars->{'type'} = $type;
128     $vars->{'token'} = $token;
129     $vars->{'check_clusions'} = 1;
130     $vars->{'can_fully_edit'} = $cgi->param('can_fully_edit');
131
132     $template->process("admin/flag-type/edit.html.tmpl", $vars)
133       || ThrowTemplateError($template->error());
134     exit;
135 }
136
137 if ($action eq 'list') {
138     my $product_id = $product ? $product->id : 0;
139     my $component_id = $component ? $component->id : 0;
140     my $show_flag_counts = $cgi->param('show_flag_counts') ? 1 : 0;
141     my $group_id = $cgi->param('group');
142     if ($group_id) {
143         detaint_natural($group_id) || ThrowUserError('invalid_group_ID');
144     }
145
146     my $bug_flagtypes;
147     my $attach_flagtypes;
148
149     # If a component is given, restrict the list to flag types available
150     # for this component.
151     if ($component) {
152         $bug_flagtypes = $component->flag_types->{'bug'};
153         $attach_flagtypes = $component->flag_types->{'attachment'};
154
155         # Filter flag types if a group ID is given.
156         $bug_flagtypes = filter_group($bug_flagtypes, $group_id);
157         $attach_flagtypes = filter_group($attach_flagtypes, $group_id);
158
159     }
160     # If only a product is specified but no component, then restrict the list
161     # to flag types available in at least one component of that product.
162     elsif ($product) {
163         $bug_flagtypes = $product->flag_types->{'bug'};
164         $attach_flagtypes = $product->flag_types->{'attachment'};
165
166         # Filter flag types if a group ID is given.
167         $bug_flagtypes = filter_group($bug_flagtypes, $group_id);
168         $attach_flagtypes = filter_group($attach_flagtypes, $group_id);
169     }
170     # If no product is given, then show all flag types available.
171     else {
172         my $flagtypes = get_editable_flagtypes(\@products, $group_id);
173         $bug_flagtypes = [grep { $_->target_type eq 'bug' } @$flagtypes];
174         $attach_flagtypes = [grep { $_->target_type eq 'attachment' } @$flagtypes];
175     }
176
177     if ($show_flag_counts) {
178         my %bug_lists;
179         my %map = ('+' => 'granted', '-' => 'denied', '?' => 'pending');
180
181         foreach my $flagtype (@$bug_flagtypes, @$attach_flagtypes) {
182             $bug_lists{$flagtype->id} = {};
183             my $flags = Bugzilla::Flag->match({type_id => $flagtype->id});
184             # Build lists of bugs, triaged by flag status.
185             push(@{$bug_lists{$flagtype->id}->{$map{$_->status}}}, $_->bug_id) foreach @$flags;
186         }
187         $vars->{'bug_lists'} = \%bug_lists;
188         $vars->{'show_flag_counts'} = 1;
189     }
190
191     $vars->{'selected_product'} = $product ? $product->name : '';
192     $vars->{'selected_component'} = $component ? $component->name : '';
193     $vars->{'bug_types'} = $bug_flagtypes;
194     $vars->{'attachment_types'} = $attach_flagtypes;
195
196     $template->process("admin/flag-type/list.html.tmpl", $vars)
197       || ThrowTemplateError($template->error());
198     exit;
199 }
200
201 if ($action eq 'enter') {
202     my $type = $cgi->param('target_type');
203     ($type eq 'bug' || $type eq 'attachment')
204       || ThrowCodeError('flag_type_target_type_invalid', { target_type => $type });
205
206     $vars->{'action'} = 'insert';
207     $vars->{'token'} = issue_session_token('add_flagtype');
208     $vars->{'type'} = { 'target_type' => $type };
209     # Only users with global editcomponents privs can add a flagtype
210     # to all products.
211     $vars->{'inclusions'} = { '__Any__:__Any__' => '0:0' }
212       if $user->in_group('editcomponents');
213     $vars->{'can_fully_edit'} = 1;
214     # Get a list of groups available to restrict this flag type against.
215     $vars->{'groups'} = get_settable_groups();
216
217     $template->process("admin/flag-type/edit.html.tmpl", $vars)
218       || ThrowTemplateError($template->error());
219     exit;
220 }
221
222 if ($action eq 'edit' || $action eq 'copy') {
223     my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
224     $vars->{'type'} = $flagtype;
225     $vars->{'can_fully_edit'} = $can_fully_edit;
226
227     if ($user->in_group('editcomponents')) {
228         $vars->{'inclusions'} = $flagtype->inclusions;
229         $vars->{'exclusions'} = $flagtype->exclusions;
230     }
231     else {
232         # Filter products the user shouldn't know about.
233         $vars->{'inclusions'} = clusion_array_to_hash([values %{$flagtype->inclusions}], \@products);
234         $vars->{'exclusions'} = clusion_array_to_hash([values %{$flagtype->exclusions}], \@products);
235     }
236
237     if ($action eq 'copy') {
238         $vars->{'action'} = "insert";
239         $vars->{'token'} = issue_session_token('add_flagtype');
240     }
241     else { 
242         $vars->{'action'} = "update";
243         $vars->{'token'} = issue_session_token('edit_flagtype');
244     }
245
246     # Get a list of groups available to restrict this flag type against.
247     $vars->{'groups'} = get_settable_groups();
248
249     $template->process("admin/flag-type/edit.html.tmpl", $vars)
250       || ThrowTemplateError($template->error());
251     exit;
252 }
253
254 if ($action eq 'insert') {
255     check_token_data($token, 'add_flagtype');
256
257     my $name             = $cgi->param('name');
258     my $description      = $cgi->param('description');
259     my $target_type      = $cgi->param('target_type');
260     my $cc_list          = $cgi->param('cc_list');
261     my $sortkey          = $cgi->param('sortkey');
262     my $is_active        = $cgi->param('is_active');
263     my $is_requestable   = $cgi->param('is_requestable');
264     my $is_specifically  = $cgi->param('is_requesteeble');
265     my $is_multiplicable = $cgi->param('is_multiplicable');
266     my $grant_group      = $cgi->param('grant_group');
267     my $request_group    = $cgi->param('request_group');
268     my @inclusions       = $cgi->param('inclusions');
269     my @exclusions       = $cgi->param('exclusions');
270
271     # Filter inclusion and exclusion lists to products the user can see.
272     unless ($user->in_group('editcomponents')) {
273         @inclusions = values %{clusion_array_to_hash(\@inclusions, \@products)};
274         @exclusions = values %{clusion_array_to_hash(\@exclusions, \@products)};
275     }
276
277     my $flagtype = Bugzilla::FlagType->create({
278         name        => $name,
279         description => $description,
280         target_type => $target_type,
281         cc_list     => $cc_list,
282         sortkey     => $sortkey,
283         is_active   => $is_active,
284         is_requestable   => $is_requestable,
285         is_requesteeble  => $is_specifically,
286         is_multiplicable => $is_multiplicable,
287         grant_group      => $grant_group,
288         request_group    => $request_group,
289         inclusions       => \@inclusions,
290         exclusions       => \@exclusions
291     });
292
293     delete_token($token);
294
295     $vars->{'name'} = $flagtype->name;
296     $vars->{'message'} = "flag_type_created";
297
298     my $flagtypes = get_editable_flagtypes(\@products);
299     $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @$flagtypes];
300     $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @$flagtypes];
301
302     $template->process("admin/flag-type/list.html.tmpl", $vars)
303       || ThrowTemplateError($template->error());
304     exit;
305 }
306
307 if ($action eq 'update') {
308     check_token_data($token, 'edit_flagtype');
309
310     my $name             = $cgi->param('name');
311     my $description      = $cgi->param('description');
312     my $cc_list          = $cgi->param('cc_list');
313     my $sortkey          = $cgi->param('sortkey');
314     my $is_active        = $cgi->param('is_active');
315     my $is_requestable   = $cgi->param('is_requestable');
316     my $is_specifically  = $cgi->param('is_requesteeble');
317     my $is_multiplicable = $cgi->param('is_multiplicable');
318     my $grant_group      = $cgi->param('grant_group');
319     my $request_group    = $cgi->param('request_group');
320     my @inclusions       = $cgi->param('inclusions');
321     my @exclusions       = $cgi->param('exclusions');
322
323     my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
324     if ($cgi->param('check_clusions') && !$user->in_group('editcomponents')) {
325         # Filter inclusion and exclusion lists to products the user can edit.
326         @inclusions = values %{clusion_array_to_hash(\@inclusions, \@products)};
327         @exclusions = values %{clusion_array_to_hash(\@exclusions, \@products)};
328         # Bring back the products the user cannot edit.
329         foreach my $item (values %{$flagtype->inclusions}) {
330             my ($prod_id, $comp_id) = split(':', $item);
331             push(@inclusions, $item) unless grep { $_->id == $prod_id } @products;
332         }
333         foreach my $item (values %{$flagtype->exclusions}) {
334             my ($prod_id, $comp_id) = split(':', $item);
335             push(@exclusions, $item) unless grep { $_->id == $prod_id } @products;
336         }
337     }
338
339     if ($can_fully_edit) {
340         $flagtype->set_name($name);
341         $flagtype->set_description($description);
342         $flagtype->set_cc_list($cc_list);
343         $flagtype->set_sortkey($sortkey);
344         $flagtype->set_is_active($is_active);
345         $flagtype->set_is_requestable($is_requestable);
346         $flagtype->set_is_specifically_requestable($is_specifically);
347         $flagtype->set_is_multiplicable($is_multiplicable);
348         $flagtype->set_grant_group($grant_group);
349         $flagtype->set_request_group($request_group);
350     }
351     $flagtype->set_clusions({ inclusions => \@inclusions, exclusions => \@exclusions})
352       if $cgi->param('check_clusions');
353     my $changes = $flagtype->update();
354
355     delete_token($token);
356
357     $vars->{'flagtype'} = $flagtype;
358     $vars->{'changes'} = $changes;
359     $vars->{'message'} = 'flag_type_updated';
360
361     my $flagtypes = get_editable_flagtypes(\@products);
362     $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @$flagtypes];
363     $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @$flagtypes];
364
365     $template->process("admin/flag-type/list.html.tmpl", $vars)
366       || ThrowTemplateError($template->error());
367     exit;
368 }
369
370 if ($action eq 'confirmdelete') {
371     my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
372     ThrowUserError('flag_type_cannot_delete', { flagtype => $flagtype }) unless $can_fully_edit;
373
374     $vars->{'flag_type'} = $flagtype;
375     $vars->{'token'} = issue_session_token('delete_flagtype');
376
377     $template->process("admin/flag-type/confirm-delete.html.tmpl", $vars)
378       || ThrowTemplateError($template->error());
379     exit;
380 }
381
382 if ($action eq 'delete') {
383     check_token_data($token, 'delete_flagtype');
384
385     my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
386     ThrowUserError('flag_type_cannot_delete', { flagtype => $flagtype }) unless $can_fully_edit;
387
388     $flagtype->remove_from_db();
389
390     delete_token($token);
391
392     $vars->{'name'} = $flagtype->name;
393     $vars->{'message'} = "flag_type_deleted";
394
395     my @flagtypes = Bugzilla::FlagType->get_all;
396     $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes];
397     $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @flagtypes];
398
399     $template->process("admin/flag-type/list.html.tmpl", $vars)
400       || ThrowTemplateError($template->error());
401     exit;
402 }
403
404 if ($action eq 'deactivate') {
405     check_token_data($token, 'delete_flagtype');
406
407     my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
408     ThrowUserError('flag_type_cannot_deactivate', { flagtype => $flagtype }) unless $can_fully_edit;
409
410     $flagtype->set_is_active(0);
411     $flagtype->update();
412
413     delete_token($token);
414
415     $vars->{'message'} = "flag_type_deactivated";
416     $vars->{'flag_type'} = $flagtype;
417
418     my @flagtypes = Bugzilla::FlagType->get_all;
419     $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes];
420     $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @flagtypes];
421
422     $template->process("admin/flag-type/list.html.tmpl", $vars)
423       || ThrowTemplateError($template->error());
424     exit;
425 }
426
427 ThrowUserError('unknown_action', {action => $action});
428
429 #####################
430 # Helper subroutines
431 #####################
432
433 sub get_products_and_components {
434     my $vars = {};
435     my $user = Bugzilla->user;
436
437     my @products;
438     if ($user->in_group('editcomponents')) {
439         if (Bugzilla->params->{useclassification}) {
440             # We want products grouped by classifications.
441             @products = map { @{ $_->products } } Bugzilla::Classification->get_all;
442         }
443         else {
444             @products = Bugzilla::Product->get_all;
445         }
446     }
447     else {
448         @products = @{$user->get_products_by_permission('editcomponents')};
449
450         if (Bugzilla->params->{useclassification}) {
451             my %class;
452             push(@{$class{$_->classification_id}}, $_) foreach @products;
453
454             # Let's sort the list by classifications.
455             @products = ();
456             push(@products, @{$class{$_->id} || []}) foreach Bugzilla::Classification->get_all;
457         }
458     }
459
460     my %components;
461     foreach my $product (@products) {
462         $components{$_->name} = 1 foreach @{$product->components};
463     }
464     $vars->{'products'} = \@products;
465     $vars->{'components'} = [sort(keys %components)];
466     return $vars;
467 }
468
469 sub get_editable_flagtypes {
470     my ($products, $group_id) = @_;
471     my $flagtypes;
472
473     if (Bugzilla->user->in_group('editcomponents')) {
474         $flagtypes = Bugzilla::FlagType::match({ group => $group_id });
475         return $flagtypes;
476     }
477
478     my %visible_flagtypes;
479     foreach my $product (@$products) {
480         foreach my $target ('bug', 'attachment') {
481             my $prod_flagtypes = $product->flag_types->{$target};
482             $visible_flagtypes{$_->id} ||= $_ foreach @$prod_flagtypes;
483         }
484     }
485     @$flagtypes = sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name }
486                     values %visible_flagtypes;
487     # Filter flag types if a group ID is given.
488     $flagtypes = filter_group($flagtypes, $group_id);
489     return $flagtypes;
490 }
491
492 sub get_settable_groups {
493     my $user = Bugzilla->user;
494     my $groups = $user->in_group('editcomponents') ? [Bugzilla::Group->get_all] : $user->groups;
495     return $groups;
496 }
497
498 sub filter_group {
499     my ($flag_types, $gid) = @_;
500     return $flag_types unless $gid;
501
502     my @flag_types = grep {($_->grant_group && $_->grant_group->id == $gid)
503                            || ($_->request_group && $_->request_group->id == $gid)} @$flag_types;
504
505     return \@flag_types;
506 }
507
508 # Convert the array @clusions('prod_ID:comp_ID') back to a hash of
509 # the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
510 sub clusion_array_to_hash {
511     my ($array, $visible_products) = @_;
512     my $user = Bugzilla->user;
513     my $has_privs = $user->in_group('editcomponents');
514
515     my %hash;
516     my %products;
517     my %components;
518
519     foreach my $ids (@$array) {
520         my ($product_id, $component_id) = split(":", $ids);
521         my $product_name = "__Any__";
522         my $component_name = "__Any__";
523
524         if ($product_id) {
525             ($products{$product_id}) = grep { $_->id == $product_id } @$visible_products;
526             next unless $products{$product_id};
527             $product_name = $products{$product_id}->name;
528
529             if ($component_id) {
530                 ($components{$component_id}) =
531                   grep { $_->id == $component_id } @{$products{$product_id}->components};
532                 next unless $components{$component_id};
533                 $component_name = $components{$component_id}->name;
534             }
535         }
536         else {
537             # Users with local editcomponents privs cannot use __Any__:__Any__.
538             next unless $has_privs;
539             # It's illegal to select a component without a product.
540             next if $component_id;
541         }
542         $hash{"$product_name:$component_name"} = $ids;
543     }
544     return \%hash;
545 }