779 lines
24 KiB
Perl
779 lines
24 KiB
Perl
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
#
|
|
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
# defined by the Mozilla Public License, v. 2.0.
|
|
|
|
package Bugzilla::FlagType;
|
|
|
|
use 5.10.1;
|
|
use strict;
|
|
use warnings;
|
|
|
|
=head1 NAME
|
|
|
|
Bugzilla::FlagType - A module to deal with Bugzilla flag types.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
FlagType.pm provides an interface to flag types as stored in Bugzilla.
|
|
See below for more information.
|
|
|
|
=head1 NOTES
|
|
|
|
=over
|
|
|
|
=item *
|
|
|
|
Use of private functions/variables outside this module may lead to
|
|
unexpected results after an upgrade. Please avoid using private
|
|
functions in other files/modules. Private functions are functions
|
|
whose names start with _ or are specifically noted as being private.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
use Bugzilla::Constants;
|
|
use Bugzilla::Error;
|
|
use Bugzilla::Util;
|
|
use Bugzilla::Group;
|
|
|
|
use Email::Address;
|
|
use List::MoreUtils qw(uniq);
|
|
|
|
use parent qw(Bugzilla::Object);
|
|
|
|
###############################
|
|
#### Initialization ####
|
|
###############################
|
|
|
|
use constant DB_TABLE => 'flagtypes';
|
|
use constant LIST_ORDER => 'sortkey, name';
|
|
|
|
use constant DB_COLUMNS => qw(
|
|
id
|
|
name
|
|
description
|
|
cc_list
|
|
target_type
|
|
sortkey
|
|
is_active
|
|
is_requestable
|
|
is_requesteeble
|
|
is_multiplicable
|
|
grant_group_id
|
|
request_group_id
|
|
);
|
|
|
|
use constant UPDATE_COLUMNS => qw(
|
|
name
|
|
description
|
|
cc_list
|
|
sortkey
|
|
is_active
|
|
is_requestable
|
|
is_requesteeble
|
|
is_multiplicable
|
|
grant_group_id
|
|
request_group_id
|
|
);
|
|
|
|
use constant VALIDATORS => {
|
|
name => \&_check_name,
|
|
description => \&_check_description,
|
|
cc_list => \&_check_cc_list,
|
|
target_type => \&_check_target_type,
|
|
sortkey => \&_check_sortkey,
|
|
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,
|
|
};
|
|
###############################
|
|
|
|
sub create {
|
|
my $class = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
$dbh->bz_start_transaction();
|
|
|
|
$class->check_required_create_fields(@_);
|
|
my $params = $class->run_create_validators(@_);
|
|
# In the DB, only the first character of the target type is stored.
|
|
$params->{target_type} = substr($params->{target_type}, 0, 1);
|
|
|
|
# Extract everything which is not a valid column name.
|
|
$params->{grant_group_id} = delete $params->{grant_group};
|
|
$params->{request_group_id} = delete $params->{request_group};
|
|
my $inclusions = delete $params->{inclusions};
|
|
my $exclusions = delete $params->{exclusions};
|
|
|
|
my $flagtype = $class->insert_create_data($params);
|
|
|
|
$flagtype->set_clusions({ inclusions => $inclusions,
|
|
exclusions => $exclusions });
|
|
$flagtype->update();
|
|
|
|
$dbh->bz_commit_transaction();
|
|
return $flagtype;
|
|
}
|
|
|
|
sub update {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $flag_id = $self->id;
|
|
|
|
$dbh->bz_start_transaction();
|
|
my $changes = $self->SUPER::update(@_);
|
|
|
|
# Update the flaginclusions and flagexclusions tables.
|
|
foreach my $category ('inclusions', 'exclusions') {
|
|
next unless delete $self->{"_update_$category"};
|
|
|
|
$dbh->do("DELETE FROM flag$category WHERE type_id = ?", undef, $flag_id);
|
|
|
|
my $sth = $dbh->prepare("INSERT INTO flag$category
|
|
(type_id, product_id, component_id) VALUES (?, ?, ?)");
|
|
|
|
foreach my $prod_comp (values %{$self->{$category}}) {
|
|
my ($prod_id, $comp_id) = split(':', $prod_comp);
|
|
$prod_id ||= undef;
|
|
$comp_id ||= undef;
|
|
$sth->execute($flag_id, $prod_id, $comp_id);
|
|
}
|
|
$changes->{$category} = [0, 1];
|
|
}
|
|
|
|
# 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 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) {
|
|
my $ids = $dbh->selectcol_arrayref(
|
|
'SELECT id FROM flags WHERE type_id = ? AND requestee_id IS NOT NULL',
|
|
undef, $self->id);
|
|
|
|
if (@$ids) {
|
|
$dbh->do('UPDATE flags SET requestee_id = NULL WHERE ' . $dbh->sql_in('id', $ids));
|
|
foreach my $id (@$ids) {
|
|
Bugzilla->memcached->clear({ table => 'flags', id => $id });
|
|
}
|
|
}
|
|
}
|
|
|
|
$dbh->bz_commit_transaction();
|
|
return $changes;
|
|
}
|
|
|
|
###############################
|
|
#### Accessors ######
|
|
###############################
|
|
|
|
=head2 METHODS
|
|
|
|
=over
|
|
|
|
=item C<id>
|
|
|
|
Returns the ID of the flagtype.
|
|
|
|
=item C<name>
|
|
|
|
Returns the name of the flagtype.
|
|
|
|
=item C<description>
|
|
|
|
Returns the description of the flagtype.
|
|
|
|
=item C<cc_list>
|
|
|
|
Returns the concatenated CC list for the flagtype, as a single string.
|
|
|
|
=item C<target_type>
|
|
|
|
Returns whether the flagtype applies to bugs or attachments.
|
|
|
|
=item C<is_active>
|
|
|
|
Returns whether the flagtype is active or disabled. Flags being
|
|
in a disabled flagtype are not deleted. It only prevents you from
|
|
adding new flags to it.
|
|
|
|
=item C<is_requestable>
|
|
|
|
Returns whether you can request for the given flagtype
|
|
(i.e. whether the '?' flag is available or not).
|
|
|
|
=item C<is_requesteeble>
|
|
|
|
Returns whether you can ask someone specifically or not.
|
|
|
|
=item C<is_multiplicable>
|
|
|
|
Returns whether you can have more than one flag for the given
|
|
flagtype in a given bug/attachment.
|
|
|
|
=item C<sortkey>
|
|
|
|
Returns the sortkey of the flagtype.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub id { return $_[0]->{'id'}; }
|
|
sub name { return $_[0]->{'name'}; }
|
|
sub description { return $_[0]->{'description'}; }
|
|
sub cc_list { return $_[0]->{'cc_list'}; }
|
|
sub target_type { return $_[0]->{'target_type'} eq 'b' ? 'bug' : 'attachment'; }
|
|
sub is_active { return $_[0]->{'is_active'}; }
|
|
sub is_requestable { return $_[0]->{'is_requestable'}; }
|
|
sub is_requesteeble { return $_[0]->{'is_requesteeble'}; }
|
|
sub is_multiplicable { return $_[0]->{'is_multiplicable'}; }
|
|
sub sortkey { return $_[0]->{'sortkey'}; }
|
|
sub request_group_id { return $_[0]->{'request_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);
|
|
my $addr_spec = $Email::Address::addr_spec;
|
|
# We do not call check_email_syntax() because these addresses do not
|
|
# require to match 'emailregexp' and do not depend on 'emailsuffix'.
|
|
foreach my $address (@addresses) {
|
|
($address !~ /\P{ASCII}/ && $address =~ /^$addr_spec$/)
|
|
|| 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_sortkey {
|
|
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 ####
|
|
###############################
|
|
|
|
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 $user = Bugzilla->user;
|
|
my %products;
|
|
my $params = {};
|
|
|
|
# If the user has editcomponents privs, then we only need to make sure
|
|
# that the product exists.
|
|
if ($user->in_group('editcomponents')) {
|
|
$params->{allow_inaccessible} = 1;
|
|
}
|
|
|
|
foreach my $category (keys %$list) {
|
|
my %clusions;
|
|
my %clusions_as_hash;
|
|
|
|
foreach my $prod_comp (@{$list->{$category} || []}) {
|
|
my ($prod_id, $comp_id) = split(':', $prod_comp);
|
|
my $prod_name = '__Any__';
|
|
my $comp_name = '__Any__';
|
|
# Does the product exist?
|
|
if ($prod_id) {
|
|
detaint_natural($prod_id)
|
|
|| ThrowCodeError('param_must_be_numeric',
|
|
{ function => 'Bugzilla::FlagType::set_clusions' });
|
|
|
|
if (!$products{$prod_id}) {
|
|
$params->{id} = $prod_id;
|
|
$products{$prod_id} = Bugzilla::Product->check($params);
|
|
}
|
|
$prod_name = $products{$prod_id}->name;
|
|
|
|
# Does the component belong to this product?
|
|
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;
|
|
}
|
|
$clusions{"$prod_name:$comp_name"} = "$prod_id:$comp_id";
|
|
$clusions_as_hash{$prod_id}->{$comp_id} = 1;
|
|
}
|
|
|
|
# Check the user has the editcomponent permission on products that are changing
|
|
if (! $user->in_group('editcomponents')) {
|
|
my $current_clusions = $self->$category;
|
|
my ($removed, $added)
|
|
= diff_arrays([ values %$current_clusions ], [ values %clusions ]);
|
|
my @changed_product_ids
|
|
= uniq map { substr($_, 0, index($_, ':')) } @$removed, @$added;
|
|
foreach my $product_id (@changed_product_ids) {
|
|
$user->in_group('editcomponents', $product_id)
|
|
|| ThrowUserError('product_access_denied',
|
|
{ name => $products{$product_id}->name });
|
|
}
|
|
}
|
|
|
|
# Set the changes
|
|
$self->{$category} = \%clusions;
|
|
$self->{"${category}_as_hash"} = \%clusions_as_hash;
|
|
$self->{"_update_$category"} = 1;
|
|
}
|
|
}
|
|
|
|
=pod
|
|
|
|
=over
|
|
|
|
=item C<grant_list>
|
|
|
|
Returns a reference to an array of users who have permission to grant this flag type.
|
|
The arrays are populated with hashrefs containing the login, identity and visibility of users.
|
|
|
|
=item C<grant_group>
|
|
|
|
Returns the group (as a Bugzilla::Group object) in which a user
|
|
must be in order to grant or deny a request.
|
|
|
|
=item C<request_group>
|
|
|
|
Returns the group (as a Bugzilla::Group object) in which a user
|
|
must be in order to request or clear a flag.
|
|
|
|
=item C<flag_count>
|
|
|
|
Returns the number of flags belonging to the flagtype.
|
|
|
|
=item C<inclusions>
|
|
|
|
Return a hash of product/component IDs and names
|
|
explicitly associated with the flagtype.
|
|
|
|
=item C<exclusions>
|
|
|
|
Return a hash of product/component IDs and names
|
|
explicitly excluded from the flagtype.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub grant_list {
|
|
my $self = shift;
|
|
require Bugzilla::User;
|
|
my @custusers;
|
|
my @allusers = @{Bugzilla->user->get_userlist};
|
|
foreach my $user (@allusers) {
|
|
my $user_obj = new Bugzilla::User({name => $user->{login}});
|
|
push(@custusers, $user) if $user_obj->can_set_flag($self);
|
|
}
|
|
return \@custusers;
|
|
}
|
|
|
|
sub grant_group {
|
|
my $self = shift;
|
|
|
|
if (!defined $self->{'grant_group'} && $self->{'grant_group_id'}) {
|
|
$self->{'grant_group'} = new Bugzilla::Group($self->{'grant_group_id'});
|
|
}
|
|
return $self->{'grant_group'};
|
|
}
|
|
|
|
sub request_group {
|
|
my $self = shift;
|
|
|
|
if (!defined $self->{'request_group'} && $self->{'request_group_id'}) {
|
|
$self->{'request_group'} = new Bugzilla::Group($self->{'request_group_id'});
|
|
}
|
|
return $self->{'request_group'};
|
|
}
|
|
|
|
sub flag_count {
|
|
my $self = shift;
|
|
|
|
if (!defined $self->{'flag_count'}) {
|
|
$self->{'flag_count'} =
|
|
Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM flags
|
|
WHERE type_id = ?', undef, $self->{'id'});
|
|
}
|
|
return $self->{'flag_count'};
|
|
}
|
|
|
|
sub inclusions {
|
|
my $self = shift;
|
|
|
|
if (!defined $self->{inclusions}) {
|
|
($self->{inclusions}, $self->{inclusions_as_hash}) = get_clusions($self->id, 'in');
|
|
}
|
|
return $self->{inclusions};
|
|
}
|
|
|
|
sub inclusions_as_hash {
|
|
my $self = shift;
|
|
|
|
$self->inclusions unless defined $self->{inclusions_as_hash};
|
|
return $self->{inclusions_as_hash};
|
|
}
|
|
|
|
sub exclusions {
|
|
my $self = shift;
|
|
|
|
if (!defined $self->{exclusions}) {
|
|
($self->{exclusions}, $self->{exclusions_as_hash}) = get_clusions($self->id, 'ex');
|
|
}
|
|
return $self->{exclusions};
|
|
}
|
|
|
|
sub exclusions_as_hash {
|
|
my $self = shift;
|
|
|
|
$self->exclusions unless defined $self->{exclusions_as_hash};
|
|
return $self->{exclusions_as_hash};
|
|
}
|
|
|
|
######################################################################
|
|
# Public Functions
|
|
######################################################################
|
|
|
|
=pod
|
|
|
|
=head1 PUBLIC FUNCTIONS/METHODS
|
|
|
|
=over
|
|
|
|
=item C<get_clusions($id, $type)>
|
|
|
|
Return a hash of product/component IDs and names
|
|
associated with the flagtype:
|
|
$clusions{'product_name:component_name'} = "product_ID:component_ID"
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub get_clusions {
|
|
my ($id, $type) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $list =
|
|
$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) {
|
|
my ($product_id, $product_name, $component_id, $component_name) = @$data;
|
|
$product_id ||= 0;
|
|
$product_name ||= "__Any__";
|
|
$component_id ||= 0;
|
|
$component_name ||= "__Any__";
|
|
$clusions{"$product_name:$component_name"} = "$product_id:$component_id";
|
|
$clusions_as_hash{$product_id}->{$component_id} = 1;
|
|
}
|
|
return (\%clusions, \%clusions_as_hash);
|
|
}
|
|
|
|
=pod
|
|
|
|
=over
|
|
|
|
=item C<match($criteria)>
|
|
|
|
Queries the database for flag types matching the given criteria
|
|
and returns a list of matching flagtype objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub match {
|
|
my ($criteria) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
# Depending on the criteria, we may have to append additional tables.
|
|
my $tables = [DB_TABLE];
|
|
my @criteria = sqlify_criteria($criteria, $tables);
|
|
$tables = join(' ', @$tables);
|
|
$criteria = join(' AND ', @criteria);
|
|
|
|
my $flagtype_ids = $dbh->selectcol_arrayref("SELECT id FROM $tables WHERE $criteria");
|
|
|
|
return Bugzilla::FlagType->new_from_list($flagtype_ids);
|
|
}
|
|
|
|
=pod
|
|
|
|
=over
|
|
|
|
=item C<count($criteria)>
|
|
|
|
Returns the total number of flag types matching the given criteria.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub count {
|
|
my ($criteria) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
# Depending on the criteria, we may have to append additional tables.
|
|
my $tables = [DB_TABLE];
|
|
my @criteria = sqlify_criteria($criteria, $tables);
|
|
$tables = join(' ', @$tables);
|
|
$criteria = join(' AND ', @criteria);
|
|
|
|
my $count = $dbh->selectrow_array("SELECT COUNT(flagtypes.id)
|
|
FROM $tables WHERE $criteria");
|
|
return $count;
|
|
}
|
|
|
|
######################################################################
|
|
# Private Functions
|
|
######################################################################
|
|
|
|
# Converts a hash of criteria into a list of SQL criteria.
|
|
# $criteria is a reference to the criteria (field => value),
|
|
# $tables is a reference to an array of tables being accessed
|
|
# by the query.
|
|
|
|
sub sqlify_criteria {
|
|
my ($criteria, $tables) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
# the generated list of SQL criteria; "1=1" is a clever way of making sure
|
|
# there's something in the list so calling code doesn't have to check list
|
|
# size before building a WHERE clause out of it
|
|
my @criteria = ("1=1");
|
|
|
|
if ($criteria->{name}) {
|
|
if (ref($criteria->{name}) eq 'ARRAY') {
|
|
my @names = map { $dbh->quote($_) } @{$criteria->{name}};
|
|
# Detaint data as we have quoted it.
|
|
foreach my $name (@names) {
|
|
trick_taint($name);
|
|
}
|
|
push @criteria, $dbh->sql_in('flagtypes.name', \@names);
|
|
}
|
|
else {
|
|
my $name = $dbh->quote($criteria->{name});
|
|
trick_taint($name); # Detaint data as we have quoted it.
|
|
push(@criteria, "flagtypes.name = $name");
|
|
}
|
|
}
|
|
if ($criteria->{target_type}) {
|
|
# The target type is stored in the database as a one-character string
|
|
# ("a" for attachment and "b" for bug), but this function takes complete
|
|
# names ("attachment" and "bug") for clarity, so we must convert them.
|
|
my $target_type = $criteria->{target_type} eq 'bug'? 'b' : 'a';
|
|
push(@criteria, "flagtypes.target_type = '$target_type'");
|
|
}
|
|
if (exists($criteria->{is_active})) {
|
|
my $is_active = $criteria->{is_active} ? "1" : "0";
|
|
push(@criteria, "flagtypes.is_active = $is_active");
|
|
}
|
|
if ($criteria->{product_id}) {
|
|
my $product_id = $criteria->{product_id};
|
|
detaint_natural($product_id)
|
|
|| ThrowCodeError('bad_arg', { argument => 'product_id',
|
|
function => 'Bugzilla::FlagType::sqlify_criteria' });
|
|
|
|
# Add inclusions to the query, which simply involves joining the table
|
|
# by flag type ID and target product/component.
|
|
push(@$tables, "INNER JOIN flaginclusions AS i ON flagtypes.id = i.type_id");
|
|
push(@criteria, "(i.product_id = $product_id OR i.product_id IS NULL)");
|
|
|
|
# Add exclusions to the query, which is more complicated. First of all,
|
|
# we do a LEFT JOIN so we don't miss flag types with no exclusions.
|
|
# Then, as with inclusions, we join on flag type ID and target product/
|
|
# component. However, since we want flag types that *aren't* on the
|
|
# exclusions list, we add a WHERE criteria to use only records with
|
|
# NULL exclusion type, i.e. without any exclusions.
|
|
my $join_clause = "flagtypes.id = e.type_id ";
|
|
|
|
my $addl_join_clause = "";
|
|
if ($criteria->{component_id}) {
|
|
my $component_id = $criteria->{component_id};
|
|
detaint_natural($component_id)
|
|
|| ThrowCodeError('bad_arg', { argument => 'component_id',
|
|
function => 'Bugzilla::FlagType::sqlify_criteria' });
|
|
|
|
push(@criteria, "(i.component_id = $component_id OR i.component_id IS NULL)");
|
|
$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) ";
|
|
}
|
|
$join_clause .= "AND ((e.product_id = $product_id $addl_join_clause) OR e.product_id IS NULL)";
|
|
|
|
push(@$tables, "LEFT JOIN flagexclusions AS e ON ($join_clause)");
|
|
push(@criteria, "e.type_id IS NULL");
|
|
}
|
|
if ($criteria->{group}) {
|
|
my $gid = $criteria->{group};
|
|
detaint_natural($gid)
|
|
|| ThrowCodeError('bad_arg', { argument => 'group',
|
|
function => 'Bugzilla::FlagType::sqlify_criteria' });
|
|
|
|
push(@criteria, "(flagtypes.grant_group_id = $gid " .
|
|
" OR flagtypes.request_group_id = $gid)");
|
|
}
|
|
|
|
return @criteria;
|
|
}
|
|
|
|
1;
|
|
|
|
=head1 B<Methods in need of POD>
|
|
|
|
=over
|
|
|
|
=item exclusions_as_hash
|
|
|
|
=item request_group_id
|
|
|
|
=item set_is_active
|
|
|
|
=item set_is_multiplicable
|
|
|
|
=item inclusions_as_hash
|
|
|
|
=item set_sortkey
|
|
|
|
=item grant_group_id
|
|
|
|
=item set_cc_list
|
|
|
|
=item set_request_group
|
|
|
|
=item set_name
|
|
|
|
=item set_is_specifically_requestable
|
|
|
|
=item set_grant_group
|
|
|
|
=item create
|
|
|
|
=item set_clusions
|
|
|
|
=item set_description
|
|
|
|
=item set_is_requestable
|
|
|
|
=item update
|
|
|
|
=back
|