Commit 67fe43be authored by Frédéric Buclin's avatar Frédéric Buclin

Bug 523205: editflagtypes.cgi should use Bugzilla::FlagType methods to create and edit flag types

a=LpSolit
parent f770095a
...@@ -48,6 +48,7 @@ whose names start with _ or are specifically noted as being private. ...@@ -48,6 +48,7 @@ whose names start with _ or are specifically noted as being private.
=cut =cut
use Bugzilla::Constants;
use Bugzilla::Error; use Bugzilla::Error;
use Bugzilla::Util; use Bugzilla::Util;
use Bugzilla::Group; use Bugzilla::Group;
...@@ -58,57 +59,124 @@ use base qw(Bugzilla::Object); ...@@ -58,57 +59,124 @@ use base qw(Bugzilla::Object);
#### Initialization #### #### Initialization ####
############################### ###############################
=begin private use constant DB_TABLE => 'flagtypes';
use constant LIST_ORDER => 'sortkey, name';
=head1 PRIVATE VARIABLES/CONSTANTS
=over
=item C<DB_COLUMNS>
basic sets of columns and tables for getting flag types from the
database.
=back
=cut
use constant DB_COLUMNS => qw( use constant DB_COLUMNS => qw(
flagtypes.id id
flagtypes.name name
flagtypes.description description
flagtypes.cc_list cc_list
flagtypes.target_type target_type
flagtypes.sortkey sortkey
flagtypes.is_active is_active
flagtypes.is_requestable is_requestable
flagtypes.is_requesteeble is_requesteeble
flagtypes.is_multiplicable is_multiplicable
flagtypes.grant_group_id grant_group_id
flagtypes.request_group_id request_group_id
); );
=pod use constant UPDATE_COLUMNS => qw(
name
description
cc_list
sortkey
is_active
is_requestable
is_requesteeble
is_multiplicable
grant_group_id
request_group_id
);
=over use constant VALIDATORS => {
name => \&_check_name,
description => \&_check_description,
cc_list => \&_check_cc_list,
target_type => \&_check_target_type,
sortkey => \&_check_sortey,
is_active => \&Bugzilla::Object::check_boolean,
is_requestable => \&Bugzilla::Object::check_boolean,
is_requesteeble => \&Bugzilla::Object::check_boolean,
is_multiplicable => \&Bugzilla::Object::check_boolean,
grant_group => \&_check_group,
request_group => \&_check_group,
};
use constant UPDATE_VALIDATORS => {
grant_group_id => \&_check_group,
request_group_id => \&_check_group,
};
###############################
=item C<DB_TABLE> sub create {
my $class = shift;
Which database(s) is the data coming from? $class->check_required_create_fields(@_);
my $params = $class->run_create_validators(@_);
Note: when adding tables to DB_TABLE, make sure to include the separator # Extract everything which is not a valid column name.
(i.e. words like "LEFT OUTER JOIN") before the table name, since tables take $params->{grant_group_id} = delete $params->{grant_group};
multiple separators based on the join type, and therefore it is not possible $params->{request_group_id} = delete $params->{request_group};
to join them later using a single known separator. my $inclusions = delete $params->{inclusions};
my $exclusions = delete $params->{exclusions};
=back my $flagtype = $class->insert_create_data($params);
=end private $flagtype->set_clusions({ inclusions => $inclusions,
exclusions => $exclusions });
return $flagtype;
}
=cut sub update {
my $self = shift;
my $dbh = Bugzilla->dbh;
use constant DB_TABLE => 'flagtypes'; $dbh->bz_start_transaction();
use constant LIST_ORDER => 'flagtypes.sortkey, flagtypes.name'; my $changes = $self->SUPER::update(@_);
# Clear existing flags for bugs/attachments in categories no longer on
# the list of inclusions or that have been added to the list of exclusions.
my $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
FROM flags
INNER JOIN bugs
ON flags.bug_id = bugs.bug_id
LEFT OUTER JOIN flaginclusions AS i
ON (flags.type_id = i.type_id
AND (bugs.product_id = i.product_id
OR i.product_id IS NULL)
AND (bugs.component_id = i.component_id
OR i.component_id IS NULL))
WHERE flags.type_id = ?
AND i.type_id IS NULL',
undef, $self->id);
Bugzilla::Flag->force_retarget($flag_ids);
$flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
FROM flags
INNER JOIN bugs
ON flags.bug_id = bugs.bug_id
INNER JOIN flagexclusions AS e
ON flags.type_id = e.type_id
WHERE flags.type_id = ?
AND (bugs.product_id = e.product_id
OR e.product_id IS NULL)
AND (bugs.component_id = e.component_id
OR e.component_id IS NULL)',
undef, $self->id);
Bugzilla::Flag->force_retarget($flag_ids);
# Silently remove requestees from flags which are no longer
# specifically requestable.
if (!$self->is_requesteeble) {
$dbh->do('UPDATE flags SET requestee_id = NULL WHERE type_id = ?',
undef, $self->id);
}
$dbh->bz_commit_transaction();
return $changes;
}
############################### ###############################
#### Accessors ###### #### Accessors ######
...@@ -179,10 +247,121 @@ sub sortkey { return $_[0]->{'sortkey'}; } ...@@ -179,10 +247,121 @@ sub sortkey { return $_[0]->{'sortkey'}; }
sub request_group_id { return $_[0]->{'request_group_id'}; } sub request_group_id { return $_[0]->{'request_group_id'}; }
sub grant_group_id { return $_[0]->{'grant_group_id'}; } sub grant_group_id { return $_[0]->{'grant_group_id'}; }
################################
# Validators
################################
sub _check_name {
my ($invocant, $name) = @_;
$name = trim($name);
($name && $name !~ /[\s,]/ && length($name) <= 50)
|| ThrowUserError('flag_type_name_invalid', { name => $name });
return $name;
}
sub _check_description {
my ($invocant, $desc) = @_;
$desc = trim($desc);
$desc || ThrowUserError('flag_type_description_invalid');
return $desc;
}
sub _check_cc_list {
my ($invocant, $cc_list) = @_;
length($cc_list) <= 200
|| ThrowUserError('flag_type_cc_list_invalid', { cc_list => $cc_list });
my @addresses = split(/[,\s]+/, $cc_list);
# We do not call Util::validate_email_syntax because these
# addresses do not require to match 'emailregexp' and do not
# depend on 'emailsuffix'. So we limit ourselves to a simple
# sanity check:
# - match the syntax of a fully qualified email address;
# - do not contain any illegal character.
foreach my $address (@addresses) {
($address =~ /^[\w\.\+\-=]+@[\w\.\-]+\.[\w\-]+$/
&& $address !~ /[\\\(\)<>&,;:"\[\] \t\r\n]/)
|| ThrowUserError('illegal_email_address',
{addr => $address, default => 1});
}
return $cc_list;
}
sub _check_target_type {
my ($invocant, $target_type) = @_;
($target_type eq 'bug' || $target_type eq 'attachment')
|| ThrowCodeError('flag_type_target_type_invalid', { target_type => $target_type });
return $target_type;
}
sub _check_sortey {
my ($invocant, $sortkey) = @_;
(detaint_natural($sortkey) && $sortkey <= MAX_SMALLINT)
|| ThrowUserError('flag_type_sortkey_invalid', { sortkey => $sortkey });
return $sortkey;
}
sub _check_group {
my ($invocant, $group) = @_;
return unless $group;
trick_taint($group);
$group = Bugzilla::Group->check($group);
return $group->id;
}
############################### ###############################
#### Methods #### #### Methods ####
############################### ###############################
sub set_name { $_[0]->set('name', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); }
sub set_cc_list { $_[0]->set('cc_list', $_[1]); }
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
sub set_is_active { $_[0]->set('is_active', $_[1]); }
sub set_is_requestable { $_[0]->set('is_requestable', $_[1]); }
sub set_is_specifically_requestable { $_[0]->set('is_requesteeble', $_[1]); }
sub set_is_multiplicable { $_[0]->set('is_multiplicable', $_[1]); }
sub set_grant_group { $_[0]->set('grant_group_id', $_[1]); }
sub set_request_group { $_[0]->set('request_group_id', $_[1]); }
sub set_clusions {
my ($self, $list) = @_;
my $dbh = Bugzilla->dbh;
my $flag_id = $self->id;
my %products;
foreach my $category (keys %$list) {
my $sth = $dbh->prepare("INSERT INTO flag$category
(type_id, product_id, component_id) VALUES (?, ?, ?)");
$dbh->do("DELETE FROM flag$category WHERE type_id = ?", undef, $flag_id);
foreach my $prod_comp (@{$list->{$category} || []}) {
my ($prod_id, $comp_id) = split(':', $prod_comp);
# 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};
# Does the component belong to this product?
if ($comp_id && detaint_natural($comp_id)) {
my $found = grep { $_->id == $comp_id } @{$products{$prod_id}->components};
next unless $found;
}
}
$prod_id ||= undef;
$comp_id ||= undef;
$sth->execute($flag_id, $prod_id, $comp_id);
}
}
}
=pod =pod
=over =over
......
...@@ -39,72 +39,104 @@ use Bugzilla::Util; ...@@ -39,72 +39,104 @@ use Bugzilla::Util;
use Bugzilla::Error; use Bugzilla::Error;
use Bugzilla::Product; use Bugzilla::Product;
use Bugzilla::Component; use Bugzilla::Component;
use Bugzilla::Bug;
use Bugzilla::Attachment;
use Bugzilla::Token; use Bugzilla::Token;
local our $cgi = Bugzilla->cgi; # Make sure the user is logged in and has the right privileges.
local our $template = Bugzilla->template; my $user = Bugzilla->login(LOGIN_REQUIRED);
local our $vars = {}; my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
# We need this everywhere. # We need this everywhere.
$vars = get_products_and_components($vars); my $vars = get_products_and_components();
print $cgi->header();
# Make sure the user is logged in and is an administrator.
my $user = Bugzilla->login(LOGIN_REQUIRED);
$user->in_group('editcomponents') $user->in_group('editcomponents')
|| ThrowUserError("auth_failure", {group => "editcomponents", || ThrowUserError("auth_failure", {group => "editcomponents",
action => "edit", action => "edit",
object => "flagtypes"}); object => "flagtypes"});
################################################################################
# Main Body Execution
################################################################################
# All calls to this script should contain an "action" variable whose value
# determines what the user wants to do. The code below checks the value of
# that variable and runs the appropriate code.
# Determine whether to use the action specified by the user or the default.
my $action = $cgi->param('action') || 'list'; my $action = $cgi->param('action') || 'list';
my $token = $cgi->param('token'); my $token = $cgi->param('token');
my @categoryActions; my $product = $cgi->param('product');
my $component = $cgi->param('component');
my $flag_id = $cgi->param('id');
if (@categoryActions = grep(/^categoryAction-.+/, $cgi->param())) { if ($product) {
$categoryActions[0] =~ s/^categoryAction-//; $product = Bugzilla::Product->check({ name => $product, allow_inaccessible => 1 });
processCategoryChange($categoryActions[0], $token);
exit;
} }
if ($action eq 'list') { list(); } if ($component) {
elsif ($action eq 'enter') { edit($action); } ($product && $product->id)
elsif ($action eq 'copy') { edit($action); } || ThrowUserError('flag_type_component_without_product');
elsif ($action eq 'edit') { edit($action); } $component = Bugzilla::Component->check({ product => $product, name => $component });
elsif ($action eq 'insert') { insert($token); }
elsif ($action eq 'update') { update($token); }
elsif ($action eq 'confirmdelete') { confirmDelete(); }
elsif ($action eq 'delete') { deleteType($token); }
elsif ($action eq 'deactivate') { deactivate($token); }
else {
ThrowUserError('unknown_action', {action => $action});
} }
exit; # If 'categoryAction' is set, it has priority over 'action'.
if (my ($category_action) = grep { $_ =~ /^categoryAction-(?:\w+)$/ } $cgi->param()) {
$category_action =~ s/^categoryAction-//;
################################################################################ my @inclusions = $cgi->param('inclusions');
# Functions my @exclusions = $cgi->param('exclusions');
################################################################################ if ($category_action eq 'include') {
my $category = ($product ? $product->id : 0) . ":" .
($component ? $component->id : 0);
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);
}
elsif ($category_action eq 'removeInclusion') {
my @inclusion_to_remove = $cgi->param('inclusion_to_remove');
foreach my $remove (@inclusion_to_remove) {
@inclusions = grep { $_ ne $remove } @inclusions;
}
}
elsif ($category_action eq 'removeExclusion') {
my @exclusion_to_remove = $cgi->param('exclusion_to_remove');
foreach my $remove (@exclusion_to_remove) {
@exclusions = grep { $_ ne $remove } @exclusions;
}
}
sub list { # Convert the array @clusions('prod_ID:comp_ID') back to a hash of
my $product = validateProduct(scalar $cgi->param('product')); # the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
my $component = validateComponent($product, scalar $cgi->param('component')); my %inclusions = clusion_array_to_hash(\@inclusions);
my %exclusions = clusion_array_to_hash(\@exclusions);
$vars->{'groups'} = [Bugzilla::Group->get_all];
$vars->{'action'} = $action;
my $type = {};
$type->{$_} = $cgi->param($_) foreach $cgi->param();
# Make sure boolean fields are defined, else they fall back to 1.
foreach my $boolean qw(is_active is_requestable is_requesteeble is_multiplicable) {
$type->{$boolean} ||= 0;
}
# That's what I call a big hack. The template expects to see a group object.
$type->{'grant_group'} = {};
$type->{'grant_group'}->{'name'} = $cgi->param('grant_group');
$type->{'request_group'} = {};
$type->{'request_group'}->{'name'} = $cgi->param('request_group');
$type->{'inclusions'} = \%inclusions;
$type->{'exclusions'} = \%exclusions;
$vars->{'type'} = $type;
$vars->{'token'} = $token;
$template->process("admin/flag-type/edit.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
if ($action eq 'list') {
my $product_id = $product ? $product->id : 0; my $product_id = $product ? $product->id : 0;
my $component_id = $component ? $component->id : 0; my $component_id = $component ? $component->id : 0;
my $show_flag_counts = (defined $cgi->param('show_flag_counts')) ? 1 : 0; my $show_flag_counts = $cgi->param('show_flag_counts') ? 1 : 0;
my $group_id = $cgi->param('group');
# Define the variables and functions that will be passed to the UI template.
$vars->{'selected_product'} = $cgi->param('product');
$vars->{'selected_component'} = $cgi->param('component');
my $bug_flagtypes; my $bug_flagtypes;
my $attach_flagtypes; my $attach_flagtypes;
...@@ -116,8 +148,8 @@ sub list { ...@@ -116,8 +148,8 @@ sub list {
$attach_flagtypes = $component->flag_types->{'attachment'}; $attach_flagtypes = $component->flag_types->{'attachment'};
# Filter flag types if a group ID is given. # Filter flag types if a group ID is given.
$bug_flagtypes = filter_group($bug_flagtypes); $bug_flagtypes = filter_group($bug_flagtypes, $group_id);
$attach_flagtypes = filter_group($attach_flagtypes); $attach_flagtypes = filter_group($attach_flagtypes, $group_id);
} }
# If only a product is specified but no component, then restrict the list # If only a product is specified but no component, then restrict the list
...@@ -127,18 +159,15 @@ sub list { ...@@ -127,18 +159,15 @@ sub list {
$attach_flagtypes = $product->flag_types->{'attachment'}; $attach_flagtypes = $product->flag_types->{'attachment'};
# Filter flag types if a group ID is given. # Filter flag types if a group ID is given.
$bug_flagtypes = filter_group($bug_flagtypes); $bug_flagtypes = filter_group($bug_flagtypes, $group_id);
$attach_flagtypes = filter_group($attach_flagtypes); $attach_flagtypes = filter_group($attach_flagtypes, $group_id);
} }
# If no product is given, then show all flag types available. # If no product is given, then show all flag types available.
else { else {
$bug_flagtypes = my $flagtypes = Bugzilla::FlagType::match({ group => $group_id });
Bugzilla::FlagType::match({'target_type' => 'bug',
'group' => scalar $cgi->param('group')});
$attach_flagtypes = $bug_flagtypes = [grep { $_->target_type eq 'bug' } @$flagtypes];
Bugzilla::FlagType::match({'target_type' => 'attachment', $attach_flagtypes = [grep { $_->target_type eq 'attachment' } @$flagtypes];
'group' => scalar $cgi->param('group')});
} }
if ($show_flag_counts) { if ($show_flag_counts) {
...@@ -149,37 +178,43 @@ sub list { ...@@ -149,37 +178,43 @@ sub list {
$bug_lists{$flagtype->id} = {}; $bug_lists{$flagtype->id} = {};
my $flags = Bugzilla::Flag->match({type_id => $flagtype->id}); my $flags = Bugzilla::Flag->match({type_id => $flagtype->id});
# Build lists of bugs, triaged by flag status. # Build lists of bugs, triaged by flag status.
map { push(@{$bug_lists{$flagtype->id}->{$map{$_->status}}}, $_->bug_id) } @$flags; push(@{$bug_lists{$flagtype->id}->{$map{$_->status}}}, $_->bug_id) foreach @$flags;
} }
$vars->{'bug_lists'} = \%bug_lists; $vars->{'bug_lists'} = \%bug_lists;
$vars->{'show_flag_counts'} = 1; $vars->{'show_flag_counts'} = 1;
} }
$vars->{'selected_product'} = $product ? $product->name : '';
$vars->{'selected_component'} = $component ? $component->name : '';
$vars->{'bug_types'} = $bug_flagtypes; $vars->{'bug_types'} = $bug_flagtypes;
$vars->{'attachment_types'} = $attach_flagtypes; $vars->{'attachment_types'} = $attach_flagtypes;
# Return the appropriate HTTP response headers.
print $cgi->header();
# Generate and return the UI (HTML page) from the appropriate template.
$template->process("admin/flag-type/list.html.tmpl", $vars) $template->process("admin/flag-type/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit;
} }
if ($action eq 'enter') {
my $type = $cgi->param('target_type');
($type eq 'bug' || $type eq 'attachment')
|| ThrowCodeError('flag_type_target_type_invalid', { target_type => $type });
$vars->{'action'} = 'insert';
$vars->{'token'} = issue_session_token('add_flagtype');
$vars->{'type'} = { 'target_type' => $type,
'inclusions' => { '__Any__:__Any__' => '0:0' } };
# Get a list of groups available to restrict this flag type against.
$vars->{'groups'} = [Bugzilla::Group->get_all];
sub edit { $template->process("admin/flag-type/edit.html.tmpl", $vars)
my ($action) = @_; || ThrowTemplateError($template->error());
exit;
}
my $flag_type; if ($action eq 'edit' || $action eq 'copy') {
if ($action eq 'enter') { $vars->{'type'} = Bugzilla::FlagType->check({ id => $flag_id });
validateTargetType();
}
else {
$flag_type = validateID();
}
$vars->{'last_action'} = $cgi->param('action'); if ($action eq 'copy') {
if ($cgi->param('action') eq 'enter' || $cgi->param('action') eq 'copy') {
$vars->{'action'} = "insert"; $vars->{'action'} = "insert";
$vars->{'token'} = issue_session_token('add_flagtype'); $vars->{'token'} = issue_session_token('add_flagtype');
} }
...@@ -188,343 +223,163 @@ sub edit { ...@@ -188,343 +223,163 @@ sub edit {
$vars->{'token'} = issue_session_token('edit_flagtype'); $vars->{'token'} = issue_session_token('edit_flagtype');
} }
# If copying or editing an existing flag type, retrieve it.
if ($cgi->param('action') eq 'copy' || $cgi->param('action') eq 'edit') {
$vars->{'type'} = $flag_type;
}
# Otherwise set the target type (the minimal information about the type
# that the template needs to know) from the URL parameter and default
# the list of inclusions to all categories.
else {
my %inclusions;
$inclusions{"__Any__:__Any__"} = "0:0";
$vars->{'type'} = { 'target_type' => scalar $cgi->param('target_type'),
'inclusions' => \%inclusions };
}
# Get a list of groups available to restrict this flag type against. # Get a list of groups available to restrict this flag type against.
my @groups = Bugzilla::Group->get_all; $vars->{'groups'} = [Bugzilla::Group->get_all];
$vars->{'groups'} = \@groups;
# Return the appropriate HTTP response headers.
print $cgi->header();
# Generate and return the UI (HTML page) from the appropriate template.
$template->process("admin/flag-type/edit.html.tmpl", $vars) $template->process("admin/flag-type/edit.html.tmpl", $vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit;
} }
sub processCategoryChange { if ($action eq 'insert') {
my ($categoryAction, $token) = @_; check_token_data($token, 'add_flagtype');
validateIsActive();
validateIsRequestable();
validateIsRequesteeble();
validateAllowMultiple();
my $name = $cgi->param('name');
my $description = $cgi->param('description');
my $target_type = $cgi->param('target_type');
my $cc_list = $cgi->param('cc_list');
my $sortkey = $cgi->param('sortkey');
my $is_active = $cgi->param('is_active');
my $is_requestable = $cgi->param('is_requestable');
my $is_specifically = $cgi->param('is_requesteeble');
my $is_multiplicable = $cgi->param('is_multiplicable');
my $grant_group = $cgi->param('grant_group');
my $request_group = $cgi->param('request_group');
my @inclusions = $cgi->param('inclusions'); my @inclusions = $cgi->param('inclusions');
my @exclusions = $cgi->param('exclusions'); my @exclusions = $cgi->param('exclusions');
if ($categoryAction eq 'include') {
my $product = validateProduct(scalar $cgi->param('product'));
my $component = validateComponent($product, scalar $cgi->param('component'));
my $category = ($product ? $product->id : 0) . ":" .
($component ? $component->id : 0);
push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
}
elsif ($categoryAction eq 'exclude') {
my $product = validateProduct(scalar $cgi->param('product'));
my $component = validateComponent($product, scalar $cgi->param('component'));
my $category = ($product ? $product->id : 0) . ":" .
($component ? $component->id : 0);
push(@exclusions, $category) unless grep($_ eq $category, @exclusions);
}
elsif ($categoryAction eq 'removeInclusion') {
my @inclusion_to_remove = $cgi->param('inclusion_to_remove');
foreach my $remove (@inclusion_to_remove) {
@inclusions = grep { $_ ne $remove } @inclusions;
}
}
elsif ($categoryAction eq 'removeExclusion') {
my @exclusion_to_remove = $cgi->param('exclusion_to_remove');
foreach my $remove (@exclusion_to_remove) {
@exclusions = grep { $_ ne $remove } @exclusions;
}
}
# 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);
my @groups = Bugzilla::Group->get_all;
$vars->{'groups'} = \@groups;
$vars->{'action'} = $cgi->param('action');
my $type = {};
foreach my $key ($cgi->param()) { $type->{$key} = $cgi->param($key) }
# That's what I call a big hack. The template expects to see a group object.
# This script needs some rewrite anyway.
$type->{'grant_group'} = {};
$type->{'grant_group'}->{'name'} = $cgi->param('grant_group');
$type->{'request_group'} = {};
$type->{'request_group'}->{'name'} = $cgi->param('request_group');
$type->{'inclusions'} = \%inclusions;
$type->{'exclusions'} = \%exclusions;
$vars->{'type'} = $type;
$vars->{'token'} = $token;
# Return the appropriate HTTP response headers. my $flagtype = Bugzilla::FlagType->create({
print $cgi->header(); name => $name,
description => $description,
target_type => $target_type,
cc_list => $cc_list,
sortkey => $sortkey,
is_active => $is_active,
is_requestable => $is_requestable,
is_requesteeble => $is_specifically,
is_multiplicable => $is_multiplicable,
grant_group => $grant_group,
request_group => $request_group,
inclusions => \@inclusions,
exclusions => \@exclusions
});
# Generate and return the UI (HTML page) from the appropriate template.
$template->process("admin/flag-type/edit.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
# 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 %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};
}
$hash{"$product_name:$component_name"} = $ids;
}
return %hash;
}
sub insert {
my $token = shift;
check_token_data($token, 'add_flagtype');
my $name = validateName();
my $description = validateDescription();
my $cc_list = validateCCList();
validateTargetType();
validateSortKey();
validateIsActive();
validateIsRequestable();
validateIsRequesteeble();
validateAllowMultiple();
validateGroups();
my $dbh = Bugzilla->dbh;
my $target_type = $cgi->param('target_type') eq "bug" ? "b" : "a";
$dbh->bz_start_transaction();
# Insert a record for the new flag type into the database.
$dbh->do('INSERT INTO flagtypes
(name, description, cc_list, target_type,
sortkey, is_active, is_requestable,
is_requesteeble, is_multiplicable,
grant_group_id, request_group_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
undef, ($name, $description, $cc_list, $target_type,
$cgi->param('sortkey'), $cgi->param('is_active'),
$cgi->param('is_requestable'), $cgi->param('is_requesteeble'),
$cgi->param('is_multiplicable'), scalar($cgi->param('grant_gid')),
scalar($cgi->param('request_gid'))));
# Get the ID of the new flag type.
my $id = $dbh->bz_last_key('flagtypes', 'id');
# Populate the list of inclusions/exclusions for this flag type.
validateAndSubmit($id);
$dbh->bz_commit_transaction();
$vars->{'name'} = $name;
$vars->{'message'} = "flag_type_created";
delete_token($token); delete_token($token);
$vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'}); $vars->{'name'} = $flagtype->name;
$vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'}); $vars->{'message'} = "flag_type_created";
# Return the appropriate HTTP response headers. my @flagtypes = Bugzilla::FlagType->get_all;
print $cgi->header(); $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) $template->process("admin/flag-type/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit;
} }
if ($action eq 'update') {
sub update {
my $token = shift;
check_token_data($token, 'edit_flagtype'); check_token_data($token, 'edit_flagtype');
my $flag_type = validateID();
my $id = $flag_type->id;
my $name = validateName();
my $description = validateDescription();
my $cc_list = validateCCList();
validateTargetType();
validateSortKey();
validateIsActive();
validateIsRequestable();
validateIsRequesteeble();
validateAllowMultiple();
validateGroups();
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
$dbh->bz_start_transaction();
$dbh->do('UPDATE flagtypes
SET name = ?, description = ?, cc_list = ?,
sortkey = ?, is_active = ?, is_requestable = ?,
is_requesteeble = ?, is_multiplicable = ?,
grant_group_id = ?, request_group_id = ?
WHERE id = ?',
undef, ($name, $description, $cc_list, $cgi->param('sortkey'),
$cgi->param('is_active'), $cgi->param('is_requestable'),
$cgi->param('is_requesteeble'), $cgi->param('is_multiplicable'),
scalar($cgi->param('grant_gid')), scalar($cgi->param('request_gid')),
$id));
# Update the list of inclusions/exclusions for this flag type.
validateAndSubmit($id);
$dbh->bz_commit_transaction();
# Clear existing flags for bugs/attachments in categories no longer on
# the list of inclusions or that have been added to the list of exclusions.
my $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
FROM flags
INNER JOIN bugs
ON flags.bug_id = bugs.bug_id
LEFT OUTER JOIN flaginclusions AS i
ON (flags.type_id = i.type_id
AND (bugs.product_id = i.product_id
OR i.product_id IS NULL)
AND (bugs.component_id = i.component_id
OR i.component_id IS NULL))
WHERE flags.type_id = ?
AND i.type_id IS NULL',
undef, $id);
Bugzilla::Flag->force_retarget($flag_ids);
$flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
FROM flags
INNER JOIN bugs
ON flags.bug_id = bugs.bug_id
INNER JOIN flagexclusions AS e
ON flags.type_id = e.type_id
WHERE flags.type_id = ?
AND (bugs.product_id = e.product_id
OR e.product_id IS NULL)
AND (bugs.component_id = e.component_id
OR e.component_id IS NULL)',
undef, $id);
Bugzilla::Flag->force_retarget($flag_ids);
# Now silently remove requestees from flags which are no longer
# specifically requestable.
if (!$cgi->param('is_requesteeble')) {
$dbh->do('UPDATE flags SET requestee_id = NULL WHERE type_id = ?',
undef, $id);
}
$vars->{'name'} = $name; my $name = $cgi->param('name');
$vars->{'message'} = "flag_type_changes_saved"; my $description = $cgi->param('description');
my $cc_list = $cgi->param('cc_list');
my $sortkey = $cgi->param('sortkey');
my $is_active = $cgi->param('is_active');
my $is_requestable = $cgi->param('is_requestable');
my $is_specifically = $cgi->param('is_requesteeble');
my $is_multiplicable = $cgi->param('is_multiplicable');
my $grant_group = $cgi->param('grant_group');
my $request_group = $cgi->param('request_group');
my @inclusions = $cgi->param('inclusions');
my @exclusions = $cgi->param('exclusions');
my $flagtype = 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);
$flagtype->set_clusions({ inclusions => \@inclusions, exclusions => \@exclusions});
$flagtype->update();
delete_token($token); delete_token($token);
$vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'}); $vars->{'name'} = $flagtype->name;
$vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'}); $vars->{'message'} = "flag_type_changes_saved";
# Return the appropriate HTTP response headers. my @flagtypes = Bugzilla::FlagType->get_all;
print $cgi->header(); $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) $template->process("admin/flag-type/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit;
} }
if ($action eq 'confirmdelete') {
sub confirmDelete { $vars->{'flag_type'} = Bugzilla::FlagType->check({ id => $flag_id });
my $flag_type = validateID();
$vars->{'flag_type'} = $flag_type;
$vars->{'token'} = issue_session_token('delete_flagtype'); $vars->{'token'} = issue_session_token('delete_flagtype');
# Return the appropriate HTTP response headers.
print $cgi->header();
# Generate and return the UI (HTML page) from the appropriate template.
$template->process("admin/flag-type/confirm-delete.html.tmpl", $vars) $template->process("admin/flag-type/confirm-delete.html.tmpl", $vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit;
} }
if ($action eq 'delete') {
sub deleteType {
my $token = shift;
check_token_data($token, 'delete_flagtype'); check_token_data($token, 'delete_flagtype');
my $flag_type = validateID();
my $id = $flag_type->id;
my $dbh = Bugzilla->dbh;
$dbh->bz_start_transaction();
# Get the name of the flag type so we can tell users my $flagtype = Bugzilla::FlagType->check({ id => $flag_id });
# what was deleted. $flagtype->remove_from_db();
$vars->{'name'} = $flag_type->name;
$dbh->do('DELETE FROM flags WHERE type_id = ?', undef, $id);
$dbh->do('DELETE FROM flaginclusions WHERE type_id = ?', undef, $id);
$dbh->do('DELETE FROM flagexclusions WHERE type_id = ?', undef, $id);
$dbh->do('DELETE FROM flagtypes WHERE id = ?', undef, $id);
$dbh->bz_commit_transaction();
$vars->{'message'} = "flag_type_deleted";
delete_token($token); delete_token($token);
$vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'}); $vars->{'name'} = $flagtype->name;
$vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'}); $vars->{'message'} = "flag_type_deleted";
# Return the appropriate HTTP response headers. my @flagtypes = Bugzilla::FlagType->get_all;
print $cgi->header(); $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) $template->process("admin/flag-type/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit;
} }
if ($action eq 'deactivate') {
sub deactivate {
my $token = shift;
check_token_data($token, 'delete_flagtype'); check_token_data($token, 'delete_flagtype');
my $flag_type = validateID();
validateIsActive();
my $dbh = Bugzilla->dbh; my $flagtype = Bugzilla::FlagType->check({ id => $flag_id });
$flagtype->set_is_active(0);
$flagtype->update();
$dbh->bz_start_transaction();
$dbh->do('UPDATE flagtypes SET is_active = 0 WHERE id = ?', undef, $flag_type->id);
$dbh->bz_commit_transaction();
$vars->{'message'} = "flag_type_deactivated";
$vars->{'flag_type'} = $flag_type;
delete_token($token); delete_token($token);
$vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'}); $vars->{'message'} = "flag_type_deactivated";
$vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'}); $vars->{'flag_type'} = $flagtype;
# Return the appropriate HTTP response headers. my @flagtypes = Bugzilla::FlagType->get_all;
print $cgi->header(); $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes];
$vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @flagtypes];
# Generate and return the UI (HTML page) from the appropriate template.
$template->process("admin/flag-type/list.html.tmpl", $vars) $template->process("admin/flag-type/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit;
} }
ThrowUserError('unknown_action', {action => $action});
#####################
# Helper subroutines
#####################
sub get_products_and_components { sub get_products_and_components {
my $vars = shift; my $vars = {};
my @products = Bugzilla::Product->get_all; my @products = Bugzilla::Product->get_all;
# We require all unique component names. # We require all unique component names.
...@@ -539,172 +394,37 @@ sub get_products_and_components { ...@@ -539,172 +394,37 @@ sub get_products_and_components {
return $vars; return $vars;
} }
################################################################################ sub filter_group {
# Data Validation / Security Authorization my ($flag_types, $gid) = @_;
################################################################################ return $flag_types unless $gid;
sub validateID {
my $id = $cgi->param('id');
my $flag_type = new Bugzilla::FlagType($id)
|| ThrowCodeError('flag_type_nonexistent', { id => $id });
return $flag_type;
}
sub validateName {
my $name = $cgi->param('name');
($name && $name !~ /[ ,]/ && length($name) <= 50)
|| ThrowUserError("flag_type_name_invalid",
{ name => $name });
trick_taint($name);
return $name;
}
sub validateDescription {
my $description = $cgi->param('description');
length($description) < 2**16-1
|| ThrowUserError("flag_type_description_invalid");
trick_taint($description);
return $description;
}
sub validateCCList {
my $cc_list = $cgi->param('cc_list');
length($cc_list) <= 200
|| ThrowUserError("flag_type_cc_list_invalid",
{ cc_list => $cc_list });
my @addresses = split(/[, ]+/, $cc_list);
# We do not call Util::validate_email_syntax because these
# addresses do not require to match 'emailregexp' and do not
# depend on 'emailsuffix'. So we limit ourselves to a simple
# sanity check:
# - match the syntax of a fully qualified email address;
# - do not contain any illegal character.
foreach my $address (@addresses) {
($address =~ /^[\w\.\+\-=]+@[\w\.\-]+\.[\w\-]+$/
&& $address !~ /[\\\(\)<>&,;:"\[\] \t\r\n]/)
|| ThrowUserError('illegal_email_address',
{addr => $address, default => 1});
}
trick_taint($cc_list);
return $cc_list;
}
sub validateProduct {
my $product_name = shift;
return unless $product_name;
my $product = Bugzilla::Product->check({ name => $product_name,
allow_inaccessible => 1 });
return $product;
}
sub validateComponent {
my ($product, $component_name) = @_;
return unless $component_name;
($product && $product->id)
|| ThrowUserError("flag_type_component_without_product");
my $component = Bugzilla::Component->check({ product => $product,
name => $component_name });
return $component;
}
sub validateSortKey {
# $sortkey is destroyed if detaint_natural fails.
my $sortkey = $cgi->param('sortkey');
detaint_natural($sortkey)
&& $sortkey < 32768
|| ThrowUserError("flag_type_sortkey_invalid",
{ sortkey => scalar $cgi->param('sortkey') });
$cgi->param('sortkey', $sortkey);
}
sub validateTargetType {
grep($cgi->param('target_type') eq $_, ("bug", "attachment"))
|| ThrowCodeError("flag_type_target_type_invalid",
{ target_type => scalar $cgi->param('target_type') });
}
sub validateIsActive {
$cgi->param('is_active', $cgi->param('is_active') ? 1 : 0);
}
sub validateIsRequestable {
$cgi->param('is_requestable', $cgi->param('is_requestable') ? 1 : 0);
}
sub validateIsRequesteeble {
$cgi->param('is_requesteeble', $cgi->param('is_requesteeble') ? 1 : 0);
}
sub validateAllowMultiple { my @flag_types = grep {($_->grant_group && $_->grant_group->id == $gid)
$cgi->param('is_multiplicable', $cgi->param('is_multiplicable') ? 1 : 0); || ($_->request_group && $_->request_group->id == $gid)} @$flag_types;
}
sub validateGroups { return \@flag_types;
my $dbh = Bugzilla->dbh;
# Convert group names to group IDs
foreach my $col ('grant', 'request') {
my $name = $cgi->param($col . '_group');
if ($name) {
trick_taint($name);
my $gid = $dbh->selectrow_array('SELECT id FROM groups
WHERE name = ?', undef, $name);
$gid || ThrowUserError("group_unknown", { name => $name });
$cgi->param($col . '_gid', $gid);
}
}
} }
# At this point, values either come the DB itself or have been recently # Convert the array @clusions('prod_ID:comp_ID') back to a hash of
# added by the user and have passed all validation tests. # the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
# The only way to have invalid product/component combinations is to sub clusion_array_to_hash {
# hack the URL. So we silently ignore them, if any. my $array = shift;
sub validateAndSubmit { my %hash;
my ($id) = @_;
my $dbh = Bugzilla->dbh;
# Cache product objects.
my %products; my %products;
foreach my $category_type ("inclusions", "exclusions") { my %components;
# Will be used several times below. foreach my $ids (@$array) {
my $sth = $dbh->prepare("INSERT INTO flag$category_type " . trick_taint($ids);
"(type_id, product_id, component_id) " . my ($product_id, $component_id) = split(":", $ids);
"VALUES (?, ?, ?)"); my $product_name = "__Any__";
$dbh->do("DELETE FROM flag$category_type WHERE type_id = ?", undef, $id);
foreach my $category ($cgi->param($category_type)) {
trick_taint($category);
my ($product_id, $component_id) = split(":", $category);
# Does the product exist?
if ($product_id) { if ($product_id) {
$products{$product_id} ||= new Bugzilla::Product($product_id); $products{$product_id} ||= new Bugzilla::Product($product_id);
next unless defined $products{$product_id}; $product_name = $products{$product_id}->name if $products{$product_id};
} }
# A component was selected without a product being selected. my $component_name = "__Any__";
next if (!$product_id && $component_id);
# Does the component belong to this product?
if ($component_id) { if ($component_id) {
my @match = grep {$_->id == $component_id} @{$products{$product_id}->components}; $components{$component_id} ||= new Bugzilla::Component($component_id);
next unless scalar(@match); $component_name = $components{$component_id}->name if $components{$component_id};
}
$product_id ||= undef;
$component_id ||= undef;
$sth->execute($id, $product_id, $component_id);
} }
$hash{"$product_name:$component_name"} = $ids;
} }
} return %hash;
sub filter_group {
my $flag_types = shift;
return $flag_types unless Bugzilla->cgi->param('group');
my $gid = scalar $cgi->param('group');
my @flag_types = grep {($_->grant_group && $_->grant_group->id == $gid)
|| ($_->request_group && $_->request_group->id == $gid)} @$flag_types;
return \@flag_types;
} }
...@@ -23,20 +23,15 @@ ...@@ -23,20 +23,15 @@
[% PROCESS "global/js-products.html.tmpl" %] [% PROCESS "global/js-products.html.tmpl" %]
[% IF type.target_type == "bug" %] [% IF action == "insert" %]
[% title = BLOCK %]Create Flag Type for [% terms.Bugs %][% END %] [% title = BLOCK %]
[% typeLabelLowerPlural = BLOCK %][% terms.bugs %][% END %] Create Flag Type for [% type.target_type == "bug" ? terms.Bugs : "Attachments" %]
[% typeLabelLowerSingular = BLOCK %][% terms.bug %][% END %] [% IF type.id %]
Based on [% type.name FILTER html %]
[% END %]
[% END %]
[% doc_section = "flags-overview.html#flags-create" %]
[% ELSE %] [% ELSE %]
[% title = "Create Flag Type for Attachments" %]
[% typeLabelLowerPlural = BLOCK %]attachments[% END %]
[% typeLabelLowerSingular = BLOCK %]attachment[% END %]
[% END %]
[% doc_section = "flags-overview.html#flags-create" %]
[% IF last_action == "copy" %]
[% title = BLOCK %]Create Flag Type Based on [% type.name FILTER html %][% END %]
[% ELSIF last_action == "edit" %]
[% title = BLOCK %]Edit Flag Type [% type.name FILTER html %][% END %] [% title = BLOCK %]Edit Flag Type [% type.name FILTER html %][% END %]
[% doc_section = "flags-overview.html#flags-edit" %] [% doc_section = "flags-overview.html#flags-edit" %]
[% END %] [% END %]
...@@ -53,10 +48,10 @@ ...@@ -53,10 +48,10 @@
%] %]
<form method="post" action="editflagtypes.cgi"> <form method="post" action="editflagtypes.cgi">
<input type="hidden" name="action" value="[% action %]"> <input type="hidden" name="action" value="[% action FILTER html %]">
<input type="hidden" name="id" value="[% type.id %]"> <input type="hidden" name="id" value="[% type.id %]">
<input type="hidden" name="token" value="[% token FILTER html %]"> <input type="hidden" name="token" value="[% token FILTER html %]">
<input type="hidden" name="target_type" value="[% type.target_type %]"> <input type="hidden" name="target_type" value="[% type.target_type FILTER html %]">
[% FOREACH category = type.inclusions %] [% FOREACH category = type.inclusions %]
<input type="hidden" name="inclusions" value="[% category.value FILTER html %]"> <input type="hidden" name="inclusions" value="[% category.value FILTER html %]">
[% END %] [% END %]
...@@ -72,7 +67,7 @@ ...@@ -72,7 +67,7 @@
<tr> <tr>
<th>Name:</th> <th>Name:</th>
<td> <td>
a short name identifying this type<br> a short name identifying this type.<br>
<input type="text" name="name" value="[% type.name FILTER html %]" <input type="text" name="name" value="[% type.name FILTER html %]"
size="50" maxlength="50"> size="50" maxlength="50">
</td> </td>
...@@ -81,7 +76,7 @@ ...@@ -81,7 +76,7 @@
<tr> <tr>
<th>Description:</th> <th>Description:</th>
<td> <td>
a comprehensive description of this type<br> a comprehensive description of this type.<br>
[% INCLUDE global/textarea.html.tmpl [% INCLUDE global/textarea.html.tmpl
name = 'description' name = 'description'
minrows = 4 minrows = 4
...@@ -95,9 +90,9 @@ ...@@ -95,9 +90,9 @@
<th>Category:</th> <th>Category:</th>
<td> <td>
the products/components to which [% typeLabelLowerPlural %] must the products/components to which [% type.target_type == "bug" ? terms.bugs : "attachments" %]
(inclusions) or must not (exclusions) belong in order for users must (inclusions) or must not (exclusions) belong in order for users
to be able to set flags of this type for them to be able to set flags of this type for them.
<table> <table>
<tr> <tr>
<td style="vertical-align: top;"> <td style="vertical-align: top;">
...@@ -139,10 +134,10 @@ ...@@ -139,10 +134,10 @@
<tr> <tr>
<th>Sort Key:</th> <th>Sort Key:</th>
<td> <td>
a number between 1 and 32767 by which this type will be sorted a number between 1 and [% constants.MAX_SMALLINT FILTER none %] by which
when displayed to users in a list; ignore if you don't care this type will be sorted when displayed to users in a list; ignore if you
what order the types appear in or if you want them to appear don't care what order the types appear in or if you want them to appear
in alphabetical order<br> 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">
</td> </td>
</tr> </tr>
...@@ -196,7 +191,7 @@ ...@@ -196,7 +191,7 @@
<input type="checkbox" id="is_multiplicable" name="is_multiplicable" <input type="checkbox" id="is_multiplicable" name="is_multiplicable"
[% " checked" IF type.is_multiplicable || !type.is_multiplicable.defined %]> [% " checked" IF type.is_multiplicable || !type.is_multiplicable.defined %]>
<label for="is_multiplicable">multiplicable (multiple flags of this type can be set on <label for="is_multiplicable">multiplicable (multiple flags of this type can be set on
the same [% typeLabelLowerSingular %])</label> the same [% type.target_type == "bug" ? terms.bug : "attachment" %])</label>
</td> </td>
</tr> </tr>
...@@ -204,7 +199,7 @@ ...@@ -204,7 +199,7 @@
<th>Grant Group:</th> <th>Grant Group:</th>
<td> <td>
the group allowed to grant/deny flags of this type the group allowed to grant/deny flags of this type
(to allow all users to grant/deny these flags, select no group)<br> (to allow all users to grant/deny these flags, select no group).<br>
[% PROCESS select selname = "grant_group" %] [% PROCESS select selname = "grant_group" %]
</td> </td>
</tr> </tr>
...@@ -213,19 +208,16 @@ ...@@ -213,19 +208,16 @@
<th>Request Group:</th> <th>Request Group:</th>
<td> <td>
if flags of this type are requestable, the group allowed to request them 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> (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> Note that the request group alone has no effect if the grant group is not defined!<br>
[% PROCESS select selname = "request_group" %] [% PROCESS select selname = "request_group" %]
</td> </td>
</tr> </tr>
<tr> <tr>
<th></th> <th>&nbsp;</th>
<td> <td>
<input type="submit" id="save" value=" <input type="submit" id="save" value="[% action == "insert" ? "Create" : "Save Changes" %]">
[%- IF (last_action == "enter" || last_action == "copy") %]Create
[%- ELSE %]Save Changes
[%- END %]">
</td> </td>
</tr> </tr>
......
...@@ -426,12 +426,8 @@ ...@@ -426,12 +426,8 @@
], ],
'admin/flag-type/edit.html.tmpl' => [ 'admin/flag-type/edit.html.tmpl' => [
'action',
'type.id', 'type.id',
'type.target_type',
'type.sortkey || 1', 'type.sortkey || 1',
'typeLabelLowerPlural',
'typeLabelLowerSingular',
'selname', 'selname',
], ],
......
...@@ -653,7 +653,7 @@ ...@@ -653,7 +653,7 @@
[% ELSIF error == "flag_type_description_invalid" %] [% ELSIF error == "flag_type_description_invalid" %]
[% title = "Flag Type Description Invalid" %] [% title = "Flag Type Description Invalid" %]
[% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags'} %] [% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags'} %]
The description must be less than 32K. You must enter a description for this flag type.
[% ELSIF error == "flag_type_name_invalid" %] [% ELSIF error == "flag_type_name_invalid" %]
[% title = "Flag Type Name Invalid" %] [% title = "Flag Type Name Invalid" %]
...@@ -687,8 +687,8 @@ ...@@ -687,8 +687,8 @@
[% ELSIF error == "flag_type_sortkey_invalid" %] [% ELSIF error == "flag_type_sortkey_invalid" %]
[% title = "Flag Type Sort Key Invalid" %] [% title = "Flag Type Sort Key Invalid" %]
The sort key must be an integer between 0 and 32767 inclusive. The sort key <em>[% sortkey FILTER html %]</em> must be an integer
It cannot be <em>[% sortkey FILTER html %]</em>. between 0 and [% constants.MAX_SMALLINT FILTER none %].
[% ELSIF error == "freetext_too_long" %] [% ELSIF error == "freetext_too_long" %]
[% title = "Text Too Long" %] [% title = "Text Too Long" %]
...@@ -756,6 +756,7 @@ ...@@ -756,6 +756,7 @@
[% title = "System Groups not deletable" %] [% title = "System Groups not deletable" %]
<em>[% name FILTER html %]</em> is a system group. <em>[% name FILTER html %]</em> is a system group.
This group cannot be deleted. This group cannot be deleted.
[% ELSIF error == "group_unknown" %] [% ELSIF error == "group_unknown" %]
[% title = "Unknown Group" %] [% title = "Unknown Group" %]
The group [% name FILTER html %] does not exist. Please specify The group [% name FILTER html %] does not exist. Please specify
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment