546 lines
20 KiB
Perl
Executable File
546 lines
20 KiB
Perl
Executable File
#!/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);
|
|
|
|
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 they 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')) {
|
|
if (Bugzilla->params->{useclassification}) {
|
|
# We want products grouped by classifications.
|
|
@products = map { @{ $_->products } } Bugzilla::Classification->get_all;
|
|
}
|
|
else {
|
|
@products = Bugzilla::Product->get_all;
|
|
}
|
|
}
|
|
else {
|
|
@products = @{$user->get_products_by_permission('editcomponents')};
|
|
|
|
if (Bugzilla->params->{useclassification}) {
|
|
my %class;
|
|
push(@{$class{$_->classification_id}}, $_) foreach @products;
|
|
|
|
# Let's sort the list by classifications.
|
|
@products = ();
|
|
push(@products, @{$class{$_->id} || []}) foreach Bugzilla::Classification->get_all;
|
|
}
|
|
}
|
|
|
|
my %components;
|
|
foreach my $product (@products) {
|
|
$components{$_->name} = 1 foreach @{$product->components};
|
|
}
|
|
$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;
|
|
}
|