Commit fcccff08 authored by Max Kanat-Alexander's avatar Max Kanat-Alexander

Bug 574556: Refactor Search.pm so that we're not doing $$some_var everywhere.

Instead, we pass around a hashref and update the hashref. This patch also includes some cleanup for bugs surrounding percentage_complete, attachments.isobsolete, attachments.ispatch, and owner_idle_time. r=mkanat, a=mkanat
parent 0e8eb403
...@@ -1632,8 +1632,7 @@ sub _check_keywords { ...@@ -1632,8 +1632,7 @@ sub _check_keywords {
my %keywords; my %keywords;
foreach my $keyword (@$keyword_array) { foreach my $keyword (@$keyword_array) {
next unless $keyword; next unless $keyword;
my $obj = new Bugzilla::Keyword({ name => $keyword }); my $obj = Bugzilla::Keyword->check($keyword);
ThrowUserError("unknown_keyword", { keyword => $keyword }) if !$obj;
$keywords{$obj->id} = $obj; $keywords{$obj->id} = $obj;
} }
return [values %keywords]; return [values %keywords];
......
...@@ -185,6 +185,16 @@ sub products { ...@@ -185,6 +185,16 @@ sub products {
#### Methods #### #### Methods ####
############################### ###############################
sub check_members_are_visible {
my $self = shift;
my $user = Bugzilla->user;
return if !Bugzilla->params->{'usevisibilitygroups'};
my $is_visible = grep { $_->id == $_ } @{ $user->visible_groups_inherited };
if (!$is_visible) {
ThrowUserError('group_not_visible', { group => $self });
}
}
sub set_description { $_[0]->set('description', $_[1]); } sub set_description { $_[0]->set('description', $_[1]); }
sub set_is_active { $_[0]->set('isactive', $_[1]); } sub set_is_active { $_[0]->set('isactive', $_[1]); }
sub set_name { $_[0]->set('name', $_[1]); } sub set_name { $_[0]->set('name', $_[1]); }
...@@ -407,25 +417,6 @@ sub create { ...@@ -407,25 +417,6 @@ sub create {
return $group; return $group;
} }
sub ValidateGroupName {
my ($name, @users) = (@_);
my $dbh = Bugzilla->dbh;
my $query = "SELECT id FROM groups " .
"WHERE name = ?";
if (Bugzilla->params->{'usevisibilitygroups'}) {
my @visible = (-1);
foreach my $user (@users) {
$user && push @visible, @{$user->visible_groups_direct};
}
my $visible = join(', ', @visible);
$query .= " AND id IN($visible)";
}
my $sth = $dbh->prepare($query);
$sth->execute($name);
my ($ret) = $sth->fetchrow_array();
return $ret;
}
############################### ###############################
### Validators ### ### Validators ###
############################### ###############################
...@@ -486,7 +477,6 @@ Bugzilla::Group - Bugzilla group class. ...@@ -486,7 +477,6 @@ Bugzilla::Group - Bugzilla group class.
my $icon_url = $group->icon_url; my $icon_url = $group->icon_url;
my $is_active_bug_group = $group->is_active_bug_group; my $is_active_bug_group = $group->is_active_bug_group;
my $group_id = Bugzilla::Group::ValidateGroupName('admin', @users);
my @groups = Bugzilla::Group->get_all; my @groups = Bugzilla::Group->get_all;
=head1 DESCRIPTION =head1 DESCRIPTION
...@@ -506,24 +496,15 @@ normally does, this function also makes the new group be inherited ...@@ -506,24 +496,15 @@ normally does, this function also makes the new group be inherited
by the C<admin> group. That is, the C<admin> group will automatically by the C<admin> group. That is, the C<admin> group will automatically
be a member of this group. be a member of this group.
=item C<ValidateGroupName($name, @users)>
Description: ValidateGroupName checks to see if ANY of the users
in the provided list of user objects can see the
named group.
Params: $name - String with the group name.
@users - An array with Bugzilla::User objects.
Returns: It returns the group id if successful
and undef otherwise.
=back
=head1 METHODS =head1 METHODS
=over =over
=item C<check_members_are_visible>
Throws an error if this group is not visible (according to
visibility groups) to the currently-logged-in user.
=item C<check_remove> =item C<check_remove>
=over =over
......
...@@ -60,20 +60,20 @@ use Storable qw(dclone); ...@@ -60,20 +60,20 @@ use Storable qw(dclone);
# If you specify a search type in the boolean charts, this describes # If you specify a search type in the boolean charts, this describes
# which operator maps to which internal function here. # which operator maps to which internal function here.
use constant OPERATORS => { use constant OPERATORS => {
equals => \&_equals, equals => \&_simple_operator,
notequals => \&_notequals, notequals => \&_simple_operator,
casesubstring => \&_casesubstring, casesubstring => \&_casesubstring,
substring => \&_substring, substring => \&_substring,
substr => \&_substring, substr => \&_substring,
notsubstring => \&_notsubstring, notsubstring => \&_notsubstring,
regexp => \&_regexp, regexp => \&_regexp,
notregexp => \&_notregexp, notregexp => \&_notregexp,
lessthan => \&_lessthan, lessthan => \&_simple_operator,
lessthaneq => \&_lessthaneq, lessthaneq => \&_simple_operator,
matches => sub { ThrowUserError("search_content_without_matches"); }, matches => sub { ThrowUserError("search_content_without_matches"); },
notmatches => sub { ThrowUserError("search_content_without_matches"); }, notmatches => sub { ThrowUserError("search_content_without_matches"); },
greaterthan => \&_greaterthan, greaterthan => \&_simple_operator,
greaterthaneq => \&_greaterthaneq, greaterthaneq => \&_simple_operator,
anyexact => \&_anyexact, anyexact => \&_anyexact,
anywordssubstr => \&_anywordsubstr, anywordssubstr => \&_anywordsubstr,
allwordssubstr => \&_allwordssubstr, allwordssubstr => \&_allwordssubstr,
...@@ -85,7 +85,35 @@ use constant OPERATORS => { ...@@ -85,7 +85,35 @@ use constant OPERATORS => {
changedafter => \&_changedbefore_changedafter, changedafter => \&_changedbefore_changedafter,
changedfrom => \&_changedfrom_changedto, changedfrom => \&_changedfrom_changedto,
changedto => \&_changedfrom_changedto, changedto => \&_changedfrom_changedto,
changedby => \&_changedby, changedby => \&_changedby,
};
# Some operators are really just standard SQL operators, and are
# all implemented by the _simple_operator function, which uses this
# constant.
use constant SIMPLE_OPERATORS => {
equals => '=',
notequals => '!=',
greaterthan => '>',
greaterthaneq => '>=',
lessthan => '<',
lessthaneq => "<=",
};
# Most operators just reverse by removing or adding "not" from/to them.
# However, some operators reverse in a different way, so those are listed
# here.
use constant OPERATOR_REVERSE => {
nowords => 'anywords',
nowordssubstr => 'anywordssubstr',
anywords => 'nowords',
anywordssubstr => 'nowordssubstr',
lessthan => 'greaterthaneq',
lessthaneq => 'greaterthan',
greaterthan => 'lessthaneq',
greaterthaneq => 'lessthan',
# The following don't currently have reversals:
# casesubstring, anyexact, allwords, allwordssubstr
}; };
use constant OPERATOR_FIELD_OVERRIDE => { use constant OPERATOR_FIELD_OVERRIDE => {
...@@ -95,7 +123,7 @@ use constant OPERATOR_FIELD_OVERRIDE => { ...@@ -95,7 +123,7 @@ use constant OPERATOR_FIELD_OVERRIDE => {
_default => \&_attachments_submitter, _default => \&_attachments_submitter,
}, },
assigned_to => { assigned_to => {
_non_changed => \&_assigned_to_reporter_nonchanged, _non_changed => \&_contact_nonchanged,
}, },
cc => { cc => {
_non_changed => \&_cc_nonchanged, _non_changed => \&_cc_nonchanged,
...@@ -104,7 +132,7 @@ use constant OPERATOR_FIELD_OVERRIDE => { ...@@ -104,7 +132,7 @@ use constant OPERATOR_FIELD_OVERRIDE => {
_default => \&_commenter, _default => \&_commenter,
}, },
reporter => { reporter => {
_non_changed => \&_assigned_to_reporter_nonchanged, _non_changed => \&_contact_nonchanged,
}, },
'requestees.login_name' => { 'requestees.login_name' => {
_default => \&_requestees_login_name, _default => \&_requestees_login_name,
...@@ -184,6 +212,7 @@ use constant OPERATOR_FIELD_OVERRIDE => { ...@@ -184,6 +212,7 @@ use constant OPERATOR_FIELD_OVERRIDE => {
greaterthaneq => \&_owner_idle_time_greater_less, greaterthaneq => \&_owner_idle_time_greater_less,
lessthan => \&_owner_idle_time_greater_less, lessthan => \&_owner_idle_time_greater_less,
lessthaneq => \&_owner_idle_time_greater_less, lessthaneq => \&_owner_idle_time_greater_less,
_default => \&_invalid_combination,
}, },
product => { product => {
...@@ -207,7 +236,7 @@ use constant OPERATOR_FIELD_OVERRIDE => { ...@@ -207,7 +236,7 @@ use constant OPERATOR_FIELD_OVERRIDE => {
# Timetracking Fields # Timetracking Fields
percentage_complete => { percentage_complete => {
_default => \&_percentage_complete, _non_changed => \&_percentage_complete,
}, },
work_time => { work_time => {
changedby => \&_work_time_changedby, changedby => \&_work_time_changedby,
...@@ -929,7 +958,6 @@ sub init { ...@@ -929,7 +958,6 @@ sub init {
$col++) $col++)
{ {
my $field = $params->param("field$chart-$row-$col") || "noop"; my $field = $params->param("field$chart-$row-$col") || "noop";
my $original_field = $field; # Saved for search_description
my $operator = $params->param("type$chart-$row-$col") || "noop"; my $operator = $params->param("type$chart-$row-$col") || "noop";
my $value = $params->param("value$chart-$row-$col"); my $value = $params->param("value$chart-$row-$col");
$value = "" if !defined $value; $value = "" if !defined $value;
...@@ -950,39 +978,40 @@ sub init { ...@@ -950,39 +978,40 @@ sub init {
my $quoted = $dbh->quote($value); my $quoted = $dbh->quote($value);
trick_taint($quoted); trick_taint($quoted);
my $term;
my $full_field = $field =~ /\./ ? $field : "bugs.$field"; my $full_field = $field =~ /\./ ? $field : "bugs.$field";
$self->do_search_function({ my %search_args = (
chartid => \$chartid, chart_id => $chartid,
sequence => \$sequence, sequence => $sequence,
f => \$field, field => $field,
ff => \$full_field, full_field => $full_field,
t => \$operator, operator => $operator,
v => \$value, value => $value,
q => \$quoted, quoted => $quoted,
term => \$term,
multi_fields => \@multi_select_fields, multi_fields => \@multi_select_fields,
supptables => \@supptables, joins => \@supptables,
wherepart => \@wherepart, where => \@wherepart,
having => \@having, having => \@having,
groupby => \@groupby, group_by => \@groupby,
chartfields => \%chartfields,
fields => \@fields, fields => \@fields,
}); chart_fields => \%chartfields,
);
if ($term) { # This should add a "term" selement to %search_args.
$self->do_search_function(\%search_args);
if ($search_args{term}) {
# All the things here that don't get pulled out of
# %search_args are their original values before
# do_search_function modified them.
$self->search_description({ $self->search_description({
field => $original_field, type => $operator, field => $field, type => $operator,
value => $value, term => $term, value => $value, term => $search_args{term},
}); });
push(@orlist, $term); push(@orlist, $search_args{term});
} }
else { else {
# This field and this type don't work together. # This field and this type don't work together.
ThrowCodeError("field_type_mismatch", ThrowUserrror("search_field_operator_invalid",
{ field => $params->param("field$chart-$row-$col"), { field => $field, operator => $operator });
type => $params->param("type$chart-$row-$col"),
});
} }
} }
if (@orlist) { if (@orlist) {
...@@ -1116,16 +1145,17 @@ sub init { ...@@ -1116,16 +1145,17 @@ sub init {
# it into SQL, using the constants at the top of this file. # it into SQL, using the constants at the top of this file.
sub do_search_function { sub do_search_function {
my ($self, $args) = @_; my ($self, $args) = @_;
my ($field, $operator, $value) = @$args{qw(f t v)}; my ($field, $operator, $value) = @$args{qw(field operator value)};
my $actual_field = FIELD_MAP->{$$field} || $$field; my $actual_field = FIELD_MAP->{$field} || $field;
$args->{field} = $actual_field;
if (my $parse_func = SPECIAL_PARSING->{$actual_field}) { if (my $parse_func = SPECIAL_PARSING->{$actual_field}) {
$self->$parse_func(%$args); $self->$parse_func($args);
# Some parsing functions set $term, though most do not. # Some parsing functions set $term, though most do not.
# For the ones that set $term, we don't need to do any further # For the ones that set $term, we don't need to do any further
# parsing. # parsing.
return if ${ $args->{term} }; return if $args->{term};
} }
my $override = OPERATOR_FIELD_OVERRIDE->{$actual_field}; my $override = OPERATOR_FIELD_OVERRIDE->{$actual_field};
...@@ -1142,14 +1172,14 @@ sub do_search_function { ...@@ -1142,14 +1172,14 @@ sub do_search_function {
} }
if ($override) { if ($override) {
my $search_func = $self->_pick_override_function($override, $$operator); my $search_func = $self->_pick_override_function($override, $operator);
$self->$search_func(%$args) if $search_func; $self->$search_func($args) if $search_func;
} }
# Some search functions set $term, and some don't. For the ones that # Some search functions set $term, and some don't. For the ones that
# don't (or for fields that don't have overrides) we now call the # don't (or for fields that don't have overrides) we now call the
# direct operator function from OPERATORS. # direct operator function from OPERATORS.
if (!${ $args->{term} }) { if (!defined $args->{term}) {
$self->_do_operator_function($args); $self->_do_operator_function($args);
} }
} }
...@@ -1158,9 +1188,19 @@ sub do_search_function { ...@@ -1158,9 +1188,19 @@ sub do_search_function {
# functions directly. # functions directly.
sub _do_operator_function { sub _do_operator_function {
my ($self, $func_args) = @_; my ($self, $func_args) = @_;
my $operator = $func_args->{t}; my $operator = $func_args->{operator};
my $operator_func = OPERATORS->{$$operator}; my $operator_func = OPERATORS->{$operator};
$self->$operator_func(%$func_args); $self->$operator_func($func_args);
}
sub _reverse_operator {
my ($self, $operator) = @_;
my $reverse = OPERATOR_REVERSE->{$operator};
return $reverse if $reverse;
if ($operator =~ s/^not//) {
return $operator;
}
return "not$operator";
} }
sub _pick_override_function { sub _pick_override_function {
...@@ -1415,180 +1455,185 @@ sub translate_old_column { ...@@ -1415,180 +1455,185 @@ sub translate_old_column {
# Search Functions # Search Functions
##################################################################### #####################################################################
sub _invalid_combination {
my ($self, $args) = @_;
my ($field, $operator) = @$args{qw(field operator)};
ThrowUserError('search_field_operator_invalid',
{ field => $field, operator => $operator });
}
sub _contact_pronoun { sub _contact_pronoun {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($value, $quoted) = @$args{qw(value quoted)};
my ($value, $quoted) = @func_args{qw(v q)};
my $user = $self->{'user'}; my $user = $self->{'user'};
if ($$value =~ /^\%group/) { if ($value =~ /^\%group/) {
$self->_contact_exact_group(%func_args); $self->_contact_exact_group($args);
} }
elsif ($$value =~ /^(%\w+%)$/) { elsif ($value =~ /^(%\w+%)$/) {
$$value = pronoun($1, $user); $args->{value} = pronoun($1, $user);
$$quoted = $$value; $args->{quoted} = $args->{value};
} }
} }
sub _contact_exact_group { sub _contact_exact_group {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($value, $operator, $field, $chart_id, $joins) =
my ($chartid, $supptables, $f, $t, $v, $term) = @$args{qw(value operator field chart_id joins)};
@func_args{qw(chartid supptables f t v term)}; my $dbh = Bugzilla->dbh;
my $user = $self->{'user'};
$$v =~ /\%group\.([^%]+)%/; $value =~ /\%group\.([^%]+)%/;
my $group = $1; my $group = Bugzilla::Group->check($1);
my $groupid = Bugzilla::Group::ValidateGroupName( $group, ($user)); $group->check_members_are_visible();
$groupid || ThrowUserError('invalid_group_name',{name => $group}); my $group_ids = Bugzilla::Group->flatten_group_membership($group->id);
my @childgroups = @{Bugzilla::Group->flatten_group_membership($groupid)}; my $table = "user_group_map_$chart_id";
my $table = "user_group_map_$$chartid"; my $join_sql =
push (@$supptables, "LEFT JOIN user_group_map AS $table " . "LEFT JOIN user_group_map AS $table"
"ON $table.user_id = bugs.$$f " . . " ON $table.user_id = bugs.$field"
"AND $table.group_id IN(" . . " AND " . $dbh->sql_in("$table.group_id", $group_ids)
join(',', @childgroups) . ") " . . " AND $table.isbless = 0";
"AND $table.isbless = 0 " . push(@$joins, $join_sql);
"AND $table.grant_type IN(" . if ($operator =~ /^not/) {
GRANT_DIRECT . "," . GRANT_REGEXP . ")" $args->{term} = "$table.group_id IS NULL";
); }
if ($$t =~ /^not/) { else {
$$term = "$table.group_id IS NULL"; $args->{term} = "$table.group_id IS NOT NULL";
} else {
$$term = "$table.group_id IS NOT NULL";
} }
} }
sub _assigned_to_reporter_nonchanged { sub _contact_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my $field = $args->{field};
my ($f, $ff, $t, $term) =
@func_args{qw(f ff t term)};
$$ff = "profiles.login_name"; $args->{full_field} = "profiles.login_name";
$self->_do_operator_function(\%func_args); $self->_do_operator_function($args);
$$term = "bugs.$$f IN (SELECT userid FROM profiles WHERE $$term)"; my $term = $args->{term};
$args->{term} = "bugs.$field IN (SELECT userid FROM profiles WHERE $term)";
} }
sub _qa_contact_nonchanged { sub _qa_contact_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my $joins = $args->{joins};
my ($supptables, $f, $ff) =
@func_args{qw(supptables f ff)};
push(@$supptables, "LEFT JOIN profiles AS map_qa_contact " . push(@$joins, "LEFT JOIN profiles AS map_qa_contact " .
"ON bugs.qa_contact = map_qa_contact.userid"); "ON bugs.qa_contact = map_qa_contact.userid");
$$ff = "COALESCE(map_$$f.login_name,'')"; $args->{full_field} = "COALESCE(map_qa_contact.login_name,'')";
} }
sub _cc_pronoun { sub _cc_pronoun {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($full_field, $value) = @$args{qw(full_field value)};
my ($full_field, $value, $quoted) = @func_args{qw(ff v q)};
my $user = $self->{'user'}; my $user = $self->{'user'};
if ($$value =~ /\%group/) { if ($value =~ /\%group/) {
return $self->_cc_exact_group(%func_args); return $self->_cc_exact_group($args);
}
elsif ($value =~ /^(%\w+%)$/) {
$args->{value} = pronoun($1, $user);
$args->{quoted} = $args->{value};
$args->{full_field} = "profiles.userid";
} }
elsif ($$value =~ /^(%\w+%)$/) {
$$value = pronoun($1, $user);
$$quoted = $$value;
$$full_field = "profiles.userid";
}
} }
sub _cc_exact_group { sub _cc_exact_group {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $sequence, $joins, $operator, $value) =
my ($chartid, $sequence, $supptables, $t, $v, $term) = @$args{qw(chart_id sequence joins operator value)};
@func_args{qw(chartid sequence supptables t v term)};
my $user = $self->{'user'}; my $user = $self->{'user'};
my $dbh = Bugzilla->dbh;
$$v =~ m/%group\.([^%]+)%/; $value =~ m/%group\.([^%]+)%/;
my $group = $1; my $group = Bugzilla::Group->check($1);
my $groupid = Bugzilla::Group::ValidateGroupName( $group, ($user)); $group->check_members_are_visible();
$groupid || ThrowUserError('invalid_group_name',{name => $group}); my $all_groups = Bugzilla::Group->flatten_group_membership($group->id);
my @childgroups = @{Bugzilla::Group->flatten_group_membership($groupid)};
my $chartseq = $$chartid; # This is for the email1, email2, email3 fields from query.cgi.
if ($$chartid eq "") { if ($chart_id eq "") {
$chartseq = "CC$$sequence"; $chart_id = "CC$$sequence";
$$sequence++; $args->{sequence}++;
} }
my $table = "user_group_map_$chartseq";
push(@$supptables, "LEFT JOIN cc AS cc_$chartseq " . my $group_table = "user_group_map_$chart_id";
"ON bugs.bug_id = cc_$chartseq.bug_id"); my $cc_table = "cc_$chart_id";
push(@$supptables, "LEFT JOIN user_group_map AS $table " . push(@$joins, "LEFT JOIN cc AS $cc_table " .
"ON $table.user_id = cc_$chartseq.who " . "ON bugs.bug_id = $cc_table.bug_id");
"AND $table.group_id IN(" . my $join_sql =
join(',', @childgroups) . ") " . "LEFT JOIN user_group_map AS $group_table"
"AND $table.isbless = 0 " . . " ON $group_table.user_id = $cc_table.who"
"AND $table.grant_type IN(" . . " AND " . $dbh->sql_in("$group_table.group_id", $all_groups)
GRANT_DIRECT . "," . GRANT_REGEXP . ")" . " AND $group_table.isbless = 0 ";
); push(@$joins, $join_sql);
if ($$t =~ /^not/) { if ($operator =~ /^not/) {
$$term = "$table.group_id IS NULL"; $args->{term} = "$group_table.group_id IS NULL";
} else { }
$$term = "$table.group_id IS NOT NULL"; else {
$args->{term} = "$group_table.group_id IS NOT NULL";
} }
} }
sub _cc_nonchanged { sub _cc_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $sequence, $field, $full_field, $operator, $joins, $value) =
my ($chartid, $sequence, $f, $ff, $t, $supptables, $term, $v) = @$args{qw(chart_id sequence field full_field operator joins value)};
@func_args{qw(chartid sequence f ff t supptables term v)};
my $chartseq = $$chartid; # This is for the email1, email2, email3 fields from query.cgi.
if ($$chartid eq "") { if ($chart_id eq "") {
$chartseq = "CC$$sequence"; $chart_id = "CC$$sequence";
$$sequence++; $args->{sequence}++;
} }
if ($$ff eq 'bugs.cc') {
$$ff = "profiles.login_name"; # $full_field might have been changed by one of the cc_pronoun
# functions, in which case we leave it alone.
if ($full_field eq 'bugs.cc') {
$args->{full_field} = "profiles.login_name";
} }
$self->_do_operator_function(\%func_args);
push(@$supptables, "LEFT JOIN cc AS cc_$chartseq " . $self->_do_operator_function($args);
"ON bugs.bug_id = cc_$chartseq.bug_id " .
"AND cc_$chartseq.who IN" . my $term = $args->{term};
"(SELECT userid FROM profiles WHERE $$term)" my $table = "cc_$chart_id";
); my $join_sql =
$$term = "cc_$chartseq.who IS NOT NULL"; "LEFT JOIN cc AS $table"
. " ON bugs.bug_id = $table.bug_id"
. " AND $table.who IN (SELECT userid FROM profiles WHERE $term)";
push(@$joins, $join_sql);
$args->{term} = "$table.who IS NOT NULL";
} }
# XXX This duplicates having Commenter as a search field.
sub _long_desc_changedby { sub _long_desc_changedby {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
my ($chartid, $supptables, $term, $v) =
@func_args{qw(chartid supptables term v)};
my $table = "longdescs_$$chartid"; my $table = "longdescs_$chart_id";
push(@$supptables, "LEFT JOIN longdescs AS $table " . push(@$joins, "LEFT JOIN longdescs AS $table " .
"ON $table.bug_id = bugs.bug_id"); "ON $table.bug_id = bugs.bug_id");
my $id = login_to_id($$v, THROW_ERROR); my $user_id = login_to_id($value, THROW_ERROR);
$$term = "$table.who = $id"; $args->{term} = "$table.who = $user_id";
} }
sub _long_desc_changedbefore_after { sub _long_desc_changedbefore_after {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $operator, $value, $joins) =
my ($chartid, $t, $v, $supptables, $term) = @$args{qw(chart_id operator value joins)};
@func_args{qw(chartid t v supptables term)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $operator = ($$t =~ /before/) ? '<' : '>'; my $sql_operator = ($operator =~ /before/) ? '<' : '>';
my $table = "longdescs_$$chartid"; my $table = "longdescs_$chart_id";
push(@$supptables, "LEFT JOIN longdescs AS $table " . my $sql_date = $dbh->quote(SqlifyDate($value));
"ON $table.bug_id = bugs.bug_id " . my $join_sql =
"AND $table.bug_when $operator " . "LEFT JOIN longdescs AS $table "
$dbh->quote(SqlifyDate($$v)) ); . " ON $table.bug_id = bugs.bug_id"
$$term = "($table.bug_when IS NOT NULL)"; . " AND $table.bug_when $sql_operator $sql_date";
push(@$joins, $join_sql);
$args->{term} = "$table.bug_when IS NOT NULL";
} }
sub _content_matches { sub _content_matches {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $group_by, $fields, $operator, $value) =
my ($chartid, $supptables, $term, $groupby, $fields, $t, $v) = @$args{qw(chart_id joins group_by fields operator value)};
@func_args{qw(chartid supptables term groupby fields t v)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
# "content" is an alias for columns containing text for which we # "content" is an alias for columns containing text for which we
...@@ -1599,25 +1644,26 @@ sub _content_matches { ...@@ -1599,25 +1644,26 @@ sub _content_matches {
# index searches. # index searches.
# Add the fulltext table to the query so we can search on it. # Add the fulltext table to the query so we can search on it.
my $table = "bugs_fulltext_$$chartid"; my $table = "bugs_fulltext_$chart_id";
my $comments_col = "comments"; my $comments_col = "comments";
$comments_col = "comments_noprivate" unless $self->{'user'}->is_insider; $comments_col = "comments_noprivate" unless $self->{'user'}->is_insider;
push(@$supptables, "LEFT JOIN bugs_fulltext AS $table " . push(@$joins, "LEFT JOIN bugs_fulltext AS $table " .
"ON bugs.bug_id = $table.bug_id"); "ON bugs.bug_id = $table.bug_id");
# Create search terms to add to the SELECT and WHERE clauses. # Create search terms to add to the SELECT and WHERE clauses.
my ($term1, $rterm1) = $dbh->sql_fulltext_search("$table.$comments_col", my ($term1, $rterm1) =
$$v, 1); $dbh->sql_fulltext_search("$table.$comments_col", $value, 1);
my ($term2, $rterm2) = $dbh->sql_fulltext_search("$table.short_desc", my ($term2, $rterm2) =
$$v, 2); $dbh->sql_fulltext_search("$table.short_desc", $value, 2);
$rterm1 = $term1 if !$rterm1; $rterm1 = $term1 if !$rterm1;
$rterm2 = $term2 if !$rterm2; $rterm2 = $term2 if !$rterm2;
# The term to use in the WHERE clause. # The term to use in the WHERE clause.
$$term = "$term1 > 0 OR $term2 > 0"; my $term = "$term1 > 0 OR $term2 > 0";
if ($$t =~ /not/i) { if ($operator =~ /not/i) {
$$term = "NOT($$term)"; $term = "NOT($term)";
} }
$args->{term} = $term;
# In order to sort by relevance (in case the user requests it), # In order to sort by relevance (in case the user requests it),
# we SELECT the relevance value so we can add it to the ORDER BY # we SELECT the relevance value so we can add it to the ORDER BY
...@@ -1629,282 +1675,262 @@ sub _content_matches { ...@@ -1629,282 +1675,262 @@ sub _content_matches {
my $current = COLUMNS->{'relevance'}->{name}; my $current = COLUMNS->{'relevance'}->{name};
$current = $current ? "$current + " : ''; $current = $current ? "$current + " : '';
# For NOT searches, we just add 0 to the relevance. # For NOT searches, we just add 0 to the relevance.
my $select_term = $$t =~ /not/ ? 0 : "($current$rterm1 + $rterm2)"; my $select_term = $operator =~ /not/ ? 0 : "($current$rterm1 + $rterm2)";
COLUMNS->{'relevance'}->{name} = $select_term; COLUMNS->{'relevance'}->{name} = $select_term;
} }
sub _timestamp_translate { sub _timestamp_translate {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my $value = $args->{value};
my ($value, $quoted) = @func_args{qw(v q)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
return if $$value !~ /^[\+\-]?\d+[hdwmy]$/i; return if $value !~ /^[\+\-]?\d+[hdwmy]$/i;
$$value = SqlifyDate($$value); $args->{value} = SqlifyDate($value);
$$quoted = $dbh->quote($$value); $args->{quoted} = $dbh->quote($args->{value});
} }
# XXX This should probably be merged with cc_pronoun.
sub _commenter_pronoun { sub _commenter_pronoun {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my $value = $args->{value};
my ($full_field, $value, $quoted) = @func_args{qw(ff v q)};
my $user = $self->{'user'}; my $user = $self->{'user'};
if ($$value =~ /^(%\w+%)$/) { if ($value =~ /^(%\w+%)$/) {
$$value = pronoun($1, $user); $args->{value} = pronoun($1, $user);
$$quoted = $$value; $args->{quoted} = $args->{value};
$$full_field = "profiles.userid"; $args->{full_field} = "profiles.userid";
} }
} }
sub _commenter { sub _commenter {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $sequence, $joins, $field, $full_field, $operator) =
my ($chartid, $sequence, $supptables, $f, $ff, $t, $term) = @$args{qw(chart_id sequence joins field full_field operator)};
@func_args{qw(chartid sequence supptables f ff t term)};
my $chartseq = $$chartid; if ($chart_id eq "") {
if ($$chartid eq "") { $chart_id = "LD$sequence";
$chartseq = "LD$$sequence"; $args->{sequence}++;
$$sequence++;
} }
my $table = "longdescs_$chartseq"; my $table = "longdescs_$chart_id";
my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1";
if ($$ff eq 'bugs.commenter') { my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
$$ff = "profiles.login_name"; # commenter_pronoun could have changed $full_field to something else,
# so we only set this if commenter_pronoun hasn't set it.
if ($full_field eq 'bugs.commenter') {
$args->{full_field} = "profiles.login_name";
} }
$self->_do_operator_function(\%func_args); $self->_do_operator_function($args);
push(@$supptables, "LEFT JOIN longdescs AS $table " . my $term = $args->{term};
"ON $table.bug_id = bugs.bug_id $extra " . my $join_sql =
"AND $table.who IN" . "LEFT JOIN longdescs AS $table"
"(SELECT userid FROM profiles WHERE $$term)" . " ON $table.bug_id = bugs.bug_id $extra"
); . " AND $table.who IN (SELECT userid FROM profiles WHERE $term)";
$$term = "$table.who IS NOT NULL"; push(@$joins, $join_sql);
$args->{term} = "$table.who IS NOT NULL";
} }
sub _long_desc { sub _long_desc {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins) = @$args{qw(chart_id joins)};
my ($chartid, $supptables, $ff) =
@func_args{qw(chartid supptables ff)};
my $table = "longdescs_$$chartid"; my $table = "longdescs_$chart_id";
my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1"; my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
push(@$supptables, "LEFT JOIN longdescs AS $table " . push(@$joins, "LEFT JOIN longdescs AS $table " .
"ON $table.bug_id = bugs.bug_id $extra"); "ON $table.bug_id = bugs.bug_id $extra");
$$ff = "$table.thetext"; $args->{full_field} = "$table.thetext";
} }
sub _longdescs_isprivate { sub _longdescs_isprivate {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins) = @$args{qw(chart_id joins)};
my ($chartid, $supptables, $ff) =
@func_args{qw(chartid supptables ff)};
my $table = "longdescs_$$chartid"; my $table = "longdescs_$chart_id";
my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1"; my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
push(@$supptables, "LEFT JOIN longdescs AS $table " . push(@$joins, "LEFT JOIN longdescs AS $table " .
"ON $table.bug_id = bugs.bug_id $extra"); "ON $table.bug_id = bugs.bug_id $extra");
$$ff = "$table.isprivate"; $args->{quoted} = $args->{value} ? 1 : 0;
$args->{full_field} = "$table.isprivate";
} }
sub _work_time_changedby { sub _work_time_changedby {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
my ($chartid, $supptables, $v, $term) =
@func_args{qw(chartid supptables v term)};
my $table = "longdescs_$$chartid"; my $table = "longdescs_$chart_id";
push(@$supptables, "LEFT JOIN longdescs AS $table " . push(@$joins, "LEFT JOIN longdescs AS $table " .
"ON $table.bug_id = bugs.bug_id"); "ON $table.bug_id = bugs.bug_id");
my $id = login_to_id($$v, THROW_ERROR); my $user_id = login_to_id($value, THROW_ERROR);
$$term = "(($table.who = $id"; $args->{term} = "$table.who = $user_id AND $table.work_time != 0";
$$term .= ") AND ($table.work_time <> 0))";
} }
sub _work_time_changedbefore_after { sub _work_time_changedbefore_after {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $operator, $value, $joins) =
my ($chartid, $t, $v, $supptables, $term) = @$args{qw(chart_id operator value joins)};
@func_args{qw(chartid t v supptables term)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $operator = ($$t =~ /before/) ? '<' : '>'; my $table = "longdescs_$chart_id";
my $table = "longdescs_$$chartid"; my $sql_operator = ($operator =~ /before/) ? '<' : '>';
push(@$supptables, "LEFT JOIN longdescs AS $table " . my $sql_date = $dbh->quote(SqlifyDate($value));
"ON $table.bug_id = bugs.bug_id " . my $join_sql =
"AND $table.work_time <> 0 " . "LEFT JOIN longdescs AS $table"
"AND $table.bug_when $operator " . . " ON $table.bug_id = bugs.bug_id"
$dbh->quote(SqlifyDate($$v)) ); . " AND $table.work_time != 0"
$$term = "($table.bug_when IS NOT NULL)"; . " AND $table.bug_when $sql_operator $sql_date";
push(@$joins, $join_sql);
$args->{term} = "$table.bug_when IS NOT NULL";
} }
sub _work_time { sub _work_time {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins) = @$args{qw(chart_id joins)};
my ($chartid, $supptables, $ff) =
@func_args{qw(chartid supptables ff)};
my $table = "longdescs_$$chartid"; my $table = "longdescs_$chart_id";
push(@$supptables, "LEFT JOIN longdescs AS $table " . push(@$joins, "LEFT JOIN longdescs AS $table " .
"ON $table.bug_id = bugs.bug_id"); "ON $table.bug_id = bugs.bug_id");
$$ff = "$table.work_time"; $args->{full_field} = "$table.work_time";
} }
sub _percentage_complete { sub _percentage_complete {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $operator, $value, $having, $fields) =
my ($t, $chartid, $supptables, $fields, $q, $v, $having, $groupby, $term) = @$args{qw(chart_id joins operator value having fields)};
@func_args{qw(t chartid supptables fields q v having groupby term)};
my $dbh = Bugzilla->dbh; my $table = "longdescs_$chart_id";
my $oper; # We can't just use "percentage_complete" as the field, because
if ($$t eq "equals") { # (a) PostgreSQL doesn't accept it in the HAVING clause
$oper = "="; # and (b) it wouldn't work in multiple chart rows, because it uses
} elsif ($$t eq "greaterthan") { # a fixed name for the table, "ldtime".
$oper = ">"; my $expression = COLUMNS->{percentage_complete}->{name};
} elsif ($$t eq "greaterthaneq") { $expression =~ s/\bldtime\b/$table/g;
$oper = ">="; $args->{full_field} = "($expression)";
} elsif ($$t eq "lessthan") { push(@$joins, "LEFT JOIN longdescs AS $table " .
$oper = "<"; "ON $table.bug_id = bugs.bug_id");
} elsif ($$t eq "lessthaneq") {
$oper = "<="; # We need remaining_time in @fields, otherwise we can't use
} elsif ($$t eq "notequal") { # it in the expression for creating percentage_complete.
$oper = "<>"; if (!grep { $_ eq 'remaining_time' } @$fields) {
} elsif ($$t eq "regexp") { push(@$fields, 'remaining_time');
# This is just a dummy to help catch bugs- $oper won't be used
# since "regexp" is treated as a special case below. But
# leaving $oper uninitialized seems risky...
$oper = "sql_regexp";
} elsif ($$t eq "notregexp") {
# This is just a dummy to help catch bugs- $oper won't be used
# since "notregexp" is treated as a special case below. But
# leaving $oper uninitialized seems risky...
$oper = "sql_not_regexp";
} else {
$oper = "noop";
}
if ($oper ne "noop") {
my $table = "longdescs_$$chartid";
if (!grep($_ eq 'remaining_time', @$fields)) {
push(@$fields, "remaining_time");
}
push(@$supptables, "LEFT JOIN longdescs AS $table " .
"ON $table.bug_id = bugs.bug_id");
my $expression = "(100 * ((SUM($table.work_time) *
COUNT(DISTINCT $table.bug_when) /
COUNT(bugs.bug_id)) /
((SUM($table.work_time) *
COUNT(DISTINCT $table.bug_when) /
COUNT(bugs.bug_id)) +
bugs.remaining_time)))";
$$q = $dbh->quote($$v);
trick_taint($$q);
if ($$t eq "regexp") {
push(@$having, $dbh->sql_regexp($expression, $$q));
} elsif ($$t eq "notregexp") {
push(@$having, $dbh->sql_not_regexp($expression, $$q));
} else {
push(@$having, "$expression $oper " . $$q);
}
push(@$groupby, "bugs.remaining_time");
} }
$$term = "0=0";
$self->_do_operator_function($args);
push(@$having, $args->{term});
# We put something into $args->{term} so that do_search_function
# stops processing.
$args->{term} = "0=0";
} }
sub _bug_group_nonchanged { sub _bug_group_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $field) = @$args{qw(chart_id joins field)};
my ($supptables, $chartid, $ff, $t, $term) =
@func_args{qw(supptables chartid ff t term)}; my $map_table = "bug_group_map_$chart_id";
push(@$joins,
push(@$supptables, "LEFT JOIN bug_group_map AS $map_table " .
"LEFT JOIN bug_group_map AS bug_group_map_$$chartid " . "ON bugs.bug_id = $map_table.bug_id");
"ON bugs.bug_id = bug_group_map_$$chartid.bug_id");
$$ff = "groups_$$chartid.name"; my $groups_table = "groups_$chart_id";
$self->_do_operator_function(\%func_args); my $full_field = "$groups_table.name";
push(@$supptables, $args->{full_field} = $full_field;
"LEFT JOIN groups AS groups_$$chartid " . $self->_do_operator_function($args);
"ON groups_$$chartid.id = bug_group_map_$$chartid.group_id " . my $term = $args->{term};
"AND $$term"); push(@$joins,
$$term = "$$ff IS NOT NULL"; "LEFT JOIN groups AS $groups_table " .
"ON $groups_table.id = $map_table.group_id AND $term");
$args->{term} = "$full_field IS NOT NULL";
} }
sub _attach_data_thedata { sub _attach_data_thedata {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins) = @$args{qw(chart_id joins)};
my ($chartid, $supptables, $ff) =
@func_args{qw(chartid supptables ff)};
my $atable = "attachments_$$chartid"; my $attach_table = "attachments_$chart_id";
my $dtable = "attachdata_$$chartid"; my $data_table = "attachdata_$chart_id";
my $extra = $self->{'user'}->is_insider ? "" : "AND $atable.isprivate = 0"; my $extra = $self->{'user'}->is_insider
push(@$supptables, "LEFT JOIN attachments AS $atable " . ? "" : "AND $attach_table.isprivate = 0";
"ON bugs.bug_id = $atable.bug_id $extra"); push(@$joins, "LEFT JOIN attachments AS $attach_table " .
push(@$supptables, "LEFT JOIN attach_data AS $dtable " . "ON bugs.bug_id = $attach_table.bug_id $extra");
"ON $dtable.id = $atable.attach_id"); push(@$joins, "LEFT JOIN attach_data AS $data_table " .
$$ff = "$dtable.thedata"; "ON $data_table.id = $attach_table.attach_id");
$args->{full_field} = "$data_table.thedata";
} }
sub _attachments_submitter { sub _attachments_submitter {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins) = @$args{qw(chart_id joins)};
my ($chartid, $supptables, $ff) =
@func_args{qw(chartid supptables ff)}; my $attach_table = "attachment_submitter_$chart_id";
my $extra = $self->{'user'}->is_insider
? "" : "AND $attach_table.isprivate = 0";
push(@$joins, "LEFT JOIN attachments AS $attach_table " .
"ON bugs.bug_id = $attach_table.bug_id $extra");
my $atable = "map_attachment_submitter_$$chartid"; my $map_table = "map_attachment_submitter_$chart_id";
my $extra = $self->{'user'}->is_insider ? "" : "AND $atable.isprivate = 0"; push(@$joins, "LEFT JOIN profiles AS $map_table " .
push(@$supptables, "LEFT JOIN attachments AS $atable " . "ON $attach_table.submitter_id = $map_table.userid");
"ON bugs.bug_id = $atable.bug_id $extra"); $args->{full_field} = "$map_table.login_name";
push(@$supptables, "LEFT JOIN profiles AS attachers_$$chartid " .
"ON $atable.submitter_id = attachers_$$chartid.userid");
$$ff = "attachers_$$chartid.login_name";
} }
sub _attachments { sub _attachments {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $field, $operator, $value) =
my ($chartid, $supptables, $f, $ff, $t, $v, $q) = @$args{qw(chart_id joins field operator value)};
@func_args{qw(chartid supptables f ff t v q)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $table = "attachments_$$chartid"; my $table = "attachments_$chart_id";
my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0"; my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
push(@$supptables, "LEFT JOIN attachments AS $table " . push(@$joins, "LEFT JOIN attachments AS $table " .
"ON bugs.bug_id = $table.bug_id $extra"); "ON bugs.bug_id = $table.bug_id $extra");
$$f =~ m/^attachments\.(.*)$/;
my $field = $1; $field =~ /^attachments\.(.+)$/;
if ($$t eq "changedby") { my $attach_field = $1;
$$v = login_to_id($$v, THROW_ERROR); # XXX This is not actually the correct method of searching for
$$q = $dbh->quote($$v); # changes in attachment values--this just tells you who posted an
$field = "submitter_id"; # attachment.
$$t = "equals"; if ($operator eq "changedby") {
} elsif ($$t eq "changedbefore") { $args->{value} = login_to_id($value, THROW_ERROR);
$$v = SqlifyDate($$v); $args->{quoted} = $args->{value};
$$q = $dbh->quote($$v); $attach_field = "submitter_id";
$field = "creation_ts"; $args->{operator} = "equals";
$$t = "lessthan"; }
} elsif ($$t eq "changedafter") { elsif ($operator eq 'changedbefore' or $operator eq 'changedafter') {
$$v = SqlifyDate($$v); $args->{value} = SqlifyDate($value);
$$q = $dbh->quote($$v); $args->{quoted} = $dbh->quote($args->{value});
$field = "creation_ts"; $attach_field = "creation_ts";
$$t = "greaterthan"; $args->{operator} = $operator eq 'changedbefore' ? "lessthan"
} : "greaterthan";
if ($field eq "ispatch" && $$v ne "0" && $$v ne "1") { }
ThrowUserError("illegal_attachment_is_patch");
} $args->{full_field} = "$table.$attach_field";
if ($field eq "isobsolete" && $$v ne "0" && $$v ne "1") { }
ThrowUserError("illegal_is_obsolete");
} sub _join_flag_tables {
$$ff = "$table.$field"; my ($self, $args) = @_;
my ($joins, $chart_id) = @$args{qw(joins chart_id)};
my $attachments = "attachments_$chart_id";
my $extra = $self->{'user'}->is_insider
? "" : "AND $attachments.isprivate = 0";
push(@$joins, "LEFT JOIN attachments AS $attachments " .
"ON bugs.bug_id = $attachments.bug_id $extra");
my $flags = "flags_$chart_id";
# We join both the bugs and the attachments table in separately,
# and then the join code will later combine the terms.
push(@$joins, "LEFT JOIN flags AS $flags " .
"ON bugs.bug_id = $flags.bug_id ");
push(@$joins, "LEFT JOIN flags AS $flags " .
"ON $flags.attach_id = $attachments.attach_id " .
"OR $flags.attach_id IS NULL");
} }
sub _flagtypes_name { sub _flagtypes_name {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $operator, $joins, $field, $having) =
my ($t, $chartid, $supptables, $ff, $having, $term) = @$args{qw(chart_id operator joins field having)};
@func_args{qw(t chartid supptables ff having term)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
# Matches bugs by flag name/status. # Matches bugs by flag name/status.
...@@ -1914,31 +1940,25 @@ sub _flagtypes_name { ...@@ -1914,31 +1940,25 @@ sub _flagtypes_name {
# Don't do anything if this condition is about changes to flags, # Don't do anything if this condition is about changes to flags,
# as the generic change condition processors can handle those. # as the generic change condition processors can handle those.
return if ($$t =~ m/^changed/); return if $operator =~ /^changed/;
# Add the flags and flagtypes tables to the query. We do # Add the flags and flagtypes tables to the query. We do
# a left join here so bugs without any flags still match # a left join here so bugs without any flags still match
# negative conditions (f.e. "flag isn't review+"). # negative conditions (f.e. "flag isn't review+").
my $attachments = "attachments_$$chartid"; $self->_join_flag_tables($args);
my $extra = $self->{'user'}->is_insider ? "" : "AND $attachments.isprivate = 0"; my $flags = "flags_$chart_id";
push(@$supptables, "LEFT JOIN attachments AS $attachments " . my $flagtypes = "flagtypes_$chart_id";
"ON bugs.bug_id = $attachments.bug_id $extra"); push(@$joins, "LEFT JOIN flagtypes AS $flagtypes " .
my $flags = "flags_$$chartid"; "ON $flags.type_id = $flagtypes.id");
push(@$supptables, "LEFT JOIN flags AS $flags " .
"ON bugs.bug_id = $flags.bug_id ");
my $flagtypes = "flagtypes_$$chartid";
push(@$supptables, "LEFT JOIN flagtypes AS $flagtypes " .
"ON $flags.type_id = $flagtypes.id");
push(@$supptables, "LEFT JOIN flags AS $flags " .
"ON $flags.attach_id = $attachments.attach_id " .
"OR $flags.attach_id IS NULL");
# Generate the condition by running the operator-specific # Generate the condition by running the operator-specific
# function. Afterwards the condition resides in the global $term # function. Afterwards the condition resides in the $args->{term}
# variable. # variable.
$$ff = $dbh->sql_string_concat("${flagtypes}.name", my $full_field = $dbh->sql_string_concat("$flagtypes.name",
"$flags.status"); "$flags.status");
$self->_do_operator_function(\%func_args); $args->{full_field} = $full_field;
$self->_do_operator_function($args);
my $term = $args->{term};
# If this is a negative condition (f.e. flag isn't "review+"), # If this is a negative condition (f.e. flag isn't "review+"),
# we only want bugs where all flags match the condition, not # we only want bugs where all flags match the condition, not
...@@ -1948,542 +1968,433 @@ sub _flagtypes_name { ...@@ -1948,542 +1968,433 @@ sub _flagtypes_name {
# of flags on each bug, then compare them in a HAVING clause. # of flags on each bug, then compare them in a HAVING clause.
# If the numbers are the same, all flags match the condition, # If the numbers are the same, all flags match the condition,
# so this bug should be included. # so this bug should be included.
if ($$t =~ m/not/) { if ($operator =~ /^not/) {
push(@$having, push(@$having,
"SUM(CASE WHEN $$ff IS NOT NULL THEN 1 ELSE 0 END) = " . "SUM(CASE WHEN $full_field IS NOT NULL THEN 1 ELSE 0 END) = " .
"SUM(CASE WHEN $$term THEN 1 ELSE 0 END)"); "SUM(CASE WHEN $term THEN 1 ELSE 0 END)");
$$term = "0=0"; $args->{term} = "0=0";
} }
} }
# XXX These two functions can probably be joined (requestees and setters).
sub _requestees_login_name { sub _requestees_login_name {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins) = @$args{qw(chart_id joins)};
my ($ff, $chartid, $supptables) = @func_args{qw(ff chartid supptables)};
my $attachments = "attachments_$$chartid"; $self->_join_flag_tables($args);
my $extra = $self->{'user'}->is_insider ? "" : "AND $attachments.isprivate = 0"; my $flags = "flags_$chart_id";
push(@$supptables, "LEFT JOIN attachments AS $attachments " . my $map_table = "map_flag_requestees_$chart_id";
"ON bugs.bug_id = $attachments.bug_id $extra"); push(@$joins, "LEFT JOIN profiles AS $map_table " .
my $flags = "flags_$$chartid"; "ON $flags.requestee_id = $map_table.userid");
push(@$supptables, "LEFT JOIN flags AS $flags " .
"ON bugs.bug_id = $flags.bug_id ");
push(@$supptables, "LEFT JOIN profiles AS requestees_$$chartid " .
"ON $flags.requestee_id = requestees_$$chartid.userid");
push(@$supptables, "LEFT JOIN flags AS $flags " .
"ON $flags.attach_id = $attachments.attach_id " .
"OR $flags.attach_id IS NULL");
$$ff = "requestees_$$chartid.login_name"; $args->{full_field} = "$map_table.login_name";
} }
sub _setters_login_name { sub _setters_login_name {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins) = @$args{qw(chart_id joins)};
my ($ff, $chartid, $supptables) = @func_args{qw(ff chartid supptables)};
$self->_join_flag_tables($args);
my $attachments = "attachments_$$chartid"; my $flags = "flags_$chart_id";
my $extra = $self->{'user'}->is_insider ? "" : "AND $attachments.isprivate = 0"; my $map_table = "map_flag_setters_$chart_id";
push(@$supptables, "LEFT JOIN attachments AS $attachments " . push(@$joins, "LEFT JOIN profiles AS $map_table " .
"ON bugs.bug_id = $attachments.bug_id $extra"); "ON $flags.setter_id = $map_table.userid");
my $flags = "flags_$$chartid";
push(@$supptables, "LEFT JOIN flags AS $flags " .
"ON bugs.bug_id = $flags.bug_id ");
push(@$supptables, "LEFT JOIN profiles AS setters_$$chartid " .
"ON $flags.setter_id = setters_$$chartid.userid");
push(@$supptables, "LEFT JOIN flags AS $flags " .
"ON $flags.attach_id = $attachments.attach_id " .
"OR $flags.attach_id IS NULL");
$$ff = "setters_$$chartid.login_name"; $args->{full_field} = "$map_table.login_name";
} }
sub _changedin_days_elapsed { sub _changedin_days_elapsed {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_;
my ($ff) = @func_args{qw(ff)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
$$ff = "(" . $dbh->sql_to_days('NOW()') . " - " . $args->{full_field} = "(" . $dbh->sql_to_days('NOW()') . " - " .
$dbh->sql_to_days('bugs.delta_ts') . ")"; $dbh->sql_to_days('bugs.delta_ts') . ")";
} }
sub _component_nonchanged { sub _component_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_;
my ($ff, $t, $term) = @func_args{qw(ff t term)};
$$ff = "components.name"; $args->{full_field} = "components.name";
$self->_do_operator_function(\%func_args); $self->_do_operator_function($args);
$$term = build_subselect("bugs.component_id", my $term = $args->{term};
"components.id", $args->{term} = build_subselect("bugs.component_id",
"components", "components.id", "components", $args->{term});
$$term);
} }
sub _product_nonchanged { sub _product_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_;
my ($ff, $t, $term) = @func_args{qw(ff t term)};
# Generate the restriction condition # Generate the restriction condition
$$ff = "products.name"; $args->{full_field} = "products.name";
$self->_do_operator_function(\%func_args); $self->_do_operator_function($args);
$$term = build_subselect("bugs.product_id", my $term = $args->{term};
"products.id", $args->{term} = build_subselect("bugs.product_id",
"products", "products.id", "products", $term);
$$term);
} }
sub _classification_nonchanged { sub _classification_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my $joins = $args->{joins};
my ($chartid, $v, $ff, $t, $supptables, $term) =
@func_args{qw(chartid v ff t supptables term)};
# Generate the restriction condition # Generate the restriction condition
push @$supptables, "INNER JOIN products AS map_products " . push(@$joins, "INNER JOIN products AS map_products " .
"ON bugs.product_id = map_products.id"; "ON bugs.product_id = map_products.id");
$$ff = "classifications.name"; $args->{full_field} = "classifications.name";
$self->_do_operator_function(\%func_args); $self->_do_operator_function($args);
$$term = build_subselect("map_products.classification_id", my $term = $args->{term};
"classifications.id", $args->{term} = build_subselect("map_products.classification_id",
"classifications", "classifications.id", "classifications", $term);
$$term);
} }
sub _keywords_exact { sub _keywords_exact {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $value, $operator) =
my ($chartid, $v, $ff, $f, $t, $term, $supptables) = @$args{qw(chart_id joins value operator)};
@func_args{qw(chartid v ff f t term supptables)}; my $dbh = Bugzilla->dbh;
my @list; my @keyword_ids;
my $table = "keywords_$$chartid"; foreach my $value (split(/[\s,]+/, $value)) {
foreach my $value (split(/[\s,]+/, $$v)) { next if $value eq '';
if ($value eq '') { my $keyword = Bugzilla::Keyword->check($value);
next; push(@keyword_ids, $keyword->id);
}
my $keyword = new Bugzilla::Keyword({name => $value});
if ($keyword) {
push(@list, "$table.keywordid = " . $keyword->id);
}
else {
ThrowUserError("unknown_keyword",
{ keyword => $$v });
}
}
my $haveawordterm;
if (@list) {
$haveawordterm = "(" . join(' OR ', @list) . ")";
if ($$t eq "anywords") {
$$term = $haveawordterm;
} elsif ($$t eq "allwords") {
$self->_allwords;
if ($$term && $haveawordterm) {
$$term = "(($$term) AND $haveawordterm)";
}
}
} }
if ($$term) {
push(@$supptables, "LEFT JOIN keywords AS $table " . # XXX We probably should instead throw an error here if there were
"ON $table.bug_id = bugs.bug_id"); # just commas in the field.
if (!@keyword_ids) {
$args->{term} = "0=0";
return;
} }
else {
$self->_keywords_nonchanged(%func_args); # This is an optimization for anywords, since we already know
# the keyword id from having checked it above.
if ($operator eq 'anywords') {
my $table = "keywords_$chart_id";
$args->{term} = $dbh->sql_in("$table.keywordid", \@keyword_ids);
push(@$joins, "LEFT JOIN keywords AS $table"
. " ON $table.bug_id = bugs.bug_id");
return;
} }
$self->_keywords_nonchanged($args);
} }
sub _keywords_nonchanged { sub _keywords_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $value, $operator) =
my ($chartid, $v, $ff, $t, $term, $supptables) = @$args{qw(chart_id joins value operator)};
@func_args{qw(chartid v ff t term supptables)};
my $k_table = "keywords_$$chartid"; my $k_table = "keywords_$chart_id";
my $kd_table = "keyworddefs_$$chartid"; my $kd_table = "keyworddefs_$chart_id";
push(@$supptables, "LEFT JOIN keywords AS $k_table " . push(@$joins, "LEFT JOIN keywords AS $k_table " .
"ON $k_table.bug_id = bugs.bug_id"); "ON $k_table.bug_id = bugs.bug_id");
push(@$supptables, "LEFT JOIN keyworddefs AS $kd_table " . push(@$joins, "LEFT JOIN keyworddefs AS $kd_table " .
"ON $kd_table.id = $k_table.keywordid"); "ON $kd_table.id = $k_table.keywordid");
$$ff = "$kd_table.name"; $args->{full_field} = "$kd_table.name";
} }
# XXX This should be combined with blocked_nonchanged.
sub _dependson_nonchanged { sub _dependson_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $field, $operator) =
my ($chartid, $ff, $f, $t, $term, $supptables) = @$args{qw(chart_id joins field operator)};
@func_args{qw(chartid ff f t term supptables)};
my $table = "dependson_" . $$chartid; my $table = "dependson_$chart_id";
$$ff = "$table.$$f"; my $full_field = "$table.$field";
$self->_do_operator_function(\%func_args); $args->{full_field} = $full_field;
push(@$supptables, "LEFT JOIN dependencies AS $table " . $self->_do_operator_function($args);
"ON $table.blocked = bugs.bug_id " . my $term = $args->{term};
"AND ($$term)"); push(@$joins, "LEFT JOIN dependencies AS $table " .
$$term = "$$ff IS NOT NULL"; "ON $table.blocked = bugs.bug_id AND ($term)");
$args->{term} = "$full_field IS NOT NULL";
} }
sub _blocked_nonchanged { sub _blocked_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $field, $operator) =
my ($chartid, $ff, $f, $t, $term, $supptables) = @$args{qw(chart_id joins field operator)};
@func_args{qw(chartid ff f t term supptables)};
my $table = "blocked_" . $$chartid; my $table = "blocked_$chart_id";
$$ff = "$table.$$f"; my $full_field = "$table.$field";
$self->_do_operator_function(\%func_args); $args->{full_field} = $full_field;
push(@$supptables, "LEFT JOIN dependencies AS $table " . $self->_do_operator_function($args);
"ON $table.dependson = bugs.bug_id " . my $term = $args->{term};
"AND ($$term)"); push(@$joins, "LEFT JOIN dependencies AS $table " .
$$term = "$$ff IS NOT NULL"; "ON $table.dependson = bugs.bug_id AND ($term)");
$args->{term} = "$full_field IS NOT NULL";
} }
sub _alias_nonchanged { sub _alias_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; $args->{full_field} = "COALESCE(bugs.alias, '')";
my ($ff, $t, $term) = @func_args{qw(ff t term)}; $self->_do_operator_function($args);
$$ff = "COALESCE(bugs.alias, '')";
$self->_do_operator_function(\%func_args);
} }
sub _owner_idle_time_greater_less { sub _owner_idle_time_greater_less {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $value, $operator) =
my ($chartid, $v, $supptables, $t, $wherepart, $term) = @$args{qw(chart_id joins value operator)};
@func_args{qw(chartid v supptables t wherepart term)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $table = "idle_" . $$chartid; my $table = "idle_$chart_id";
$$v =~ /^(\d+)\s*([hHdDwWmMyY])?$/; my $quoted = $dbh->quote(SqlifyDate($value));
my $quantity = $1 || 0;
my $unit = lc $2; my $ld_table = "comment_$table";
my $unitinterval = 'DAY'; my $comments_join =
if ($unit eq 'h') { "LEFT JOIN longdescs AS $ld_table"
$unitinterval = 'HOUR'; . " ON $ld_table.who = bugs.assigned_to"
} elsif ($unit eq 'w') { . " AND $ld_table.bug_id = bugs.bug_id"
$unitinterval = ' * 7 DAY'; . " AND $ld_table.bug_when > $quoted";
} elsif ($unit eq 'm') { push(@$joins, $comments_join);
$unitinterval = 'MONTH';
} elsif ($unit eq 'y') { my $act_table = "activity_$table";
$unitinterval = 'YEAR';
}
my $cutoff = "NOW() - " .
$dbh->sql_interval($quantity, $unitinterval);
my $assigned_fieldid = get_field_id('assigned_to'); my $assigned_fieldid = get_field_id('assigned_to');
push(@$supptables, "LEFT JOIN longdescs AS comment_$table " .
"ON comment_$table.who = bugs.assigned_to " . # XXX Why are we joining using $assignedto_fieldid here? It shouldn't
"AND comment_$table.bug_id = bugs.bug_id " . # matter when or if the assignee changed.
"AND comment_$table.bug_when > $cutoff"); my $activity_join =
push(@$supptables, "LEFT JOIN bugs_activity AS activity_$table " . "LEFT JOIN bugs_activity AS $act_table"
"ON (activity_$table.who = bugs.assigned_to " . . " ON ( $act_table.who = bugs.assigned_to"
"OR activity_$table.fieldid = $assigned_fieldid) " . . " OR $act_table.fieldid = $assigned_fieldid )"
"AND activity_$table.bug_id = bugs.bug_id " . . " AND $act_table.bug_id = bugs.bug_id"
"AND activity_$table.bug_when > $cutoff"); . " AND $act_table.bug_when > $quoted";
if ($$t =~ /greater/) { push(@$joins, $activity_join);
push(@$wherepart, "(comment_$table.who IS NULL " .
"AND activity_$table.who IS NULL)"); if ($operator =~ /greater/) {
$args->{term} =
"$ld_table.who IS NULL AND $act_table.who IS NULL)";
} else { } else {
push(@$wherepart, "(comment_$table.who IS NOT NULL " . $args->{term} =
"OR activity_$table.who IS NOT NULL)"); "$ld_table.who IS NOT NULL OR $act_table.who IS NOT NULL";
} }
$$term = "0=0";
} }
sub _multiselect_negative { sub _multiselect_negative {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($field, $operator) = @$args{qw(field operator)};
my ($f, $ff, $t, $term) = @func_args{qw(f ff t term)};
my %map = (
notequals => 'equals',
notregexp => 'regexp',
notsubstring => 'substring',
nowords => 'anywords',
nowordssubstr => 'anywordssubstr',
);
my $table; my $table;
if ($$f eq 'keywords') { if ($field eq 'keywords') {
$table = "keywords LEFT JOIN keyworddefs" $table = "keywords LEFT JOIN keyworddefs"
. " ON keywords.keywordid = keyworddefs.id"; . " ON keywords.keywordid = keyworddefs.id";
$$ff = "keyworddefs.name"; $args->{full_field} = "keyworddefs.name";
} }
else { else {
$table = "bug_$$f"; $table = "bug_$field";
$$ff = "$table.value"; $args->{full_field} = "$table.value";
} }
$args->{operator} = $self->_reverse_operator($operator);
$$t = $map{$$t}; $self->_do_operator_function($args);
$self->_do_operator_function(\%func_args); my $term = $args->{term};
$$term = "bugs.bug_id NOT IN (SELECT bug_id FROM $table WHERE $$term)"; $args->{term} =
"bugs.bug_id NOT IN (SELECT bug_id FROM $table WHERE $term)";
} }
sub _multiselect_multiple { sub _multiselect_multiple {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $field, $operator, $value)
my ($f, $ff, $t, $v, $term) = @func_args{qw(f ff t v term)}; = @$args{qw(chart_id joins field operator value)};
my @terms; my $table = "bug_$field";
my $table = "bug_$$f"; $args->{full_field} = "$table.value";
$$ff = "$table.value";
foreach my $word (split(/[\s,]+/, $$v)) { my @terms;
$$v = $word; foreach my $word (split(/[\s,]+/, $value)) {
$self->_do_operator_function(\%func_args); $args->{value} = $word;
push(@terms, "bugs.bug_id IN $self->_do_operator_function($args);
(SELECT bug_id FROM $table WHERE $$term)"); my $term = $args->{term};
push(@terms, "bugs.bug_id IN (SELECT bug_id FROM $table WHERE $term)");
} }
if ($$t eq 'anyexact') { if ($operator eq 'anyexact') {
$$term = "(" . join(" OR ", @terms) . ")"; $args->{term} = join(" OR ", @terms);
} }
else { else {
$$term = "(" . join(" AND ", @terms) . ")"; $args->{term} = join(" AND ", @terms);
} }
} }
sub _multiselect_nonchanged { sub _multiselect_nonchanged {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $field, $operator) =
my ($chartid, $f, $ff, $t, $supptables) = @$args{qw(chart_id joins field operator)};
@func_args{qw(chartid f ff t supptables)};
my $table = $$f."_".$$chartid;
$$ff = "$table.value";
$self->_do_operator_function(\%func_args);
push(@$supptables, "LEFT JOIN bug_$$f AS $table " .
"ON $table.bug_id = bugs.bug_id ");
}
sub _equals { my $table = "${field}_$chart_id";
my $self = shift; $args->{full_field} = "$table.value";
my %func_args = @_; push(@$joins, "LEFT JOIN bug_$field AS $table " .
my ($ff, $q, $term) = @func_args{qw(ff q term)}; "ON $table.bug_id = bugs.bug_id ");
$$term = "$$ff = $$q";
} }
sub _notequals { sub _simple_operator {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($full_field, $quoted, $operator) =
my ($ff, $q, $term) = @func_args{qw(ff q term)}; @$args{qw(full_field quoted operator)};
my $sql_operator = SIMPLE_OPERATORS->{$operator};
$$term = "$$ff != $$q"; $args->{term} = "$full_field $sql_operator $quoted";
} }
sub _casesubstring { sub _casesubstring {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($full_field, $quoted) = @$args{qw(full_field quoted)};
my ($ff, $q, $term) = @func_args{qw(ff q term)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
$$term = $dbh->sql_position($$q, $$ff) . " > 0"; $args->{term} = $dbh->sql_position($quoted, $full_field) . " > 0";
} }
sub _substring { sub _substring {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($full_field, $quoted) = @$args{qw(full_field quoted)};
my ($ff, $q, $term) = @func_args{qw(ff q term)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
$$term = $dbh->sql_iposition($$q, $$ff) . " > 0"; # XXX This should probably be changed to just use LIKE
$args->{term} = $dbh->sql_iposition($quoted, $full_field) . " > 0";
} }
sub _notsubstring { sub _notsubstring {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($full_field, $quoted) = @$args{qw(full_field quoted)};
my ($ff, $q, $term) = @func_args{qw(ff q term)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
$$term = $dbh->sql_iposition($$q, $$ff) . " = 0"; # XXX This should probably be changed to just use NOT LIKE
$args->{term} = $dbh->sql_iposition($quoted, $full_field) . " = 0";
} }
sub _regexp { sub _regexp {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($full_field, $quoted) = @$args{qw(full_field quoted)};
my ($ff, $q, $term) = @func_args{qw(ff q term)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
$$term = $dbh->sql_regexp($$ff, $$q); $args->{term} = $dbh->sql_regexp($full_field, $quoted);
} }
sub _notregexp { sub _notregexp {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($full_field, $quoted) = @$args{qw(full_field quoted)};
my ($ff, $q, $term) = @func_args{qw(ff q term)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
$$term = $dbh->sql_not_regexp($$ff, $$q); $args->{term} = $dbh->sql_not_regexp($full_field, $quoted);
}
sub _lessthan {
my $self = shift;
my %func_args = @_;
my ($ff, $q, $term) = @func_args{qw(ff q term)};
$$term = "$$ff < $$q";
}
sub _lessthaneq {
my $self = shift;
my %func_args = @_;
my ($ff, $q, $term) = @func_args{qw(ff q term)};
$$term = "$$ff <= $$q";
}
sub _greaterthan {
my $self = shift;
my %func_args = @_;
my ($ff, $q, $term) = @func_args{qw(ff q term)};
$$term = "$$ff > $$q";
}
sub _greaterthaneq {
my $self = shift;
my %func_args = @_;
my ($ff, $q, $term) = @func_args{qw(ff q term)};
$$term = "$$ff >= $$q";
} }
sub _anyexact { sub _anyexact {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($field, $value, $full_field) = @$args{qw(field value full_field)};
my ($f, $ff, $v, $q, $term) = @func_args{qw(f ff v q term)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my @list; my @list;
foreach my $w (split(/,/, $$v)) { foreach my $word (split(/,/, $value)) {
if ($w eq "---" && $$f =~ /resolution/) { if ($word eq "---" && $field eq 'resolution') {
$w = ""; $word = "";
} }
$$q = $dbh->quote($w); my $quoted_word = $dbh->quote($word);
trick_taint($$q); trick_taint($quoted_word);
push(@list, $$q); push(@list, $quoted_word);
} }
if (@list) { if (@list) {
$$term = $dbh->sql_in($$ff, \@list); $args->{term} = $dbh->sql_in($full_field, \@list);
}
# XXX Perhaps if it's all commas, we should just throw an error.
else {
$args->{term} = "0=0";
} }
} }
sub _anywordsubstr { sub _anywordsubstr {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($full_field, $value) = @$args{qw(full_field value)};
my ($ff, $v, $term) = @func_args{qw(ff v term)};
$$term = join(" OR ", @{GetByWordListSubstr($$ff, $$v)}); my $list = GetByWordListSubstr($full_field, $value);
$args->{term} = join(" OR ", @$list);
} }
sub _allwordssubstr { sub _allwordssubstr {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($full_field, $value) = @$args{qw(full_field value)};
my ($ff, $v, $term) = @func_args{qw(ff v term)};
$$term = join(" AND ", @{GetByWordListSubstr($$ff, $$v)}); my $list = GetByWordListSubstr($full_field, $value);
$args->{term} = join(" AND ", @$list);
} }
sub _nowordssubstr { sub _nowordssubstr {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; $self->_anywordsubstr($args);
my ($ff, $v, $term) = @func_args{qw(ff v term)}; my $term = $args->{term};
$args->{term} = "NOT($term)";
my @list = @{GetByWordListSubstr($$ff, $$v)};
if (@list) {
$$term = "NOT (" . join(" OR ", @list) . ")";
}
} }
sub _anywords { sub _anywords {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($full_field, $value) = @$args{qw(full_field value)};
my ($ff, $v, $term) = @func_args{qw(ff v term)};
$$term = join(" OR ", @{GetByWordList($$ff, $$v)}); my $list = GetByWordList($full_field, $value);
$args->{term} = join(" OR ", @$list);
} }
sub _allwords { sub _allwords {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($full_field, $value) = @$args{qw(full_field value)};
my ($ff, $v, $term) = @func_args{qw(ff v term)};
$$term = join(" AND ", @{GetByWordList($$ff, $$v)}); my $list = GetByWordList($full_field, $value);
$args->{term} = join(" AND ", @$list);
} }
sub _nowords { sub _nowords {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; $self->_anywords($args);
my ($ff, $v, $term) = @func_args{qw(ff v term)}; my $term = $args->{term};
$args->{term} = "NOT($term)";
my @list = @{GetByWordList($$ff, $$v)};
if (@list) {
$$term = "NOT (" . join(" OR ", @list) . ")";
}
} }
sub _changedbefore_changedafter { sub _changedbefore_changedafter {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $field, $operator, $value) =
my ($chartid, $f, $ff, $t, $v, $chartfields, $supptables, $term) = @$args{qw(chart_id joins field operator value)};
@func_args{qw(chartid f ff t v chartfields supptables term)};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $operator = ($$t =~ /before/) ? '<' : '>'; my $sql_operator = ($operator =~ /before/) ? '<' : '>';
my $table = "act_$$chartid"; my $table = "act_$chart_id";
my $fieldid = $$chartfields{$$f}; my $field_id = get_field_id($field);
if (!$fieldid) { my $sql_date = $dbh->quote(SqlifyDate($value));
ThrowCodeError("invalid_field_name", {field => $$f}); push(@$joins,
} "LEFT JOIN bugs_activity AS $table"
push(@$supptables, "LEFT JOIN bugs_activity AS $table " . . " ON $table.bug_id = bugs.bug_id"
"ON $table.bug_id = bugs.bug_id " . . " AND $table.fieldid = $field_id"
"AND $table.fieldid = $fieldid " . . " AND $table.bug_when $sql_operator $sql_date");
"AND $table.bug_when $operator " . $args->{term} = "$table.bug_when IS NOT NULL";
$dbh->quote(SqlifyDate($$v)) );
$$term = "($table.bug_when IS NOT NULL)";
} }
sub _changedfrom_changedto { sub _changedfrom_changedto {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $field, $operator, $quoted) =
my ($chartid, $chartfields, $f, $t, $v, $q, $supptables, $term) = @$args{qw(chart_id joins field operator quoted)};
@func_args{qw(chartid chartfields f t v q supptables term)};
my $operator = ($$t =~ /from/) ? 'removed' : 'added'; my $column = ($operator =~ /from/) ? 'removed' : 'added';
my $table = "act_$$chartid"; my $table = "act_$chart_id";
my $fieldid = $$chartfields{$$f}; my $field_id = get_field_id($field);
if (!$fieldid) { push(@$joins,
ThrowCodeError("invalid_field_name", {field => $$f}); "LEFT JOIN bugs_activity AS $table"
} . " ON $table.bug_id = bugs.bug_id"
push(@$supptables, "LEFT JOIN bugs_activity AS $table " . . " AND $table.fieldid = $field_id"
"ON $table.bug_id = bugs.bug_id " . . " AND $table.$column = $quoted");
"AND $table.fieldid = $fieldid " . $args->{term} = "$table.bug_when IS NOT NULL";
"AND $table.$operator = $$q");
$$term = "($table.bug_when IS NOT NULL)";
} }
sub _changedby { sub _changedby {
my $self = shift; my ($self, $args) = @_;
my %func_args = @_; my ($chart_id, $joins, $field, $operator, $value) =
my ($chartid, $chartfields, $f, $v, $supptables, $term) = @$args{qw(chart_id joins field operator value)};
@func_args{qw(chartid chartfields f v supptables term)};
my $table = "act_$chart_id";
my $table = "act_$$chartid"; my $field_id = get_field_id($field);
my $fieldid = $$chartfields{$$f}; my $user_id = login_to_id($value, THROW_ERROR);
if (!$fieldid) { push(@$joins,
ThrowCodeError("invalid_field_name", {field => $$f}); "LEFT JOIN bugs_activity AS $table"
} . " ON $table.bug_id = bugs.bug_id"
my $id = login_to_id($$v, THROW_ERROR); . " AND $table.fieldid = $field_id"
push(@$supptables, "LEFT JOIN bugs_activity AS $table " . . " AND $table.who = $user_id");
"ON $table.bug_id = bugs.bug_id " . $args->{term} = "$table.bug_when IS NOT NULL";
"AND $table.fieldid = $fieldid " .
"AND $table.who = $id");
$$term = "($table.bug_when IS NOT NULL)";
} }
1; 1;
...@@ -184,10 +184,6 @@ ...@@ -184,10 +184,6 @@
'[% field.description FILTER html %]' ([% field.name FILTER html %]) '[% field.description FILTER html %]' ([% field.name FILTER html %])
is not a custom field. is not a custom field.
[% ELSIF error == "field_type_mismatch" %]
Cannot seem to handle <code>[% field FILTER html %]</code>
and <code>[% type FILTER html %]</code> together.
[% ELSIF error == "field_type_not_specified" %] [% ELSIF error == "field_type_not_specified" %]
[% title = "Field Type Not Specified" %] [% title = "Field Type Not Specified" %]
You must specify a type when creating a custom field. You must specify a type when creating a custom field.
......
...@@ -744,11 +744,15 @@ ...@@ -744,11 +744,15 @@
[% title = "Group not specified" %] [% title = "Group not specified" %]
No group was specified. No group was specified.
[% ELSIF error == "group_not_visible" %]
[% title = "Group Not Allowed" %]
You are not allowed to see members of the [% group.name FILTER html %]
group.
[% ELSIF error == "system_group_not_deletable" %] [% ELSIF error == "system_group_not_deletable" %]
[% 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
...@@ -1288,6 +1292,9 @@ ...@@ -1288,6 +1292,9 @@
[% IF class == "Bugzilla::User" %] [% IF class == "Bugzilla::User" %]
Either you mis-typed the name or that user has not yet registered Either you mis-typed the name or that user has not yet registered
for a [% terms.Bugzilla %] account. for a [% terms.Bugzilla %] account.
[% ELSIF class == "Bugzilla::Keyword" %]
The legal keyword names are <a href="describekeywords.cgi">listed
here</a>.
[% END %] [% END %]
[% ELSIF error == "old_password_incorrect" %] [% ELSIF error == "old_password_incorrect" %]
...@@ -1477,6 +1484,12 @@ ...@@ -1477,6 +1484,12 @@
and the "matches" search can only be used with the "content" and the "matches" search can only be used with the "content"
field. field.
[% ELSIF error == "search_field_operator_invalid" %]
[% terms.Bugzilla %] does not support using the
"[%+ field_descs.$field FILTER html %]" ([% field FILTER html %])
field with the "[% search_descs.$operator %]" ([% operator FILTER html %])
search type.
[% ELSIF error == "series_already_exists" %] [% ELSIF error == "series_already_exists" %]
[% title = "Series Already Exists" %] [% title = "Series Already Exists" %]
[% docslinks = {'reporting.html' => 'Reporting'} %] [% docslinks = {'reporting.html' => 'Reporting'} %]
...@@ -1562,11 +1575,6 @@ ...@@ -1562,11 +1575,6 @@
I could not figure out what you wanted to do. I could not figure out what you wanted to do.
[% END %] [% END %]
[% ELSIF error == "unknown_keyword" %]
[% title = "Unknown Keyword" %]
<code>[% keyword FILTER html %]</code> is not a known keyword.
The legal keyword names are <a href="describekeywords.cgi">listed here</a>.
[% ELSIF error == "unknown_tab" %] [% ELSIF error == "unknown_tab" %]
[% title = "Unknown Tab" %] [% title = "Unknown Tab" %]
<code>[% current_tab_name FILTER html %]</code> is not a legal tab name. <code>[% current_tab_name FILTER html %]</code> is not a legal tab name.
...@@ -1759,6 +1767,8 @@ ...@@ -1759,6 +1767,8 @@
field field
[% ELSIF class == "Bugzilla::Group" %] [% ELSIF class == "Bugzilla::Group" %]
group group
[% ELSIF class == "Bugzilla::Keyword" %]
keyword
[% ELSIF class == "Bugzilla::Product" %] [% ELSIF class == "Bugzilla::Product" %]
product product
[% ELSIF class == "Bugzilla::Search::Recent" %] [% ELSIF class == "Bugzilla::Search::Recent" %]
......
...@@ -179,22 +179,6 @@ use constant FIELD_SUBSTR_SIZE => { ...@@ -179,22 +179,6 @@ use constant FIELD_SUBSTR_SIZE => {
# See the KNOWN_BROKEN constant for a general description of these # See the KNOWN_BROKEN constant for a general description of these
# "_BROKEN" constants. # "_BROKEN" constants.
# Search.pm currently enforces "this must be a 0 or 1" in situations
# where it should not, with two of the attachment booleans.
use constant ATTACHMENT_BOOLEANS_SEARCH_BROKEN => (
'attachments.ispatch' => { search => 1 },
'attachments.isobsolete' => { search => 1 },
);
# Sometimes the search for attachment booleans works, but then contains
# the wrong results, because it does not contain bugs that fully lack
# attachments.
use constant ATTACHMENT_BOOLEANS_CONTAINS_BROKEN => (
'attachments.isobsolete' => { contains => [5] },
'attachments.ispatch' => { contains => [5] },
'attachments.isprivate' => { contains => [5] },
);
# Certain fields fail all the "negative" search tests: # Certain fields fail all the "negative" search tests:
# #
# Blocked and Dependson "notequals" only finds bugs that have # Blocked and Dependson "notequals" only finds bugs that have
...@@ -223,7 +207,9 @@ use constant ATTACHMENT_BOOLEANS_CONTAINS_BROKEN => ( ...@@ -223,7 +207,9 @@ use constant ATTACHMENT_BOOLEANS_CONTAINS_BROKEN => (
# #
# requestees.login_name doesn't find bugs that fully lack requestees. # requestees.login_name doesn't find bugs that fully lack requestees.
use constant NEGATIVE_BROKEN => ( use constant NEGATIVE_BROKEN => (
ATTACHMENT_BOOLEANS_CONTAINS_BROKEN, 'attachments.isobsolete' => { contains => [5] },
'attachments.ispatch' => { contains => [5] },
'attachments.isprivate' => { contains => [5] },
'attach_data.thedata' => { contains => [5] }, 'attach_data.thedata' => { contains => [5] },
'attachments.description' => { contains => [5] }, 'attachments.description' => { contains => [5] },
'attachments.filename' => { contains => [5] }, 'attachments.filename' => { contains => [5] },
...@@ -237,7 +223,6 @@ use constant NEGATIVE_BROKEN => ( ...@@ -237,7 +223,6 @@ use constant NEGATIVE_BROKEN => (
dependson => { contains => [2,4,5] }, dependson => { contains => [2,4,5] },
longdesc => { contains => [1] }, longdesc => { contains => [1] },
'longdescs.isprivate' => { contains => [1] }, 'longdescs.isprivate' => { contains => [1] },
percentage_complete => { contains => [1] },
'requestees.login_name' => { contains => [3,4,5] }, 'requestees.login_name' => { contains => [3,4,5] },
'setters.login_name' => { contains => [5] }, 'setters.login_name' => { contains => [5] },
work_time => { contains => [1] }, work_time => { contains => [1] },
...@@ -271,16 +256,12 @@ use constant GREATERTHAN_BROKEN => ( ...@@ -271,16 +256,12 @@ use constant GREATERTHAN_BROKEN => (
# allwordssubstr work_time only matches against a single comment, # allwordssubstr work_time only matches against a single comment,
# instead of matching against all comments on a bug. Same is true # instead of matching against all comments on a bug. Same is true
# for the other longdesc fields, cc, keywords, and bug_group. # for the other longdesc fields, cc, keywords, and bug_group.
#
# percentage_complete just drops in 0=0 for the term.
use constant ALLWORDS_BROKEN => ( use constant ALLWORDS_BROKEN => (
ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
bug_group => { contains => [1] }, bug_group => { contains => [1] },
cc => { contains => [1] }, cc => { contains => [1] },
keywords => { contains => [1] }, keywords => { contains => [1] },
longdesc => { contains => [1] }, longdesc => { contains => [1] },
work_time => { contains => [1] }, work_time => { contains => [1] },
percentage_complete => { contains => [2,3,4,5] },
); );
# nowords and nowordssubstr have these broken tests in common. # nowords and nowordssubstr have these broken tests in common.
...@@ -306,7 +287,7 @@ use constant NOWORDS_BROKEN => ( ...@@ -306,7 +287,7 @@ use constant NOWORDS_BROKEN => (
use constant CHANGED_BROKEN => ( use constant CHANGED_BROKEN => (
classification => { contains => [1] }, classification => { contains => [1] },
commenter => { contains => [1] }, commenter => { contains => [1] },
percentage_complete => { contains => [2,3,4,5] }, percentage_complete => { contains => [1] },
'requestees.login_name' => { contains => [1] }, 'requestees.login_name' => { contains => [1] },
'setters.login_name' => { contains => [1] }, 'setters.login_name' => { contains => [1] },
delta_ts => { contains => [1] }, delta_ts => { contains => [1] },
...@@ -346,28 +327,9 @@ use constant CHANGED_VALUE_BROKEN => ( ...@@ -346,28 +327,9 @@ use constant CHANGED_VALUE_BROKEN => (
# while the other fails. In this case, we have a special override for # while the other fails. In this case, we have a special override for
# "operator-value", which uniquely identifies tests. # "operator-value", which uniquely identifies tests.
use constant KNOWN_BROKEN => { use constant KNOWN_BROKEN => {
notequals => { NEGATIVE_BROKEN }, notequals => { NEGATIVE_BROKEN },
# percentage_complete substring matches every bug, regardless of
# its percentage_complete value.
substring => {
percentage_complete => { contains => [2,3,4,5] },
},
casesubstring => {
percentage_complete => { contains => [2,3,4,5] },
},
notsubstring => { NEGATIVE_BROKEN }, notsubstring => { NEGATIVE_BROKEN },
notregexp => { NEGATIVE_BROKEN },
# Attachment noolean fields don't work with regexes, right now,
# because they throw an error that regexes are not valid booleans.
'regexp-^1-' => { ATTACHMENT_BOOLEANS_SEARCH_BROKEN },
# percentage_complete notregexp fails to match bugs that
# fully lack hours worked.
notregexp => {
NEGATIVE_BROKEN,
percentage_complete => { contains => [5] },
},
'notregexp-^1-' => { ATTACHMENT_BOOLEANS_SEARCH_BROKEN },
# percentage_complete doesn't match bugs with 0 hours worked or remaining. # percentage_complete doesn't match bugs with 0 hours worked or remaining.
# #
...@@ -376,19 +338,13 @@ use constant KNOWN_BROKEN => { ...@@ -376,19 +338,13 @@ use constant KNOWN_BROKEN => {
# also broken in this way, but all our comments come from the same user.) # also broken in this way, but all our comments come from the same user.)
# Also, the attachments ones don't find bugs that have no attachments # Also, the attachments ones don't find bugs that have no attachments
# at all (which might be OK?). # at all (which might be OK?).
#
# attachments.isprivate lessthan doesn't find bugs without attachments.
lessthan => { lessthan => {
ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
'attachments.isprivate' => { contains => [5] },
'longdescs.isprivate' => { contains => [1] }, 'longdescs.isprivate' => { contains => [1] },
percentage_complete => { contains => [5] },
work_time => { contains => [1,2,3,4] }, work_time => { contains => [1,2,3,4] },
}, },
# The lessthaneq tests are broken for the same reasons, but they work # The lessthaneq tests are broken for the same reasons, but they work
# slightly differently so they have a different set of broken tests. # slightly differently so they have a different set of broken tests.
lessthaneq => { lessthaneq => {
ATTACHMENT_BOOLEANS_CONTAINS_BROKEN,
'longdescs.isprivate' => { contains => [1] }, 'longdescs.isprivate' => { contains => [1] },
work_time => { contains => [2,3,4] }, work_time => { contains => [2,3,4] },
}, },
...@@ -401,24 +357,18 @@ use constant KNOWN_BROKEN => { ...@@ -401,24 +357,18 @@ use constant KNOWN_BROKEN => {
percentage_complete => { contains => [2] }, percentage_complete => { contains => [2] },
}, },
# percentage_complete just throws 0=0 into the search term, returning # percentage_complete doesn't do a numeric comparison, so
# all bugs. # it doesn't find decimal values.
anyexact => { anyexact => {
ATTACHMENT_BOOLEANS_SEARCH_BROKEN, percentage_complete => { contains => [2] },
percentage_complete => { contains => [3,4,5] },
}, },
# bug_group anywordssubstr returns all our bugs. Not sure why. # bug_group anywordssubstr returns all our bugs. Not sure why.
anywordssubstr => { anywordssubstr => {
ATTACHMENT_BOOLEANS_SEARCH_BROKEN, percentage_complete => { contains => [2] },
percentage_complete => { contains => [3,4,5] },
bug_group => { contains => [3,4,5] }, bug_group => { contains => [3,4,5] },
}, },
'allwordssubstr-<1>' => { ALLWORDS_BROKEN }, 'allwordssubstr-<1>' => { ALLWORDS_BROKEN },
'allwordssubstr-<1>,<2>' => {
ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
percentage_complete => { contains => [1,2,3,4,5] },
},
# flagtypes.name does not work here, probably because they all try to # flagtypes.name does not work here, probably because they all try to
# match against a single flag. # match against a single flag.
# Same for attach_data.thedata. # Same for attach_data.thedata.
...@@ -427,10 +377,6 @@ use constant KNOWN_BROKEN => { ...@@ -427,10 +377,6 @@ use constant KNOWN_BROKEN => {
'attach_data.thedata' => { contains => [1] }, 'attach_data.thedata' => { contains => [1] },
'flagtypes.name' => { contains => [1] }, 'flagtypes.name' => { contains => [1] },
}, },
'allwords-<1> <2>' => {
ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
percentage_complete => { contains => [1,2,3,4,5] },
},
nowordssubstr => { NOWORDS_BROKEN }, nowordssubstr => { NOWORDS_BROKEN },
# attach_data.thedata doesn't match properly with any of the plain # attach_data.thedata doesn't match properly with any of the plain
...@@ -446,15 +392,13 @@ use constant KNOWN_BROKEN => { ...@@ -446,15 +392,13 @@ use constant KNOWN_BROKEN => {
# attach_data doesn't work (perhaps because it's the entire # attach_data doesn't work (perhaps because it's the entire
# data, or some problem with the regex?). # data, or some problem with the regex?).
anywords => { anywords => {
ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
'attach_data.thedata' => { contains => [1] }, 'attach_data.thedata' => { contains => [1] },
bug_group => { contains => [2,3,4,5] }, bug_group => { contains => [2,3,4,5] },
percentage_complete => { contains => [2,3,4,5] },
work_time => { contains => [1] }, work_time => { contains => [1] },
}, },
'anywords-<1> <2>' => { 'anywords-<1> <2>' => {
bug_group => { contains => [3,4,5] }, bug_group => { contains => [3,4,5] },
percentage_complete => { contains => [3,4,5] }, percentage_complete => { contains => [2] },
'attach_data.thedata' => { contains => [1,2] }, 'attach_data.thedata' => { contains => [1,2] },
work_time => { contains => [1,2] }, work_time => { contains => [1,2] },
}, },
...@@ -496,7 +440,7 @@ use constant KNOWN_BROKEN => { ...@@ -496,7 +440,7 @@ use constant KNOWN_BROKEN => {
commenter => { contains => [2,3,4] }, commenter => { contains => [2,3,4] },
creation_ts => { contains => [2,3,4] }, creation_ts => { contains => [2,3,4] },
delta_ts => { contains => [2,3,4] }, delta_ts => { contains => [2,3,4] },
percentage_complete => { contains => [1,5] }, percentage_complete => { contains => [2,3,4] },
'requestees.login_name' => { contains => [2,3,4] }, 'requestees.login_name' => { contains => [2,3,4] },
'setters.login_name' => { contains => [2,3,4] }, 'setters.login_name' => { contains => [2,3,4] },
}, },
...@@ -552,11 +496,13 @@ use constant REGEX_OVERRIDE => { ...@@ -552,11 +496,13 @@ use constant REGEX_OVERRIDE => {
blocked => { value => '^<1>$' }, blocked => { value => '^<1>$' },
dependson => { value => '^<1>$' }, dependson => { value => '^<1>$' },
bug_id => { value => '^<1>$' }, bug_id => { value => '^<1>$' },
'attachments.isprivate' => { value => '^1' }, 'attachments.isobsolete' => { value => '^1'},
cclist_accessible => { value => '^1' }, 'attachments.ispatch' => { value => '^1'},
reporter_accessible => { value => '^1' }, 'attachments.isprivate' => { value => '^1' },
everconfirmed => { value => '^1' }, cclist_accessible => { value => '^1' },
'longdescs.isprivate' => { value => '^1' }, reporter_accessible => { value => '^1' },
everconfirmed => { value => '^1' },
'longdescs.isprivate' => { value => '^1' },
creation_ts => { value => '^2037-01-01' }, creation_ts => { value => '^2037-01-01' },
delta_ts => { value => '^2037-01-01' }, delta_ts => { value => '^2037-01-01' },
deadline => { value => '^2037-02-01' }, deadline => { value => '^2037-02-01' },
...@@ -734,11 +680,13 @@ use constant TESTS => { ...@@ -734,11 +680,13 @@ use constant TESTS => {
blocked => { value => '<4-id>', contains => [1,2] }, blocked => { value => '<4-id>', contains => [1,2] },
dependson => { value => '<3-id>', contains => [1,3] }, dependson => { value => '<3-id>', contains => [1,3] },
bug_id => { value => '<2-id>' }, bug_id => { value => '<2-id>' },
'attachments.isprivate' => { value => 1, contains => [2,3,4,5] }, 'attachments.isprivate' => { value => 1, contains => [2,3,4] },
cclist_accessible => { value => 1, contains => [2,3,4,5] }, 'attachments.isobsolete' => { value => 1, contains => [2,3,4] },
reporter_accessible => { value => 1, contains => [2,3,4,5] }, 'attachments.ispatch' => { value => 1, contains => [2,3,4] },
'longdescs.isprivate' => { value => 1, contains => [2,3,4,5] }, cclist_accessible => { value => 1, contains => [2,3,4,5] },
everconfirmed => { value => 1, contains => [2,3,4,5] }, reporter_accessible => { value => 1, contains => [2,3,4,5] },
'longdescs.isprivate' => { value => 1, contains => [2,3,4,5] },
everconfirmed => { value => 1, contains => [2,3,4,5] },
creation_ts => { value => '2037-01-02', contains => [1,5] }, creation_ts => { value => '2037-01-02', contains => [1,5] },
delta_ts => { value => '2037-01-02', contains => [1,5] }, delta_ts => { value => '2037-01-02', contains => [1,5] },
deadline => { value => '2037-02-02' }, deadline => { value => '2037-02-02' },
...@@ -755,9 +703,9 @@ use constant TESTS => { ...@@ -755,9 +703,9 @@ use constant TESTS => {
lessthaneq => [ lessthaneq => [
{ contains => [1], value => '<1>', { contains => [1], value => '<1>',
override => { override => {
'attachments.ispatch' => { value => 0, contains => [2,3,4,5] }, 'attachments.isobsolete' => { value => 0, contains => [2,3,4] },
'attachments.isobsolete' => { value => 0, contains => [2,3,4,5] }, 'attachments.ispatch' => { value => 0, contains => [2,3,4] },
'attachments.isprivate' => { value => 0, contains => [2,3,4,5] }, 'attachments.isprivate' => { value => 0, contains => [2,3,4] },
cclist_accessible => { value => 0, contains => [2,3,4,5] }, cclist_accessible => { value => 0, contains => [2,3,4,5] },
reporter_accessible => { value => 0, contains => [2,3,4,5] }, reporter_accessible => { value => 0, contains => [2,3,4,5] },
'longdescs.isprivate' => { value => 0, contains => [2,3,4,5] }, 'longdescs.isprivate' => { value => 0, contains => [2,3,4,5] },
...@@ -768,6 +716,7 @@ use constant TESTS => { ...@@ -768,6 +716,7 @@ use constant TESTS => {
delta_ts => { contains => [1,5] }, delta_ts => { contains => [1,5] },
remaining_time => { contains => [1,5] }, remaining_time => { contains => [1,5] },
longdesc => { contains => [1,5] }, longdesc => { contains => [1,5] },
percentage_complete => { contains => [1,5] },
work_time => { value => 1, contains => [1,5] }, work_time => { value => 1, contains => [1,5] },
LESSTHAN_OVERRIDE, LESSTHAN_OVERRIDE,
}, },
...@@ -936,13 +885,7 @@ use constant TESTS => { ...@@ -936,13 +885,7 @@ use constant TESTS => {
# operator_ok overrides the "brokenness" of certain operators, so that they # operator_ok overrides the "brokenness" of certain operators, so that they
# are always OK for that field/operator combination. # are always OK for that field/operator combination.
use constant INJECTION_BROKEN_FIELD => { use constant INJECTION_BROKEN_FIELD => {
'attachments.isobsolete' => { search => 1 }, owner_idle_time => { search => 1 },
'attachments.ispatch' => { search => 1 },
owner_idle_time => {
sql_error => qr/bugs\.owner_idle_time.+where clause/,
operator_ok => [qw(changedfrom changedto greaterthan greaterthaneq
lessthan lessthaneq)]
},
keywords => { keywords => {
search => 1, search => 1,
operator_ok => [qw(allwordssubstr anywordssubstr casesubstring operator_ok => [qw(allwordssubstr anywordssubstr casesubstring
...@@ -957,9 +900,9 @@ use constant INJECTION_BROKEN_FIELD => { ...@@ -957,9 +900,9 @@ use constant INJECTION_BROKEN_FIELD => {
# search => 1 means the Bugzilla::Search creation fails, but # search => 1 means the Bugzilla::Search creation fails, but
# field_ok contains fields that it does actually succeed for. # field_ok contains fields that it does actually succeed for.
use constant INJECTION_BROKEN_OPERATOR => { use constant INJECTION_BROKEN_OPERATOR => {
changedafter => { search => 1, field_ok => ['percentage_complete'] }, changedafter => { search => 1 },
changedbefore => { search => 1, field_ok => ['percentage_complete'] }, changedbefore => { search => 1 },
changedby => { search => 1, field_ok => ['percentage_complete'] }, changedby => { search => 1 },
}; };
# Tests run by Bugzilla::Test::Search::InjectionTest. # Tests run by Bugzilla::Test::Search::InjectionTest.
......
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