summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Bug.pm3
-rw-r--r--Bugzilla/Group.pm49
-rw-r--r--Bugzilla/Search.pm1499
-rw-r--r--template/en/default/global/code-error.html.tmpl4
-rw-r--r--template/en/default/global/user-error.html.tmpl22
-rw-r--r--xt/lib/Bugzilla/Test/Search/Constants.pm125
6 files changed, 771 insertions, 931 deletions
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index e32add2e1..efee14826 100644
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -1632,8 +1632,7 @@ sub _check_keywords {
my %keywords;
foreach my $keyword (@$keyword_array) {
next unless $keyword;
- my $obj = new Bugzilla::Keyword({ name => $keyword });
- ThrowUserError("unknown_keyword", { keyword => $keyword }) if !$obj;
+ my $obj = Bugzilla::Keyword->check($keyword);
$keywords{$obj->id} = $obj;
}
return [values %keywords];
diff --git a/Bugzilla/Group.pm b/Bugzilla/Group.pm
index f047ef365..29b2a2f1c 100644
--- a/Bugzilla/Group.pm
+++ b/Bugzilla/Group.pm
@@ -185,6 +185,16 @@ sub products {
#### 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_is_active { $_[0]->set('isactive', $_[1]); }
sub set_name { $_[0]->set('name', $_[1]); }
@@ -407,25 +417,6 @@ sub create {
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 ###
###############################
@@ -486,7 +477,6 @@ Bugzilla::Group - Bugzilla group class.
my $icon_url = $group->icon_url;
my $is_active_bug_group = $group->is_active_bug_group;
- my $group_id = Bugzilla::Group::ValidateGroupName('admin', @users);
my @groups = Bugzilla::Group->get_all;
=head1 DESCRIPTION
@@ -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
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
=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>
=over
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index 89e2dfa61..7128b8751 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -60,20 +60,20 @@ use Storable qw(dclone);
# If you specify a search type in the boolean charts, this describes
# which operator maps to which internal function here.
use constant OPERATORS => {
- equals => \&_equals,
- notequals => \&_notequals,
+ equals => \&_simple_operator,
+ notequals => \&_simple_operator,
casesubstring => \&_casesubstring,
substring => \&_substring,
substr => \&_substring,
notsubstring => \&_notsubstring,
regexp => \&_regexp,
notregexp => \&_notregexp,
- lessthan => \&_lessthan,
- lessthaneq => \&_lessthaneq,
+ lessthan => \&_simple_operator,
+ lessthaneq => \&_simple_operator,
matches => sub { ThrowUserError("search_content_without_matches"); },
notmatches => sub { ThrowUserError("search_content_without_matches"); },
- greaterthan => \&_greaterthan,
- greaterthaneq => \&_greaterthaneq,
+ greaterthan => \&_simple_operator,
+ greaterthaneq => \&_simple_operator,
anyexact => \&_anyexact,
anywordssubstr => \&_anywordsubstr,
allwordssubstr => \&_allwordssubstr,
@@ -85,7 +85,35 @@ use constant OPERATORS => {
changedafter => \&_changedbefore_changedafter,
changedfrom => \&_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 => {
@@ -95,7 +123,7 @@ use constant OPERATOR_FIELD_OVERRIDE => {
_default => \&_attachments_submitter,
},
assigned_to => {
- _non_changed => \&_assigned_to_reporter_nonchanged,
+ _non_changed => \&_contact_nonchanged,
},
cc => {
_non_changed => \&_cc_nonchanged,
@@ -104,7 +132,7 @@ use constant OPERATOR_FIELD_OVERRIDE => {
_default => \&_commenter,
},
reporter => {
- _non_changed => \&_assigned_to_reporter_nonchanged,
+ _non_changed => \&_contact_nonchanged,
},
'requestees.login_name' => {
_default => \&_requestees_login_name,
@@ -184,6 +212,7 @@ use constant OPERATOR_FIELD_OVERRIDE => {
greaterthaneq => \&_owner_idle_time_greater_less,
lessthan => \&_owner_idle_time_greater_less,
lessthaneq => \&_owner_idle_time_greater_less,
+ _default => \&_invalid_combination,
},
product => {
@@ -207,7 +236,7 @@ use constant OPERATOR_FIELD_OVERRIDE => {
# Timetracking Fields
percentage_complete => {
- _default => \&_percentage_complete,
+ _non_changed => \&_percentage_complete,
},
work_time => {
changedby => \&_work_time_changedby,
@@ -929,7 +958,6 @@ sub init {
$col++)
{
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 $value = $params->param("value$chart-$row-$col");
$value = "" if !defined $value;
@@ -950,39 +978,40 @@ sub init {
my $quoted = $dbh->quote($value);
trick_taint($quoted);
- my $term;
my $full_field = $field =~ /\./ ? $field : "bugs.$field";
- $self->do_search_function({
- chartid => \$chartid,
- sequence => \$sequence,
- f => \$field,
- ff => \$full_field,
- t => \$operator,
- v => \$value,
- q => \$quoted,
- term => \$term,
+ my %search_args = (
+ chart_id => $chartid,
+ sequence => $sequence,
+ field => $field,
+ full_field => $full_field,
+ operator => $operator,
+ value => $value,
+ quoted => $quoted,
multi_fields => \@multi_select_fields,
- supptables => \@supptables,
- wherepart => \@wherepart,
+ joins => \@supptables,
+ where => \@wherepart,
having => \@having,
- groupby => \@groupby,
- chartfields => \%chartfields,
+ group_by => \@groupby,
fields => \@fields,
- });
-
- if ($term) {
+ chart_fields => \%chartfields,
+ );
+ # 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({
- field => $original_field, type => $operator,
- value => $value, term => $term,
+ field => $field, type => $operator,
+ value => $value, term => $search_args{term},
});
- push(@orlist, $term);
+ push(@orlist, $search_args{term});
}
else {
# This field and this type don't work together.
- ThrowCodeError("field_type_mismatch",
- { field => $params->param("field$chart-$row-$col"),
- type => $params->param("type$chart-$row-$col"),
- });
+ ThrowUserrror("search_field_operator_invalid",
+ { field => $field, operator => $operator });
}
}
if (@orlist) {
@@ -1116,16 +1145,17 @@ sub init {
# it into SQL, using the constants at the top of this file.
sub do_search_function {
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}) {
- $self->$parse_func(%$args);
+ $self->$parse_func($args);
# Some parsing functions set $term, though most do not.
# For the ones that set $term, we don't need to do any further
# parsing.
- return if ${ $args->{term} };
+ return if $args->{term};
}
my $override = OPERATOR_FIELD_OVERRIDE->{$actual_field};
@@ -1142,14 +1172,14 @@ sub do_search_function {
}
if ($override) {
- my $search_func = $self->_pick_override_function($override, $$operator);
- $self->$search_func(%$args) if $search_func;
+ my $search_func = $self->_pick_override_function($override, $operator);
+ $self->$search_func($args) if $search_func;
}
# 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
# direct operator function from OPERATORS.
- if (!${ $args->{term} }) {
+ if (!defined $args->{term}) {
$self->_do_operator_function($args);
}
}
@@ -1158,9 +1188,19 @@ sub do_search_function {
# functions directly.
sub _do_operator_function {
my ($self, $func_args) = @_;
- my $operator = $func_args->{t};
- my $operator_func = OPERATORS->{$$operator};
- $self->$operator_func(%$func_args);
+ my $operator = $func_args->{operator};
+ my $operator_func = OPERATORS->{$operator};
+ $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 {
@@ -1415,180 +1455,185 @@ sub translate_old_column {
# 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 {
- my $self = shift;
- my %func_args = @_;
- my ($value, $quoted) = @func_args{qw(v q)};
+ my ($self, $args) = @_;
+ my ($value, $quoted) = @$args{qw(value quoted)};
my $user = $self->{'user'};
- if ($$value =~ /^\%group/) {
- $self->_contact_exact_group(%func_args);
+ if ($value =~ /^\%group/) {
+ $self->_contact_exact_group($args);
}
- elsif ($$value =~ /^(%\w+%)$/) {
- $$value = pronoun($1, $user);
- $$quoted = $$value;
+ elsif ($value =~ /^(%\w+%)$/) {
+ $args->{value} = pronoun($1, $user);
+ $args->{quoted} = $args->{value};
}
-
}
sub _contact_exact_group {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $f, $t, $v, $term) =
- @func_args{qw(chartid supptables f t v term)};
- my $user = $self->{'user'};
+ my ($self, $args) = @_;
+ my ($value, $operator, $field, $chart_id, $joins) =
+ @$args{qw(value operator field chart_id joins)};
+ my $dbh = Bugzilla->dbh;
- $$v =~ /\%group\.([^%]+)%/;
- my $group = $1;
- my $groupid = Bugzilla::Group::ValidateGroupName( $group, ($user));
- $groupid || ThrowUserError('invalid_group_name',{name => $group});
- my @childgroups = @{Bugzilla::Group->flatten_group_membership($groupid)};
- my $table = "user_group_map_$$chartid";
- push (@$supptables, "LEFT JOIN user_group_map AS $table " .
- "ON $table.user_id = bugs.$$f " .
- "AND $table.group_id IN(" .
- join(',', @childgroups) . ") " .
- "AND $table.isbless = 0 " .
- "AND $table.grant_type IN(" .
- GRANT_DIRECT . "," . GRANT_REGEXP . ")"
- );
- if ($$t =~ /^not/) {
- $$term = "$table.group_id IS NULL";
- } else {
- $$term = "$table.group_id IS NOT NULL";
+ $value =~ /\%group\.([^%]+)%/;
+ my $group = Bugzilla::Group->check($1);
+ $group->check_members_are_visible();
+ my $group_ids = Bugzilla::Group->flatten_group_membership($group->id);
+ my $table = "user_group_map_$chart_id";
+ my $join_sql =
+ "LEFT JOIN user_group_map AS $table"
+ . " ON $table.user_id = bugs.$field"
+ . " AND " . $dbh->sql_in("$table.group_id", $group_ids)
+ . " AND $table.isbless = 0";
+ push(@$joins, $join_sql);
+ if ($operator =~ /^not/) {
+ $args->{term} = "$table.group_id IS NULL";
+ }
+ else {
+ $args->{term} = "$table.group_id IS NOT NULL";
}
}
-sub _assigned_to_reporter_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($f, $ff, $t, $term) =
- @func_args{qw(f ff t term)};
+sub _contact_nonchanged {
+ my ($self, $args) = @_;
+ my $field = $args->{field};
- $$ff = "profiles.login_name";
- $self->_do_operator_function(\%func_args);
- $$term = "bugs.$$f IN (SELECT userid FROM profiles WHERE $$term)";
+ $args->{full_field} = "profiles.login_name";
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $args->{term} = "bugs.$field IN (SELECT userid FROM profiles WHERE $term)";
}
sub _qa_contact_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($supptables, $f, $ff) =
- @func_args{qw(supptables f ff)};
+ my ($self, $args) = @_;
+ my $joins = $args->{joins};
- push(@$supptables, "LEFT JOIN profiles AS map_qa_contact " .
- "ON bugs.qa_contact = map_qa_contact.userid");
- $$ff = "COALESCE(map_$$f.login_name,'')";
+ push(@$joins, "LEFT JOIN profiles AS map_qa_contact " .
+ "ON bugs.qa_contact = map_qa_contact.userid");
+ $args->{full_field} = "COALESCE(map_qa_contact.login_name,'')";
}
sub _cc_pronoun {
- my $self = shift;
- my %func_args = @_;
- my ($full_field, $value, $quoted) = @func_args{qw(ff v q)};
+ my ($self, $args) = @_;
+ my ($full_field, $value) = @$args{qw(full_field value)};
my $user = $self->{'user'};
- if ($$value =~ /\%group/) {
- return $self->_cc_exact_group(%func_args);
+ if ($value =~ /\%group/) {
+ 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 {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $sequence, $supptables, $t, $v, $term) =
- @func_args{qw(chartid sequence supptables t v term)};
+ my ($self, $args) = @_;
+ my ($chart_id, $sequence, $joins, $operator, $value) =
+ @$args{qw(chart_id sequence joins operator value)};
my $user = $self->{'user'};
+ my $dbh = Bugzilla->dbh;
- $$v =~ m/%group\.([^%]+)%/;
- my $group = $1;
- my $groupid = Bugzilla::Group::ValidateGroupName( $group, ($user));
- $groupid || ThrowUserError('invalid_group_name',{name => $group});
- my @childgroups = @{Bugzilla::Group->flatten_group_membership($groupid)};
- my $chartseq = $$chartid;
- if ($$chartid eq "") {
- $chartseq = "CC$$sequence";
- $$sequence++;
- }
- my $table = "user_group_map_$chartseq";
- push(@$supptables, "LEFT JOIN cc AS cc_$chartseq " .
- "ON bugs.bug_id = cc_$chartseq.bug_id");
- push(@$supptables, "LEFT JOIN user_group_map AS $table " .
- "ON $table.user_id = cc_$chartseq.who " .
- "AND $table.group_id IN(" .
- join(',', @childgroups) . ") " .
- "AND $table.isbless = 0 " .
- "AND $table.grant_type IN(" .
- GRANT_DIRECT . "," . GRANT_REGEXP . ")"
- );
- if ($$t =~ /^not/) {
- $$term = "$table.group_id IS NULL";
- } else {
- $$term = "$table.group_id IS NOT NULL";
+ $value =~ m/%group\.([^%]+)%/;
+ my $group = Bugzilla::Group->check($1);
+ $group->check_members_are_visible();
+ my $all_groups = Bugzilla::Group->flatten_group_membership($group->id);
+
+ # This is for the email1, email2, email3 fields from query.cgi.
+ if ($chart_id eq "") {
+ $chart_id = "CC$$sequence";
+ $args->{sequence}++;
+ }
+
+ my $group_table = "user_group_map_$chart_id";
+ my $cc_table = "cc_$chart_id";
+ push(@$joins, "LEFT JOIN cc AS $cc_table " .
+ "ON bugs.bug_id = $cc_table.bug_id");
+ my $join_sql =
+ "LEFT JOIN user_group_map AS $group_table"
+ . " ON $group_table.user_id = $cc_table.who"
+ . " AND " . $dbh->sql_in("$group_table.group_id", $all_groups)
+ . " AND $group_table.isbless = 0 ";
+ push(@$joins, $join_sql);
+ if ($operator =~ /^not/) {
+ $args->{term} = "$group_table.group_id IS NULL";
+ }
+ else {
+ $args->{term} = "$group_table.group_id IS NOT NULL";
}
}
sub _cc_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $sequence, $f, $ff, $t, $supptables, $term, $v) =
- @func_args{qw(chartid sequence f ff t supptables term v)};
+ my ($self, $args) = @_;
+ my ($chart_id, $sequence, $field, $full_field, $operator, $joins, $value) =
+ @$args{qw(chart_id sequence field full_field operator joins value)};
- my $chartseq = $$chartid;
- if ($$chartid eq "") {
- $chartseq = "CC$$sequence";
- $$sequence++;
+ # This is for the email1, email2, email3 fields from query.cgi.
+ if ($chart_id eq "") {
+ $chart_id = "CC$$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 " .
- "ON bugs.bug_id = cc_$chartseq.bug_id " .
- "AND cc_$chartseq.who IN" .
- "(SELECT userid FROM profiles WHERE $$term)"
- );
- $$term = "cc_$chartseq.who IS NOT NULL";
+
+ $self->_do_operator_function($args);
+
+ my $term = $args->{term};
+ my $table = "cc_$chart_id";
+ my $join_sql =
+ "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 {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $term, $v) =
- @func_args{qw(chartid supptables term v)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
- my $table = "longdescs_$$chartid";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id");
- my $id = login_to_id($$v, THROW_ERROR);
- $$term = "$table.who = $id";
+ my $table = "longdescs_$chart_id";
+ push(@$joins, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id");
+ my $user_id = login_to_id($value, THROW_ERROR);
+ $args->{term} = "$table.who = $user_id";
}
sub _long_desc_changedbefore_after {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $t, $v, $supptables, $term) =
- @func_args{qw(chartid t v supptables term)};
+ my ($self, $args) = @_;
+ my ($chart_id, $operator, $value, $joins) =
+ @$args{qw(chart_id operator value joins)};
my $dbh = Bugzilla->dbh;
- my $operator = ($$t =~ /before/) ? '<' : '>';
- my $table = "longdescs_$$chartid";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id " .
- "AND $table.bug_when $operator " .
- $dbh->quote(SqlifyDate($$v)) );
- $$term = "($table.bug_when IS NOT NULL)";
+ my $sql_operator = ($operator =~ /before/) ? '<' : '>';
+ my $table = "longdescs_$chart_id";
+ my $sql_date = $dbh->quote(SqlifyDate($value));
+ my $join_sql =
+ "LEFT JOIN longdescs AS $table "
+ . " ON $table.bug_id = bugs.bug_id"
+ . " AND $table.bug_when $sql_operator $sql_date";
+ push(@$joins, $join_sql);
+ $args->{term} = "$table.bug_when IS NOT NULL";
}
sub _content_matches {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $term, $groupby, $fields, $t, $v) =
- @func_args{qw(chartid supptables term groupby fields t v)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $group_by, $fields, $operator, $value) =
+ @$args{qw(chart_id joins group_by fields operator value)};
my $dbh = Bugzilla->dbh;
# "content" is an alias for columns containing text for which we
@@ -1599,25 +1644,26 @@ sub _content_matches {
# index searches.
# 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";
$comments_col = "comments_noprivate" unless $self->{'user'}->is_insider;
- push(@$supptables, "LEFT JOIN bugs_fulltext AS $table " .
- "ON bugs.bug_id = $table.bug_id");
+ push(@$joins, "LEFT JOIN bugs_fulltext AS $table " .
+ "ON bugs.bug_id = $table.bug_id");
# Create search terms to add to the SELECT and WHERE clauses.
- my ($term1, $rterm1) = $dbh->sql_fulltext_search("$table.$comments_col",
- $$v, 1);
- my ($term2, $rterm2) = $dbh->sql_fulltext_search("$table.short_desc",
- $$v, 2);
+ my ($term1, $rterm1) =
+ $dbh->sql_fulltext_search("$table.$comments_col", $value, 1);
+ my ($term2, $rterm2) =
+ $dbh->sql_fulltext_search("$table.short_desc", $value, 2);
$rterm1 = $term1 if !$rterm1;
$rterm2 = $term2 if !$rterm2;
# The term to use in the WHERE clause.
- $$term = "$term1 > 0 OR $term2 > 0";
- if ($$t =~ /not/i) {
- $$term = "NOT($$term)";
+ my $term = "$term1 > 0 OR $term2 > 0";
+ if ($operator =~ /not/i) {
+ $term = "NOT($term)";
}
+ $args->{term} = $term;
# 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
@@ -1629,282 +1675,262 @@ sub _content_matches {
my $current = COLUMNS->{'relevance'}->{name};
$current = $current ? "$current + " : '';
# 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;
}
sub _timestamp_translate {
- my $self = shift;
- my %func_args = @_;
- my ($value, $quoted) = @func_args{qw(v q)};
+ my ($self, $args) = @_;
+ my $value = $args->{value};
my $dbh = Bugzilla->dbh;
- return if $$value !~ /^[\+\-]?\d+[hdwmy]$/i;
+ return if $value !~ /^[\+\-]?\d+[hdwmy]$/i;
- $$value = SqlifyDate($$value);
- $$quoted = $dbh->quote($$value);
+ $args->{value} = SqlifyDate($value);
+ $args->{quoted} = $dbh->quote($args->{value});
}
+# XXX This should probably be merged with cc_pronoun.
sub _commenter_pronoun {
- my $self = shift;
- my %func_args = @_;
- my ($full_field, $value, $quoted) = @func_args{qw(ff v q)};
+ my ($self, $args) = @_;
+ my $value = $args->{value};
my $user = $self->{'user'};
- if ($$value =~ /^(%\w+%)$/) {
- $$value = pronoun($1, $user);
- $$quoted = $$value;
- $$full_field = "profiles.userid";
+ if ($value =~ /^(%\w+%)$/) {
+ $args->{value} = pronoun($1, $user);
+ $args->{quoted} = $args->{value};
+ $args->{full_field} = "profiles.userid";
}
}
sub _commenter {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $sequence, $supptables, $f, $ff, $t, $term) =
- @func_args{qw(chartid sequence supptables f ff t term)};
+ my ($self, $args) = @_;
+ my ($chart_id, $sequence, $joins, $field, $full_field, $operator) =
+ @$args{qw(chart_id sequence joins field full_field operator)};
- my $chartseq = $$chartid;
- if ($$chartid eq "") {
- $chartseq = "LD$$sequence";
- $$sequence++;
+ if ($chart_id eq "") {
+ $chart_id = "LD$sequence";
+ $args->{sequence}++;
}
- my $table = "longdescs_$chartseq";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1";
- if ($$ff eq 'bugs.commenter') {
- $$ff = "profiles.login_name";
+ my $table = "longdescs_$chart_id";
+
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
+ # 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);
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id $extra " .
- "AND $table.who IN" .
- "(SELECT userid FROM profiles WHERE $$term)"
- );
- $$term = "$table.who IS NOT NULL";
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ my $join_sql =
+ "LEFT JOIN longdescs AS $table"
+ . " ON $table.bug_id = bugs.bug_id $extra"
+ . " AND $table.who IN (SELECT userid FROM profiles WHERE $term)";
+ push(@$joins, $join_sql);
+ $args->{term} = "$table.who IS NOT NULL";
}
sub _long_desc {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $ff) =
- @func_args{qw(chartid supptables ff)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins) = @$args{qw(chart_id joins)};
- my $table = "longdescs_$$chartid";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id $extra");
- $$ff = "$table.thetext";
+ my $table = "longdescs_$chart_id";
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
+ push(@$joins, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id $extra");
+ $args->{full_field} = "$table.thetext";
}
sub _longdescs_isprivate {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $ff) =
- @func_args{qw(chartid supptables ff)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins) = @$args{qw(chart_id joins)};
- my $table = "longdescs_$$chartid";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id $extra");
- $$ff = "$table.isprivate";
+ my $table = "longdescs_$chart_id";
+ my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
+ push(@$joins, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id $extra");
+ $args->{quoted} = $args->{value} ? 1 : 0;
+ $args->{full_field} = "$table.isprivate";
}
sub _work_time_changedby {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $v, $term) =
- @func_args{qw(chartid supptables v term)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
- my $table = "longdescs_$$chartid";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id");
- my $id = login_to_id($$v, THROW_ERROR);
- $$term = "(($table.who = $id";
- $$term .= ") AND ($table.work_time <> 0))";
+ my $table = "longdescs_$chart_id";
+ push(@$joins, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id");
+ my $user_id = login_to_id($value, THROW_ERROR);
+ $args->{term} = "$table.who = $user_id AND $table.work_time != 0";
}
sub _work_time_changedbefore_after {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $t, $v, $supptables, $term) =
- @func_args{qw(chartid t v supptables term)};
+ my ($self, $args) = @_;
+ my ($chart_id, $operator, $value, $joins) =
+ @$args{qw(chart_id operator value joins)};
my $dbh = Bugzilla->dbh;
- my $operator = ($$t =~ /before/) ? '<' : '>';
- my $table = "longdescs_$$chartid";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id " .
- "AND $table.work_time <> 0 " .
- "AND $table.bug_when $operator " .
- $dbh->quote(SqlifyDate($$v)) );
- $$term = "($table.bug_when IS NOT NULL)";
+ my $table = "longdescs_$chart_id";
+ my $sql_operator = ($operator =~ /before/) ? '<' : '>';
+ my $sql_date = $dbh->quote(SqlifyDate($value));
+ my $join_sql =
+ "LEFT JOIN longdescs AS $table"
+ . " ON $table.bug_id = bugs.bug_id"
+ . " AND $table.work_time != 0"
+ . " AND $table.bug_when $sql_operator $sql_date";
+ push(@$joins, $join_sql);
+
+ $args->{term} = "$table.bug_when IS NOT NULL";
}
sub _work_time {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $ff) =
- @func_args{qw(chartid supptables ff)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins) = @$args{qw(chart_id joins)};
- my $table = "longdescs_$$chartid";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id");
- $$ff = "$table.work_time";
+ my $table = "longdescs_$chart_id";
+ push(@$joins, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id");
+ $args->{full_field} = "$table.work_time";
}
sub _percentage_complete {
- my $self = shift;
- my %func_args = @_;
- my ($t, $chartid, $supptables, $fields, $q, $v, $having, $groupby, $term) =
- @func_args{qw(t chartid supptables fields q v having groupby term)};
- my $dbh = Bugzilla->dbh;
-
- my $oper;
- if ($$t eq "equals") {
- $oper = "=";
- } elsif ($$t eq "greaterthan") {
- $oper = ">";
- } elsif ($$t eq "greaterthaneq") {
- $oper = ">=";
- } elsif ($$t eq "lessthan") {
- $oper = "<";
- } elsif ($$t eq "lessthaneq") {
- $oper = "<=";
- } elsif ($$t eq "notequal") {
- $oper = "<>";
- } elsif ($$t eq "regexp") {
- # 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");
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $operator, $value, $having, $fields) =
+ @$args{qw(chart_id joins operator value having fields)};
+
+ my $table = "longdescs_$chart_id";
+
+ # We can't just use "percentage_complete" as the field, because
+ # (a) PostgreSQL doesn't accept it in the HAVING clause
+ # and (b) it wouldn't work in multiple chart rows, because it uses
+ # a fixed name for the table, "ldtime".
+ my $expression = COLUMNS->{percentage_complete}->{name};
+ $expression =~ s/\bldtime\b/$table/g;
+ $args->{full_field} = "($expression)";
+ push(@$joins, "LEFT JOIN longdescs AS $table " .
+ "ON $table.bug_id = bugs.bug_id");
+
+ # We need remaining_time in @fields, otherwise we can't use
+ # it in the expression for creating percentage_complete.
+ if (!grep { $_ eq 'remaining_time' } @$fields) {
+ push(@$fields, '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 {
- my $self = shift;
- my %func_args = @_;
- my ($supptables, $chartid, $ff, $t, $term) =
- @func_args{qw(supptables chartid ff t term)};
-
- push(@$supptables,
- "LEFT JOIN bug_group_map AS bug_group_map_$$chartid " .
- "ON bugs.bug_id = bug_group_map_$$chartid.bug_id");
- $$ff = "groups_$$chartid.name";
- $self->_do_operator_function(\%func_args);
- push(@$supptables,
- "LEFT JOIN groups AS groups_$$chartid " .
- "ON groups_$$chartid.id = bug_group_map_$$chartid.group_id " .
- "AND $$term");
- $$term = "$$ff IS NOT NULL";
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field) = @$args{qw(chart_id joins field)};
+
+ my $map_table = "bug_group_map_$chart_id";
+ push(@$joins,
+ "LEFT JOIN bug_group_map AS $map_table " .
+ "ON bugs.bug_id = $map_table.bug_id");
+
+ my $groups_table = "groups_$chart_id";
+ my $full_field = "$groups_table.name";
+ $args->{full_field} = $full_field;
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ push(@$joins,
+ "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 {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $ff) =
- @func_args{qw(chartid supptables ff)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins) = @$args{qw(chart_id joins)};
- my $atable = "attachments_$$chartid";
- my $dtable = "attachdata_$$chartid";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $atable.isprivate = 0";
- push(@$supptables, "LEFT JOIN attachments AS $atable " .
- "ON bugs.bug_id = $atable.bug_id $extra");
- push(@$supptables, "LEFT JOIN attach_data AS $dtable " .
- "ON $dtable.id = $atable.attach_id");
- $$ff = "$dtable.thedata";
+ my $attach_table = "attachments_$chart_id";
+ my $data_table = "attachdata_$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");
+ push(@$joins, "LEFT JOIN attach_data AS $data_table " .
+ "ON $data_table.id = $attach_table.attach_id");
+ $args->{full_field} = "$data_table.thedata";
}
sub _attachments_submitter {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $ff) =
- @func_args{qw(chartid supptables ff)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins) = @$args{qw(chart_id joins)};
+
+ 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 $extra = $self->{'user'}->is_insider ? "" : "AND $atable.isprivate = 0";
- push(@$supptables, "LEFT JOIN attachments AS $atable " .
- "ON bugs.bug_id = $atable.bug_id $extra");
- push(@$supptables, "LEFT JOIN profiles AS attachers_$$chartid " .
- "ON $atable.submitter_id = attachers_$$chartid.userid");
- $$ff = "attachers_$$chartid.login_name";
+ my $map_table = "map_attachment_submitter_$chart_id";
+ push(@$joins, "LEFT JOIN profiles AS $map_table " .
+ "ON $attach_table.submitter_id = $map_table.userid");
+ $args->{full_field} = "$map_table.login_name";
}
sub _attachments {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $f, $ff, $t, $v, $q) =
- @func_args{qw(chartid supptables f ff t v q)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator, $value) =
+ @$args{qw(chart_id joins field operator value)};
my $dbh = Bugzilla->dbh;
- my $table = "attachments_$$chartid";
+ my $table = "attachments_$chart_id";
my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
- push(@$supptables, "LEFT JOIN attachments AS $table " .
- "ON bugs.bug_id = $table.bug_id $extra");
- $$f =~ m/^attachments\.(.*)$/;
- my $field = $1;
- if ($$t eq "changedby") {
- $$v = login_to_id($$v, THROW_ERROR);
- $$q = $dbh->quote($$v);
- $field = "submitter_id";
- $$t = "equals";
- } elsif ($$t eq "changedbefore") {
- $$v = SqlifyDate($$v);
- $$q = $dbh->quote($$v);
- $field = "creation_ts";
- $$t = "lessthan";
- } elsif ($$t eq "changedafter") {
- $$v = SqlifyDate($$v);
- $$q = $dbh->quote($$v);
- $field = "creation_ts";
- $$t = "greaterthan";
- }
- if ($field eq "ispatch" && $$v ne "0" && $$v ne "1") {
- ThrowUserError("illegal_attachment_is_patch");
- }
- if ($field eq "isobsolete" && $$v ne "0" && $$v ne "1") {
- ThrowUserError("illegal_is_obsolete");
- }
- $$ff = "$table.$field";
+ push(@$joins, "LEFT JOIN attachments AS $table " .
+ "ON bugs.bug_id = $table.bug_id $extra");
+
+ $field =~ /^attachments\.(.+)$/;
+ my $attach_field = $1;
+ # XXX This is not actually the correct method of searching for
+ # changes in attachment values--this just tells you who posted an
+ # attachment.
+ if ($operator eq "changedby") {
+ $args->{value} = login_to_id($value, THROW_ERROR);
+ $args->{quoted} = $args->{value};
+ $attach_field = "submitter_id";
+ $args->{operator} = "equals";
+ }
+ elsif ($operator eq 'changedbefore' or $operator eq 'changedafter') {
+ $args->{value} = SqlifyDate($value);
+ $args->{quoted} = $dbh->quote($args->{value});
+ $attach_field = "creation_ts";
+ $args->{operator} = $operator eq 'changedbefore' ? "lessthan"
+ : "greaterthan";
+ }
+
+ $args->{full_field} = "$table.$attach_field";
+}
+
+sub _join_flag_tables {
+ 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 {
- my $self = shift;
- my %func_args = @_;
- my ($t, $chartid, $supptables, $ff, $having, $term) =
- @func_args{qw(t chartid supptables ff having term)};
+ my ($self, $args) = @_;
+ my ($chart_id, $operator, $joins, $field, $having) =
+ @$args{qw(chart_id operator joins field having)};
my $dbh = Bugzilla->dbh;
# Matches bugs by flag name/status.
@@ -1914,31 +1940,25 @@ sub _flagtypes_name {
# Don't do anything if this condition is about changes to flags,
# 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
# a left join here so bugs without any flags still match
# negative conditions (f.e. "flag isn't review+").
- my $attachments = "attachments_$$chartid";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $attachments.isprivate = 0";
- push(@$supptables, "LEFT JOIN attachments AS $attachments " .
- "ON bugs.bug_id = $attachments.bug_id $extra");
- my $flags = "flags_$$chartid";
- 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");
+ $self->_join_flag_tables($args);
+ my $flags = "flags_$chart_id";
+ my $flagtypes = "flagtypes_$chart_id";
+ push(@$joins, "LEFT JOIN flagtypes AS $flagtypes " .
+ "ON $flags.type_id = $flagtypes.id");
# 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.
- $$ff = $dbh->sql_string_concat("${flagtypes}.name",
- "$flags.status");
- $self->_do_operator_function(\%func_args);
+ my $full_field = $dbh->sql_string_concat("$flagtypes.name",
+ "$flags.status");
+ $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+"),
# we only want bugs where all flags match the condition, not
@@ -1948,542 +1968,433 @@ sub _flagtypes_name {
# of flags on each bug, then compare them in a HAVING clause.
# If the numbers are the same, all flags match the condition,
# so this bug should be included.
- if ($$t =~ m/not/) {
+ if ($operator =~ /^not/) {
push(@$having,
- "SUM(CASE WHEN $$ff IS NOT NULL THEN 1 ELSE 0 END) = " .
- "SUM(CASE WHEN $$term THEN 1 ELSE 0 END)");
- $$term = "0=0";
+ "SUM(CASE WHEN $full_field IS NOT NULL THEN 1 ELSE 0 END) = " .
+ "SUM(CASE WHEN $term THEN 1 ELSE 0 END)");
+ $args->{term} = "0=0";
}
}
+# XXX These two functions can probably be joined (requestees and setters).
sub _requestees_login_name {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $chartid, $supptables) = @func_args{qw(ff chartid supptables)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins) = @$args{qw(chart_id joins)};
- my $attachments = "attachments_$$chartid";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $attachments.isprivate = 0";
- push(@$supptables, "LEFT JOIN attachments AS $attachments " .
- "ON bugs.bug_id = $attachments.bug_id $extra");
- my $flags = "flags_$$chartid";
- 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");
+ $self->_join_flag_tables($args);
+ my $flags = "flags_$chart_id";
+ my $map_table = "map_flag_requestees_$chart_id";
+ push(@$joins, "LEFT JOIN profiles AS $map_table " .
+ "ON $flags.requestee_id = $map_table.userid");
- $$ff = "requestees_$$chartid.login_name";
+ $args->{full_field} = "$map_table.login_name";
}
sub _setters_login_name {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $chartid, $supptables) = @func_args{qw(ff chartid supptables)};
-
- my $attachments = "attachments_$$chartid";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $attachments.isprivate = 0";
- push(@$supptables, "LEFT JOIN attachments AS $attachments " .
- "ON bugs.bug_id = $attachments.bug_id $extra");
- 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");
+ my ($self, $args) = @_;
+ my ($chart_id, $joins) = @$args{qw(chart_id joins)};
+
+ $self->_join_flag_tables($args);
+ my $flags = "flags_$chart_id";
+ my $map_table = "map_flag_setters_$chart_id";
+ push(@$joins, "LEFT JOIN profiles AS $map_table " .
+ "ON $flags.setter_id = $map_table.userid");
- $$ff = "setters_$$chartid.login_name";
+ $args->{full_field} = "$map_table.login_name";
}
sub _changedin_days_elapsed {
- my $self = shift;
- my %func_args = @_;
- my ($ff) = @func_args{qw(ff)};
+ my ($self, $args) = @_;
my $dbh = Bugzilla->dbh;
- $$ff = "(" . $dbh->sql_to_days('NOW()') . " - " .
- $dbh->sql_to_days('bugs.delta_ts') . ")";
+ $args->{full_field} = "(" . $dbh->sql_to_days('NOW()') . " - " .
+ $dbh->sql_to_days('bugs.delta_ts') . ")";
}
sub _component_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $t, $term) = @func_args{qw(ff t term)};
+ my ($self, $args) = @_;
- $$ff = "components.name";
- $self->_do_operator_function(\%func_args);
- $$term = build_subselect("bugs.component_id",
- "components.id",
- "components",
- $$term);
+ $args->{full_field} = "components.name";
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $args->{term} = build_subselect("bugs.component_id",
+ "components.id", "components", $args->{term});
}
+
sub _product_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $t, $term) = @func_args{qw(ff t term)};
+ my ($self, $args) = @_;
# Generate the restriction condition
- $$ff = "products.name";
- $self->_do_operator_function(\%func_args);
- $$term = build_subselect("bugs.product_id",
- "products.id",
- "products",
- $$term);
+ $args->{full_field} = "products.name";
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $args->{term} = build_subselect("bugs.product_id",
+ "products.id", "products", $term);
}
sub _classification_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $v, $ff, $t, $supptables, $term) =
- @func_args{qw(chartid v ff t supptables term)};
+ my ($self, $args) = @_;
+ my $joins = $args->{joins};
# Generate the restriction condition
- push @$supptables, "INNER JOIN products AS map_products " .
- "ON bugs.product_id = map_products.id";
- $$ff = "classifications.name";
- $self->_do_operator_function(\%func_args);
- $$term = build_subselect("map_products.classification_id",
- "classifications.id",
- "classifications",
- $$term);
+ push(@$joins, "INNER JOIN products AS map_products " .
+ "ON bugs.product_id = map_products.id");
+ $args->{full_field} = "classifications.name";
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $args->{term} = build_subselect("map_products.classification_id",
+ "classifications.id", "classifications", $term);
}
sub _keywords_exact {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $v, $ff, $f, $t, $term, $supptables) =
- @func_args{qw(chartid v ff f t term supptables)};
-
- my @list;
- my $table = "keywords_$$chartid";
- foreach my $value (split(/[\s,]+/, $$v)) {
- if ($value eq '') {
- next;
- }
- 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)";
- }
- }
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $value, $operator) =
+ @$args{qw(chart_id joins value operator)};
+ my $dbh = Bugzilla->dbh;
+
+ my @keyword_ids;
+ foreach my $value (split(/[\s,]+/, $value)) {
+ next if $value eq '';
+ my $keyword = Bugzilla::Keyword->check($value);
+ push(@keyword_ids, $keyword->id);
}
- if ($$term) {
- push(@$supptables, "LEFT JOIN keywords AS $table " .
- "ON $table.bug_id = bugs.bug_id");
+
+ # XXX We probably should instead throw an error here if there were
+ # 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 {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $v, $ff, $t, $term, $supptables) =
- @func_args{qw(chartid v ff t term supptables)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $value, $operator) =
+ @$args{qw(chart_id joins value operator)};
- my $k_table = "keywords_$$chartid";
- my $kd_table = "keyworddefs_$$chartid";
+ my $k_table = "keywords_$chart_id";
+ my $kd_table = "keyworddefs_$chart_id";
- push(@$supptables, "LEFT JOIN keywords AS $k_table " .
- "ON $k_table.bug_id = bugs.bug_id");
- push(@$supptables, "LEFT JOIN keyworddefs AS $kd_table " .
- "ON $kd_table.id = $k_table.keywordid");
+ push(@$joins, "LEFT JOIN keywords AS $k_table " .
+ "ON $k_table.bug_id = bugs.bug_id");
+ push(@$joins, "LEFT JOIN keyworddefs AS $kd_table " .
+ "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 {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $ff, $f, $t, $term, $supptables) =
- @func_args{qw(chartid ff f t term supptables)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator) =
+ @$args{qw(chart_id joins field operator)};
- my $table = "dependson_" . $$chartid;
- $$ff = "$table.$$f";
- $self->_do_operator_function(\%func_args);
- push(@$supptables, "LEFT JOIN dependencies AS $table " .
- "ON $table.blocked = bugs.bug_id " .
- "AND ($$term)");
- $$term = "$$ff IS NOT NULL";
+ my $table = "dependson_$chart_id";
+ my $full_field = "$table.$field";
+ $args->{full_field} = $full_field;
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ push(@$joins, "LEFT JOIN dependencies AS $table " .
+ "ON $table.blocked = bugs.bug_id AND ($term)");
+ $args->{term} = "$full_field IS NOT NULL";
}
sub _blocked_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $ff, $f, $t, $term, $supptables) =
- @func_args{qw(chartid ff f t term supptables)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator) =
+ @$args{qw(chart_id joins field operator)};
- my $table = "blocked_" . $$chartid;
- $$ff = "$table.$$f";
- $self->_do_operator_function(\%func_args);
- push(@$supptables, "LEFT JOIN dependencies AS $table " .
- "ON $table.dependson = bugs.bug_id " .
- "AND ($$term)");
- $$term = "$$ff IS NOT NULL";
+ my $table = "blocked_$chart_id";
+ my $full_field = "$table.$field";
+ $args->{full_field} = $full_field;
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ push(@$joins, "LEFT JOIN dependencies AS $table " .
+ "ON $table.dependson = bugs.bug_id AND ($term)");
+ $args->{term} = "$full_field IS NOT NULL";
}
sub _alias_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $t, $term) = @func_args{qw(ff t term)};
-
- $$ff = "COALESCE(bugs.alias, '')";
-
- $self->_do_operator_function(\%func_args);
+ my ($self, $args) = @_;
+ $args->{full_field} = "COALESCE(bugs.alias, '')";
+ $self->_do_operator_function($args);
}
sub _owner_idle_time_greater_less {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $v, $supptables, $t, $wherepart, $term) =
- @func_args{qw(chartid v supptables t wherepart term)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $value, $operator) =
+ @$args{qw(chart_id joins value operator)};
my $dbh = Bugzilla->dbh;
- my $table = "idle_" . $$chartid;
- $$v =~ /^(\d+)\s*([hHdDwWmMyY])?$/;
- my $quantity = $1 || 0;
- my $unit = lc $2;
- my $unitinterval = 'DAY';
- if ($unit eq 'h') {
- $unitinterval = 'HOUR';
- } elsif ($unit eq 'w') {
- $unitinterval = ' * 7 DAY';
- } elsif ($unit eq 'm') {
- $unitinterval = 'MONTH';
- } elsif ($unit eq 'y') {
- $unitinterval = 'YEAR';
- }
- my $cutoff = "NOW() - " .
- $dbh->sql_interval($quantity, $unitinterval);
+ my $table = "idle_$chart_id";
+ my $quoted = $dbh->quote(SqlifyDate($value));
+
+ my $ld_table = "comment_$table";
+ my $comments_join =
+ "LEFT JOIN longdescs AS $ld_table"
+ . " ON $ld_table.who = bugs.assigned_to"
+ . " AND $ld_table.bug_id = bugs.bug_id"
+ . " AND $ld_table.bug_when > $quoted";
+ push(@$joins, $comments_join);
+
+ my $act_table = "activity_$table";
my $assigned_fieldid = get_field_id('assigned_to');
- push(@$supptables, "LEFT JOIN longdescs AS comment_$table " .
- "ON comment_$table.who = bugs.assigned_to " .
- "AND comment_$table.bug_id = bugs.bug_id " .
- "AND comment_$table.bug_when > $cutoff");
- push(@$supptables, "LEFT JOIN bugs_activity AS activity_$table " .
- "ON (activity_$table.who = bugs.assigned_to " .
- "OR activity_$table.fieldid = $assigned_fieldid) " .
- "AND activity_$table.bug_id = bugs.bug_id " .
- "AND activity_$table.bug_when > $cutoff");
- if ($$t =~ /greater/) {
- push(@$wherepart, "(comment_$table.who IS NULL " .
- "AND activity_$table.who IS NULL)");
+
+ # XXX Why are we joining using $assignedto_fieldid here? It shouldn't
+ # matter when or if the assignee changed.
+ my $activity_join =
+ "LEFT JOIN bugs_activity AS $act_table"
+ . " ON ( $act_table.who = bugs.assigned_to"
+ . " OR $act_table.fieldid = $assigned_fieldid )"
+ . " AND $act_table.bug_id = bugs.bug_id"
+ . " AND $act_table.bug_when > $quoted";
+ push(@$joins, $activity_join);
+
+ if ($operator =~ /greater/) {
+ $args->{term} =
+ "$ld_table.who IS NULL AND $act_table.who IS NULL)";
} else {
- push(@$wherepart, "(comment_$table.who IS NOT NULL " .
- "OR activity_$table.who IS NOT NULL)");
+ $args->{term} =
+ "$ld_table.who IS NOT NULL OR $act_table.who IS NOT NULL";
}
- $$term = "0=0";
}
sub _multiselect_negative {
- my $self = shift;
- my %func_args = @_;
- 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 ($self, $args) = @_;
+ my ($field, $operator) = @$args{qw(field operator)};
my $table;
- if ($$f eq 'keywords') {
+ if ($field eq 'keywords') {
$table = "keywords LEFT JOIN keyworddefs"
. " ON keywords.keywordid = keyworddefs.id";
- $$ff = "keyworddefs.name";
+ $args->{full_field} = "keyworddefs.name";
}
- else {
- $table = "bug_$$f";
- $$ff = "$table.value";
+ else {
+ $table = "bug_$field";
+ $args->{full_field} = "$table.value";
}
-
- $$t = $map{$$t};
- $self->_do_operator_function(\%func_args);
- $$term = "bugs.bug_id NOT IN (SELECT bug_id FROM $table WHERE $$term)";
+ $args->{operator} = $self->_reverse_operator($operator);
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $args->{term} =
+ "bugs.bug_id NOT IN (SELECT bug_id FROM $table WHERE $term)";
}
sub _multiselect_multiple {
- my $self = shift;
- my %func_args = @_;
- my ($f, $ff, $t, $v, $term) = @func_args{qw(f ff t v term)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator, $value)
+ = @$args{qw(chart_id joins field operator value)};
- my @terms;
- my $table = "bug_$$f";
- $$ff = "$table.value";
+ my $table = "bug_$field";
+ $args->{full_field} = "$table.value";
- foreach my $word (split(/[\s,]+/, $$v)) {
- $$v = $word;
- $self->_do_operator_function(\%func_args);
- push(@terms, "bugs.bug_id IN
- (SELECT bug_id FROM $table WHERE $$term)");
+ my @terms;
+ foreach my $word (split(/[\s,]+/, $value)) {
+ $args->{value} = $word;
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ push(@terms, "bugs.bug_id IN (SELECT bug_id FROM $table WHERE $term)");
}
- if ($$t eq 'anyexact') {
- $$term = "(" . join(" OR ", @terms) . ")";
+ if ($operator eq 'anyexact') {
+ $args->{term} = join(" OR ", @terms);
}
else {
- $$term = "(" . join(" AND ", @terms) . ")";
+ $args->{term} = join(" AND ", @terms);
}
}
sub _multiselect_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $f, $ff, $t, $supptables) =
- @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 ");
-}
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator) =
+ @$args{qw(chart_id joins field operator)};
-sub _equals {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
-
- $$term = "$$ff = $$q";
+ my $table = "${field}_$chart_id";
+ $args->{full_field} = "$table.value";
+ push(@$joins, "LEFT JOIN bug_$field AS $table " .
+ "ON $table.bug_id = bugs.bug_id ");
}
-sub _notequals {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
-
- $$term = "$$ff != $$q";
+sub _simple_operator {
+ my ($self, $args) = @_;
+ my ($full_field, $quoted, $operator) =
+ @$args{qw(full_field quoted operator)};
+ my $sql_operator = SIMPLE_OPERATORS->{$operator};
+ $args->{term} = "$full_field $sql_operator $quoted";
}
sub _casesubstring {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
my $dbh = Bugzilla->dbh;
- $$term = $dbh->sql_position($$q, $$ff) . " > 0";
+ $args->{term} = $dbh->sql_position($quoted, $full_field) . " > 0";
}
sub _substring {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
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 {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
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 {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
my $dbh = Bugzilla->dbh;
- $$term = $dbh->sql_regexp($$ff, $$q);
+ $args->{term} = $dbh->sql_regexp($full_field, $quoted);
}
sub _notregexp {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
my $dbh = Bugzilla->dbh;
- $$term = $dbh->sql_not_regexp($$ff, $$q);
-}
-
-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";
+ $args->{term} = $dbh->sql_not_regexp($full_field, $quoted);
}
sub _anyexact {
- my $self = shift;
- my %func_args = @_;
- my ($f, $ff, $v, $q, $term) = @func_args{qw(f ff v q term)};
+ my ($self, $args) = @_;
+ my ($field, $value, $full_field) = @$args{qw(field value full_field)};
my $dbh = Bugzilla->dbh;
my @list;
- foreach my $w (split(/,/, $$v)) {
- if ($w eq "---" && $$f =~ /resolution/) {
- $w = "";
+ foreach my $word (split(/,/, $value)) {
+ if ($word eq "---" && $field eq 'resolution') {
+ $word = "";
}
- $$q = $dbh->quote($w);
- trick_taint($$q);
- push(@list, $$q);
+ my $quoted_word = $dbh->quote($word);
+ trick_taint($quoted_word);
+ push(@list, $quoted_word);
}
+
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 {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
+ my ($self, $args) = @_;
+ my ($full_field, $value) = @$args{qw(full_field value)};
- $$term = join(" OR ", @{GetByWordListSubstr($$ff, $$v)});
+ my $list = GetByWordListSubstr($full_field, $value);
+ $args->{term} = join(" OR ", @$list);
}
sub _allwordssubstr {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
+ my ($self, $args) = @_;
+ my ($full_field, $value) = @$args{qw(full_field value)};
- $$term = join(" AND ", @{GetByWordListSubstr($$ff, $$v)});
+ my $list = GetByWordListSubstr($full_field, $value);
+ $args->{term} = join(" AND ", @$list);
}
sub _nowordssubstr {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
-
- my @list = @{GetByWordListSubstr($$ff, $$v)};
- if (@list) {
- $$term = "NOT (" . join(" OR ", @list) . ")";
- }
+ my ($self, $args) = @_;
+ $self->_anywordsubstr($args);
+ my $term = $args->{term};
+ $args->{term} = "NOT($term)";
}
sub _anywords {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
+ my ($self, $args) = @_;
+ my ($full_field, $value) = @$args{qw(full_field value)};
- $$term = join(" OR ", @{GetByWordList($$ff, $$v)});
+ my $list = GetByWordList($full_field, $value);
+ $args->{term} = join(" OR ", @$list);
}
sub _allwords {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
+ my ($self, $args) = @_;
+ my ($full_field, $value) = @$args{qw(full_field value)};
- $$term = join(" AND ", @{GetByWordList($$ff, $$v)});
+ my $list = GetByWordList($full_field, $value);
+ $args->{term} = join(" AND ", @$list);
}
sub _nowords {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
-
- my @list = @{GetByWordList($$ff, $$v)};
- if (@list) {
- $$term = "NOT (" . join(" OR ", @list) . ")";
- }
+ my ($self, $args) = @_;
+ $self->_anywords($args);
+ my $term = $args->{term};
+ $args->{term} = "NOT($term)";
}
sub _changedbefore_changedafter {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $f, $ff, $t, $v, $chartfields, $supptables, $term) =
- @func_args{qw(chartid f ff t v chartfields supptables term)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator, $value) =
+ @$args{qw(chart_id joins field operator value)};
my $dbh = Bugzilla->dbh;
- my $operator = ($$t =~ /before/) ? '<' : '>';
- my $table = "act_$$chartid";
- my $fieldid = $$chartfields{$$f};
- if (!$fieldid) {
- ThrowCodeError("invalid_field_name", {field => $$f});
- }
- push(@$supptables, "LEFT JOIN bugs_activity AS $table " .
- "ON $table.bug_id = bugs.bug_id " .
- "AND $table.fieldid = $fieldid " .
- "AND $table.bug_when $operator " .
- $dbh->quote(SqlifyDate($$v)) );
- $$term = "($table.bug_when IS NOT NULL)";
+ my $sql_operator = ($operator =~ /before/) ? '<' : '>';
+ my $table = "act_$chart_id";
+ my $field_id = get_field_id($field);
+ my $sql_date = $dbh->quote(SqlifyDate($value));
+ push(@$joins,
+ "LEFT JOIN bugs_activity AS $table"
+ . " ON $table.bug_id = bugs.bug_id"
+ . " AND $table.fieldid = $field_id"
+ . " AND $table.bug_when $sql_operator $sql_date");
+ $args->{term} = "$table.bug_when IS NOT NULL";
}
sub _changedfrom_changedto {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $chartfields, $f, $t, $v, $q, $supptables, $term) =
- @func_args{qw(chartid chartfields f t v q supptables term)};
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator, $quoted) =
+ @$args{qw(chart_id joins field operator quoted)};
- my $operator = ($$t =~ /from/) ? 'removed' : 'added';
- my $table = "act_$$chartid";
- my $fieldid = $$chartfields{$$f};
- if (!$fieldid) {
- ThrowCodeError("invalid_field_name", {field => $$f});
- }
- push(@$supptables, "LEFT JOIN bugs_activity AS $table " .
- "ON $table.bug_id = bugs.bug_id " .
- "AND $table.fieldid = $fieldid " .
- "AND $table.$operator = $$q");
- $$term = "($table.bug_when IS NOT NULL)";
+ my $column = ($operator =~ /from/) ? 'removed' : 'added';
+ my $table = "act_$chart_id";
+ my $field_id = get_field_id($field);
+ push(@$joins,
+ "LEFT JOIN bugs_activity AS $table"
+ . " ON $table.bug_id = bugs.bug_id"
+ . " AND $table.fieldid = $field_id"
+ . " AND $table.$column = $quoted");
+ $args->{term} = "$table.bug_when IS NOT NULL";
}
sub _changedby {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $chartfields, $f, $v, $supptables, $term) =
- @func_args{qw(chartid chartfields f v supptables term)};
-
- my $table = "act_$$chartid";
- my $fieldid = $$chartfields{$$f};
- if (!$fieldid) {
- ThrowCodeError("invalid_field_name", {field => $$f});
- }
- my $id = login_to_id($$v, THROW_ERROR);
- push(@$supptables, "LEFT JOIN bugs_activity AS $table " .
- "ON $table.bug_id = bugs.bug_id " .
- "AND $table.fieldid = $fieldid " .
- "AND $table.who = $id");
- $$term = "($table.bug_when IS NOT NULL)";
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator, $value) =
+ @$args{qw(chart_id joins field operator value)};
+
+ my $table = "act_$chart_id";
+ my $field_id = get_field_id($field);
+ my $user_id = login_to_id($value, THROW_ERROR);
+ push(@$joins,
+ "LEFT JOIN bugs_activity AS $table"
+ . " ON $table.bug_id = bugs.bug_id"
+ . " AND $table.fieldid = $field_id"
+ . " AND $table.who = $user_id");
+ $args->{term} = "$table.bug_when IS NOT NULL";
}
1;
diff --git a/template/en/default/global/code-error.html.tmpl b/template/en/default/global/code-error.html.tmpl
index 43644f703..849008adb 100644
--- a/template/en/default/global/code-error.html.tmpl
+++ b/template/en/default/global/code-error.html.tmpl
@@ -184,10 +184,6 @@
'[% field.description FILTER html %]' ([% field.name FILTER html %])
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" %]
[% title = "Field Type Not Specified" %]
You must specify a type when creating a custom field.
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
index 490941807..3f7248c40 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -744,11 +744,15 @@
[% title = "Group not 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" %]
[% title = "System Groups not deletable" %]
<em>[% name FILTER html %]</em> is a system group.
This group cannot be deleted.
-
[% ELSIF error == "group_unknown" %]
[% title = "Unknown Group" %]
The group [% name FILTER html %] does not exist. Please specify
@@ -1288,6 +1292,9 @@
[% IF class == "Bugzilla::User" %]
Either you mis-typed the name or that user has not yet registered
for a [% terms.Bugzilla %] account.
+ [% ELSIF class == "Bugzilla::Keyword" %]
+ The legal keyword names are <a href="describekeywords.cgi">listed
+ here</a>.
[% END %]
[% ELSIF error == "old_password_incorrect" %]
@@ -1477,6 +1484,12 @@
and the "matches" search can only be used with the "content"
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" %]
[% title = "Series Already Exists" %]
[% docslinks = {'reporting.html' => 'Reporting'} %]
@@ -1562,11 +1575,6 @@
I could not figure out what you wanted to do.
[% 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" %]
[% title = "Unknown Tab" %]
<code>[% current_tab_name FILTER html %]</code> is not a legal tab name.
@@ -1759,6 +1767,8 @@
field
[% ELSIF class == "Bugzilla::Group" %]
group
+ [% ELSIF class == "Bugzilla::Keyword" %]
+ keyword
[% ELSIF class == "Bugzilla::Product" %]
product
[% ELSIF class == "Bugzilla::Search::Recent" %]
diff --git a/xt/lib/Bugzilla/Test/Search/Constants.pm b/xt/lib/Bugzilla/Test/Search/Constants.pm
index 86e195ae0..7f35b3719 100644
--- a/xt/lib/Bugzilla/Test/Search/Constants.pm
+++ b/xt/lib/Bugzilla/Test/Search/Constants.pm
@@ -179,22 +179,6 @@ use constant FIELD_SUBSTR_SIZE => {
# See the KNOWN_BROKEN constant for a general description of these
# "_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:
#
# Blocked and Dependson "notequals" only finds bugs that have
@@ -223,7 +207,9 @@ use constant ATTACHMENT_BOOLEANS_CONTAINS_BROKEN => (
#
# requestees.login_name doesn't find bugs that fully lack requestees.
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] },
'attachments.description' => { contains => [5] },
'attachments.filename' => { contains => [5] },
@@ -237,7 +223,6 @@ use constant NEGATIVE_BROKEN => (
dependson => { contains => [2,4,5] },
longdesc => { contains => [1] },
'longdescs.isprivate' => { contains => [1] },
- percentage_complete => { contains => [1] },
'requestees.login_name' => { contains => [3,4,5] },
'setters.login_name' => { contains => [5] },
work_time => { contains => [1] },
@@ -271,16 +256,12 @@ use constant GREATERTHAN_BROKEN => (
# allwordssubstr work_time only matches against a single comment,
# instead of matching against all comments on a bug. Same is true
# for the other longdesc fields, cc, keywords, and bug_group.
-#
-# percentage_complete just drops in 0=0 for the term.
use constant ALLWORDS_BROKEN => (
- ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
bug_group => { contains => [1] },
cc => { contains => [1] },
keywords => { contains => [1] },
longdesc => { contains => [1] },
work_time => { contains => [1] },
- percentage_complete => { contains => [2,3,4,5] },
);
# nowords and nowordssubstr have these broken tests in common.
@@ -306,7 +287,7 @@ use constant NOWORDS_BROKEN => (
use constant CHANGED_BROKEN => (
classification => { contains => [1] },
commenter => { contains => [1] },
- percentage_complete => { contains => [2,3,4,5] },
+ percentage_complete => { contains => [1] },
'requestees.login_name' => { contains => [1] },
'setters.login_name' => { contains => [1] },
delta_ts => { contains => [1] },
@@ -346,28 +327,9 @@ use constant CHANGED_VALUE_BROKEN => (
# while the other fails. In this case, we have a special override for
# "operator-value", which uniquely identifies tests.
use constant KNOWN_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] },
- },
+ notequals => { NEGATIVE_BROKEN },
notsubstring => { 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 },
+ notregexp => { NEGATIVE_BROKEN },
# percentage_complete doesn't match bugs with 0 hours worked or remaining.
#
@@ -376,19 +338,13 @@ use constant KNOWN_BROKEN => {
# 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
# at all (which might be OK?).
- #
- # attachments.isprivate lessthan doesn't find bugs without attachments.
lessthan => {
- ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
- 'attachments.isprivate' => { contains => [5] },
'longdescs.isprivate' => { contains => [1] },
- percentage_complete => { contains => [5] },
work_time => { contains => [1,2,3,4] },
},
# The lessthaneq tests are broken for the same reasons, but they work
# slightly differently so they have a different set of broken tests.
lessthaneq => {
- ATTACHMENT_BOOLEANS_CONTAINS_BROKEN,
'longdescs.isprivate' => { contains => [1] },
work_time => { contains => [2,3,4] },
},
@@ -401,24 +357,18 @@ use constant KNOWN_BROKEN => {
percentage_complete => { contains => [2] },
},
- # percentage_complete just throws 0=0 into the search term, returning
- # all bugs.
+ # percentage_complete doesn't do a numeric comparison, so
+ # it doesn't find decimal values.
anyexact => {
- ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
- percentage_complete => { contains => [3,4,5] },
+ percentage_complete => { contains => [2] },
},
# bug_group anywordssubstr returns all our bugs. Not sure why.
anywordssubstr => {
- ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
- percentage_complete => { contains => [3,4,5] },
+ percentage_complete => { contains => [2] },
bug_group => { contains => [3,4,5] },
},
'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
# match against a single flag.
# Same for attach_data.thedata.
@@ -427,10 +377,6 @@ use constant KNOWN_BROKEN => {
'attach_data.thedata' => { contains => [1] },
'flagtypes.name' => { contains => [1] },
},
- 'allwords-<1> <2>' => {
- ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
- percentage_complete => { contains => [1,2,3,4,5] },
- },
nowordssubstr => { NOWORDS_BROKEN },
# attach_data.thedata doesn't match properly with any of the plain
@@ -446,15 +392,13 @@ use constant KNOWN_BROKEN => {
# attach_data doesn't work (perhaps because it's the entire
# data, or some problem with the regex?).
anywords => {
- ATTACHMENT_BOOLEANS_SEARCH_BROKEN,
'attach_data.thedata' => { contains => [1] },
bug_group => { contains => [2,3,4,5] },
- percentage_complete => { contains => [2,3,4,5] },
work_time => { contains => [1] },
},
'anywords-<1> <2>' => {
bug_group => { contains => [3,4,5] },
- percentage_complete => { contains => [3,4,5] },
+ percentage_complete => { contains => [2] },
'attach_data.thedata' => { contains => [1,2] },
work_time => { contains => [1,2] },
},
@@ -496,7 +440,7 @@ use constant KNOWN_BROKEN => {
commenter => { contains => [2,3,4] },
creation_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] },
'setters.login_name' => { contains => [2,3,4] },
},
@@ -552,11 +496,13 @@ use constant REGEX_OVERRIDE => {
blocked => { value => '^<1>$' },
dependson => { value => '^<1>$' },
bug_id => { value => '^<1>$' },
- 'attachments.isprivate' => { value => '^1' },
- cclist_accessible => { value => '^1' },
- reporter_accessible => { value => '^1' },
- everconfirmed => { value => '^1' },
- 'longdescs.isprivate' => { value => '^1' },
+ 'attachments.isobsolete' => { value => '^1'},
+ 'attachments.ispatch' => { value => '^1'},
+ 'attachments.isprivate' => { value => '^1' },
+ cclist_accessible => { value => '^1' },
+ reporter_accessible => { value => '^1' },
+ everconfirmed => { value => '^1' },
+ 'longdescs.isprivate' => { value => '^1' },
creation_ts => { value => '^2037-01-01' },
delta_ts => { value => '^2037-01-01' },
deadline => { value => '^2037-02-01' },
@@ -734,11 +680,13 @@ use constant TESTS => {
blocked => { value => '<4-id>', contains => [1,2] },
dependson => { value => '<3-id>', contains => [1,3] },
bug_id => { value => '<2-id>' },
- 'attachments.isprivate' => { value => 1, contains => [2,3,4,5] },
- cclist_accessible => { 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] },
+ 'attachments.isprivate' => { value => 1, contains => [2,3,4] },
+ 'attachments.isobsolete' => { value => 1, contains => [2,3,4] },
+ 'attachments.ispatch' => { value => 1, contains => [2,3,4] },
+ cclist_accessible => { 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] },
delta_ts => { value => '2037-01-02', contains => [1,5] },
deadline => { value => '2037-02-02' },
@@ -755,9 +703,9 @@ use constant TESTS => {
lessthaneq => [
{ contains => [1], value => '<1>',
override => {
- 'attachments.ispatch' => { value => 0, contains => [2,3,4,5] },
- 'attachments.isobsolete' => { value => 0, contains => [2,3,4,5] },
- 'attachments.isprivate' => { value => 0, contains => [2,3,4,5] },
+ 'attachments.isobsolete' => { value => 0, contains => [2,3,4] },
+ 'attachments.ispatch' => { value => 0, contains => [2,3,4] },
+ 'attachments.isprivate' => { value => 0, contains => [2,3,4] },
cclist_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] },
@@ -768,6 +716,7 @@ use constant TESTS => {
delta_ts => { contains => [1,5] },
remaining_time => { contains => [1,5] },
longdesc => { contains => [1,5] },
+ percentage_complete => { contains => [1,5] },
work_time => { value => 1, contains => [1,5] },
LESSTHAN_OVERRIDE,
},
@@ -936,13 +885,7 @@ use constant TESTS => {
# operator_ok overrides the "brokenness" of certain operators, so that they
# are always OK for that field/operator combination.
use constant INJECTION_BROKEN_FIELD => {
- 'attachments.isobsolete' => { 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)]
- },
+ owner_idle_time => { search => 1 },
keywords => {
search => 1,
operator_ok => [qw(allwordssubstr anywordssubstr casesubstring
@@ -957,9 +900,9 @@ use constant INJECTION_BROKEN_FIELD => {
# search => 1 means the Bugzilla::Search creation fails, but
# field_ok contains fields that it does actually succeed for.
use constant INJECTION_BROKEN_OPERATOR => {
- changedafter => { search => 1, field_ok => ['percentage_complete'] },
- changedbefore => { search => 1, field_ok => ['percentage_complete'] },
- changedby => { search => 1, field_ok => ['percentage_complete'] },
+ changedafter => { search => 1 },
+ changedbefore => { search => 1 },
+ changedby => { search => 1 },
};
# Tests run by Bugzilla::Test::Search::InjectionTest.