summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Search
diff options
context:
space:
mode:
authorPerl Tidy <perltidy@bugzilla.org>2018-12-05 21:38:52 +0100
committerDylan William Hardison <dylan@hardison.net>2018-12-05 23:49:08 +0100
commit8ec8da0491ad89604700b3e29a227966f6d84ba1 (patch)
tree9d270f173330ca19700e0ba9f2ee931300646de1 /Bugzilla/Search
parenta7bb5a65b71644d9efce5fed783ed545b9336548 (diff)
downloadbugzilla-8ec8da0491ad89604700b3e29a227966f6d84ba1.tar.gz
bugzilla-8ec8da0491ad89604700b3e29a227966f6d84ba1.tar.xz
no bug - reformat all the code using the new perltidy rules
Diffstat (limited to 'Bugzilla/Search')
-rw-r--r--Bugzilla/Search/Clause.pm170
-rw-r--r--Bugzilla/Search/ClauseGroup.pm121
-rw-r--r--Bugzilla/Search/Condition.pm70
-rw-r--r--Bugzilla/Search/Quicksearch.pm1148
-rw-r--r--Bugzilla/Search/Recent.pm116
-rw-r--r--Bugzilla/Search/Saved.pm369
6 files changed, 1021 insertions, 973 deletions
diff --git a/Bugzilla/Search/Clause.pm b/Bugzilla/Search/Clause.pm
index 4426ea576..b0eaddeb0 100644
--- a/Bugzilla/Search/Clause.pm
+++ b/Bugzilla/Search/Clause.pm
@@ -16,121 +16,123 @@ use Bugzilla::Search::Condition qw(condition);
use Bugzilla::Util qw(trick_taint);
sub new {
- my ($class, $joiner) = @_;
- if ($joiner and $joiner ne 'OR' and $joiner ne 'AND') {
- ThrowCodeError('search_invalid_joiner', { joiner => $joiner });
- }
- # This will go into SQL directly so needs to be untainted.
- trick_taint($joiner) if $joiner;
- bless { joiner => $joiner || 'AND' }, $class;
+ my ($class, $joiner) = @_;
+ if ($joiner and $joiner ne 'OR' and $joiner ne 'AND') {
+ ThrowCodeError('search_invalid_joiner', {joiner => $joiner});
+ }
+
+ # This will go into SQL directly so needs to be untainted.
+ trick_taint($joiner) if $joiner;
+ bless {joiner => $joiner || 'AND'}, $class;
}
sub children {
- my ($self) = @_;
- $self->{children} ||= [];
- return $self->{children};
+ my ($self) = @_;
+ $self->{children} ||= [];
+ return $self->{children};
}
sub update_search_args {
- my ($self, $search_args) = @_;
- # abstract
+ my ($self, $search_args) = @_;
+
+ # abstract
}
sub joiner { return $_[0]->{joiner} }
sub has_translated_conditions {
- my ($self) = @_;
- my $children = $self->children;
- return 1 if grep { $_->isa('Bugzilla::Search::Condition')
- && $_->translated } @$children;
- foreach my $child (@$children) {
- next if $child->isa('Bugzilla::Search::Condition');
- return 1 if $child->has_translated_conditions;
- }
- return 0;
+ my ($self) = @_;
+ my $children = $self->children;
+ return 1
+ if grep { $_->isa('Bugzilla::Search::Condition') && $_->translated }
+ @$children;
+ foreach my $child (@$children) {
+ next if $child->isa('Bugzilla::Search::Condition');
+ return 1 if $child->has_translated_conditions;
+ }
+ return 0;
}
sub add {
- my $self = shift;
- my $children = $self->children;
- if (@_ == 3) {
- push(@$children, condition(@_));
- return;
- }
-
- my ($child) = @_;
- return if !defined $child;
- $child->isa(__PACKAGE__) || $child->isa('Bugzilla::Search::Condition')
- || die 'child not the right type: ' . $child;
- push(@{ $self->children }, $child);
+ my $self = shift;
+ my $children = $self->children;
+ if (@_ == 3) {
+ push(@$children, condition(@_));
+ return;
+ }
+
+ my ($child) = @_;
+ return if !defined $child;
+ $child->isa(__PACKAGE__)
+ || $child->isa('Bugzilla::Search::Condition')
+ || die 'child not the right type: ' . $child;
+ push(@{$self->children}, $child);
}
sub negate {
- my ($self, $value) = @_;
- if (@_ == 2) {
- $self->{negate} = $value ? 1 : 0;
- }
- return $self->{negate};
+ my ($self, $value) = @_;
+ if (@_ == 2) {
+ $self->{negate} = $value ? 1 : 0;
+ }
+ return $self->{negate};
}
sub walk_conditions {
- my ($self, $callback) = @_;
- foreach my $child (@{ $self->children }) {
- if ($child->isa('Bugzilla::Search::Condition')) {
- $callback->($self, $child);
- }
- else {
- $child->walk_conditions($callback);
- }
+ my ($self, $callback) = @_;
+ foreach my $child (@{$self->children}) {
+ if ($child->isa('Bugzilla::Search::Condition')) {
+ $callback->($self, $child);
+ }
+ else {
+ $child->walk_conditions($callback);
}
+ }
}
sub as_string {
- my ($self) = @_;
- if (!$self->{sql}) {
- my @strings;
- foreach my $child (@{ $self->children }) {
- next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions;
- next if $child->isa('Bugzilla::Search::Condition')
- && !$child->translated;
-
- my $string = $child->as_string;
- next unless $string;
- if ($self->joiner eq 'AND') {
- $string = "( $string )" if $string =~ /OR/;
- }
- else {
- $string = "( $string )" if $string =~ /AND/;
- }
- push(@strings, $string);
- }
-
- my $sql = join(' ' . $self->joiner . ' ', @strings);
- $sql = "NOT( $sql )" if $sql && $self->negate;
- $self->{sql} = $sql;
+ my ($self) = @_;
+ if (!$self->{sql}) {
+ my @strings;
+ foreach my $child (@{$self->children}) {
+ next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions;
+ next if $child->isa('Bugzilla::Search::Condition') && !$child->translated;
+
+ my $string = $child->as_string;
+ next unless $string;
+ if ($self->joiner eq 'AND') {
+ $string = "( $string )" if $string =~ /OR/;
+ }
+ else {
+ $string = "( $string )" if $string =~ /AND/;
+ }
+ push(@strings, $string);
}
- return $self->{sql};
+
+ my $sql = join(' ' . $self->joiner . ' ', @strings);
+ $sql = "NOT( $sql )" if $sql && $self->negate;
+ $self->{sql} = $sql;
+ }
+ return $self->{sql};
}
# Search.pm converts URL parameters to Clause objects. This helps do the
# reverse.
sub as_params {
- my ($self) = @_;
- my @params;
- foreach my $child (@{ $self->children }) {
- if ($child->isa(__PACKAGE__)) {
- my %open_paren = (f => 'OP', n => scalar $child->negate,
- j => $child->joiner);
- push(@params, \%open_paren);
- push(@params, $child->as_params);
- my %close_paren = (f => 'CP');
- push(@params, \%close_paren);
- }
- else {
- push(@params, $child->as_params);
- }
+ my ($self) = @_;
+ my @params;
+ foreach my $child (@{$self->children}) {
+ if ($child->isa(__PACKAGE__)) {
+ my %open_paren = (f => 'OP', n => scalar $child->negate, j => $child->joiner);
+ push(@params, \%open_paren);
+ push(@params, $child->as_params);
+ my %close_paren = (f => 'CP');
+ push(@params, \%close_paren);
+ }
+ else {
+ push(@params, $child->as_params);
}
- return @params;
+ }
+ return @params;
}
1;
diff --git a/Bugzilla/Search/ClauseGroup.pm b/Bugzilla/Search/ClauseGroup.pm
index c7d3e6ae7..5c063a803 100644
--- a/Bugzilla/Search/ClauseGroup.pm
+++ b/Bugzilla/Search/ClauseGroup.pm
@@ -19,79 +19,82 @@ use Bugzilla::Util qw(trick_taint);
use List::MoreUtils qw(uniq);
use constant UNSUPPORTED_FIELDS => qw(
- classification
- commenter
- component
- longdescs.count
- product
- owner_idle_time
+ classification
+ commenter
+ component
+ longdescs.count
+ product
+ owner_idle_time
);
sub new {
- my ($class) = @_;
- my $self = bless({ joiner => 'AND' }, $class);
- # Add a join back to the bugs table which will be used to group conditions
- # for this clause
- my $condition = Bugzilla::Search::Condition->new({});
- $condition->translated({
- joins => [{
- table => 'bugs',
- as => 'bugs_g0',
- from => 'bug_id',
- to => 'bug_id',
- extra => [],
- }],
- term => '1 = 1',
- });
- $self->SUPER::add($condition);
- $self->{group_condition} = $condition;
- return $self;
+ my ($class) = @_;
+ my $self = bless({joiner => 'AND'}, $class);
+
+ # Add a join back to the bugs table which will be used to group conditions
+ # for this clause
+ my $condition = Bugzilla::Search::Condition->new({});
+ $condition->translated({
+ joins => [{
+ table => 'bugs',
+ as => 'bugs_g0',
+ from => 'bug_id',
+ to => 'bug_id',
+ extra => [],
+ }],
+ term => '1 = 1',
+ });
+ $self->SUPER::add($condition);
+ $self->{group_condition} = $condition;
+ return $self;
}
sub add {
- my ($self, @args) = @_;
- my $field = scalar(@args) == 3 ? $args[0] : $args[0]->{field};
-
- # We don't support nesting of conditions under this clause
- if (scalar(@args) == 1 && !$args[0]->isa('Bugzilla::Search::Condition')) {
- ThrowUserError('search_grouped_invalid_nesting');
- }
-
- # Ensure all conditions use the same field
- if (!$self->{_field}) {
- $self->{_field} = $field;
- } elsif ($field ne $self->{_field}) {
- ThrowUserError('search_grouped_field_mismatch');
- }
-
- # Unsupported fields
- if (grep { $_ eq $field } UNSUPPORTED_FIELDS ) {
- ThrowUserError('search_grouped_field_invalid', { field => $field });
- }
-
- $self->SUPER::add(@args);
+ my ($self, @args) = @_;
+ my $field = scalar(@args) == 3 ? $args[0] : $args[0]->{field};
+
+ # We don't support nesting of conditions under this clause
+ if (scalar(@args) == 1 && !$args[0]->isa('Bugzilla::Search::Condition')) {
+ ThrowUserError('search_grouped_invalid_nesting');
+ }
+
+ # Ensure all conditions use the same field
+ if (!$self->{_field}) {
+ $self->{_field} = $field;
+ }
+ elsif ($field ne $self->{_field}) {
+ ThrowUserError('search_grouped_field_mismatch');
+ }
+
+ # Unsupported fields
+ if (grep { $_ eq $field } UNSUPPORTED_FIELDS) {
+ ThrowUserError('search_grouped_field_invalid', {field => $field});
+ }
+
+ $self->SUPER::add(@args);
}
sub update_search_args {
- my ($self, $search_args) = @_;
+ my ($self, $search_args) = @_;
- # No need to change things if there's only one child condition
- return unless scalar(@{ $self->children }) > 1;
+ # No need to change things if there's only one child condition
+ return unless scalar(@{$self->children}) > 1;
- # we want all the terms to use the same join table
- if (!exists $self->{_first_chart_id}) {
- $self->{_first_chart_id} = $search_args->{chart_id};
- } else {
- $search_args->{chart_id} = $self->{_first_chart_id};
- }
+ # we want all the terms to use the same join table
+ if (!exists $self->{_first_chart_id}) {
+ $self->{_first_chart_id} = $search_args->{chart_id};
+ }
+ else {
+ $search_args->{chart_id} = $self->{_first_chart_id};
+ }
- my $suffix = '_g' . $self->{_first_chart_id};
- $self->{group_condition}->{translated}->{joins}->[0]->{as} = "bugs$suffix";
+ my $suffix = '_g' . $self->{_first_chart_id};
+ $self->{group_condition}->{translated}->{joins}->[0]->{as} = "bugs$suffix";
- $search_args->{full_field} =~ s/^bugs\./bugs$suffix\./;
+ $search_args->{full_field} =~ s/^bugs\./bugs$suffix\./;
- $search_args->{table_suffix} = $suffix;
- $search_args->{bugs_table} = "bugs$suffix";
+ $search_args->{table_suffix} = $suffix;
+ $search_args->{bugs_table} = "bugs$suffix";
}
1;
diff --git a/Bugzilla/Search/Condition.pm b/Bugzilla/Search/Condition.pm
index 1c38c1f3e..33b0bfd8b 100644
--- a/Bugzilla/Search/Condition.pm
+++ b/Bugzilla/Search/Condition.pm
@@ -15,55 +15,59 @@ use base qw(Exporter);
our @EXPORT_OK = qw(condition);
sub new {
- my ($class, $params) = @_;
- my %self = %$params;
- bless \%self, $class;
- return \%self;
+ my ($class, $params) = @_;
+ my %self = %$params;
+ bless \%self, $class;
+ return \%self;
}
-sub field { return $_[0]->{field} }
-sub value { return $_[0]->{value} }
+sub field { return $_[0]->{field} }
+sub value { return $_[0]->{value} }
sub operator {
- my ($self, $value) = @_;
- if (@_ == 2) {
- $self->{operator} = $value;
- }
- return $self->{operator};
+ my ($self, $value) = @_;
+ if (@_ == 2) {
+ $self->{operator} = $value;
+ }
+ return $self->{operator};
}
sub fov {
- my ($self) = @_;
- return ($self->field, $self->operator, $self->value);
+ my ($self) = @_;
+ return ($self->field, $self->operator, $self->value);
}
sub translated {
- my ($self, $params) = @_;
- if (@_ == 2) {
- $self->{translated} = $params;
- }
- return $self->{translated};
+ my ($self, $params) = @_;
+ if (@_ == 2) {
+ $self->{translated} = $params;
+ }
+ return $self->{translated};
}
sub as_string {
- my ($self) = @_;
- my $term = $self->translated->{term};
- $term = "NOT( $term )" if $term && $self->negate;
- return $term;
+ my ($self) = @_;
+ my $term = $self->translated->{term};
+ $term = "NOT( $term )" if $term && $self->negate;
+ return $term;
}
sub as_params {
- my ($self) = @_;
- return { f => $self->field, o => $self->operator, v => $self->value,
- n => scalar $self->negate };
+ my ($self) = @_;
+ return {
+ f => $self->field,
+ o => $self->operator,
+ v => $self->value,
+ n => scalar $self->negate
+ };
}
sub negate {
- my ($self, $value) = @_;
- if (@_ == 2) {
- $self->{negate} = $value ? 1 : 0;
- }
- return $self->{negate};
+ my ($self, $value) = @_;
+ if (@_ == 2) {
+ $self->{negate} = $value ? 1 : 0;
+ }
+ return $self->{negate};
}
###########################
@@ -71,9 +75,9 @@ sub negate {
###########################
sub condition {
- my ($field, $operator, $value) = @_;
- return __PACKAGE__->new({ field => $field, operator => $operator,
- value => $value });
+ my ($field, $operator, $value) = @_;
+ return __PACKAGE__->new(
+ {field => $field, operator => $operator, value => $value});
}
1;
diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm
index 6c0253e27..f2bb83e2e 100644
--- a/Bugzilla/Search/Quicksearch.pm
+++ b/Bugzilla/Search/Quicksearch.pm
@@ -27,256 +27,265 @@ use base qw(Exporter);
# Custom mappings for some fields.
use constant MAPPINGS => {
- # Status, Resolution, Platform, OS, Priority, Severity
- "status" => "bug_status",
- "platform" => "rep_platform",
- "os" => "op_sys",
- "severity" => "bug_severity",
-
- # People: AssignedTo, Reporter, QA Contact, CC, etc.
- "assignee" => "assigned_to",
- "owner" => "assigned_to",
- "mentor" => "bug_mentor",
-
- # Product, Version, Component, Target Milestone
- "milestone" => "target_milestone",
-
- # Summary, Description, URL, Status whiteboard, Keywords
- "summary" => "short_desc",
- "description" => "longdesc",
- "comment" => "longdesc",
- "url" => "bug_file_loc",
- "whiteboard" => "status_whiteboard",
- "sw" => "status_whiteboard",
- "kw" => "keywords",
- "group" => "bug_group",
-
- # Flags
- "flag" => "flagtypes.name",
- "requestee" => "requestees.login_name",
- "setter" => "setters.login_name",
-
- # Attachments
- "attachment" => "attachments.description",
- "attachmentdesc" => "attachments.description",
- "attachdesc" => "attachments.description",
- "attachmentmimetype" => "attachments.mimetype",
- "attachmimetype" => "attachments.mimetype"
+
+ # Status, Resolution, Platform, OS, Priority, Severity
+ "status" => "bug_status",
+ "platform" => "rep_platform",
+ "os" => "op_sys",
+ "severity" => "bug_severity",
+
+ # People: AssignedTo, Reporter, QA Contact, CC, etc.
+ "assignee" => "assigned_to",
+ "owner" => "assigned_to",
+ "mentor" => "bug_mentor",
+
+ # Product, Version, Component, Target Milestone
+ "milestone" => "target_milestone",
+
+ # Summary, Description, URL, Status whiteboard, Keywords
+ "summary" => "short_desc",
+ "description" => "longdesc",
+ "comment" => "longdesc",
+ "url" => "bug_file_loc",
+ "whiteboard" => "status_whiteboard",
+ "sw" => "status_whiteboard",
+ "kw" => "keywords",
+ "group" => "bug_group",
+
+ # Flags
+ "flag" => "flagtypes.name",
+ "requestee" => "requestees.login_name",
+ "setter" => "setters.login_name",
+
+ # Attachments
+ "attachment" => "attachments.description",
+ "attachmentdesc" => "attachments.description",
+ "attachdesc" => "attachments.description",
+ "attachmentmimetype" => "attachments.mimetype",
+ "attachmimetype" => "attachments.mimetype"
};
sub FIELD_MAP {
- my $cache = Bugzilla->request_cache;
- return $cache->{quicksearch_fields} if $cache->{quicksearch_fields};
-
- # Get all the fields whose names don't contain periods. (Fields that
- # contain periods are always handled in MAPPINGS.)
- my @db_fields = grep { $_->name !~ /\./ }
- @{ Bugzilla->fields({ obsolete => 0 }) };
- my %full_map = (%{ MAPPINGS() }, map { $_->name => $_->name } @db_fields);
-
- # Eliminate the fields that start with bug_ or rep_, because those are
- # handled by the MAPPINGS instead, and we don't want too many names
- # for them. (Also, otherwise "rep" doesn't match "reporter".)
- #
- # Remove "status_whiteboard" because we have "whiteboard" for it in
- # the mappings, and otherwise "stat" can't match "status".
- #
- # Also, don't allow searching the _accessible stuff via quicksearch
- # (both because it's unnecessary and because otherwise
- # "reporter_accessible" and "reporter" both match "rep".
- delete @full_map{qw(rep_platform bug_status bug_file_loc bug_group
- bug_severity bug_status
- status_whiteboard
- cclist_accessible reporter_accessible)};
-
- Bugzilla::Hook::process('quicksearch_map', {'map' => \%full_map} );
-
- $cache->{quicksearch_fields} = \%full_map;
-
- return $cache->{quicksearch_fields};
+ my $cache = Bugzilla->request_cache;
+ return $cache->{quicksearch_fields} if $cache->{quicksearch_fields};
+
+ # Get all the fields whose names don't contain periods. (Fields that
+ # contain periods are always handled in MAPPINGS.)
+ my @db_fields = grep { $_->name !~ /\./ } @{Bugzilla->fields({obsolete => 0})};
+ my %full_map = (%{MAPPINGS()}, map { $_->name => $_->name } @db_fields);
+
+ # Eliminate the fields that start with bug_ or rep_, because those are
+ # handled by the MAPPINGS instead, and we don't want too many names
+ # for them. (Also, otherwise "rep" doesn't match "reporter".)
+ #
+ # Remove "status_whiteboard" because we have "whiteboard" for it in
+ # the mappings, and otherwise "stat" can't match "status".
+ #
+ # Also, don't allow searching the _accessible stuff via quicksearch
+ # (both because it's unnecessary and because otherwise
+ # "reporter_accessible" and "reporter" both match "rep".
+ delete @full_map{
+ qw(rep_platform bug_status bug_file_loc bug_group
+ bug_severity bug_status
+ status_whiteboard
+ cclist_accessible reporter_accessible)
+ };
+
+ Bugzilla::Hook::process('quicksearch_map', {'map' => \%full_map});
+
+ $cache->{quicksearch_fields} = \%full_map;
+
+ return $cache->{quicksearch_fields};
}
# Certain fields, when specified like "field:value" get an operator other
# than "substring"
-use constant FIELD_OPERATOR => {
- content => 'matches',
- owner_idle_time => 'greaterthan',
-};
+use constant FIELD_OPERATOR =>
+ {content => 'matches', owner_idle_time => 'greaterthan',};
# Mappings for operators symbols to support operators other than "substring"
use constant OPERATOR_SYMBOLS => {
- ':' => 'substring',
- '=' => 'equals',
- '!=' => 'notequals',
- '>=' => 'greaterthaneq',
- '<=' => 'lessthaneq',
- '>' => 'greaterthan',
- '<' => 'lessthan',
+ ':' => 'substring',
+ '=' => 'equals',
+ '!=' => 'notequals',
+ '>=' => 'greaterthaneq',
+ '<=' => 'lessthaneq',
+ '>' => 'greaterthan',
+ '<' => 'lessthan',
};
# We might want to put this into localconfig or somewhere
use constant PRODUCT_EXCEPTIONS => (
- 'row', # [Browser]
- # ^^^
- 'new', # [MailNews]
- # ^^^
+ 'row', # [Browser]
+ # ^^^
+ 'new', # [MailNews]
+ # ^^^
);
use constant COMPONENT_EXCEPTIONS => (
- 'hang' # [Bugzilla: Component/Keyword Changes]
- # ^^^^
+ 'hang' # [Bugzilla: Component/Keyword Changes]
+ # ^^^^
);
# Quicksearch-wide globals for boolean charts.
our ($chart, $and, $or, $fulltext, $bug_status_set, $ELASTIC);
sub quicksearch {
- my ($searchstring) = (@_);
- my $cgi = Bugzilla->cgi;
+ my ($searchstring) = (@_);
+ my $cgi = Bugzilla->cgi;
- $chart = 0;
- $and = 0;
- $or = 0;
+ $chart = 0;
+ $and = 0;
+ $or = 0;
- # Remove leading and trailing commas and whitespace.
- $searchstring =~ s/(^[\s,]+|[\s,]+$)//g;
- ThrowUserError('buglist_parameters_required') unless ($searchstring);
+ # Remove leading and trailing commas and whitespace.
+ $searchstring =~ s/(^[\s,]+|[\s,]+$)//g;
+ ThrowUserError('buglist_parameters_required') unless ($searchstring);
- if ($searchstring =~ m/^[0-9,\s]*$/) {
- _bug_numbers_only($searchstring);
- }
- else {
- _handle_alias($searchstring);
-
- # Retain backslashes and quotes, to know which strings are quoted,
- # and which ones are not.
- my @words = _parse_line('\s+', 1, $searchstring);
- # If parse_line() returns no data, this means strings are badly quoted.
- # Rather than trying to guess what the user wanted to do, we throw an error.
- scalar(@words)
- || ThrowUserError('quicksearch_unbalanced_quotes',
- { string => $searchstring, quicksearch => $searchstring });
-
- # A query cannot start with AND or OR, nor can it end with AND, OR or NOT.
+ if ($searchstring =~ m/^[0-9,\s]*$/) {
+ _bug_numbers_only($searchstring);
+ }
+ else {
+ _handle_alias($searchstring);
+
+ # Retain backslashes and quotes, to know which strings are quoted,
+ # and which ones are not.
+ my @words = _parse_line('\s+', 1, $searchstring);
+
+ # If parse_line() returns no data, this means strings are badly quoted.
+ # Rather than trying to guess what the user wanted to do, we throw an error.
+ scalar(@words) || ThrowUserError('quicksearch_unbalanced_quotes',
+ {string => $searchstring, quicksearch => $searchstring});
+
+ # A query cannot start with AND or OR, nor can it end with AND, OR or NOT.
+ ThrowUserError('quicksearch_invalid_query', {quicksearch => $searchstring})
+ if ($words[0] =~ /^(?:AND|OR)$/ || $words[$#words] =~ /^(?:AND|OR|NOT)$/);
+
+ $fulltext = Bugzilla->user->setting('quicksearch_fulltext') eq 'on' ? 1 : 0;
+
+ my (@qswords, @or_group);
+ while (scalar @words) {
+ my $word = shift @words;
+
+ # AND is the default word separator, similar to a whitespace,
+ # but |a AND OR b| is not a valid combination.
+ if ($word eq 'AND') {
ThrowUserError('quicksearch_invalid_query',
- { quicksearch => $searchstring })
- if ($words[0] =~ /^(?:AND|OR)$/ || $words[$#words] =~ /^(?:AND|OR|NOT)$/);
-
- $fulltext = Bugzilla->user->setting('quicksearch_fulltext') eq 'on' ? 1 : 0;
-
- my (@qswords, @or_group);
- while (scalar @words) {
- my $word = shift @words;
- # AND is the default word separator, similar to a whitespace,
- # but |a AND OR b| is not a valid combination.
- if ($word eq 'AND') {
- ThrowUserError('quicksearch_invalid_query',
- { operators => ['AND', 'OR'], quicksearch => $searchstring })
- if $words[0] eq 'OR';
- }
- # |a OR AND b| is not a valid combination.
- # |a OR OR b| is equivalent to |a OR b| and so is harmless.
- elsif ($word eq 'OR') {
- ThrowUserError('quicksearch_invalid_query',
- { operators => ['OR', 'AND'], quicksearch => $searchstring })
- if $words[0] eq 'AND';
- }
- # NOT negates the following word.
- # |NOT AND| and |NOT OR| are not valid combinations.
- # |NOT NOT| is fine but has no effect as they cancel themselves.
- elsif ($word eq 'NOT') {
- $word = shift @words;
- next if $word eq 'NOT';
- if ($word eq 'AND' || $word eq 'OR') {
- ThrowUserError('quicksearch_invalid_query',
- { operators => ['NOT', $word], quicksearch => $searchstring });
- }
- unshift(@words, "-$word");
- }
- # --comment and ++comment disable or enable fulltext searching
- elsif ($word =~ /^(--|\+\+)comments?$/i) {
- $fulltext = $1 eq '--' ? 0 : 1;
- }
- else {
- # OR groups words together, as OR has higher precedence than AND.
- push(@or_group, $word);
- # If the next word is not OR, then we are not in a OR group,
- # or we are leaving it.
- if (!defined $words[0] || $words[0] ne 'OR') {
- push(@qswords, join('|', @or_group));
- @or_group = ();
- }
- }
- }
+ {operators => ['AND', 'OR'], quicksearch => $searchstring})
+ if $words[0] eq 'OR';
+ }
- _handle_status_and_resolution($qswords[0]);
- shift(@qswords) if $bug_status_set;
-
- my (@unknownFields, %ambiguous_fields);
-
- # Loop over all main-level QuickSearch words.
- foreach my $qsword (@qswords) {
- my @or_operand = _parse_line('\|', 1, $qsword);
- foreach my $term (@or_operand) {
- next unless defined $term;
- my $negate = substr($term, 0, 1) eq '-';
- if ($negate) {
- $term = substr($term, 1);
- }
-
- next if _handle_special_first_chars($term, $negate);
- next if _handle_field_names($term, $negate, \@unknownFields,
- \%ambiguous_fields);
-
- # Having ruled out the special cases, we may now split
- # by comma, which is another legal boolean OR indicator.
- # Remove quotes from quoted words, if any.
- @words = _parse_line(',', 0, $term);
- foreach my $word (@words) {
- if (!_special_field_syntax($word, $negate)) {
- _default_quicksearch_word($word, $negate);
- }
- _handle_urls($word, $negate);
- }
- }
- $chart++;
- $and = 0;
- $or = 0;
+ # |a OR AND b| is not a valid combination.
+ # |a OR OR b| is equivalent to |a OR b| and so is harmless.
+ elsif ($word eq 'OR') {
+ ThrowUserError('quicksearch_invalid_query',
+ {operators => ['OR', 'AND'], quicksearch => $searchstring})
+ if $words[0] eq 'AND';
+ }
+
+ # NOT negates the following word.
+ # |NOT AND| and |NOT OR| are not valid combinations.
+ # |NOT NOT| is fine but has no effect as they cancel themselves.
+ elsif ($word eq 'NOT') {
+ $word = shift @words;
+ next if $word eq 'NOT';
+ if ($word eq 'AND' || $word eq 'OR') {
+ ThrowUserError('quicksearch_invalid_query',
+ {operators => ['NOT', $word], quicksearch => $searchstring});
}
-
- # If there is no mention of a bug status, we restrict the query
- # to open bugs by default.
- unless ($bug_status_set) {
- $cgi->param('bug_status', BUG_STATE_OPEN);
+ unshift(@words, "-$word");
+ }
+
+ # --comment and ++comment disable or enable fulltext searching
+ elsif ($word =~ /^(--|\+\+)comments?$/i) {
+ $fulltext = $1 eq '--' ? 0 : 1;
+ }
+ else {
+ # OR groups words together, as OR has higher precedence than AND.
+ push(@or_group, $word);
+
+ # If the next word is not OR, then we are not in a OR group,
+ # or we are leaving it.
+ if (!defined $words[0] || $words[0] ne 'OR') {
+ push(@qswords, join('|', @or_group));
+ @or_group = ();
}
+ }
+ }
+
+ _handle_status_and_resolution($qswords[0]);
+ shift(@qswords) if $bug_status_set;
+
+ my (@unknownFields, %ambiguous_fields);
- # Inform user about any unknown fields
- if (scalar(@unknownFields) || scalar(keys %ambiguous_fields)) {
- ThrowUserError("quicksearch_unknown_field",
- { unknown => \@unknownFields,
- ambiguous => \%ambiguous_fields,
- quicksearch => $searchstring });
+ # Loop over all main-level QuickSearch words.
+ foreach my $qsword (@qswords) {
+ my @or_operand = _parse_line('\|', 1, $qsword);
+ foreach my $term (@or_operand) {
+ next unless defined $term;
+ my $negate = substr($term, 0, 1) eq '-';
+ if ($negate) {
+ $term = substr($term, 1);
}
- # Make sure we have some query terms left
- scalar($cgi->param())>0 || ThrowUserError("buglist_parameters_required");
+ next if _handle_special_first_chars($term, $negate);
+ next
+ if _handle_field_names($term, $negate, \@unknownFields, \%ambiguous_fields);
+
+ # Having ruled out the special cases, we may now split
+ # by comma, which is another legal boolean OR indicator.
+ # Remove quotes from quoted words, if any.
+ @words = _parse_line(',', 0, $term);
+ foreach my $word (@words) {
+ if (!_special_field_syntax($word, $negate)) {
+ _default_quicksearch_word($word, $negate);
+ }
+ _handle_urls($word, $negate);
+ }
+ }
+ $chart++;
+ $and = 0;
+ $or = 0;
+ }
+
+ # If there is no mention of a bug status, we restrict the query
+ # to open bugs by default.
+ unless ($bug_status_set) {
+ $cgi->param('bug_status', BUG_STATE_OPEN);
+ }
+
+ # Inform user about any unknown fields
+ if (scalar(@unknownFields) || scalar(keys %ambiguous_fields)) {
+ ThrowUserError(
+ "quicksearch_unknown_field",
+ {
+ unknown => \@unknownFields,
+ ambiguous => \%ambiguous_fields,
+ quicksearch => $searchstring
+ }
+ );
}
- # List of quicksearch-specific CGI parameters to get rid of.
- my @params_to_strip = ('quicksearch', 'load', 'run');
- my $modified_query_string = $cgi->canonicalise_query(@params_to_strip);
+ # Make sure we have some query terms left
+ scalar($cgi->param()) > 0 || ThrowUserError("buglist_parameters_required");
+ }
- if ($cgi->param('load')) {
- my $urlbase = Bugzilla->localconfig->{urlbase};
- # Param 'load' asks us to display the query in the advanced search form.
- print $cgi->redirect(-uri => "${urlbase}query.cgi?format=advanced&amp;"
- . $modified_query_string);
- }
+ # List of quicksearch-specific CGI parameters to get rid of.
+ my @params_to_strip = ('quicksearch', 'load', 'run');
+ my $modified_query_string = $cgi->canonicalise_query(@params_to_strip);
+
+ if ($cgi->param('load')) {
+ my $urlbase = Bugzilla->localconfig->{urlbase};
- # Otherwise, pass the modified query string to the caller.
- # We modified $cgi->params, so the caller can choose to look at that, too,
- # and disregard the return value.
- $cgi->delete(@params_to_strip);
- return $modified_query_string;
+ # Param 'load' asks us to display the query in the advanced search form.
+ print $cgi->redirect(
+ -uri => "${urlbase}query.cgi?format=advanced&amp;" . $modified_query_string);
+ }
+
+ # Otherwise, pass the modified query string to the caller.
+ # We modified $cgi->params, so the caller can choose to look at that, too,
+ # and disregard the return value.
+ $cgi->delete(@params_to_strip);
+ return $modified_query_string;
}
##########################
@@ -284,336 +293,353 @@ sub quicksearch {
##########################
sub _parse_line {
- my ($delim, $keep, $line) = @_;
- return () unless defined $line;
-
- # parse_line always treats ' as a quote character, making it impossible
- # to sanely search for contractions. As this behavour isn't
- # configurable, we replace ' with a placeholder to hide it from the
- # parser.
-
- # only treat ' at the start or end of words as quotes
- # it's easier to do this in reverse with regexes
- $line =~ s/(^|\s|:)'/$1\001/g;
- $line =~ s/'($|\s)/\001$1/g;
- $line =~ s/\\?'/\000/g;
- $line =~ tr/\001/'/;
-
- my @words = parse_line($delim, $keep, $line);
- foreach my $word (@words) {
- $word =~ tr/\000/'/ if defined $word;
- }
- return @words;
+ my ($delim, $keep, $line) = @_;
+ return () unless defined $line;
+
+ # parse_line always treats ' as a quote character, making it impossible
+ # to sanely search for contractions. As this behavour isn't
+ # configurable, we replace ' with a placeholder to hide it from the
+ # parser.
+
+ # only treat ' at the start or end of words as quotes
+ # it's easier to do this in reverse with regexes
+ $line =~ s/(^|\s|:)'/$1\001/g;
+ $line =~ s/'($|\s)/\001$1/g;
+ $line =~ s/\\?'/\000/g;
+ $line =~ tr/\001/'/;
+
+ my @words = parse_line($delim, $keep, $line);
+ foreach my $word (@words) {
+ $word =~ tr/\000/'/ if defined $word;
+ }
+ return @words;
}
sub _bug_numbers_only {
- my $searchstring = shift;
- my $cgi = Bugzilla->cgi;
- # Allow separation by comma or whitespace.
- $searchstring =~ s/[,\s]+/,/g;
-
- if ($searchstring !~ /,/ && !i_am_webservice()) {
- # Single bug number; shortcut to show_bug.cgi.
- print $cgi->redirect(
- -uri => Bugzilla->localconfig->{urlbase} . "show_bug.cgi?id=$searchstring");
- exit;
- }
- else {
- # List of bug numbers.
- $cgi->param('bug_id', $searchstring);
- $cgi->param('order', 'bugs.bug_id');
- $cgi->param('bug_id_type', 'anyexact');
- }
+ my $searchstring = shift;
+ my $cgi = Bugzilla->cgi;
+
+ # Allow separation by comma or whitespace.
+ $searchstring =~ s/[,\s]+/,/g;
+
+ if ($searchstring !~ /,/ && !i_am_webservice()) {
+
+ # Single bug number; shortcut to show_bug.cgi.
+ print $cgi->redirect(
+ -uri => Bugzilla->localconfig->{urlbase} . "show_bug.cgi?id=$searchstring");
+ exit;
+ }
+ else {
+ # List of bug numbers.
+ $cgi->param('bug_id', $searchstring);
+ $cgi->param('order', 'bugs.bug_id');
+ $cgi->param('bug_id_type', 'anyexact');
+ }
}
sub _handle_alias {
- my $searchstring = shift;
- if ($searchstring =~ /^([^,\s]+)$/) {
- my $alias = $1;
- # We use this direct SQL because we want quicksearch to be VERY fast.
- my $bug_id = Bugzilla->dbh->selectrow_array(
- q{SELECT bug_id FROM bugs WHERE alias = ?}, undef, $alias);
- # If the user cannot see the bug or if we are using a webservice,
- # do not resolve its alias.
- if ($bug_id && Bugzilla->user->can_see_bug($bug_id) && !i_am_webservice()) {
- $alias = url_quote($alias);
- print Bugzilla->cgi->redirect(
- -uri => Bugzilla->localconfig->{urlbase} . "show_bug.cgi?id=$alias");
- exit;
- }
- }
+ my $searchstring = shift;
+ if ($searchstring =~ /^([^,\s]+)$/) {
+ my $alias = $1;
+
+ # We use this direct SQL because we want quicksearch to be VERY fast.
+ my $bug_id
+ = Bugzilla->dbh->selectrow_array(q{SELECT bug_id FROM bugs WHERE alias = ?},
+ undef, $alias);
+
+ # If the user cannot see the bug or if we are using a webservice,
+ # do not resolve its alias.
+ if ($bug_id && Bugzilla->user->can_see_bug($bug_id) && !i_am_webservice()) {
+ $alias = url_quote($alias);
+ print Bugzilla->cgi->redirect(
+ -uri => Bugzilla->localconfig->{urlbase} . "show_bug.cgi?id=$alias");
+ exit;
+ }
+ }
}
sub _handle_status_and_resolution {
- my $word = shift;
- my $legal_statuses = get_legal_field_values('bug_status');
- my (%states, %resolutions);
- $bug_status_set = 1;
-
- if ($word =~ s/^(ALL|OPEN)\+$/$1/) {
- Bugzilla->cgi->param('limit' => 0);
- }
-
- if ($word eq 'OPEN') {
- $states{$_} = 1 foreach BUG_STATE_OPEN;
- }
- # If we want all bugs, then there is nothing to do.
- elsif ($word ne 'ALL'
- && !matchPrefixes(\%states, \%resolutions, $word, $legal_statuses))
- {
- $bug_status_set = 0;
- }
-
- # If we have wanted resolutions, allow closed states
- if (keys(%resolutions)) {
- foreach my $status (@$legal_statuses) {
- $states{$status} = 1 unless is_open_state($status);
- }
- }
-
- Bugzilla->cgi->param('bug_status', keys(%states));
- Bugzilla->cgi->param('resolution', keys(%resolutions));
+ my $word = shift;
+ my $legal_statuses = get_legal_field_values('bug_status');
+ my (%states, %resolutions);
+ $bug_status_set = 1;
+
+ if ($word =~ s/^(ALL|OPEN)\+$/$1/) {
+ Bugzilla->cgi->param('limit' => 0);
+ }
+
+ if ($word eq 'OPEN') {
+ $states{$_} = 1 foreach BUG_STATE_OPEN;
+ }
+
+ # If we want all bugs, then there is nothing to do.
+ elsif ($word ne 'ALL'
+ && !matchPrefixes(\%states, \%resolutions, $word, $legal_statuses))
+ {
+ $bug_status_set = 0;
+ }
+
+ # If we have wanted resolutions, allow closed states
+ if (keys(%resolutions)) {
+ foreach my $status (@$legal_statuses) {
+ $states{$status} = 1 unless is_open_state($status);
+ }
+ }
+
+ Bugzilla->cgi->param('bug_status', keys(%states));
+ Bugzilla->cgi->param('resolution', keys(%resolutions));
}
sub _handle_special_first_chars {
- my ($qsword, $negate) = @_;
- return 0 if !defined $qsword || length($qsword) <= 1;
-
- my $firstChar = substr($qsword, 0, 1);
- my $baseWord = substr($qsword, 1);
- my @subWords = split(/,/, $baseWord);
-
- if ($firstChar eq '#') {
- addChart('short_desc', 'substring', $baseWord, $negate);
- addChart('content', 'matches', _matches_phrase($baseWord), $negate) if $fulltext;
- return 1;
- }
- if ($firstChar eq ':') {
- foreach (@subWords) {
- addChart('product', 'substring', $_, $negate);
- addChart('component', 'substring', $_, $negate);
- }
- return 1;
- }
- if ($firstChar eq '@') {
- addChart('assigned_to', 'substring', $_, $negate) foreach (@subWords);
- return 1;
- }
- if ($firstChar eq '[') {
- addChart('short_desc', 'substring', $baseWord, $negate);
- addChart('status_whiteboard', 'substring', $baseWord, $negate);
- return 1;
- }
- if ($firstChar eq '!') {
- addChart('keywords', 'anywords', $baseWord, $negate);
- return 1;
- }
- return 0;
+ my ($qsword, $negate) = @_;
+ return 0 if !defined $qsword || length($qsword) <= 1;
+
+ my $firstChar = substr($qsword, 0, 1);
+ my $baseWord = substr($qsword, 1);
+ my @subWords = split(/,/, $baseWord);
+
+ if ($firstChar eq '#') {
+ addChart('short_desc', 'substring', $baseWord, $negate);
+ addChart('content', 'matches', _matches_phrase($baseWord), $negate)
+ if $fulltext;
+ return 1;
+ }
+ if ($firstChar eq ':') {
+ foreach (@subWords) {
+ addChart('product', 'substring', $_, $negate);
+ addChart('component', 'substring', $_, $negate);
+ }
+ return 1;
+ }
+ if ($firstChar eq '@') {
+ addChart('assigned_to', 'substring', $_, $negate) foreach (@subWords);
+ return 1;
+ }
+ if ($firstChar eq '[') {
+ addChart('short_desc', 'substring', $baseWord, $negate);
+ addChart('status_whiteboard', 'substring', $baseWord, $negate);
+ return 1;
+ }
+ if ($firstChar eq '!') {
+ addChart('keywords', 'anywords', $baseWord, $negate);
+ return 1;
+ }
+ return 0;
}
sub _handle_field_names {
- my ($or_operand, $negate, $unknownFields, $ambiguous_fields) = @_;
-
- # Flag and requestee shortcut
- if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
- # BMO: Do not treat custom fields as flags if value is ?
- if ($1 !~ /^cf_/) {
- my ($flagtype, $requestee) = ($1, $2);
- addChart('flagtypes.name', 'substring', $flagtype, $negate);
- if ($requestee) {
- # AND
- $chart++;
- $and = $or = 0;
- addChart('requestees.login_name', 'substring', $requestee, $negate);
- }
- return 1;
+ my ($or_operand, $negate, $unknownFields, $ambiguous_fields) = @_;
+
+ # Flag and requestee shortcut
+ if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
+
+ # BMO: Do not treat custom fields as flags if value is ?
+ if ($1 !~ /^cf_/) {
+ my ($flagtype, $requestee) = ($1, $2);
+ addChart('flagtypes.name', 'substring', $flagtype, $negate);
+ if ($requestee) {
+
+ # AND
+ $chart++;
+ $and = $or = 0;
+ addChart('requestees.login_name', 'substring', $requestee, $negate);
+ }
+ return 1;
+ }
+ }
+
+ # Generic field1,field2,field3:value1,value2 notation.
+ # We have to correctly ignore commas and colons in quotes.
+ # Longer operators must be tested first as we don't want single character
+ # operators such as <, > and = to be tested before <=, >= and !=.
+ my @operators = sort { length($b) <=> length($a) } keys %{OPERATOR_SYMBOLS()};
+
+ foreach my $symbol (@operators) {
+ my @field_values = _parse_line($symbol, 1, $or_operand);
+ next unless scalar @field_values == 2;
+ my @fields = _parse_line(',', 1, $field_values[0]);
+ my @values = _parse_line(',', 1, $field_values[1]);
+ foreach my $field (@fields) {
+ my $translated = _translate_field_name($field);
+
+ # Skip and record any unknown fields
+ if (!defined $translated) {
+ push(@$unknownFields, $field);
+ }
+
+ # If we got back an array, that means the substring is
+ # ambiguous and could match more than field name
+ elsif (ref $translated) {
+ $ambiguous_fields->{$field} = $translated;
+ }
+ else {
+ if ($translated eq 'bug_status' || $translated eq 'resolution') {
+ $bug_status_set = 1;
}
- }
-
- # Generic field1,field2,field3:value1,value2 notation.
- # We have to correctly ignore commas and colons in quotes.
- # Longer operators must be tested first as we don't want single character
- # operators such as <, > and = to be tested before <=, >= and !=.
- my @operators = sort { length($b) <=> length($a) } keys %{ OPERATOR_SYMBOLS() };
-
- foreach my $symbol (@operators) {
- my @field_values = _parse_line($symbol, 1, $or_operand);
- next unless scalar @field_values == 2;
- my @fields = _parse_line(',', 1, $field_values[0]);
- my @values = _parse_line(',', 1, $field_values[1]);
- foreach my $field (@fields) {
- my $translated = _translate_field_name($field);
- # Skip and record any unknown fields
- if (!defined $translated) {
- push(@$unknownFields, $field);
- }
- # If we got back an array, that means the substring is
- # ambiguous and could match more than field name
- elsif (ref $translated) {
- $ambiguous_fields->{$field} = $translated;
- }
- else {
- if ($translated eq 'bug_status' || $translated eq 'resolution') {
- $bug_status_set = 1;
- }
- foreach my $value (@values) {
- next unless defined $value;
- my $operator = FIELD_OPERATOR->{$translated}
- || OPERATOR_SYMBOLS->{$symbol}
- || 'substring';
- # If the string was quoted to protect some special
- # characters such as commas and colons, we need
- # to remove quotes.
- if ($value =~ /^(["'])(.+)\1$/) {
- $value = $2;
- $value =~ s/\\(["'])/$1/g;
- }
- # If the value is a pair of matching quotes, the person wanted the empty string
- elsif ($value =~ /^(["'])\1$/ || $translated eq 'resolution' && $value eq '---') {
- $value = "";
- $operator = "isempty";
- }
- addChart($translated, $operator, $value, $negate);
- }
- }
+ foreach my $value (@values) {
+ next unless defined $value;
+ my $operator
+ = FIELD_OPERATOR->{$translated} || OPERATOR_SYMBOLS->{$symbol} || 'substring';
+
+ # If the string was quoted to protect some special
+ # characters such as commas and colons, we need
+ # to remove quotes.
+ if ($value =~ /^(["'])(.+)\1$/) {
+ $value = $2;
+ $value =~ s/\\(["'])/$1/g;
+ }
+
+ # If the value is a pair of matching quotes, the person wanted the empty string
+ elsif ($value =~ /^(["'])\1$/ || $translated eq 'resolution' && $value eq '---')
+ {
+ $value = "";
+ $operator = "isempty";
+ }
+ addChart($translated, $operator, $value, $negate);
}
- return 1;
+ }
}
- return 0;
+ return 1;
+ }
+ return 0;
}
sub _translate_field_name {
- my $field = shift;
- $field = lc($field);
- my $field_map = FIELD_MAP;
-
- # If the field exactly matches a mapping, just return right now.
- return $field_map->{$field} if exists $field_map->{$field};
-
- # Check if we match, as a starting substring, exactly one field.
- my @field_names = keys %$field_map;
- my @matches = grep { $_ =~ /^\Q$field\E/ } @field_names;
- # Eliminate duplicates that are actually the same field
- # (otherwise "assi" matches both "assignee" and "assigned_to", and
- # the lines below fail when they shouldn't.)
- my %match_unique = map { $field_map->{$_} => $_ } @matches;
- @matches = values %match_unique;
-
- if (scalar(@matches) == 1) {
- return $field_map->{$matches[0]};
- }
- elsif (scalar(@matches) > 1) {
- return \@matches;
- }
-
- # Check if we match exactly one custom field, ignoring the cf_ on the
- # custom fields (to allow people to type things like "build" for
- # "cf_build").
- my %cfless;
- foreach my $name (@field_names) {
- my $no_cf = $name;
- if ($no_cf =~ s/^cf_//) {
- if ($field eq $no_cf) {
- return $field_map->{$name};
- }
- $cfless{$no_cf} = $name;
- }
- }
-
- # See if we match exactly one substring of any of the cf_-less fields.
- my @cfless_matches = grep { $_ =~ /^\Q$field\E/ } (keys %cfless);
-
- if (scalar(@cfless_matches) == 1) {
- my $match = $cfless_matches[0];
- my $actual_field = $cfless{$match};
- return $field_map->{$actual_field};
- }
- elsif (scalar(@matches) > 1) {
- return \@matches;
- }
-
- return undef;
+ my $field = shift;
+ $field = lc($field);
+ my $field_map = FIELD_MAP;
+
+ # If the field exactly matches a mapping, just return right now.
+ return $field_map->{$field} if exists $field_map->{$field};
+
+ # Check if we match, as a starting substring, exactly one field.
+ my @field_names = keys %$field_map;
+ my @matches = grep { $_ =~ /^\Q$field\E/ } @field_names;
+
+ # Eliminate duplicates that are actually the same field
+ # (otherwise "assi" matches both "assignee" and "assigned_to", and
+ # the lines below fail when they shouldn't.)
+ my %match_unique = map { $field_map->{$_} => $_ } @matches;
+ @matches = values %match_unique;
+
+ if (scalar(@matches) == 1) {
+ return $field_map->{$matches[0]};
+ }
+ elsif (scalar(@matches) > 1) {
+ return \@matches;
+ }
+
+ # Check if we match exactly one custom field, ignoring the cf_ on the
+ # custom fields (to allow people to type things like "build" for
+ # "cf_build").
+ my %cfless;
+ foreach my $name (@field_names) {
+ my $no_cf = $name;
+ if ($no_cf =~ s/^cf_//) {
+ if ($field eq $no_cf) {
+ return $field_map->{$name};
+ }
+ $cfless{$no_cf} = $name;
+ }
+ }
+
+ # See if we match exactly one substring of any of the cf_-less fields.
+ my @cfless_matches = grep { $_ =~ /^\Q$field\E/ } (keys %cfless);
+
+ if (scalar(@cfless_matches) == 1) {
+ my $match = $cfless_matches[0];
+ my $actual_field = $cfless{$match};
+ return $field_map->{$actual_field};
+ }
+ elsif (scalar(@matches) > 1) {
+ return \@matches;
+ }
+
+ return undef;
}
sub _special_field_syntax {
- my ($word, $negate) = @_;
- return unless defined($word);
-
- # P1-5 Syntax
- if ($word =~ m/^P(\d+)(?:-(\d+))?$/i) {
- my ($p_start, $p_end) = ($1, $2);
- my $legal_priorities = get_legal_field_values('priority');
-
- # If Pn exists explicitly, use it.
- my $start = firstidx { $_ eq "P$p_start" } @$legal_priorities;
- my $end;
- $end = firstidx { $_ eq "P$p_end" } @$legal_priorities if defined $p_end;
-
- # If Pn doesn't exist explicitly, then we mean the nth priority.
- if ($start == -1) {
- $start = max(0, $p_start - 1);
- }
- my $prios = $legal_priorities->[$start];
-
- if (defined $end) {
- # If Pn doesn't exist explicitly, then we mean the nth priority.
- if ($end == -1) {
- $end = min(scalar(@$legal_priorities), $p_end) - 1;
- $end = max(0, $end); # Just in case the user typed P0.
- }
- ($start, $end) = ($end, $start) if $end < $start;
- $prios = join(',', @$legal_priorities[$start..$end])
- }
+ my ($word, $negate) = @_;
+ return unless defined($word);
- addChart('priority', 'anyexact', $prios, $negate);
- return 1;
- }
- return 0;
-}
+ # P1-5 Syntax
+ if ($word =~ m/^P(\d+)(?:-(\d+))?$/i) {
+ my ($p_start, $p_end) = ($1, $2);
+ my $legal_priorities = get_legal_field_values('priority');
-sub _default_quicksearch_word {
- my ($word, $negate) = @_;
- return unless defined($word);
+ # If Pn exists explicitly, use it.
+ my $start = firstidx { $_ eq "P$p_start" } @$legal_priorities;
+ my $end;
+ $end = firstidx { $_ eq "P$p_end" } @$legal_priorities if defined $p_end;
- if (!grep { lc($word) eq $_ } PRODUCT_EXCEPTIONS and length($word) > 2) {
- addChart('product', 'substring', $word, $negate);
+ # If Pn doesn't exist explicitly, then we mean the nth priority.
+ if ($start == -1) {
+ $start = max(0, $p_start - 1);
}
+ my $prios = $legal_priorities->[$start];
- if (!grep { lc($word) eq $_ } COMPONENT_EXCEPTIONS and length($word) > 2) {
- addChart('component', 'substring', $word, $negate);
- }
+ if (defined $end) {
- my @legal_keywords = map($_->name, Bugzilla::Keyword->get_all);
- if (grep { lc($word) eq lc($_) } @legal_keywords) {
- addChart('keywords', 'substring', $word, $negate);
+ # If Pn doesn't exist explicitly, then we mean the nth priority.
+ if ($end == -1) {
+ $end = min(scalar(@$legal_priorities), $p_end) - 1;
+ $end = max(0, $end); # Just in case the user typed P0.
+ }
+ ($start, $end) = ($end, $start) if $end < $start;
+ $prios = join(',', @$legal_priorities[$start .. $end]);
}
- addChart('alias', 'substring', $word, $negate);
- addChart('short_desc', 'substring', $word, $negate);
- addChart('status_whiteboard', 'substring', $word, $negate);
- addChart('longdesc', 'substring', $word, $negate) if $ELASTIC;
- addChart('content', 'matches', _matches_phrase($word), $negate) if $fulltext && !$ELASTIC;
+ addChart('priority', 'anyexact', $prios, $negate);
+ return 1;
+ }
+ return 0;
+}
- # BMO Bug 664124 - Include the crash signature (sig:) field in default quicksearches
- addChart('cf_crash_signature', 'substring', $word, $negate);
+sub _default_quicksearch_word {
+ my ($word, $negate) = @_;
+ return unless defined($word);
+
+ if (!grep { lc($word) eq $_ } PRODUCT_EXCEPTIONS and length($word) > 2) {
+ addChart('product', 'substring', $word, $negate);
+ }
+
+ if (!grep { lc($word) eq $_ } COMPONENT_EXCEPTIONS and length($word) > 2) {
+ addChart('component', 'substring', $word, $negate);
+ }
+
+ my @legal_keywords = map($_->name, Bugzilla::Keyword->get_all);
+ if (grep { lc($word) eq lc($_) } @legal_keywords) {
+ addChart('keywords', 'substring', $word, $negate);
+ }
+
+ addChart('alias', 'substring', $word, $negate);
+ addChart('short_desc', 'substring', $word, $negate);
+ addChart('status_whiteboard', 'substring', $word, $negate);
+ addChart('longdesc', 'substring', $word, $negate) if $ELASTIC;
+ addChart('content', 'matches', _matches_phrase($word), $negate)
+ if $fulltext && !$ELASTIC;
+
+# BMO Bug 664124 - Include the crash signature (sig:) field in default quicksearches
+ addChart('cf_crash_signature', 'substring', $word, $negate);
}
sub _handle_urls {
- my ($word, $negate) = @_;
- return unless defined($word);
-
- # URL field (for IP addrs, host.names,
- # scheme://urls)
- if ($word =~ m/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/
- || $word =~ /^[A-Za-z]+(\.[A-Za-z]+)+/
- || $word =~ /:[\\\/][\\\/]/
- || $word =~ /localhost/
- || $word =~ /mailto[:]?/)
- # || $word =~ /[A-Za-z]+[:][0-9]+/ #host:port
- {
- addChart('bug_file_loc', 'substring', $word, $negate);
- }
+ my ($word, $negate) = @_;
+ return unless defined($word);
+
+ # URL field (for IP addrs, host.names,
+ # scheme://urls)
+ if ( $word =~ m/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/
+ || $word =~ /^[A-Za-z]+(\.[A-Za-z]+)+/
+ || $word =~ /:[\\\/][\\\/]/
+ || $word =~ /localhost/
+ || $word =~ /mailto[:]?/)
+
+ # || $word =~ /[A-Za-z]+[:][0-9]+/ #host:port
+ {
+ addChart('bug_file_loc', 'substring', $word, $negate);
+ }
}
###########################################################################
@@ -622,77 +648,77 @@ sub _handle_urls {
# Quote and escape a phrase appropriately for a "content matches" search.
sub _matches_phrase {
- my ($phrase) = @_;
- return $phrase if $ELASTIC;
- $phrase =~ s/"/\\"/g;
- return "\"$phrase\"";
+ my ($phrase) = @_;
+ return $phrase if $ELASTIC;
+ $phrase =~ s/"/\\"/g;
+ return "\"$phrase\"";
}
# Expand found prefixes to states or resolutions
sub matchPrefixes {
- my ($hr_states, $hr_resolutions, $word, $ar_check_states) = @_;
- return unless $word =~ /^[A-Z_]+(,[A-Z_]+)*\+?$/;
-
- my @ar_prefixes = split(/,/, $word);
- if ($ar_prefixes[-1] =~ s/\+$//) {
- Bugzilla->cgi->param(limit => 0);
- }
- my $ar_check_resolutions = get_legal_field_values('resolution');
- my $foundMatch = 0;
-
- foreach my $prefix (@ar_prefixes) {
- foreach (@$ar_check_states) {
- if (/^$prefix/) {
- $$hr_states{$_} = 1;
- $foundMatch = 1;
- }
- }
- foreach (@$ar_check_resolutions) {
- if (/^$prefix/) {
- $$hr_resolutions{$_} = 1;
- $foundMatch = 1;
- }
- }
- }
- return $foundMatch;
+ my ($hr_states, $hr_resolutions, $word, $ar_check_states) = @_;
+ return unless $word =~ /^[A-Z_]+(,[A-Z_]+)*\+?$/;
+
+ my @ar_prefixes = split(/,/, $word);
+ if ($ar_prefixes[-1] =~ s/\+$//) {
+ Bugzilla->cgi->param(limit => 0);
+ }
+ my $ar_check_resolutions = get_legal_field_values('resolution');
+ my $foundMatch = 0;
+
+ foreach my $prefix (@ar_prefixes) {
+ foreach (@$ar_check_states) {
+ if (/^$prefix/) {
+ $$hr_states{$_} = 1;
+ $foundMatch = 1;
+ }
+ }
+ foreach (@$ar_check_resolutions) {
+ if (/^$prefix/) {
+ $$hr_resolutions{$_} = 1;
+ $foundMatch = 1;
+ }
+ }
+ }
+ return $foundMatch;
}
# Negate comparison type
sub negateComparisonType {
- my $comparisonType = shift;
-
- if ($comparisonType eq 'anywords') {
- return 'nowords';
- }
- elsif ($comparisonType eq 'isempty') {
- return 'isnotempty';
- }
- return "not$comparisonType";
+ my $comparisonType = shift;
+
+ if ($comparisonType eq 'anywords') {
+ return 'nowords';
+ }
+ elsif ($comparisonType eq 'isempty') {
+ return 'isnotempty';
+ }
+ return "not$comparisonType";
}
# Add a boolean chart
sub addChart {
- my ($field, $comparisonType, $value, $negate) = @_;
-
- $negate && ($comparisonType = negateComparisonType($comparisonType));
- makeChart("$chart-$and-$or", $field, $comparisonType, $value);
- if ($negate) {
- $and++;
- $or = 0;
- }
- else {
- $or++;
- }
+ my ($field, $comparisonType, $value, $negate) = @_;
+
+ $negate && ($comparisonType = negateComparisonType($comparisonType));
+ makeChart("$chart-$and-$or", $field, $comparisonType, $value);
+ if ($negate) {
+ $and++;
+ $or = 0;
+ }
+ else {
+ $or++;
+ }
}
# Create the CGI parameters for a boolean chart
sub makeChart {
- my ($expr, $field, $type, $value) = @_;
+ my ($expr, $field, $type, $value) = @_;
- my $cgi = Bugzilla->cgi;
- $cgi->param("field$expr", $field);
- $cgi->param("type$expr", $type);
- $cgi->param("value$expr", $value);
+ my $cgi = Bugzilla->cgi;
+ $cgi->param("field$expr", $field);
+ $cgi->param("type$expr", $type);
+ $cgi->param("value$expr", $value);
}
1;
diff --git a/Bugzilla/Search/Recent.pm b/Bugzilla/Search/Recent.pm
index a5d9e2417..5738dc93f 100644
--- a/Bugzilla/Search/Recent.pm
+++ b/Bugzilla/Search/Recent.pm
@@ -21,24 +21,25 @@ use Bugzilla::Util;
# Constants #
#############
-use constant DB_TABLE => 'profile_search';
+use constant DB_TABLE => 'profile_search';
use constant LIST_ORDER => 'id DESC';
+
# Do not track buglists viewed by users.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
- id
- user_id
- bug_list
- list_order
+ id
+ user_id
+ bug_list
+ list_order
);
use constant VALIDATORS => {
- user_id => \&_check_user_id,
- bug_list => \&_check_bug_list,
- list_order => \&_check_list_order,
+ user_id => \&_check_user_id,
+ bug_list => \&_check_bug_list,
+ list_order => \&_check_list_order,
};
use constant UPDATE_COLUMNS => qw(bug_list list_order);
@@ -51,29 +52,30 @@ use constant USE_MEMCACHED => 0;
###################
sub create {
- my $class = shift;
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- my $search = $class->SUPER::create(@_);
- my $user_id = $search->user_id;
-
- # Enforce there only being SAVE_NUM_SEARCHES per user.
- my @ids = @{ $dbh->selectcol_arrayref(
- "SELECT id FROM profile_search WHERE user_id = ? ORDER BY id",
- undef, $user_id) };
- if (scalar(@ids) > SAVE_NUM_SEARCHES) {
- splice(@ids, - SAVE_NUM_SEARCHES);
- $dbh->do(
- "DELETE FROM profile_search WHERE id IN (" . join(',', @ids) . ")");
- }
- $dbh->bz_commit_transaction();
- return $search;
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ my $search = $class->SUPER::create(@_);
+ my $user_id = $search->user_id;
+
+ # Enforce there only being SAVE_NUM_SEARCHES per user.
+ my @ids = @{
+ $dbh->selectcol_arrayref(
+ "SELECT id FROM profile_search WHERE user_id = ? ORDER BY id", undef,
+ $user_id
+ )
+ };
+ if (scalar(@ids) > SAVE_NUM_SEARCHES) {
+ splice(@ids, - SAVE_NUM_SEARCHES);
+ $dbh->do("DELETE FROM profile_search WHERE id IN (" . join(',', @ids) . ")");
+ }
+ $dbh->bz_commit_transaction();
+ return $search;
}
sub create_placeholder {
- my $class = shift;
- return $class->create({ user_id => Bugzilla->user->id,
- bug_list => '' });
+ my $class = shift;
+ return $class->create({user_id => Bugzilla->user->id, bug_list => ''});
}
###############
@@ -81,41 +83,43 @@ sub create_placeholder {
###############
sub check {
- my $class = shift;
- my $search = $class->SUPER::check(@_);
- my $user = Bugzilla->user;
- if ($search->user_id != $user->id) {
- ThrowUserError('object_does_not_exist', { id => $search->id });
- }
- return $search;
+ my $class = shift;
+ my $search = $class->SUPER::check(@_);
+ my $user = Bugzilla->user;
+ if ($search->user_id != $user->id) {
+ ThrowUserError('object_does_not_exist', {id => $search->id});
+ }
+ return $search;
}
sub check_quietly {
- my $class = shift;
- my $error_mode = Bugzilla->error_mode;
- Bugzilla->error_mode(ERROR_MODE_DIE);
- my $search = eval { $class->check(@_) };
- Bugzilla->error_mode($error_mode);
- return $search;
+ my $class = shift;
+ my $error_mode = Bugzilla->error_mode;
+ Bugzilla->error_mode(ERROR_MODE_DIE);
+ my $search = eval { $class->check(@_) };
+ Bugzilla->error_mode($error_mode);
+ return $search;
}
sub new_from_cookie {
- my ($invocant, $bug_ids) = @_;
- my $class = ref($invocant) || $invocant;
+ my ($invocant, $bug_ids) = @_;
+ my $class = ref($invocant) || $invocant;
- my $search = { id => 'cookie',
- user_id => Bugzilla->user->id,
- bug_list => join(',', @$bug_ids) };
+ my $search = {
+ id => 'cookie',
+ user_id => Bugzilla->user->id,
+ bug_list => join(',', @$bug_ids)
+ };
- bless $search, $class;
- return $search;
+ bless $search, $class;
+ return $search;
}
####################
# Simple Accessors #
####################
-sub bug_list { return [split(',', $_[0]->{'bug_list'})]; }
+sub bug_list { return [split(',', $_[0]->{'bug_list'})]; }
sub list_order { return $_[0]->{'list_order'}; }
sub user_id { return $_[0]->{'user_id'}; }
@@ -131,17 +135,17 @@ sub set_list_order { $_[0]->set('list_order', $_[1]); }
##############
sub _check_user_id {
- my ($invocant, $id) = @_;
- require Bugzilla::User;
- return Bugzilla::User->check({ id => $id })->id;
+ my ($invocant, $id) = @_;
+ require Bugzilla::User;
+ return Bugzilla::User->check({id => $id})->id;
}
sub _check_bug_list {
- my ($invocant, $list) = @_;
+ my ($invocant, $list) = @_;
- my @bug_ids = ref($list) ? @$list : split(',', $list || '');
- detaint_natural($_) foreach @bug_ids;
- return join(',', @bug_ids);
+ my @bug_ids = ref($list) ? @$list : split(',', $list || '');
+ detaint_natural($_) foreach @bug_ids;
+ return join(',', @bug_ids);
}
sub _check_list_order { defined $_[1] ? trim($_[1]) : '' }
diff --git a/Bugzilla/Search/Saved.pm b/Bugzilla/Search/Saved.pm
index 1511cd87b..c24d333a8 100644
--- a/Bugzilla/Search/Saved.pm
+++ b/Bugzilla/Search/Saved.pm
@@ -28,22 +28,23 @@ use Scalar::Util qw(blessed);
#############
use constant DB_TABLE => 'namedqueries';
+
# Do not track buglists saved by users.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
- id
- userid
- name
- query
+ id
+ userid
+ name
+ query
);
use constant VALIDATORS => {
- name => \&_check_name,
- query => \&_check_query,
- link_in_footer => \&_check_link_in_footer,
+ name => \&_check_name,
+ query => \&_check_query,
+ link_in_footer => \&_check_link_in_footer,
};
use constant UPDATE_COLUMNS => qw(name query);
@@ -53,56 +54,53 @@ use constant UPDATE_COLUMNS => qw(name query);
###############
sub new {
- my $class = shift;
- my $param = shift;
- my $dbh = Bugzilla->dbh;
-
- my $user;
- if (ref $param) {
- $user = $param->{user} || Bugzilla->user;
- my $name = $param->{name};
- if (!defined $name) {
- ThrowCodeError('bad_arg',
- {argument => 'name',
- function => "${class}::new"});
- }
- my $condition = 'userid = ? AND name = ?';
- my $user_id = blessed $user ? $user->id : $user;
- detaint_natural($user_id)
- || ThrowCodeError('param_must_be_numeric',
- {function => $class . '::_init', param => 'user'});
- my @values = ($user_id, $name);
- $param = { condition => $condition, values => \@values };
- }
-
- unshift @_, $param;
- my $self = $class->SUPER::new(@_);
- if ($self) {
- $self->{user} = $user if blessed $user;
-
- # Some DBs (read: Oracle) incorrectly mark the query string as UTF-8
- # when it's coming out of the database, even though it has no UTF-8
- # characters in it, which prevents Bugzilla::CGI from later reading
- # it correctly.
- utf8::downgrade($self->{query}) if utf8::is_utf8($self->{query});
+ my $class = shift;
+ my $param = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $user;
+ if (ref $param) {
+ $user = $param->{user} || Bugzilla->user;
+ my $name = $param->{name};
+ if (!defined $name) {
+ ThrowCodeError('bad_arg', {argument => 'name', function => "${class}::new"});
}
- return $self;
+ my $condition = 'userid = ? AND name = ?';
+ my $user_id = blessed $user ? $user->id : $user;
+ detaint_natural($user_id)
+ || ThrowCodeError('param_must_be_numeric',
+ {function => $class . '::_init', param => 'user'});
+ my @values = ($user_id, $name);
+ $param = {condition => $condition, values => \@values};
+ }
+
+ unshift @_, $param;
+ my $self = $class->SUPER::new(@_);
+ if ($self) {
+ $self->{user} = $user if blessed $user;
+
+ # Some DBs (read: Oracle) incorrectly mark the query string as UTF-8
+ # when it's coming out of the database, even though it has no UTF-8
+ # characters in it, which prevents Bugzilla::CGI from later reading
+ # it correctly.
+ utf8::downgrade($self->{query}) if utf8::is_utf8($self->{query});
+ }
+ return $self;
}
sub check {
- my $class = shift;
- my $search = $class->SUPER::check(@_);
- my $user = Bugzilla->user;
- return $search if $search->user->id == $user->id;
-
- if (!$search->shared_with_group
- or !$user->in_group($search->shared_with_group))
- {
- ThrowUserError('missing_query', { name => $search->name,
- sharer_id => $search->user->id });
- }
-
- return $search;
+ my $class = shift;
+ my $search = $class->SUPER::check(@_);
+ my $user = Bugzilla->user;
+ return $search if $search->user->id == $user->id;
+
+ if (!$search->shared_with_group or !$user->in_group($search->shared_with_group))
+ {
+ ThrowUserError('missing_query',
+ {name => $search->name, sharer_id => $search->user->id});
+ }
+
+ return $search;
}
##############
@@ -112,24 +110,25 @@ sub check {
sub _check_link_in_footer { return $_[1] ? 1 : 0; }
sub _check_name {
- my ($invocant, $name) = @_;
- $name = trim($name);
- $name || ThrowUserError("query_name_missing");
- $name !~ /[<>&]/ || ThrowUserError("illegal_query_name");
- if (length($name) > MAX_LEN_QUERY_NAME) {
- ThrowUserError("query_name_too_long");
- }
- return $name;
+ my ($invocant, $name) = @_;
+ $name = trim($name);
+ $name || ThrowUserError("query_name_missing");
+ $name !~ /[<>&]/ || ThrowUserError("illegal_query_name");
+ if (length($name) > MAX_LEN_QUERY_NAME) {
+ ThrowUserError("query_name_too_long");
+ }
+ return $name;
}
sub _check_query {
- my ($invocant, $query) = @_;
- $query || ThrowUserError("buglist_parameters_required");
- my $cgi = new Bugzilla::CGI($query);
- $cgi->clean_search_url;
- # Don't store the query name as a parameter.
- $cgi->delete('known_name');
- return $cgi->query_string;
+ my ($invocant, $query) = @_;
+ $query || ThrowUserError("buglist_parameters_required");
+ my $cgi = new Bugzilla::CGI($query);
+ $cgi->clean_search_url;
+
+ # Don't store the query name as a parameter.
+ $cgi->delete('known_name');
+ return $cgi->query_string;
}
#########################
@@ -137,170 +136,180 @@ sub _check_query {
#########################
sub create {
- my $class = shift;
- Bugzilla->login(LOGIN_REQUIRED);
- my $dbh = Bugzilla->dbh;
- $class->check_required_create_fields(@_);
- $dbh->bz_start_transaction();
- my $params = $class->run_create_validators(@_);
-
- # Right now you can only create a Saved Search for the current user.
- $params->{userid} = Bugzilla->user->id;
-
- my $lif = delete $params->{link_in_footer};
- my $obj = $class->insert_create_data($params);
- if ($lif) {
- $dbh->do('INSERT INTO namedqueries_link_in_footer
- (user_id, namedquery_id) VALUES (?,?)',
- undef, $params->{userid}, $obj->id);
- }
- $dbh->bz_commit_transaction();
-
- return $obj;
+ my $class = shift;
+ Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
+ $class->check_required_create_fields(@_);
+ $dbh->bz_start_transaction();
+ my $params = $class->run_create_validators(@_);
+
+ # Right now you can only create a Saved Search for the current user.
+ $params->{userid} = Bugzilla->user->id;
+
+ my $lif = delete $params->{link_in_footer};
+ my $obj = $class->insert_create_data($params);
+ if ($lif) {
+ $dbh->do(
+ 'INSERT INTO namedqueries_link_in_footer
+ (user_id, namedquery_id) VALUES (?,?)', undef, $params->{userid},
+ $obj->id
+ );
+ }
+ $dbh->bz_commit_transaction();
+
+ return $obj;
}
sub rename_field_value {
- my ($class, $field, $old_value, $new_value) = @_;
-
- my $old = url_quote($old_value);
- my $new = url_quote($new_value);
- my $old_sql = $old;
- $old_sql =~ s/([_\%])/\\$1/g;
-
- my $table = $class->DB_TABLE;
- my $id_field = $class->ID_FIELD;
-
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
-
- my %queries = @{ $dbh->selectcol_arrayref(
- "SELECT $id_field, query FROM $table WHERE query LIKE ?",
- {Columns=>[1,2]}, "\%$old_sql\%") };
- foreach my $id (keys %queries) {
- my $query = $queries{$id};
- $query =~ s/\b$field=\Q$old\E\b/$field=$new/gi;
- # Fix boolean charts.
- while ($query =~ /\bfield(\d+-\d+-\d+)=\Q$field\E\b/gi) {
- my $chart_id = $1;
- # Note that this won't handle lists or substrings inside of
- # boolean charts. Users will have to fix those themselves.
- $query =~ s/\bvalue\Q$chart_id\E=\Q$old\E\b/value$chart_id=$new/i;
- }
- $dbh->do("UPDATE $table SET query = ? WHERE $id_field = ?",
- undef, $query, $id);
- Bugzilla->memcached->clear({ table => $table, id => $id });
+ my ($class, $field, $old_value, $new_value) = @_;
+
+ my $old = url_quote($old_value);
+ my $new = url_quote($new_value);
+ my $old_sql = $old;
+ $old_sql =~ s/([_\%])/\\$1/g;
+
+ my $table = $class->DB_TABLE;
+ my $id_field = $class->ID_FIELD;
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+
+ my %queries = @{
+ $dbh->selectcol_arrayref(
+ "SELECT $id_field, query FROM $table WHERE query LIKE ?",
+ {Columns => [1, 2]},
+ "\%$old_sql\%"
+ )
+ };
+ foreach my $id (keys %queries) {
+ my $query = $queries{$id};
+ $query =~ s/\b$field=\Q$old\E\b/$field=$new/gi;
+
+ # Fix boolean charts.
+ while ($query =~ /\bfield(\d+-\d+-\d+)=\Q$field\E\b/gi) {
+ my $chart_id = $1;
+
+ # Note that this won't handle lists or substrings inside of
+ # boolean charts. Users will have to fix those themselves.
+ $query =~ s/\bvalue\Q$chart_id\E=\Q$old\E\b/value$chart_id=$new/i;
}
+ $dbh->do("UPDATE $table SET query = ? WHERE $id_field = ?", undef, $query, $id);
+ Bugzilla->memcached->clear({table => $table, id => $id});
+ }
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
}
sub preload {
- my ($searches) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($searches) = @_;
+ my $dbh = Bugzilla->dbh;
- return unless scalar @$searches;
+ return unless scalar @$searches;
- my @query_ids = map { $_->id } @$searches;
- my $queries_in_footer = $dbh->selectcol_arrayref(
- 'SELECT namedquery_id
+ my @query_ids = map { $_->id } @$searches;
+ my $queries_in_footer = $dbh->selectcol_arrayref(
+ 'SELECT namedquery_id
FROM namedqueries_link_in_footer
WHERE ' . $dbh->sql_in('namedquery_id', \@query_ids) . ' AND user_id = ?',
- undef, Bugzilla->user->id);
+ undef, Bugzilla->user->id
+ );
- my %links_in_footer = map { $_ => 1 } @$queries_in_footer;
- foreach my $query (@$searches) {
- $query->{link_in_footer} = ($links_in_footer{$query->id}) ? 1 : 0;
- }
+ my %links_in_footer = map { $_ => 1 } @$queries_in_footer;
+ foreach my $query (@$searches) {
+ $query->{link_in_footer} = ($links_in_footer{$query->id}) ? 1 : 0;
+ }
}
#####################
# Complex Accessors #
#####################
sub edit_link {
- my ($self) = @_;
- return $self->{edit_link} if defined $self->{edit_link};
- my $cgi = new Bugzilla::CGI($self->url);
- if (!$cgi->param('query_type')
- || !IsValidQueryType($cgi->param('query_type')))
- {
- $cgi->param('query_type', 'advanced');
- }
- $self->{edit_link} = $cgi->canonicalise_query;
- return $self->{edit_link};
+ my ($self) = @_;
+ return $self->{edit_link} if defined $self->{edit_link};
+ my $cgi = new Bugzilla::CGI($self->url);
+ if (!$cgi->param('query_type') || !IsValidQueryType($cgi->param('query_type')))
+ {
+ $cgi->param('query_type', 'advanced');
+ }
+ $self->{edit_link} = $cgi->canonicalise_query;
+ return $self->{edit_link};
}
sub used_in_whine {
- my ($self) = @_;
- return $self->{used_in_whine} if exists $self->{used_in_whine};
- ($self->{used_in_whine}) = Bugzilla->dbh->selectrow_array(
- 'SELECT 1 FROM whine_events INNER JOIN whine_queries
+ my ($self) = @_;
+ return $self->{used_in_whine} if exists $self->{used_in_whine};
+ ($self->{used_in_whine}) = Bugzilla->dbh->selectrow_array(
+ 'SELECT 1 FROM whine_events INNER JOIN whine_queries
ON whine_events.id = whine_queries.eventid
WHERE whine_events.owner_userid = ? AND query_name = ?', undef,
- $self->{userid}, $self->name) || 0;
- return $self->{used_in_whine};
+ $self->{userid}, $self->name
+ ) || 0;
+ return $self->{used_in_whine};
}
sub link_in_footer {
- my ($self, $user) = @_;
- # We only cache link_in_footer for the current Bugzilla->user.
- return $self->{link_in_footer} if exists $self->{link_in_footer} && !$user;
- my $user_id = $user ? $user->id : Bugzilla->user->id;
- my $link_in_footer = Bugzilla->dbh->selectrow_array(
- 'SELECT 1 FROM namedqueries_link_in_footer
- WHERE namedquery_id = ? AND user_id = ?',
- undef, $self->id, $user_id) || 0;
- $self->{link_in_footer} = $link_in_footer if !$user;
- return $link_in_footer;
+ my ($self, $user) = @_;
+
+ # We only cache link_in_footer for the current Bugzilla->user.
+ return $self->{link_in_footer} if exists $self->{link_in_footer} && !$user;
+ my $user_id = $user ? $user->id : Bugzilla->user->id;
+ my $link_in_footer = Bugzilla->dbh->selectrow_array(
+ 'SELECT 1 FROM namedqueries_link_in_footer
+ WHERE namedquery_id = ? AND user_id = ?', undef, $self->id, $user_id
+ ) || 0;
+ $self->{link_in_footer} = $link_in_footer if !$user;
+ return $link_in_footer;
}
sub shared_with_group {
- my ($self) = @_;
- return $self->{shared_with_group} if exists $self->{shared_with_group};
- # Bugzilla only currently supports sharing with one group, even
- # though the database backend allows for an infinite number.
- my ($group_id) = Bugzilla->dbh->selectrow_array(
- 'SELECT group_id FROM namedquery_group_map WHERE namedquery_id = ?',
- undef, $self->id);
- $self->{shared_with_group} = $group_id ? new Bugzilla::Group($group_id)
- : undef;
- return $self->{shared_with_group};
+ my ($self) = @_;
+ return $self->{shared_with_group} if exists $self->{shared_with_group};
+
+ # Bugzilla only currently supports sharing with one group, even
+ # though the database backend allows for an infinite number.
+ my ($group_id)
+ = Bugzilla->dbh->selectrow_array(
+ 'SELECT group_id FROM namedquery_group_map WHERE namedquery_id = ?',
+ undef, $self->id);
+ $self->{shared_with_group} = $group_id ? new Bugzilla::Group($group_id) : undef;
+ return $self->{shared_with_group};
}
sub shared_with_users {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!exists $self->{shared_with_users}) {
- $self->{shared_with_users} =
- $dbh->selectrow_array('SELECT COUNT(*)
+ if (!exists $self->{shared_with_users}) {
+ $self->{shared_with_users} = $dbh->selectrow_array(
+ 'SELECT COUNT(*)
FROM namedqueries_link_in_footer
INNER JOIN namedqueries
ON namedquery_id = id
WHERE namedquery_id = ?
- AND user_id != userid',
- undef, $self->id);
- }
- return $self->{shared_with_users};
+ AND user_id != userid', undef, $self->id
+ );
+ }
+ return $self->{shared_with_users};
}
####################
# Simple Accessors #
####################
-sub url { return $_[0]->{'query'}; }
+sub url { return $_[0]->{'query'}; }
sub user {
- my ($self) = @_;
- return $self->{user} ||=
- Bugzilla::User->new({ id => $self->{userid}, cache => 1 });
+ my ($self) = @_;
+ return $self->{user}
+ ||= Bugzilla::User->new({id => $self->{userid}, cache => 1});
}
############
# Mutators #
############
-sub set_name { $_[0]->set('name', $_[1]); }
-sub set_url { $_[0]->set('query', $_[1]); }
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_url { $_[0]->set('query', $_[1]); }
1;