Rubber-stamped by Brady Eidson.
[webbrowser.git] / BugsSite / editflagtypes.cgi
blob97db82a3ca61d17fabce07decc87e3f83a9ffb6e
1 #!/usr/bin/env perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
4 # The contents of this file are subject to the Mozilla Public
5 # License Version 1.1 (the "License"); you may not use this file
6 # except in compliance with the License. You may obtain a copy of
7 # the License at http://www.mozilla.org/MPL/
9 # Software distributed under the License is distributed on an "AS
10 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11 # implied. See the License for the specific language governing
12 # rights and limitations under the License.
14 # The Original Code is the Bugzilla Bug Tracking System.
16 # The Initial Developer of the Original Code is Netscape Communications
17 # Corporation. Portions created by Netscape are
18 # Copyright (C) 1998 Netscape Communications Corporation. All
19 # Rights Reserved.
21 # Contributor(s): Myk Melez <myk@mozilla.org>
22 # Frédéric Buclin <LpSolit@gmail.com>
24 ################################################################################
25 # Script Initialization
26 ################################################################################
28 # Make it harder for us to do dangerous things in Perl.
29 use strict;
30 use lib qw(. lib);
32 # Use Bugzilla's flag modules for handling flag types.
33 use Bugzilla;
34 use Bugzilla::Constants;
35 use Bugzilla::Flag;
36 use Bugzilla::FlagType;
37 use Bugzilla::Group;
38 use Bugzilla::Util;
39 use Bugzilla::Error;
40 use Bugzilla::Product;
41 use Bugzilla::Component;
42 use Bugzilla::Bug;
43 use Bugzilla::Attachment;
44 use Bugzilla::Token;
46 local our $cgi = Bugzilla->cgi;
47 local our $template = Bugzilla->template;
48 local our $vars = {};
50 # Make sure the user is logged in and is an administrator.
51 my $user = Bugzilla->login(LOGIN_REQUIRED);
52 $user->in_group('editcomponents')
53 || ThrowUserError("auth_failure", {group => "editcomponents",
54 action => "edit",
55 object => "flagtypes"});
57 ################################################################################
58 # Main Body Execution
59 ################################################################################
61 # All calls to this script should contain an "action" variable whose value
62 # determines what the user wants to do. The code below checks the value of
63 # that variable and runs the appropriate code.
65 # Determine whether to use the action specified by the user or the default.
66 my $action = $cgi->param('action') || 'list';
67 my $token = $cgi->param('token');
68 my @categoryActions;
70 if (@categoryActions = grep(/^categoryAction-.+/, $cgi->param())) {
71 $categoryActions[0] =~ s/^categoryAction-//;
72 processCategoryChange($categoryActions[0], $token);
73 exit;
76 if ($action eq 'list') { list(); }
77 elsif ($action eq 'enter') { edit($action); }
78 elsif ($action eq 'copy') { edit($action); }
79 elsif ($action eq 'edit') { edit($action); }
80 elsif ($action eq 'insert') { insert($token); }
81 elsif ($action eq 'update') { update($token); }
82 elsif ($action eq 'confirmdelete') { confirmDelete(); }
83 elsif ($action eq 'delete') { deleteType($token); }
84 elsif ($action eq 'deactivate') { deactivate($token); }
85 else {
86 ThrowCodeError("action_unrecognized", { action => $action });
89 exit;
91 ################################################################################
92 # Functions
93 ################################################################################
95 sub list {
96 # Restrict the list to the given product and component, if given.
97 $vars = get_products_and_components($vars);
99 my $product = validateProduct(scalar $cgi->param('product'));
100 my $component = validateComponent($product, scalar $cgi->param('component'));
101 my $product_id = $product ? $product->id : 0;
102 my $component_id = $component ? $component->id : 0;
104 # Define the variables and functions that will be passed to the UI template.
105 $vars->{'selected_product'} = $cgi->param('product');
106 $vars->{'selected_component'} = $cgi->param('component');
108 my $bug_flagtypes;
109 my $attach_flagtypes;
111 # If a component is given, restrict the list to flag types available
112 # for this component.
113 if ($component) {
114 $bug_flagtypes = $component->flag_types->{'bug'};
115 $attach_flagtypes = $component->flag_types->{'attachment'};
117 # Filter flag types if a group ID is given.
118 $bug_flagtypes = filter_group($bug_flagtypes);
119 $attach_flagtypes = filter_group($attach_flagtypes);
122 # If only a product is specified but no component, then restrict the list
123 # to flag types available in at least one component of that product.
124 elsif ($product) {
125 $bug_flagtypes = $product->flag_types->{'bug'};
126 $attach_flagtypes = $product->flag_types->{'attachment'};
128 # Filter flag types if a group ID is given.
129 $bug_flagtypes = filter_group($bug_flagtypes);
130 $attach_flagtypes = filter_group($attach_flagtypes);
132 # If no product is given, then show all flag types available.
133 else {
134 $bug_flagtypes =
135 Bugzilla::FlagType::match({'target_type' => 'bug',
136 'group' => scalar $cgi->param('group')});
138 $attach_flagtypes =
139 Bugzilla::FlagType::match({'target_type' => 'attachment',
140 'group' => scalar $cgi->param('group')});
143 $vars->{'bug_types'} = $bug_flagtypes;
144 $vars->{'attachment_types'} = $attach_flagtypes;
146 # Return the appropriate HTTP response headers.
147 print $cgi->header();
149 # Generate and return the UI (HTML page) from the appropriate template.
150 $template->process("admin/flag-type/list.html.tmpl", $vars)
151 || ThrowTemplateError($template->error());
155 sub edit {
156 my ($action) = @_;
158 my $flag_type;
159 if ($action eq 'enter') {
160 validateTargetType();
162 else {
163 $flag_type = validateID();
166 # Fill $vars with products and components data.
167 $vars = get_products_and_components($vars);
169 $vars->{'last_action'} = $cgi->param('action');
170 if ($cgi->param('action') eq 'enter' || $cgi->param('action') eq 'copy') {
171 $vars->{'action'} = "insert";
172 $vars->{'token'} = issue_session_token('add_flagtype');
174 else {
175 $vars->{'action'} = "update";
176 $vars->{'token'} = issue_session_token('edit_flagtype');
179 # If copying or editing an existing flag type, retrieve it.
180 if ($cgi->param('action') eq 'copy' || $cgi->param('action') eq 'edit') {
181 $vars->{'type'} = $flag_type;
183 # Otherwise set the target type (the minimal information about the type
184 # that the template needs to know) from the URL parameter and default
185 # the list of inclusions to all categories.
186 else {
187 my %inclusions;
188 $inclusions{"__Any__:__Any__"} = "0:0";
189 $vars->{'type'} = { 'target_type' => scalar $cgi->param('target_type'),
190 'inclusions' => \%inclusions };
192 # Get a list of groups available to restrict this flag type against.
193 my @groups = Bugzilla::Group->get_all;
194 $vars->{'groups'} = \@groups;
195 # Return the appropriate HTTP response headers.
196 print $cgi->header();
198 # Generate and return the UI (HTML page) from the appropriate template.
199 $template->process("admin/flag-type/edit.html.tmpl", $vars)
200 || ThrowTemplateError($template->error());
203 sub processCategoryChange {
204 my ($categoryAction, $token) = @_;
205 validateIsActive();
206 validateIsRequestable();
207 validateIsRequesteeble();
208 validateAllowMultiple();
210 my @inclusions = $cgi->param('inclusions');
211 my @exclusions = $cgi->param('exclusions');
212 if ($categoryAction eq 'include') {
213 my $product = validateProduct(scalar $cgi->param('product'));
214 my $component = validateComponent($product, scalar $cgi->param('component'));
215 my $category = ($product ? $product->id : 0) . ":" .
216 ($component ? $component->id : 0);
217 push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
219 elsif ($categoryAction eq 'exclude') {
220 my $product = validateProduct(scalar $cgi->param('product'));
221 my $component = validateComponent($product, scalar $cgi->param('component'));
222 my $category = ($product ? $product->id : 0) . ":" .
223 ($component ? $component->id : 0);
224 push(@exclusions, $category) unless grep($_ eq $category, @exclusions);
226 elsif ($categoryAction eq 'removeInclusion') {
227 my @inclusion_to_remove = $cgi->param('inclusion_to_remove');
228 @inclusions = map {(lsearch(\@inclusion_to_remove, $_) < 0) ? $_ : ()} @inclusions;
230 elsif ($categoryAction eq 'removeExclusion') {
231 my @exclusion_to_remove = $cgi->param('exclusion_to_remove');
232 @exclusions = map {(lsearch(\@exclusion_to_remove, $_) < 0) ? $_ : ()} @exclusions;
235 # Convert the array @clusions('prod_ID:comp_ID') back to a hash of
236 # the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
237 my %inclusions = clusion_array_to_hash(\@inclusions);
238 my %exclusions = clusion_array_to_hash(\@exclusions);
240 # Fill $vars with products and components data.
241 $vars = get_products_and_components($vars);
243 my @groups = Bugzilla::Group->get_all;
244 $vars->{'groups'} = \@groups;
245 $vars->{'action'} = $cgi->param('action');
247 my $type = {};
248 foreach my $key ($cgi->param()) { $type->{$key} = $cgi->param($key) }
249 # That's what I call a big hack. The template expects to see a group object.
250 # This script needs some rewrite anyway.
251 $type->{'grant_group'} = {};
252 $type->{'grant_group'}->{'name'} = $cgi->param('grant_group');
253 $type->{'request_group'} = {};
254 $type->{'request_group'}->{'name'} = $cgi->param('request_group');
256 $type->{'inclusions'} = \%inclusions;
257 $type->{'exclusions'} = \%exclusions;
258 $vars->{'type'} = $type;
259 $vars->{'token'} = $token;
261 # Return the appropriate HTTP response headers.
262 print $cgi->header();
264 # Generate and return the UI (HTML page) from the appropriate template.
265 $template->process("admin/flag-type/edit.html.tmpl", $vars)
266 || ThrowTemplateError($template->error());
269 # Convert the array @clusions('prod_ID:comp_ID') back to a hash of
270 # the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
271 sub clusion_array_to_hash {
272 my $array = shift;
273 my %hash;
274 my %products;
275 my %components;
276 foreach my $ids (@$array) {
277 trick_taint($ids);
278 my ($product_id, $component_id) = split(":", $ids);
279 my $product_name = "__Any__";
280 if ($product_id) {
281 $products{$product_id} ||= new Bugzilla::Product($product_id);
282 $product_name = $products{$product_id}->name if $products{$product_id};
284 my $component_name = "__Any__";
285 if ($component_id) {
286 $components{$component_id} ||= new Bugzilla::Component($component_id);
287 $component_name = $components{$component_id}->name if $components{$component_id};
289 $hash{"$product_name:$component_name"} = $ids;
291 return %hash;
294 sub insert {
295 my $token = shift;
296 check_token_data($token, 'add_flagtype');
297 my $name = validateName();
298 my $description = validateDescription();
299 my $cc_list = validateCCList();
300 validateTargetType();
301 validateSortKey();
302 validateIsActive();
303 validateIsRequestable();
304 validateIsRequesteeble();
305 validateAllowMultiple();
306 validateGroups();
308 my $dbh = Bugzilla->dbh;
310 my $target_type = $cgi->param('target_type') eq "bug" ? "b" : "a";
312 $dbh->bz_start_transaction();
314 # Insert a record for the new flag type into the database.
315 $dbh->do('INSERT INTO flagtypes
316 (name, description, cc_list, target_type,
317 sortkey, is_active, is_requestable,
318 is_requesteeble, is_multiplicable,
319 grant_group_id, request_group_id)
320 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
321 undef, ($name, $description, $cc_list, $target_type,
322 $cgi->param('sortkey'), $cgi->param('is_active'),
323 $cgi->param('is_requestable'), $cgi->param('is_requesteeble'),
324 $cgi->param('is_multiplicable'), scalar($cgi->param('grant_gid')),
325 scalar($cgi->param('request_gid'))));
327 # Get the ID of the new flag type.
328 my $id = $dbh->bz_last_key('flagtypes', 'id');
330 # Populate the list of inclusions/exclusions for this flag type.
331 validateAndSubmit($id);
333 $dbh->bz_commit_transaction();
335 $vars->{'name'} = $name;
336 $vars->{'message'} = "flag_type_created";
337 delete_token($token);
339 $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
340 $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
342 # Return the appropriate HTTP response headers.
343 print $cgi->header();
345 $template->process("admin/flag-type/list.html.tmpl", $vars)
346 || ThrowTemplateError($template->error());
350 sub update {
351 my $token = shift;
352 check_token_data($token, 'edit_flagtype');
353 my $flag_type = validateID();
354 my $id = $flag_type->id;
355 my $name = validateName();
356 my $description = validateDescription();
357 my $cc_list = validateCCList();
358 validateTargetType();
359 validateSortKey();
360 validateIsActive();
361 validateIsRequestable();
362 validateIsRequesteeble();
363 validateAllowMultiple();
364 validateGroups();
366 my $dbh = Bugzilla->dbh;
367 my $user = Bugzilla->user;
368 $dbh->bz_start_transaction();
369 $dbh->do('UPDATE flagtypes
370 SET name = ?, description = ?, cc_list = ?,
371 sortkey = ?, is_active = ?, is_requestable = ?,
372 is_requesteeble = ?, is_multiplicable = ?,
373 grant_group_id = ?, request_group_id = ?
374 WHERE id = ?',
375 undef, ($name, $description, $cc_list, $cgi->param('sortkey'),
376 $cgi->param('is_active'), $cgi->param('is_requestable'),
377 $cgi->param('is_requesteeble'), $cgi->param('is_multiplicable'),
378 scalar($cgi->param('grant_gid')), scalar($cgi->param('request_gid')),
379 $id));
381 # Update the list of inclusions/exclusions for this flag type.
382 validateAndSubmit($id);
384 $dbh->bz_commit_transaction();
386 # Clear existing flags for bugs/attachments in categories no longer on
387 # the list of inclusions or that have been added to the list of exclusions.
388 my $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
389 FROM flags
390 INNER JOIN bugs
391 ON flags.bug_id = bugs.bug_id
392 LEFT OUTER JOIN flaginclusions AS i
393 ON (flags.type_id = i.type_id
394 AND (bugs.product_id = i.product_id
395 OR i.product_id IS NULL)
396 AND (bugs.component_id = i.component_id
397 OR i.component_id IS NULL))
398 WHERE flags.type_id = ?
399 AND i.type_id IS NULL',
400 undef, $id);
401 my $flags = Bugzilla::Flag->new_from_list($flag_ids);
402 foreach my $flag (@$flags) {
403 my $bug = new Bugzilla::Bug($flag->bug_id);
404 Bugzilla::Flag::clear($flag, $bug, $flag->attachment);
407 $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
408 FROM flags
409 INNER JOIN bugs
410 ON flags.bug_id = bugs.bug_id
411 INNER JOIN flagexclusions AS e
412 ON flags.type_id = e.type_id
413 WHERE flags.type_id = ?
414 AND (bugs.product_id = e.product_id
415 OR e.product_id IS NULL)
416 AND (bugs.component_id = e.component_id
417 OR e.component_id IS NULL)',
418 undef, $id);
419 $flags = Bugzilla::Flag->new_from_list($flag_ids);
420 foreach my $flag (@$flags) {
421 my $bug = new Bugzilla::Bug($flag->bug_id);
422 Bugzilla::Flag::clear($flag, $bug, $flag->attachment);
425 # Now silently remove requestees from flags which are no longer
426 # specifically requestable.
427 if (!$cgi->param('is_requesteeble')) {
428 $dbh->do('UPDATE flags SET requestee_id = NULL WHERE type_id = ?',
429 undef, $id);
432 $vars->{'name'} = $name;
433 $vars->{'message'} = "flag_type_changes_saved";
434 delete_token($token);
436 $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
437 $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
439 # Return the appropriate HTTP response headers.
440 print $cgi->header();
442 $template->process("admin/flag-type/list.html.tmpl", $vars)
443 || ThrowTemplateError($template->error());
447 sub confirmDelete {
448 my $flag_type = validateID();
450 $vars->{'flag_type'} = $flag_type;
451 $vars->{'token'} = issue_session_token('delete_flagtype');
452 # Return the appropriate HTTP response headers.
453 print $cgi->header();
455 # Generate and return the UI (HTML page) from the appropriate template.
456 $template->process("admin/flag-type/confirm-delete.html.tmpl", $vars)
457 || ThrowTemplateError($template->error());
461 sub deleteType {
462 my $token = shift;
463 check_token_data($token, 'delete_flagtype');
464 my $flag_type = validateID();
465 my $id = $flag_type->id;
466 my $dbh = Bugzilla->dbh;
468 $dbh->bz_start_transaction();
470 # Get the name of the flag type so we can tell users
471 # what was deleted.
472 $vars->{'name'} = $flag_type->name;
474 $dbh->do('DELETE FROM flags WHERE type_id = ?', undef, $id);
475 $dbh->do('DELETE FROM flaginclusions WHERE type_id = ?', undef, $id);
476 $dbh->do('DELETE FROM flagexclusions WHERE type_id = ?', undef, $id);
477 $dbh->do('DELETE FROM flagtypes WHERE id = ?', undef, $id);
478 $dbh->bz_commit_transaction();
480 $vars->{'message'} = "flag_type_deleted";
481 delete_token($token);
483 $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
484 $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
486 # Return the appropriate HTTP response headers.
487 print $cgi->header();
489 $template->process("admin/flag-type/list.html.tmpl", $vars)
490 || ThrowTemplateError($template->error());
494 sub deactivate {
495 my $token = shift;
496 check_token_data($token, 'delete_flagtype');
497 my $flag_type = validateID();
498 validateIsActive();
500 my $dbh = Bugzilla->dbh;
502 $dbh->bz_start_transaction();
503 $dbh->do('UPDATE flagtypes SET is_active = 0 WHERE id = ?', undef, $flag_type->id);
504 $dbh->bz_commit_transaction();
506 $vars->{'message'} = "flag_type_deactivated";
507 $vars->{'flag_type'} = $flag_type;
508 delete_token($token);
510 $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
511 $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
513 # Return the appropriate HTTP response headers.
514 print $cgi->header();
516 # Generate and return the UI (HTML page) from the appropriate template.
517 $template->process("admin/flag-type/list.html.tmpl", $vars)
518 || ThrowTemplateError($template->error());
521 sub get_products_and_components {
522 my $vars = shift;
524 my @products = Bugzilla::Product->get_all;
525 # We require all unique component names.
526 my %components;
527 foreach my $product (@products) {
528 foreach my $component (@{$product->components}) {
529 $components{$component->name} = 1;
532 $vars->{'products'} = \@products;
533 $vars->{'components'} = [sort(keys %components)];
534 return $vars;
537 ################################################################################
538 # Data Validation / Security Authorization
539 ################################################################################
541 sub validateID {
542 my $id = $cgi->param('id');
543 my $flag_type = new Bugzilla::FlagType($id)
544 || ThrowCodeError('flag_type_nonexistent', { id => $id });
546 return $flag_type;
549 sub validateName {
550 my $name = $cgi->param('name');
551 ($name && $name !~ /[ ,]/ && length($name) <= 50)
552 || ThrowUserError("flag_type_name_invalid",
553 { name => $name });
554 trick_taint($name);
555 return $name;
558 sub validateDescription {
559 my $description = $cgi->param('description');
560 length($description) < 2**16-1
561 || ThrowUserError("flag_type_description_invalid");
562 trick_taint($description);
563 return $description;
566 sub validateCCList {
567 my $cc_list = $cgi->param('cc_list');
568 length($cc_list) <= 200
569 || ThrowUserError("flag_type_cc_list_invalid",
570 { cc_list => $cc_list });
572 my @addresses = split(/[, ]+/, $cc_list);
573 # We do not call Util::validate_email_syntax because these
574 # addresses do not require to match 'emailregexp' and do not
575 # depend on 'emailsuffix'. So we limit ourselves to a simple
576 # sanity check:
577 # - match the syntax of a fully qualified email address;
578 # - do not contain any illegal character.
579 foreach my $address (@addresses) {
580 ($address =~ /^[\w\.\+\-=]+@[\w\.\-]+\.[\w\-]+$/
581 && $address !~ /[\\\(\)<>&,;:"\[\] \t\r\n]/)
582 || ThrowUserError('illegal_email_address',
583 {addr => $address, default => 1});
585 trick_taint($cc_list);
586 return $cc_list;
589 sub validateProduct {
590 my $product_name = shift;
591 return unless $product_name;
593 my $product = Bugzilla::Product::check_product($product_name);
594 return $product;
597 sub validateComponent {
598 my ($product, $component_name) = @_;
599 return unless $component_name;
601 ($product && $product->id)
602 || ThrowUserError("flag_type_component_without_product");
604 my $component = Bugzilla::Component->check({ product => $product,
605 name => $component_name });
606 return $component;
609 sub validateSortKey {
610 # $sortkey is destroyed if detaint_natural fails.
611 my $sortkey = $cgi->param('sortkey');
612 detaint_natural($sortkey)
613 && $sortkey < 32768
614 || ThrowUserError("flag_type_sortkey_invalid",
615 { sortkey => scalar $cgi->param('sortkey') });
616 $cgi->param('sortkey', $sortkey);
619 sub validateTargetType {
620 grep($cgi->param('target_type') eq $_, ("bug", "attachment"))
621 || ThrowCodeError("flag_type_target_type_invalid",
622 { target_type => scalar $cgi->param('target_type') });
625 sub validateIsActive {
626 $cgi->param('is_active', $cgi->param('is_active') ? 1 : 0);
629 sub validateIsRequestable {
630 $cgi->param('is_requestable', $cgi->param('is_requestable') ? 1 : 0);
633 sub validateIsRequesteeble {
634 $cgi->param('is_requesteeble', $cgi->param('is_requesteeble') ? 1 : 0);
637 sub validateAllowMultiple {
638 $cgi->param('is_multiplicable', $cgi->param('is_multiplicable') ? 1 : 0);
641 sub validateGroups {
642 my $dbh = Bugzilla->dbh;
643 # Convert group names to group IDs
644 foreach my $col ('grant', 'request') {
645 my $name = $cgi->param($col . '_group');
646 if ($name) {
647 trick_taint($name);
648 my $gid = $dbh->selectrow_array('SELECT id FROM groups
649 WHERE name = ?', undef, $name);
650 $gid || ThrowUserError("group_unknown", { name => $name });
651 $cgi->param($col . '_gid', $gid);
656 # At this point, values either come the DB itself or have been recently
657 # added by the user and have passed all validation tests.
658 # The only way to have invalid product/component combinations is to
659 # hack the URL. So we silently ignore them, if any.
660 sub validateAndSubmit {
661 my ($id) = @_;
662 my $dbh = Bugzilla->dbh;
664 # Cache product objects.
665 my %products;
666 foreach my $category_type ("inclusions", "exclusions") {
667 # Will be used several times below.
668 my $sth = $dbh->prepare("INSERT INTO flag$category_type " .
669 "(type_id, product_id, component_id) " .
670 "VALUES (?, ?, ?)");
672 $dbh->do("DELETE FROM flag$category_type WHERE type_id = ?", undef, $id);
673 foreach my $category ($cgi->param($category_type)) {
674 trick_taint($category);
675 my ($product_id, $component_id) = split(":", $category);
676 # Does the product exist?
677 if ($product_id) {
678 $products{$product_id} ||= new Bugzilla::Product($product_id);
679 next unless defined $products{$product_id};
681 # A component was selected without a product being selected.
682 next if (!$product_id && $component_id);
683 # Does the component belong to this product?
684 if ($component_id) {
685 my @match = grep {$_->id == $component_id} @{$products{$product_id}->components};
686 next unless scalar(@match);
688 $product_id ||= undef;
689 $component_id ||= undef;
690 $sth->execute($id, $product_id, $component_id);
695 sub filter_group {
696 my $flag_types = shift;
697 return $flag_types unless Bugzilla->cgi->param('group');
699 my $gid = scalar $cgi->param('group');
700 my @flag_types = grep {($_->grant_group && $_->grant_group->id == $gid)
701 || ($_->request_group && $_->request_group->id == $gid)} @$flag_types;
703 return \@flag_types;