diff options
author | Frédéric Buclin <LpSolit@gmail.com> | 2010-12-04 02:22:49 +0100 |
---|---|---|
committer | Frédéric Buclin <LpSolit@gmail.com> | 2010-12-04 02:22:49 +0100 |
commit | 6fac08b94b6cd67d8dd7ae8f7eeb5de5233c59d7 (patch) | |
tree | 3125cfcf7eddc39feb0966957bc3d59265fe112b | |
parent | 4c2a23f4df1041fe21d534b38654a0d1c911282e (diff) | |
download | bugzilla-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.pm | 63 | ||||
-rw-r--r-- | Bugzilla/User.pm | 58 | ||||
-rwxr-xr-x | editflagtypes.cgi | 215 | ||||
-rw-r--r-- | skins/standard/attachment.css | 4 | ||||
-rw-r--r-- | skins/standard/global.css | 4 | ||||
-rw-r--r-- | template/en/default/admin/admin.html.tmpl | 3 | ||||
-rw-r--r-- | template/en/default/admin/flag-type/edit.html.tmpl | 66 | ||||
-rw-r--r-- | template/en/default/global/user-error.html.tmpl | 25 |
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> </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 %]
\ 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 |