Bug 529974: Let users with local editcomponents privs manage flags for products they can administer
a=LpSolit (module owner) git-svn-id: svn://10.0.0.236/trunk@261622 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
@@ -1 +1 @@
|
||||
7610
|
||||
7611
|
||||
@@ -112,6 +112,9 @@ use constant UPDATE_VALIDATORS => {
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
$class->check_required_create_fields(@_);
|
||||
my $params = $class->run_create_validators(@_);
|
||||
@@ -126,6 +129,9 @@ sub create {
|
||||
|
||||
$flagtype->set_clusions({ inclusions => $inclusions,
|
||||
exclusions => $exclusions });
|
||||
$flagtype->update();
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
return $flagtype;
|
||||
}
|
||||
|
||||
@@ -161,7 +167,7 @@ sub update {
|
||||
FROM flags
|
||||
INNER JOIN bugs
|
||||
ON flags.bug_id = bugs.bug_id
|
||||
LEFT OUTER JOIN flaginclusions AS i
|
||||
LEFT JOIN flaginclusions AS i
|
||||
ON (flags.type_id = i.type_id
|
||||
AND (bugs.product_id = i.product_id
|
||||
OR i.product_id IS NULL)
|
||||
@@ -351,7 +357,6 @@ sub set_request_group { $_[0]->set('request_group_id', $_[1]); }
|
||||
|
||||
sub set_clusions {
|
||||
my ($self, $list) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my %products;
|
||||
|
||||
foreach my $category (keys %$list) {
|
||||
@@ -360,22 +365,33 @@ sub set_clusions {
|
||||
|
||||
foreach my $prod_comp (@{$list->{$category} || []}) {
|
||||
my ($prod_id, $comp_id) = split(':', $prod_comp);
|
||||
my $component;
|
||||
my $prod_name = '__Any__';
|
||||
my $comp_name = '__Any__';
|
||||
# Does the product exist?
|
||||
if ($prod_id && detaint_natural($prod_id)) {
|
||||
$products{$prod_id} ||= new Bugzilla::Product($prod_id);
|
||||
next unless defined $products{$prod_id};
|
||||
if ($prod_id) {
|
||||
$products{$prod_id} ||= Bugzilla::Product->check({ id => $prod_id });
|
||||
detaint_natural($prod_id);
|
||||
$prod_name = $products{$prod_id}->name;
|
||||
|
||||
# Does the component belong to this product?
|
||||
if ($comp_id && detaint_natural($comp_id)) {
|
||||
($component) = grep { $_->id == $comp_id } @{$products{$prod_id}->components};
|
||||
next unless $component;
|
||||
if ($comp_id) {
|
||||
detaint_natural($comp_id)
|
||||
|| ThrowCodeError('param_must_be_numeric',
|
||||
{ function => 'Bugzilla::FlagType::set_clusions' });
|
||||
|
||||
my ($component) = grep { $_->id == $comp_id } @{$products{$prod_id}->components}
|
||||
or ThrowUserError('product_unknown_component',
|
||||
{ product => $prod_name, comp_id => $comp_id });
|
||||
$comp_name = $component->name;
|
||||
}
|
||||
else {
|
||||
$comp_id = 0;
|
||||
}
|
||||
}
|
||||
$prod_id ||= 0;
|
||||
$comp_id ||= 0;
|
||||
my $prod_name = $prod_id ? $products{$prod_id}->name : '__Any__';
|
||||
my $comp_name = $comp_id ? $component->name : '__Any__';
|
||||
else {
|
||||
$prod_id = 0;
|
||||
$comp_id = 0;
|
||||
}
|
||||
$clusions{"$prod_name:$comp_name"} = "$prod_id:$comp_id";
|
||||
$clusions_as_hash{$prod_id}->{$comp_id} = 1;
|
||||
}
|
||||
@@ -520,15 +536,16 @@ sub get_clusions {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $list =
|
||||
$dbh->selectall_arrayref("SELECT products.id, products.name, " .
|
||||
" components.id, components.name " .
|
||||
"FROM flagtypes, flag${type}clusions " .
|
||||
"LEFT OUTER JOIN products " .
|
||||
" ON flag${type}clusions.product_id = products.id " .
|
||||
"LEFT OUTER JOIN components " .
|
||||
" ON flag${type}clusions.component_id = components.id " .
|
||||
"WHERE flagtypes.id = ? " .
|
||||
" AND flag${type}clusions.type_id = flagtypes.id",
|
||||
$dbh->selectall_arrayref("SELECT products.id, products.name,
|
||||
components.id, components.name
|
||||
FROM flagtypes
|
||||
INNER JOIN flag${type}clusions
|
||||
ON flag${type}clusions.type_id = flagtypes.id
|
||||
LEFT JOIN products
|
||||
ON flag${type}clusions.product_id = products.id
|
||||
LEFT JOIN components
|
||||
ON flag${type}clusions.component_id = components.id
|
||||
WHERE flagtypes.id = ?",
|
||||
undef, $id);
|
||||
my (%clusions, %clusions_as_hash);
|
||||
foreach my $data (@$list) {
|
||||
@@ -667,7 +684,7 @@ sub sqlify_criteria {
|
||||
$join_clause .= "AND (e.component_id = $component_id OR e.component_id IS NULL) ";
|
||||
}
|
||||
else {
|
||||
$addl_join_clause = "AND e.component_id IS NULL OR (i.component_id != e.component_id) ";
|
||||
$addl_join_clause = "AND e.component_id IS NULL OR (i.component_id = e.component_id) ";
|
||||
}
|
||||
$join_clause .= "AND ((e.product_id = $product_id $addl_join_clause) OR e.product_id IS NULL)";
|
||||
|
||||
|
||||
@@ -1006,6 +1006,49 @@ sub check_can_admin_product {
|
||||
return $product;
|
||||
}
|
||||
|
||||
sub check_can_admin_flagtype {
|
||||
my ($self, $flagtype_id) = @_;
|
||||
|
||||
my $flagtype = Bugzilla::FlagType->check({ id => $flagtype_id });
|
||||
my $can_fully_edit = 1;
|
||||
|
||||
if (!$self->in_group('editcomponents')) {
|
||||
my $products = $self->get_products_by_permission('editcomponents');
|
||||
# You need editcomponents privs for at least one product to have
|
||||
# a chance to edit the flagtype.
|
||||
scalar(@$products)
|
||||
|| ThrowUserError('auth_failure', {group => 'editcomponents',
|
||||
action => 'edit',
|
||||
object => 'flagtypes'});
|
||||
my $can_admin = 0;
|
||||
my $i = $flagtype->inclusions_as_hash;
|
||||
my $e = $flagtype->exclusions_as_hash;
|
||||
|
||||
# If there is at least one product for which the user doesn't have
|
||||
# editcomponents privs, then don't allow him to do everything with
|
||||
# this flagtype, independently of whether this product is in the
|
||||
# exclusion list or not.
|
||||
my %product_ids;
|
||||
map { $product_ids{$_->id} = 1 } @$products;
|
||||
$can_fully_edit = 0 if grep { !$product_ids{$_} } keys %$i;
|
||||
|
||||
unless ($e->{0}->{0}) {
|
||||
foreach my $product (@$products) {
|
||||
my $id = $product->id;
|
||||
next if $e->{$id}->{0};
|
||||
# If we are here, the product has not been explicitly excluded.
|
||||
# Check whether it's explicitly included, or at least one of
|
||||
# its components.
|
||||
$can_admin = ($i->{0}->{0} || $i->{$id}->{0}
|
||||
|| scalar(grep { !$e->{$id}->{$_} } keys %{$i->{$id}}));
|
||||
last if $can_admin;
|
||||
}
|
||||
}
|
||||
$can_admin || ThrowUserError('flag_type_not_editable', { flagtype => $flagtype });
|
||||
}
|
||||
return wantarray ? ($flagtype, $can_fully_edit) : $flagtype;
|
||||
}
|
||||
|
||||
sub can_request_flag {
|
||||
my ($self, $flag_type) = @_;
|
||||
|
||||
@@ -2261,6 +2304,21 @@ not be aware of the existence of the product.
|
||||
|
||||
Returns: On success, a product object. On failure, an error is thrown.
|
||||
|
||||
=item C<check_can_admin_flagtype($flagtype_id)>
|
||||
|
||||
Description: Checks whether the user is allowed to edit properties of the flag type.
|
||||
If the flag type is also used by some products for which the user
|
||||
hasn't editcomponents privs, then the user is only allowed to edit
|
||||
the inclusion and exclusion lists for products he can administrate.
|
||||
|
||||
Params: $flagtype_id - a flag type ID.
|
||||
|
||||
Returns: On success, a flag type object. On failure, an error is thrown.
|
||||
In list context, a boolean indicating whether the user can edit
|
||||
all properties of the flag type is also returned. The boolean
|
||||
is false if the user can only edit the inclusion and exclusions
|
||||
lists.
|
||||
|
||||
=item C<can_request_flag($flag_type)>
|
||||
|
||||
Description: Checks whether the user can request flags of the given type.
|
||||
|
||||
@@ -38,7 +38,6 @@ use Bugzilla::Group;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Product;
|
||||
use Bugzilla::Component;
|
||||
use Bugzilla::Token;
|
||||
|
||||
# Make sure the user is logged in and has the right privileges.
|
||||
@@ -46,16 +45,18 @@ my $user = Bugzilla->login(LOGIN_REQUIRED);
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
# We need this everywhere.
|
||||
my $vars = get_products_and_components();
|
||||
|
||||
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 $product = $cgi->param('product');
|
||||
@@ -63,13 +64,18 @@ my $component = $cgi->param('component');
|
||||
my $flag_id = $cgi->param('id');
|
||||
|
||||
if ($product) {
|
||||
$product = Bugzilla::Product->check({ name => $product, allow_inaccessible => 1 });
|
||||
# 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($product) } @products;
|
||||
$product || ThrowUserError('product_access_denied', { name => $cgi->param('product') });
|
||||
}
|
||||
|
||||
if ($component) {
|
||||
($product && $product->id)
|
||||
|| ThrowUserError('flag_type_component_without_product');
|
||||
$component = Bugzilla::Component->check({ product => $product, name => $component });
|
||||
($component) = grep { lc($_->name) eq lc($component) } @{$product->components};
|
||||
$component || ThrowUserError('product_unknown_component', { product => $product->name,
|
||||
comp => $cgi->param('component') });
|
||||
}
|
||||
|
||||
# If 'categoryAction' is set, it has priority over 'action'.
|
||||
@@ -78,15 +84,30 @@ if (my ($category_action) = grep { $_ =~ /^categoryAction-(?:\w+)$/ } $cgi->para
|
||||
|
||||
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') {
|
||||
my $category = ($product ? $product->id : 0) . ":" .
|
||||
($component ? $component->id : 0);
|
||||
push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
|
||||
foreach my $category (@categories) {
|
||||
push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
|
||||
}
|
||||
}
|
||||
elsif ($category_action eq 'exclude') {
|
||||
my $category = ($product ? $product->id : 0) . ":" .
|
||||
($component ? $component->id : 0);
|
||||
push(@exclusions, $category) unless grep($_ eq $category, @exclusions);
|
||||
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');
|
||||
@@ -101,11 +122,6 @@ if (my ($category_action) = grep { $_ =~ /^categoryAction-(?:\w+)$/ } $cgi->para
|
||||
}
|
||||
}
|
||||
|
||||
# Convert the array @clusions('prod_ID:comp_ID') back to a hash of
|
||||
# the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
|
||||
my %inclusions = clusion_array_to_hash(\@inclusions);
|
||||
my %exclusions = clusion_array_to_hash(\@exclusions);
|
||||
|
||||
$vars->{'groups'} = [Bugzilla::Group->get_all];
|
||||
$vars->{'action'} = $action;
|
||||
|
||||
@@ -122,11 +138,13 @@ if (my ($category_action) = grep { $_ =~ /^categoryAction-(?:\w+)$/ } $cgi->para
|
||||
$type->{'request_group'} = {};
|
||||
$type->{'request_group'}->{'name'} = $cgi->param('request_group');
|
||||
|
||||
$type->{'inclusions'} = \%inclusions;
|
||||
$type->{'exclusions'} = \%exclusions;
|
||||
$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());
|
||||
@@ -165,8 +183,7 @@ if ($action eq 'list') {
|
||||
}
|
||||
# If no product is given, then show all flag types available.
|
||||
else {
|
||||
my $flagtypes = Bugzilla::FlagType::match({ group => $group_id });
|
||||
|
||||
my $flagtypes = get_editable_flagtypes(\@products, $group_id);
|
||||
$bug_flagtypes = [grep { $_->target_type eq 'bug' } @$flagtypes];
|
||||
$attach_flagtypes = [grep { $_->target_type eq 'attachment' } @$flagtypes];
|
||||
}
|
||||
@@ -202,8 +219,12 @@ if ($action eq 'enter') {
|
||||
|
||||
$vars->{'action'} = 'insert';
|
||||
$vars->{'token'} = issue_session_token('add_flagtype');
|
||||
$vars->{'type'} = { 'target_type' => $type,
|
||||
'inclusions' => { '__Any__:__Any__' => '0:0' } };
|
||||
$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'} = [Bugzilla::Group->get_all];
|
||||
|
||||
@@ -213,7 +234,19 @@ if ($action eq 'enter') {
|
||||
}
|
||||
|
||||
if ($action eq 'edit' || $action eq 'copy') {
|
||||
$vars->{'type'} = Bugzilla::FlagType->check({ id => $flag_id });
|
||||
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";
|
||||
@@ -249,6 +282,12 @@ if ($action eq 'insert') {
|
||||
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,
|
||||
@@ -270,9 +309,9 @@ if ($action eq 'insert') {
|
||||
$vars->{'name'} = $flagtype->name;
|
||||
$vars->{'message'} = "flag_type_created";
|
||||
|
||||
my @flagtypes = Bugzilla::FlagType->get_all;
|
||||
$vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes];
|
||||
$vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @flagtypes];
|
||||
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());
|
||||
@@ -295,17 +334,34 @@ if ($action eq 'update') {
|
||||
my @inclusions = $cgi->param('inclusions');
|
||||
my @exclusions = $cgi->param('exclusions');
|
||||
|
||||
my $flagtype = Bugzilla::FlagType->check({ id => $flag_id });
|
||||
$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);
|
||||
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();
|
||||
@@ -316,9 +372,9 @@ if ($action eq 'update') {
|
||||
$vars->{'changes'} = $changes;
|
||||
$vars->{'message'} = 'flag_type_updated';
|
||||
|
||||
my @flagtypes = Bugzilla::FlagType->get_all;
|
||||
$vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes];
|
||||
$vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @flagtypes];
|
||||
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());
|
||||
@@ -326,7 +382,10 @@ if ($action eq 'update') {
|
||||
}
|
||||
|
||||
if ($action eq 'confirmdelete') {
|
||||
$vars->{'flag_type'} = Bugzilla::FlagType->check({ id => $flag_id });
|
||||
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)
|
||||
@@ -337,7 +396,9 @@ if ($action eq 'confirmdelete') {
|
||||
if ($action eq 'delete') {
|
||||
check_token_data($token, 'delete_flagtype');
|
||||
|
||||
my $flagtype = Bugzilla::FlagType->check({ id => $flag_id });
|
||||
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);
|
||||
@@ -357,7 +418,9 @@ if ($action eq 'delete') {
|
||||
if ($action eq 'deactivate') {
|
||||
check_token_data($token, 'delete_flagtype');
|
||||
|
||||
my $flagtype = Bugzilla::FlagType->check({ id => $flag_id });
|
||||
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();
|
||||
|
||||
@@ -383,8 +446,15 @@ ThrowUserError('unknown_action', {action => $action});
|
||||
|
||||
sub get_products_and_components {
|
||||
my $vars = {};
|
||||
my $user = Bugzilla->user;
|
||||
|
||||
my @products = Bugzilla::Product->get_all;
|
||||
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) {
|
||||
@@ -397,6 +467,29 @@ sub get_products_and_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 filter_group {
|
||||
my ($flag_types, $gid) = @_;
|
||||
return $flag_types unless $gid;
|
||||
@@ -410,24 +503,38 @@ sub filter_group {
|
||||
# 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 = shift;
|
||||
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) {
|
||||
trick_taint($ids);
|
||||
my ($product_id, $component_id) = split(":", $ids);
|
||||
my $product_name = "__Any__";
|
||||
if ($product_id) {
|
||||
$products{$product_id} ||= new Bugzilla::Product($product_id);
|
||||
$product_name = $products{$product_id}->name if $products{$product_id};
|
||||
}
|
||||
my $component_name = "__Any__";
|
||||
if ($component_id) {
|
||||
$components{$component_id} ||= new Bugzilla::Component($component_id);
|
||||
$component_name = $components{$component_id}->name if $components{$component_id};
|
||||
|
||||
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;
|
||||
return \%hash;
|
||||
}
|
||||
|
||||
@@ -99,10 +99,6 @@ tbody.file pre:empty {
|
||||
width: 3em;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: red
|
||||
}
|
||||
|
||||
table.attachment_info th {
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
|
||||
@@ -385,6 +385,10 @@ input.requestee {
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.throw_error {
|
||||
background-color: #ff0000;
|
||||
color: black;
|
||||
|
||||
@@ -76,7 +76,8 @@
|
||||
<a href="editcomponents.cgi">components</a>, <a href="editversions.cgi">versions</a>
|
||||
and <a href="editmilestones.cgi">milestones</a> directly.</dd>
|
||||
|
||||
[% class = user.in_group('editcomponents') ? "" : "forbidden" %]
|
||||
[% class = (user.in_group('editcomponents')
|
||||
|| user.get_products_by_permission('editcomponents').size) ? "" : "forbidden" %]
|
||||
<dt id="flags" class="[% class %]"><a href="editflagtypes.cgi">Flags</a></dt>
|
||||
<dd class="[% class %]">A flag is a custom 4-states attribute of [% terms.bugs %]
|
||||
and/or attachments. These states are: granted, denied, requested and undefined.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#
|
||||
# Contributor(s): Myk Melez <myk@mozilla.org>
|
||||
# Mark Bickford <markhb@maine.rr.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#%]
|
||||
|
||||
[% PROCESS global/variables.none.tmpl %]
|
||||
@@ -42,22 +43,24 @@
|
||||
table#form th { text-align: right; vertical-align: baseline; white-space: nowrap; }
|
||||
table#form td { text-align: left; vertical-align: baseline; }
|
||||
"
|
||||
onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, '__Any__');"
|
||||
onload="var f = document.forms['flagtype_properties'];
|
||||
selectProduct(f.product, f.component, null, null, '__Any__');"
|
||||
javascript_urls=["js/productform.js"]
|
||||
doc_section = doc_section
|
||||
%]
|
||||
|
||||
<form method="post" action="editflagtypes.cgi">
|
||||
<form id="flagtype_properties" method="post" action="editflagtypes.cgi">
|
||||
<input type="hidden" name="action" value="[% action FILTER html %]">
|
||||
<input type="hidden" name="can_fully_edit" value="[% can_fully_edit FILTER html %]">
|
||||
<input type="hidden" name="id" value="[% type.id %]">
|
||||
<input type="hidden" name="token" value="[% token FILTER html %]">
|
||||
<input type="hidden" name="target_type" value="[% type.target_type FILTER html %]">
|
||||
<input type="hidden" name="check_clusions" value="[% check_clusions FILTER none %]">
|
||||
[% FOREACH category = type.inclusions %]
|
||||
<input type="hidden" name="inclusions" value="[% category.value FILTER html %]">
|
||||
[% FOREACH category = inclusions.values %]
|
||||
<input type="hidden" name="inclusions" value="[% category FILTER html %]">
|
||||
[% END %]
|
||||
[% FOREACH category = type.exclusions %]
|
||||
<input type="hidden" name="exclusions" value="[% category.value FILTER html %]">
|
||||
[% FOREACH category = exclusions.values %]
|
||||
<input type="hidden" name="exclusions" value="[% category FILTER html %]">
|
||||
[% END %]
|
||||
|
||||
[%# Add a hidden button at the top of the form so that the user pressing "return"
|
||||
@@ -69,8 +72,8 @@
|
||||
<th>Name:</th>
|
||||
<td>
|
||||
a short name identifying this type.<br>
|
||||
<input type="text" name="name" value="[% type.name FILTER html %]"
|
||||
size="50" maxlength="50">
|
||||
<input type="text" name="name" value="[% type.name FILTER html %]" size="50"
|
||||
maxlength="50" [%- ' disabled="disabled"' UNLESS can_fully_edit %]>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -83,6 +86,7 @@
|
||||
minrows = 4
|
||||
cols = 80
|
||||
defaultcontent = type.description
|
||||
disabled = !can_fully_edit
|
||||
%]
|
||||
</td>
|
||||
</tr>
|
||||
@@ -94,6 +98,12 @@
|
||||
the products/components to which [% type.target_type == "bug" ? terms.bugs : "attachments" %]
|
||||
must (inclusions) or must not (exclusions) belong in order for users
|
||||
to be able to set flags of this type for them.
|
||||
[% UNLESS can_fully_edit %]
|
||||
<p class="warning">This flagtype also applies to some products you are not allowed
|
||||
to edit (and so which are not displayed in the lists below). Your limited privileges
|
||||
means you are only allowed to add and remove this flagtype to/from products you can
|
||||
edit, but not to edit other properties of the flagtype.</p>
|
||||
[% END %]
|
||||
<table>
|
||||
<tr>
|
||||
<td style="vertical-align: top;">
|
||||
@@ -101,17 +111,13 @@
|
||||
<select name="product" onchange="selectProduct(this, this.form.component, null, null, '__Any__');">
|
||||
<option value="">__Any__</option>
|
||||
[% FOREACH prod = products %]
|
||||
<option value="[% prod.name FILTER html %]"
|
||||
[% "selected" IF type.product.name == prod.name %]>
|
||||
[% prod.name FILTER html %]</option>
|
||||
<option value="[% prod.name FILTER html %]">[% prod.name FILTER html %]</option>
|
||||
[% END %]
|
||||
</select><br>
|
||||
<select name="component">
|
||||
<option value="">__Any__</option>
|
||||
[% FOREACH comp = components %]
|
||||
<option value="[% comp FILTER html %]"
|
||||
[% "selected" IF type.component.name == comp %]>
|
||||
[% comp FILTER html %]</option>
|
||||
<option value="[% comp FILTER html %]">[% comp FILTER html %]</option>
|
||||
[% END %]
|
||||
</select><br>
|
||||
<input type="submit" name="categoryAction-include" value="Include">
|
||||
@@ -119,12 +125,12 @@
|
||||
</td>
|
||||
<td style="vertical-align: top;">
|
||||
<b>Inclusions:</b><br>
|
||||
[% PROCESS "global/select-menu.html.tmpl" name="inclusion_to_remove" multiple="1" size="7" options=type.inclusions %]<br>
|
||||
[% PROCESS category_select name="inclusion_to_remove" categories = inclusions %]<br>
|
||||
<input type="submit" name="categoryAction-removeInclusion" value="Remove Inclusion">
|
||||
</td>
|
||||
<td style="vertical-align: top;">
|
||||
<b>Exclusions:</b><br>
|
||||
[% PROCESS "global/select-menu.html.tmpl" name="exclusion_to_remove" multiple="1" size="7" options=type.exclusions %]<br>
|
||||
[% PROCESS category_select name="exclusion_to_remove" categories = exclusions %]<br>
|
||||
<input type="submit" name="categoryAction-removeExclusion" value="Remove Exclusion">
|
||||
</td>
|
||||
</tr>
|
||||
@@ -139,7 +145,8 @@
|
||||
this type will be sorted when displayed to users in a list; ignore if you
|
||||
don't care what order the types appear in or if you want them to appear
|
||||
in alphabetical order.<br>
|
||||
<input type="text" name="sortkey" value="[% type.sortkey || 1 %]" size="5" maxlength="5">
|
||||
<input type="text" name="sortkey" value="[% type.sortkey || 1 %]" size="5" maxlength="5"
|
||||
[%- ' disabled="disabled"' UNLESS can_fully_edit %]>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -147,6 +154,7 @@
|
||||
<th> </th>
|
||||
<td>
|
||||
<input type="checkbox" id="is_active" name="is_active"
|
||||
[%- ' disabled="disabled"' UNLESS can_fully_edit %]
|
||||
[% " checked" IF type.is_active || !type.is_active.defined %]>
|
||||
<label for="is_active">active (flags of this type appear in the UI and can be set)</label>
|
||||
</td>
|
||||
@@ -156,6 +164,7 @@
|
||||
<th> </th>
|
||||
<td>
|
||||
<input type="checkbox" id="is_requestable" name="is_requestable"
|
||||
[%- ' disabled="disabled"' UNLESS can_fully_edit %]
|
||||
[% " checked" IF type.is_requestable || !type.is_requestable.defined %]>
|
||||
<label for="is_requestable">requestable (users can ask for flags of this type to be set)</label>
|
||||
</td>
|
||||
@@ -172,7 +181,8 @@
|
||||
<kbd>[% Param('emailsuffix') %]</kbd> will <em>not</em> be appended
|
||||
to these addresses, so you should add it explicitly if so desired.
|
||||
[% END %]<br>
|
||||
<input type="text" name="cc_list" value="[% type.cc_list FILTER html %]" size="80" maxlength="200">
|
||||
<input type="text" name="cc_list" value="[% type.cc_list FILTER html %]" size="80"
|
||||
maxlength="200" [%- ' disabled="disabled"' UNLESS can_fully_edit %]>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -180,6 +190,7 @@
|
||||
<th> </th>
|
||||
<td>
|
||||
<input type="checkbox" id="is_requesteeble" name="is_requesteeble"
|
||||
[%- ' disabled="disabled"' UNLESS can_fully_edit %]
|
||||
[% " checked" IF type.is_requesteeble || !type.is_requesteeble.defined %]>
|
||||
<label for="is_requesteeble">specifically requestable (users can ask specific other users
|
||||
to set flags of this type as opposed to just asking the wind)</label>
|
||||
@@ -190,6 +201,7 @@
|
||||
<th> </th>
|
||||
<td>
|
||||
<input type="checkbox" id="is_multiplicable" name="is_multiplicable"
|
||||
[%- ' disabled="disabled"' UNLESS can_fully_edit %]
|
||||
[% " checked" IF type.is_multiplicable || !type.is_multiplicable.defined %]>
|
||||
<label for="is_multiplicable">multiplicable (multiple flags of this type can be set on
|
||||
the same [% type.target_type == "bug" ? terms.bug : "attachment" %])</label>
|
||||
@@ -201,7 +213,7 @@
|
||||
<td>
|
||||
the group allowed to grant/deny flags of this type
|
||||
(to allow all users to grant/deny these flags, select no group).<br>
|
||||
[% PROCESS select selname = "grant_group" %]
|
||||
[% PROCESS group_select selname = "grant_group" %]
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -211,7 +223,7 @@
|
||||
if flags of this type are requestable, the group allowed to request them
|
||||
(to allow all users to request these flags, select no group).<br>
|
||||
Note that the request group alone has no effect if the grant group is not defined!<br>
|
||||
[% PROCESS select selname = "request_group" %]
|
||||
[% PROCESS group_select selname = "request_group" %]
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -233,8 +245,8 @@
|
||||
[%# Block for SELECT fields #%]
|
||||
[%############################################################################%]
|
||||
|
||||
[% BLOCK select %]
|
||||
<select name="[% selname %]" id="[% selname %]">
|
||||
[% BLOCK group_select %]
|
||||
<select name="[% selname %]" id="[% selname %]" [%- ' disabled="disabled"' UNLESS can_fully_edit %]>
|
||||
<option value="">(no group)</option>
|
||||
[% FOREACH group = groups %]
|
||||
<option value="[% group.name FILTER html %]"
|
||||
@@ -244,3 +256,13 @@
|
||||
[% END %]
|
||||
</select>
|
||||
[% END %]
|
||||
|
||||
[% BLOCK category_select %]
|
||||
<select name="[% name FILTER html %]" multiple="multiple" size="7">
|
||||
[% FOREACH option = categories.keys.sort %]
|
||||
<option value="[% categories.$option FILTER html %]">
|
||||
[% option FILTER html %]
|
||||
</option>
|
||||
[% END %]
|
||||
</select>
|
||||
[% END %]
|
||||
@@ -641,6 +641,16 @@
|
||||
[% END %]
|
||||
is invalid.
|
||||
|
||||
[% ELSIF error == "flag_type_cannot_deactivate" %]
|
||||
[% title = "Cannot Deactivate Flag Type" %]
|
||||
Sorry, but the flag type '[% flagtype.name FILTER html %]' also applies to some
|
||||
products you cannot see, and so you are not allowed to deactivate it.
|
||||
|
||||
[% ELSIF error == "flag_type_cannot_delete" %]
|
||||
[% title = "Flag Type Deletion Not Allowed" %]
|
||||
Sorry, but the flag type '[% flagtype.name FILTER html %]' also applies to some
|
||||
products you cannot see, and so you are not allowed to delete it.
|
||||
|
||||
[% ELSIF error == "flag_type_cc_list_invalid" %]
|
||||
[% title = "Flag Type CC List Invalid" %]
|
||||
[% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags'} %]
|
||||
@@ -661,6 +671,11 @@
|
||||
The name <em>[% name FILTER html %]</em> must be 1-50 characters long
|
||||
and must not contain any spaces or commas.
|
||||
|
||||
[% ELSIF error == "flag_type_not_editable" %]
|
||||
[% title = "Flag Type Not Editable" %]
|
||||
You are not allowed to edit properties of the '[% flagtype.name FILTER html %]'
|
||||
flag type, because this flag type is not available for the products you can administer.
|
||||
|
||||
[% ELSIF error == "flag_type_not_multiplicable" %]
|
||||
[% docslinks = {'flags-overview.html' => 'An overview on Flags',
|
||||
'flags.html' => 'Using Flags'} %]
|
||||
@@ -1311,6 +1326,7 @@
|
||||
[%+ constants.USER_PASSWORD_MIN_LENGTH FILTER html %] characters long.
|
||||
|
||||
[% ELSIF error == "product_access_denied" %]
|
||||
[% title = "Product Access Denied" %]
|
||||
Either the product
|
||||
[%+ IF id.defined %]
|
||||
with the id [% id FILTER html %]
|
||||
@@ -1388,6 +1404,15 @@
|
||||
'versions.html' => 'Administering versions'} %]
|
||||
You must enter a valid version to create a new product.
|
||||
|
||||
[% ELSIF error == "product_unknown_component" %]
|
||||
[% title = "Unknown Component" %]
|
||||
Product '[% product FILTER html %]' has no component
|
||||
[% IF comp_id %]
|
||||
with ID [% comp_id FILTER html %].
|
||||
[% ELSE %]
|
||||
named '[% comp FILTER html %]'.
|
||||
[% END %]
|
||||
|
||||
[% ELSIF error == "query_name_exists" %]
|
||||
[% title = "Search Name Already In Use" %]
|
||||
The name <em>[% name FILTER html %]</em> is already used by another
|
||||
|
||||
Reference in New Issue
Block a user