#!/usr/bin/perl -T # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # This Source Code Form is "Incompatible With Secondary Licenses", as # defined by the Mozilla Public License, v. 2.0. use 5.10.1; use strict; use warnings; use lib qw(. lib local/lib/perl5); use Bugzilla; use Bugzilla::Constants; use Bugzilla::Flag; use Bugzilla::FlagType; use Bugzilla::Group; use Bugzilla::Util; use Bugzilla::Error; use Bugzilla::Product; use Bugzilla::Token; # Make sure the user is logged in and has the right privileges. my $user = Bugzilla->login(LOGIN_REQUIRED); my $cgi = Bugzilla->cgi; my $template = Bugzilla->template; print $cgi->header(); $user->in_group('editcomponents') || scalar(@{$user->get_products_by_permission('editcomponents')}) || ThrowUserError("auth_failure", {group => "editcomponents", action => "edit", object => "flagtypes"}); # We need this everywhere. my $vars = get_products_and_components(); my @products = @{$vars->{products}}; my $action = $cgi->param('action') || 'list'; my $token = $cgi->param('token'); my $prod_name = $cgi->param('product'); my $comp_name = $cgi->param('component'); my $flag_id = $cgi->param('id'); my ($product, $component); if ($prod_name) { # Make sure the user is allowed to view this product name. # Users with global editcomponents privs can see all product names. ($product) = grep { lc($_->name) eq lc($prod_name) } @products; $product || ThrowUserError('product_access_denied', { name => $prod_name }); } if ($comp_name) { $product || ThrowUserError('flag_type_component_without_product'); ($component) = grep { lc($_->name) eq lc($comp_name) } @{$product->components}; $component || ThrowUserError('product_unknown_component', { product => $product->name, comp => $comp_name }); } # If 'categoryAction' is set, it has priority over 'action'. if (my ($category_action) = grep { $_ =~ /^categoryAction-(?:\w+)$/ } $cgi->param()) { $category_action =~ s/^categoryAction-//; my @inclusions = $cgi->param('inclusions'); my @exclusions = $cgi->param('exclusions'); my @categories; if ($category_action =~ /^(in|ex)clude$/) { if (!$user->in_group('editcomponents') && !$product) { # The user can only add the flag type to products he can administrate. foreach my $prod (@products) { push(@categories, $prod->id . ':0') } } else { my $category = ($product ? $product->id : 0) . ':' . ($component ? $component->id : 0); push(@categories, $category); } } if ($category_action eq 'include') { foreach my $category (@categories) { push(@inclusions, $category) unless grep($_ eq $category, @inclusions); } } elsif ($category_action eq 'exclude') { foreach my $category (@categories) { push(@exclusions, $category) unless grep($_ eq $category, @exclusions); } } elsif ($category_action eq 'removeInclusion') { my @inclusion_to_remove = $cgi->param('inclusion_to_remove'); foreach my $remove (@inclusion_to_remove) { @inclusions = grep { $_ ne $remove } @inclusions; } } elsif ($category_action eq 'removeExclusion') { my @exclusion_to_remove = $cgi->param('exclusion_to_remove'); foreach my $remove (@exclusion_to_remove) { @exclusions = grep { $_ ne $remove } @exclusions; } } $vars->{'groups'} = get_settable_groups(); $vars->{'action'} = $action; my $type = {}; $type->{$_} = $cgi->param($_) foreach $cgi->param(); # Make sure boolean fields are defined, else they fall back to 1. foreach my $boolean (qw(is_active is_requestable is_requesteeble is_multiplicable)) { $type->{$boolean} ||= 0; } # That's what I call a big hack. The template expects to see a group object. $type->{'grant_group'} = {}; $type->{'grant_group'}->{'name'} = $cgi->param('grant_group'); $type->{'request_group'} = {}; $type->{'request_group'}->{'name'} = $cgi->param('request_group'); $vars->{'inclusions'} = clusion_array_to_hash(\@inclusions, \@products); $vars->{'exclusions'} = clusion_array_to_hash(\@exclusions, \@products); $vars->{'type'} = $type; $vars->{'token'} = $token; $vars->{'check_clusions'} = 1; $vars->{'can_fully_edit'} = $cgi->param('can_fully_edit'); $template->process("admin/flag-type/edit.html.tmpl", $vars) || ThrowTemplateError($template->error()); exit; } if ($action eq 'list') { my $product_id = $product ? $product->id : 0; my $component_id = $component ? $component->id : 0; my $show_flag_counts = $cgi->param('show_flag_counts') ? 1 : 0; my $group_id = $cgi->param('group'); if ($group_id) { detaint_natural($group_id) || ThrowUserError('invalid_group_ID'); } my $bug_flagtypes; my $attach_flagtypes; # If a component is given, restrict the list to flag types available # for this component. if ($component) { $bug_flagtypes = $component->flag_types->{'bug'}; $attach_flagtypes = $component->flag_types->{'attachment'}; # Filter flag types if a group ID is given. $bug_flagtypes = filter_group($bug_flagtypes, $group_id); $attach_flagtypes = filter_group($attach_flagtypes, $group_id); } # If only a product is specified but no component, then restrict the list # to flag types available in at least one component of that product. elsif ($product) { $bug_flagtypes = $product->flag_types->{'bug'}; $attach_flagtypes = $product->flag_types->{'attachment'}; # Filter flag types if a group ID is given. $bug_flagtypes = filter_group($bug_flagtypes, $group_id); $attach_flagtypes = filter_group($attach_flagtypes, $group_id); } # If no product is given, then show all flag types available. else { my $flagtypes = get_editable_flagtypes(\@products, $group_id); $bug_flagtypes = [grep { $_->target_type eq 'bug' } @$flagtypes]; $attach_flagtypes = [grep { $_->target_type eq 'attachment' } @$flagtypes]; } if ($show_flag_counts) { my %bug_lists; my %map = ('+' => 'granted', '-' => 'denied', '?' => 'pending'); foreach my $flagtype (@$bug_flagtypes, @$attach_flagtypes) { $bug_lists{$flagtype->id} = {}; my $flags = Bugzilla::Flag->match({type_id => $flagtype->id}); # Build lists of bugs, triaged by flag status. push(@{$bug_lists{$flagtype->id}->{$map{$_->status}}}, $_->bug_id) foreach @$flags; } $vars->{'bug_lists'} = \%bug_lists; $vars->{'show_flag_counts'} = 1; } $vars->{'selected_product'} = $product ? $product->name : ''; $vars->{'selected_component'} = $component ? $component->name : ''; $vars->{'bug_types'} = $bug_flagtypes; $vars->{'attachment_types'} = $attach_flagtypes; $template->process("admin/flag-type/list.html.tmpl", $vars) || ThrowTemplateError($template->error()); exit; } if ($action eq 'enter') { my $type = $cgi->param('target_type'); ($type eq 'bug' || $type eq 'attachment') || ThrowCodeError('flag_type_target_type_invalid', { target_type => $type }); $vars->{'action'} = 'insert'; $vars->{'token'} = issue_session_token('add_flagtype'); $vars->{'type'} = { 'target_type' => $type }; # Only users with global editcomponents privs can add a flagtype # to all products. $vars->{'inclusions'} = { '__Any__:__Any__' => '0:0' } if $user->in_group('editcomponents'); $vars->{'can_fully_edit'} = 1; # Get a list of groups available to restrict this flag type against. $vars->{'groups'} = get_settable_groups(); $template->process("admin/flag-type/edit.html.tmpl", $vars) || ThrowTemplateError($template->error()); exit; } if ($action eq 'edit' || $action eq 'copy') { my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id); $vars->{'type'} = $flagtype; $vars->{'can_fully_edit'} = $can_fully_edit; if ($user->in_group('editcomponents')) { $vars->{'inclusions'} = $flagtype->inclusions; $vars->{'exclusions'} = $flagtype->exclusions; } else { # Filter products the user shouldn't know about. $vars->{'inclusions'} = clusion_array_to_hash([values %{$flagtype->inclusions}], \@products); $vars->{'exclusions'} = clusion_array_to_hash([values %{$flagtype->exclusions}], \@products); } if ($action eq 'copy') { $vars->{'action'} = "insert"; $vars->{'token'} = issue_session_token('add_flagtype'); } else { $vars->{'action'} = "update"; $vars->{'token'} = issue_session_token('edit_flagtype'); } # Get a list of groups available to restrict this flag type against. $vars->{'groups'} = get_settable_groups(); $template->process("admin/flag-type/edit.html.tmpl", $vars) || ThrowTemplateError($template->error()); exit; } if ($action eq 'insert') { check_token_data($token, 'add_flagtype'); my $name = $cgi->param('name'); my $description = $cgi->param('description'); my $target_type = $cgi->param('target_type'); my $cc_list = $cgi->param('cc_list'); my $sortkey = $cgi->param('sortkey'); my $is_active = $cgi->param('is_active'); my $is_requestable = $cgi->param('is_requestable'); my $is_specifically = $cgi->param('is_requesteeble'); my $is_multiplicable = $cgi->param('is_multiplicable'); my $grant_group = $cgi->param('grant_group'); my $request_group = $cgi->param('request_group'); my @inclusions = $cgi->param('inclusions'); my @exclusions = $cgi->param('exclusions'); # Filter inclusion and exclusion lists to products the user can see. unless ($user->in_group('editcomponents')) { @inclusions = values %{clusion_array_to_hash(\@inclusions, \@products)}; @exclusions = values %{clusion_array_to_hash(\@exclusions, \@products)}; } my $flagtype = Bugzilla::FlagType->create({ name => $name, description => $description, target_type => $target_type, cc_list => $cc_list, sortkey => $sortkey, is_active => $is_active, is_requestable => $is_requestable, is_requesteeble => $is_specifically, is_multiplicable => $is_multiplicable, grant_group => $grant_group, request_group => $request_group, inclusions => \@inclusions, exclusions => \@exclusions }); delete_token($token); $vars->{'name'} = $flagtype->name; $vars->{'message'} = "flag_type_created"; my $flagtypes = get_editable_flagtypes(\@products); $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @$flagtypes]; $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @$flagtypes]; $template->process("admin/flag-type/list.html.tmpl", $vars) || ThrowTemplateError($template->error()); exit; } if ($action eq 'update') { check_token_data($token, 'edit_flagtype'); my $name = $cgi->param('name'); my $description = $cgi->param('description'); my $cc_list = $cgi->param('cc_list'); my $sortkey = $cgi->param('sortkey'); my $is_active = $cgi->param('is_active'); my $is_requestable = $cgi->param('is_requestable'); my $is_specifically = $cgi->param('is_requesteeble'); my $is_multiplicable = $cgi->param('is_multiplicable'); my $grant_group = $cgi->param('grant_group'); my $request_group = $cgi->param('request_group'); my @inclusions = $cgi->param('inclusions'); my @exclusions = $cgi->param('exclusions'); my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id); if ($cgi->param('check_clusions') && !$user->in_group('editcomponents')) { # Filter inclusion and exclusion lists to products the user can edit. @inclusions = values %{clusion_array_to_hash(\@inclusions, \@products)}; @exclusions = values %{clusion_array_to_hash(\@exclusions, \@products)}; # Bring back the products the user cannot edit. foreach my $item (values %{$flagtype->inclusions}) { my ($prod_id, $comp_id) = split(':', $item); push(@inclusions, $item) unless grep { $_->id == $prod_id } @products; } foreach my $item (values %{$flagtype->exclusions}) { my ($prod_id, $comp_id) = split(':', $item); push(@exclusions, $item) unless grep { $_->id == $prod_id } @products; } } if ($can_fully_edit) { $flagtype->set_name($name); $flagtype->set_description($description); $flagtype->set_cc_list($cc_list); $flagtype->set_sortkey($sortkey); $flagtype->set_is_active($is_active); $flagtype->set_is_requestable($is_requestable); $flagtype->set_is_specifically_requestable($is_specifically); $flagtype->set_is_multiplicable($is_multiplicable); $flagtype->set_grant_group($grant_group); $flagtype->set_request_group($request_group); } $flagtype->set_clusions({ inclusions => \@inclusions, exclusions => \@exclusions}) if $cgi->param('check_clusions'); my $changes = $flagtype->update(); delete_token($token); $vars->{'flagtype'} = $flagtype; $vars->{'changes'} = $changes; $vars->{'message'} = 'flag_type_updated'; my $flagtypes = get_editable_flagtypes(\@products); $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @$flagtypes]; $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @$flagtypes]; $template->process("admin/flag-type/list.html.tmpl", $vars) || ThrowTemplateError($template->error()); exit; } if ($action eq 'confirmdelete') { my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id); ThrowUserError('flag_type_cannot_delete', { flagtype => $flagtype }) unless $can_fully_edit; $vars->{'flag_type'} = $flagtype; $vars->{'token'} = issue_session_token('delete_flagtype'); $template->process("admin/flag-type/confirm-delete.html.tmpl", $vars) || ThrowTemplateError($template->error()); exit; } if ($action eq 'delete') { check_token_data($token, 'delete_flagtype'); my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id); ThrowUserError('flag_type_cannot_delete', { flagtype => $flagtype }) unless $can_fully_edit; $flagtype->remove_from_db(); delete_token($token); $vars->{'name'} = $flagtype->name; $vars->{'message'} = "flag_type_deleted"; my @flagtypes = Bugzilla::FlagType->get_all; $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes]; $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @flagtypes]; $template->process("admin/flag-type/list.html.tmpl", $vars) || ThrowTemplateError($template->error()); exit; } if ($action eq 'deactivate') { check_token_data($token, 'delete_flagtype'); my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id); ThrowUserError('flag_type_cannot_deactivate', { flagtype => $flagtype }) unless $can_fully_edit; $flagtype->set_is_active(0); $flagtype->update(); delete_token($token); $vars->{'message'} = "flag_type_deactivated"; $vars->{'flag_type'} = $flagtype; my @flagtypes = Bugzilla::FlagType->get_all; $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes]; $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @flagtypes]; $template->process("admin/flag-type/list.html.tmpl", $vars) || ThrowTemplateError($template->error()); exit; } ThrowUserError('unknown_action', {action => $action}); ##################### # Helper subroutines ##################### sub get_products_and_components { my $vars = {}; my $user = Bugzilla->user; my @products; if ($user->in_group('editcomponents')) { @products = Bugzilla::Product->get_all; } else { @products = @{$user->get_products_by_permission('editcomponents')}; } # We require all unique component names. my %components; foreach my $product (@products) { foreach my $component (@{$product->components}) { $components{$component->name} = 1; } } $vars->{'products'} = \@products; $vars->{'components'} = [sort(keys %components)]; return $vars; } sub get_editable_flagtypes { my ($products, $group_id) = @_; my $flagtypes; if (Bugzilla->user->in_group('editcomponents')) { $flagtypes = Bugzilla::FlagType::match({ group => $group_id }); return $flagtypes; } my %visible_flagtypes; foreach my $product (@$products) { foreach my $target ('bug', 'attachment') { my $prod_flagtypes = $product->flag_types->{$target}; $visible_flagtypes{$_->id} ||= $_ foreach @$prod_flagtypes; } } @$flagtypes = sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name } values %visible_flagtypes; # Filter flag types if a group ID is given. $flagtypes = filter_group($flagtypes, $group_id); return $flagtypes; } sub get_settable_groups { my $user = Bugzilla->user; my $groups = $user->in_group('editcomponents') ? [Bugzilla::Group->get_all] : $user->groups; return $groups; } sub filter_group { my ($flag_types, $gid) = @_; return $flag_types unless $gid; my @flag_types = grep {($_->grant_group && $_->grant_group->id == $gid) || ($_->request_group && $_->request_group->id == $gid)} @$flag_types; return \@flag_types; } # Convert the array @clusions('prod_ID:comp_ID') back to a hash of # the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID' sub clusion_array_to_hash { my ($array, $visible_products) = @_; my $user = Bugzilla->user; my $has_privs = $user->in_group('editcomponents'); my %hash; my %products; my %components; foreach my $ids (@$array) { my ($product_id, $component_id) = split(":", $ids); my $product_name = "__Any__"; my $component_name = "__Any__"; if ($product_id) { ($products{$product_id}) = grep { $_->id == $product_id } @$visible_products; next unless $products{$product_id}; $product_name = $products{$product_id}->name; if ($component_id) { ($components{$component_id}) = grep { $_->id == $component_id } @{$products{$product_id}->components}; next unless $components{$component_id}; $component_name = $components{$component_id}->name; } } else { # Users with local editcomponents privs cannot use __Any__:__Any__. next unless $has_privs; # It's illegal to select a component without a product. next if $component_id; } $hash{"$product_name:$component_name"} = $ids; } return \%hash; }