summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFrédéric Buclin <LpSolit@gmail.com>2010-12-04 02:22:49 +0100
committerFrédéric Buclin <LpSolit@gmail.com>2010-12-04 02:22:49 +0100
commit6fac08b94b6cd67d8dd7ae8f7eeb5de5233c59d7 (patch)
tree3125cfcf7eddc39feb0966957bc3d59265fe112b
parent4c2a23f4df1041fe21d534b38654a0d1c911282e (diff)
downloadbugzilla-6fac08b94b6cd67d8dd7ae8f7eeb5de5233c59d7.tar.gz
bugzilla-6fac08b94b6cd67d8dd7ae8f7eeb5de5233c59d7.tar.xz
Bug 529974: Let users with local editcomponents privs manage flags for products they can administer
a=LpSolit (module owner)
-rw-r--r--Bugzilla/FlagType.pm63
-rw-r--r--Bugzilla/User.pm58
-rwxr-xr-xeditflagtypes.cgi215
-rw-r--r--skins/standard/attachment.css4
-rw-r--r--skins/standard/global.css4
-rw-r--r--template/en/default/admin/admin.html.tmpl3
-rw-r--r--template/en/default/admin/flag-type/edit.html.tmpl66
-rw-r--r--template/en/default/global/user-error.html.tmpl25
8 files changed, 334 insertions, 104 deletions
diff --git a/Bugzilla/FlagType.pm b/Bugzilla/FlagType.pm
index d6a122609..bd3f7b054 100644
--- a/Bugzilla/FlagType.pm
+++ b/Bugzilla/FlagType.pm
@@ -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;
+ }
+ }
+ else {
+ $prod_id = 0;
+ $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__';
$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)";
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index de2d0dcc7..47f923f20 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -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.
diff --git a/editflagtypes.cgi b/editflagtypes.cgi
index ecfa32ca8..4d11eecdb 100755
--- a/editflagtypes.cgi
+++ b/editflagtypes.cgi
@@ -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__";
+ my $component_name = "__Any__";
+
if ($product_id) {
- $products{$product_id} ||= new Bugzilla::Product($product_id);
- $product_name = $products{$product_id}->name if $products{$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;
+ }
}
- 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};
+ 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;
}
diff --git a/skins/standard/attachment.css b/skins/standard/attachment.css
index b42b2224c..3c93ec6fb 100644
--- a/skins/standard/attachment.css
+++ b/skins/standard/attachment.css
@@ -99,10 +99,6 @@ tbody.file pre:empty {
width: 3em;
}
-.warning {
- color: red
-}
-
table.attachment_info th {
text-align: right;
vertical-align: top;
diff --git a/skins/standard/global.css b/skins/standard/global.css
index 0deb4b94a..50f5d442b 100644
--- a/skins/standard/global.css
+++ b/skins/standard/global.css
@@ -385,6 +385,10 @@ input.requestee {
font-size: x-large;
}
+.warning {
+ color: red;
+}
+
.throw_error {
background-color: #ff0000;
color: black;
diff --git a/template/en/default/admin/admin.html.tmpl b/template/en/default/admin/admin.html.tmpl
index 145360bfa..98f729b02 100644
--- a/template/en/default/admin/admin.html.tmpl
+++ b/template/en/default/admin/admin.html.tmpl
@@ -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.
diff --git a/template/en/default/admin/flag-type/edit.html.tmpl b/template/en/default/admin/flag-type/edit.html.tmpl
index eb0211377..13db9fab1 100644
--- a/template/en/default/admin/flag-type/edit.html.tmpl
+++ b/template/en/default/admin/flag-type/edit.html.tmpl
@@ -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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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 %] \ No newline at end of file
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
index 5aa8955fd..410636c60 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -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