summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Search.pm860
-rwxr-xr-xbuglist.cgi865
2 files changed, 881 insertions, 844 deletions
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
new file mode 100644
index 000000000..862602685
--- /dev/null
+++ b/Bugzilla/Search.pm
@@ -0,0 +1,860 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Gervase Markham <gerv@gerv.net>
+# Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Stephan Niemz <st.n@gmx.net>
+# Andreas Franke <afranke@mathweb.org>
+# Myk Melez <myk@mozilla.org>
+# Michael Schindler <michael@compressconsult.com>
+
+use diagnostics;
+use strict;
+
+require "globals.pl";
+require "CGI.pl";
+
+use vars qw($userid $usergroupset);
+
+package Bugzilla::Search;
+
+# Create a new Search
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+
+ my $self = { @_ };
+ bless($self, $class);
+
+ $self->init();
+
+ return $self;
+}
+
+sub init {
+ my $self = shift;
+ my $fieldsref = $self->{'fields'};
+ my $urlstr = $self->{'url'};
+
+ my $debug = 0;
+
+ my @fields;
+ my @supptables;
+ my @wherepart;
+ @fields = @$fieldsref if $fieldsref;
+ my %F;
+ my %M;
+ &::ParseUrlString($urlstr, \%F, \%M);
+ my @specialchart;
+ my @andlist;
+
+ # First, deal with all the old hard-coded non-chart-based poop.
+ unshift(@supptables,
+ ("profiles map_assigned_to",
+ "profiles map_reporter",
+ "LEFT JOIN profiles map_qa_contact ON bugs.qa_contact = map_qa_contact.userid"));
+ unshift(@wherepart,
+ ("bugs.assigned_to = map_assigned_to.userid",
+ "bugs.reporter = map_reporter.userid"));
+
+ my $minvotes;
+ if (defined $F{'votes'}) {
+ my $c = &::trim($F{'votes'});
+ if ($c ne "") {
+ if ($c !~ /^[0-9]*$/) {
+ my $htmlc = html_quote($c);
+ &::ThrowUserError("The <em>At least ___ votes</em> field must
+ be a simple number. You entered
+ <tt>$htmlc</tt>, which doesn't cut it.");
+ }
+ push(@specialchart, ["votes", "greaterthan", $c - 1]);
+ }
+ }
+
+ if ($M{'bug_id'}) {
+ my $type = "anyexact";
+ if ($F{'bugidtype'} && $F{'bugidtype'} eq 'exclude') {
+ $type = "nowords";
+ }
+ push(@specialchart, ["bug_id", $type, join(',', @{$M{'bug_id'}})]);
+ }
+
+ my @legal_fields = ("product", "version", "rep_platform", "op_sys",
+ "bug_status", "resolution", "priority", "bug_severity",
+ "assigned_to", "reporter", "component",
+ "target_milestone", "groupset");
+
+ foreach my $field (keys %F) {
+ if (&::lsearch(\@legal_fields, $field) != -1) {
+ push(@specialchart, [$field, "anyexact",
+ join(',', @{$M{$field}})]);
+ }
+ }
+
+ if ($F{'keywords'}) {
+ my $t = $F{'keywords_type'};
+ if (!$t || $t eq "or") {
+ $t = "anywords";
+ }
+ push(@specialchart, ["keywords", $t, $F{'keywords'}]);
+ }
+
+ foreach my $id ("1", "2") {
+ if (!defined ($F{"email$id"})) {
+ next;
+ }
+ my $email = &::trim($F{"email$id"});
+ if ($email eq "") {
+ next;
+ }
+ my $type = $F{"emailtype$id"};
+ if ($type eq "exact") {
+ $type = "anyexact";
+ foreach my $name (split(',', $email)) {
+ $name = &::trim($name);
+ if ($name) {
+ &::DBNameToIdAndCheck($name);
+ }
+ }
+ }
+
+ my @clist;
+ foreach my $field ("assigned_to", "reporter", "cc", "qa_contact") {
+ if ($F{"email$field$id"}) {
+ push(@clist, $field, $type, $email);
+ }
+ }
+ if ($F{"emaillongdesc$id"}) {
+ my $table = "longdescs_";
+ push(@supptables, "longdescs $table");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ my $ptable = "longdescnames_";
+ push(@supptables, "profiles $ptable");
+ push(@wherepart, "$table.who = $ptable.userid");
+ push(@clist, "$ptable.login_name", $type, $email);
+ }
+ if (@clist) {
+ push(@specialchart, \@clist);
+ } else {
+ my $htmlemail = html_quote($email);
+ &::ThrowUserError("You must specify one or more fields in which
+ to search for <tt>$htmlemail</tt>.");
+ }
+ }
+
+
+ if (defined $F{'changedin'}) {
+ my $c = &::trim($F{'changedin'});
+ if ($c ne "") {
+ if ($c !~ /^[0-9]*$/) {
+ my $htmlc = &::html_quote($c);
+ &::ThrowUserError("The <em>changed in last ___ days</em> field
+ must be a simple number. You entered
+ <tt>$htmlc</tt>, which doesn't cut it.");
+ }
+ push(@specialchart, ["changedin",
+ "lessthan", $c + 1]);
+ }
+ }
+
+ my $ref = $M{'chfield'};
+
+ if (defined $ref) {
+ my $which = &::lsearch($ref, "[Bug creation]");
+ if ($which >= 0) {
+ splice(@$ref, $which, 1);
+ push(@specialchart, ["creation_ts", "greaterthan",
+ SqlifyDate($F{'chfieldfrom'})]);
+ my $to = $F{'chfieldto'};
+ if (defined $to) {
+ $to = &::trim($to);
+ if ($to ne "" && $to !~ /^now$/i) {
+ push(@specialchart, ["creation_ts", "lessthan",
+ SqlifyDate($to)]);
+ }
+ }
+ }
+ }
+
+ if (defined $ref && 0 < @$ref) {
+ push(@supptables, "bugs_activity actcheck");
+
+ my @list;
+ foreach my $f (@$ref) {
+ push(@list, "\nactcheck.fieldid = " . &::GetFieldID($f));
+ }
+ push(@wherepart, "actcheck.bug_id = bugs.bug_id");
+ push(@wherepart, "(" . join(' OR ', @list) . ")");
+ push(@wherepart, "actcheck.bug_when >= " .
+ &::SqlQuote(SqlifyDate($F{'chfieldfrom'})));
+ my $to = $F{'chfieldto'};
+ if (defined $to) {
+ $to = &::trim($to);
+ if ($to ne "" && $to !~ /^now$/i) {
+ push(@wherepart, "actcheck.bug_when <= " .
+ &::SqlQuote(SqlifyDate($to)));
+ }
+ }
+ my $value = $F{'chfieldvalue'};
+ if (defined $value) {
+ $value = &::trim($value);
+ if ($value ne "") {
+ push(@wherepart, "actcheck.added = " .
+ &::SqlQuote($value))
+ }
+ }
+ }
+
+ foreach my $f ("short_desc", "long_desc", "bug_file_loc",
+ "status_whiteboard") {
+ if (defined $F{$f}) {
+ my $s = &::trim($F{$f});
+ if ($s ne "") {
+ my $n = $f;
+ my $q = &::SqlQuote($s);
+ my $type = $F{$f . "_type"};
+ push(@specialchart, [$f, $type, $s]);
+ }
+ }
+ }
+
+ my $chartid;
+ # $statusid is used by the code that queries for attachment statuses.
+ my $statusid = 0;
+ my $f;
+ my $ff;
+ my $t;
+ my $q;
+ my $v;
+ my $term;
+ my %funcsbykey;
+ my @funcdefs =
+ (
+ "^(assigned_to|reporter)," => sub {
+ push(@supptables, "profiles map_$f");
+ push(@wherepart, "bugs.$f = map_$f.userid");
+ $f = "map_$f.login_name";
+ },
+ "^qa_contact," => sub {
+ push(@supptables,
+ "LEFT JOIN profiles map_qa_contact ON bugs.qa_contact = map_qa_contact.userid");
+ $f = "map_$f.login_name";
+ },
+
+ "^cc," => sub {
+ push(@supptables, "LEFT JOIN cc cc_$chartid ON bugs.bug_id = cc_$chartid.bug_id");
+
+ push(@supptables, "LEFT JOIN profiles map_cc_$chartid ON cc_$chartid.who = map_cc_$chartid.userid");
+ $f = "map_cc_$chartid.login_name";
+ },
+
+ "^long_?desc,changedby" => sub {
+ my $table = "longdescs_$chartid";
+ push(@supptables, "longdescs $table");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ my $id = &::DBNameToIdAndCheck($v);
+ $term = "$table.who = $id";
+ },
+ "^long_?desc,changedbefore" => sub {
+ my $table = "longdescs_$chartid";
+ push(@supptables, "longdescs $table");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ $term = "$table.bug_when < " . &::SqlQuote(SqlifyDate($v));
+ },
+ "^long_?desc,changedafter" => sub {
+ my $table = "longdescs_$chartid";
+ push(@supptables, "longdescs $table");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ $term = "$table.bug_when > " . &::SqlQuote(SqlifyDate($v));
+ },
+ "^long_?desc," => sub {
+ my $table = "longdescs_$chartid";
+ push(@supptables, "longdescs $table");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ $f = "$table.thetext";
+ },
+ "^attachments\..*," => sub {
+ my $table = "attachments_$chartid";
+ push(@supptables, "attachments $table");
+ push(@wherepart, "bugs.bug_id = $table.bug_id");
+ $f =~ m/^attachments\.(.*)$/;
+ my $field = $1;
+ if ($t eq "changedby") {
+ $v = &::DBNameToIdAndCheck($v);
+ $q = &::SqlQuote($v);
+ $field = "submitter_id";
+ $t = "equals";
+ } elsif ($t eq "changedbefore") {
+ $v = SqlifyDate($v);
+ $q = &::SqlQuote($v);
+ $field = "creation_ts";
+ $t = "lessthan";
+ } elsif ($t eq "changedafter") {
+ $v = SqlifyDate($v);
+ $q = &::SqlQuote($v);
+ $field = "creation_ts";
+ $t = "greaterthan";
+ }
+ if ($field eq "ispatch" && $v ne "0" && $v ne "1") {
+ &::ThrowUserError("The only legal values for the
+ <em>Attachment is patch</em> field are
+ 0 and 1.");
+ }
+ if ($field eq "isobsolete" && $v ne "0" && $v ne "1") {
+ &::ThrowUserError("The only legal values for the
+ <em>Attachment is obsolete</em> field are
+ 0 and 1.");
+ }
+ $f = "$table.$field";
+ },
+ "^attachstatusdefs.name," => sub {
+ # The below has Fun with the names for attachment statuses. This
+ # isn't needed for changed* queries, so exclude those - the
+ # generic stuff will cope
+ return if ($t =~ m/^changed/);
+
+ # Searching for "status != 'bar'" wants us to look for an
+ # attachment without the 'bar' status, not for an attachment with
+ # a status not equal to 'bar' (Which would pick up an attachment
+ # with more than one status). We do this by LEFT JOINS, after
+ # grabbing the matching attachment status ids.
+ # Note that this still won't find bugs with no attachments, since
+ # that isn't really what people would expect.
+
+ # First, get the attachment status ids, using the other funcs
+ # to match the WHERE term.
+ # Note that we need to reverse the negated bits for this to work
+ # This somewhat abuses the definitions of the various terms -
+ # eg, does 'contains all' mean that the status has to contain all
+ # those words, or that all those words must be exact matches to
+ # statuses, which must all be on a single attachment, or should
+ # the match on the status descriptions be a contains match, too?
+
+ my $inverted = 0;
+ if ($t =~ m/not(.*)/) {
+ $t = $1;
+ $inverted = 1;
+ }
+
+ $ref = $funcsbykey{",$t"};
+ &$ref;
+ &::SendSQL("SELECT id FROM attachstatusdefs WHERE $term");
+
+ my @as_ids;
+ while (&::MoreSQLData()) {
+ push @as_ids, &::FetchOneColumn();
+ }
+
+ # When searching for multiple statuses within a single boolean chart,
+ # we want to match each status record separately. In other words,
+ # "status = 'foo' AND status = 'bar'" should match attachments with
+ # one status record equal to "foo" and another one equal to "bar",
+ # not attachments where the same status record equals both "foo" and
+ # "bar" (which is nonsensical). In order to do this we must add an
+ # additional counter to the end of the "attachstatuses" table
+ # reference.
+ ++$statusid;
+
+ my $attachtable = "attachments_$chartid";
+ my $statustable = "attachstatuses_${chartid}_$statusid";
+
+ push(@supptables, "attachments $attachtable");
+ my $join = "LEFT JOIN attachstatuses $statustable ON ".
+ "($attachtable.attach_id = $statustable.attach_id AND " .
+ "$statustable.statusid IN (" . join(",", @as_ids) . "))";
+ push(@supptables, $join);
+ push(@wherepart, "bugs.bug_id = $attachtable.bug_id");
+ if ($inverted) {
+ $term = "$statustable.statusid IS NULL";
+ } else {
+ $term = "$statustable.statusid IS NOT NULL";
+ }
+ },
+ "^changedin," => sub {
+ $f = "(to_days(now()) - to_days(bugs.delta_ts))";
+ },
+
+ "^keywords," => sub {
+ &::GetVersionTable();
+ my @list;
+ my $table = "keywords_$chartid";
+ foreach my $value (split(/[\s,]+/, $v)) {
+ if ($value eq '') {
+ next;
+ }
+ my $id = &::GetKeywordIdFromName($value);
+ if ($id) {
+ push(@list, "$table.keywordid = $id");
+ }
+ else {
+ my $htmlv = &::html_quote($v);
+ &::ThrowUserError("There is no keyword named<code>$htmlv
+ </code>. To search for keywords, consult
+ the <a href='describekeywords.cgi'>list
+ of legal keywords</a>.");
+ }
+ }
+ my $haveawordterm;
+ if (@list) {
+ $haveawordterm = "(" . join(' OR ', @list) . ")";
+ if ($t eq "anywords") {
+ $term = $haveawordterm;
+ } elsif ($t eq "allwords") {
+ $ref = $funcsbykey{",$t"};
+ &$ref;
+ if ($term && $haveawordterm) {
+ $term = "(($term) AND $haveawordterm)";
+ }
+ }
+ }
+ if ($term) {
+ push(@supptables, "keywords $table");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ }
+ },
+
+ "^dependson," => sub {
+ my $table = "dependson_" . $chartid;
+ push(@supptables, "dependencies $table");
+ $ff = "$table.$f";
+ $ref = $funcsbykey{",$t"};
+ &$ref;
+ push(@wherepart, "$table.blocked = bugs.bug_id");
+ },
+
+ "^blocked," => sub {
+ my $table = "blocked_" . $chartid;
+ push(@supptables, "dependencies $table");
+ $ff = "$table.$f";
+ $ref = $funcsbykey{",$t"};
+ &$ref;
+ push(@wherepart, "$table.dependson = bugs.bug_id");
+ },
+
+
+ ",equals" => sub {
+ $term = "$ff = $q";
+ },
+ ",notequals" => sub {
+ $term = "$ff != $q";
+ },
+ ",casesubstring" => sub {
+ $term = "INSTR($ff, $q)";
+ },
+ ",(substring|substr)" => sub {
+ $term = "INSTR(LOWER($ff), " . lc($q) . ")";
+ },
+ ",notsubstring" => sub {
+ $term = "INSTR(LOWER($ff), " . lc($q) . ") = 0";
+ },
+ ",regexp" => sub {
+ $term = "LOWER($ff) REGEXP $q";
+ },
+ ",notregexp" => sub {
+ $term = "LOWER($ff) NOT REGEXP $q";
+ },
+ ",lessthan" => sub {
+ $term = "$ff < $q";
+ },
+ ",greaterthan" => sub {
+ $term = "$ff > $q";
+ },
+ ",anyexact" => sub {
+ my @list;
+ foreach my $w (split(/,/, $v)) {
+ if ($w eq "---" && $f !~ /milestone/) {
+ $w = "";
+ }
+ push(@list, "$ff = " . &::SqlQuote($w));
+ }
+ $term = join(" OR ", @list);
+ },
+ ",anywordssubstr" => sub {
+ $term = join(" OR ", @{GetByWordListSubstr($ff, $v)});
+ },
+ ",allwordssubstr" => sub {
+ $term = join(" AND ", @{GetByWordListSubstr($ff, $v)});
+ },
+ ",nowordssubstr" => sub {
+ my @list = @{GetByWordListSubstr($ff, $v)};
+ if (@list) {
+ $term = "NOT (" . join(" OR ", @list) . ")";
+ }
+ },
+ ",anywords" => sub {
+ $term = join(" OR ", @{GetByWordList($ff, $v)});
+ },
+ ",allwords" => sub {
+ $term = join(" AND ", @{GetByWordList($ff, $v)});
+ },
+ ",nowords" => sub {
+ my @list = @{GetByWordList($ff, $v)};
+ if (@list) {
+ $term = "NOT (" . join(" OR ", @list) . ")";
+ }
+ },
+ ",changedbefore" => sub {
+ my $table = "act_$chartid";
+ my $ftable = "fielddefs_$chartid";
+ push(@supptables, "bugs_activity $table");
+ push(@supptables, "fielddefs $ftable");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ push(@wherepart, "$table.fieldid = $ftable.fieldid");
+ $term = "($ftable.name = '$f' AND $table.bug_when < $q)";
+ },
+ ",changedafter" => sub {
+ my $table = "act_$chartid";
+ my $ftable = "fielddefs_$chartid";
+ push(@supptables, "bugs_activity $table");
+ push(@supptables, "fielddefs $ftable");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ push(@wherepart, "$table.fieldid = $ftable.fieldid");
+ $term = "($ftable.name = '$f' AND $table.bug_when > $q)";
+ },
+ ",changedfrom" => sub {
+ my $table = "act_$chartid";
+ my $ftable = "fielddefs_$chartid";
+ push(@supptables, "bugs_activity $table");
+ push(@supptables, "fielddefs $ftable");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ push(@wherepart, "$table.fieldid = $ftable.fieldid");
+ $term = "($ftable.name = '$f' AND $table.removed = $q)";
+ },
+ ",changedto" => sub {
+ my $table = "act_$chartid";
+ my $ftable = "fielddefs_$chartid";
+ push(@supptables, "bugs_activity $table");
+ push(@supptables, "fielddefs $ftable");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ push(@wherepart, "$table.fieldid = $ftable.fieldid");
+ $term = "($ftable.name = '$f' AND $table.added = $q)";
+ },
+ ",changedby" => sub {
+ my $table = "act_$chartid";
+ my $ftable = "fielddefs_$chartid";
+ push(@supptables, "bugs_activity $table");
+ push(@supptables, "fielddefs $ftable");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ push(@wherepart, "$table.fieldid = $ftable.fieldid");
+ my $id = &::DBNameToIdAndCheck($v);
+ $term = "($ftable.name = '$f' AND $table.who = $id)";
+ },
+ );
+ my @funcnames;
+ while (@funcdefs) {
+ my $key = shift(@funcdefs);
+ my $value = shift(@funcdefs);
+ if ($key =~ /^[^,]*$/) {
+ die "All defs in %funcs must have a comma in their name: $key";
+ }
+ if (exists $funcsbykey{$key}) {
+ die "Duplicate key in %funcs: $key";
+ }
+ $funcsbykey{$key} = $value;
+ push(@funcnames, $key);
+ }
+
+ # first we delete any sign of "Chart #-1" from the HTML form hash
+ # since we want to guarantee the user didn't hide something here
+ my @badcharts = grep /^(field|type|value)-1-/, (keys %F);
+ foreach my $field (@badcharts) {
+ delete $F{$field};
+ }
+
+ # now we take our special chart and stuff it into the form hash
+ my $chart = -1;
+ my $row = 0;
+ foreach my $ref (@specialchart) {
+ my $col = 0;
+ while (@$ref) {
+ $F{"field$chart-$row-$col"} = shift(@$ref);
+ $F{"type$chart-$row-$col"} = shift(@$ref);
+ $F{"value$chart-$row-$col"} = shift(@$ref);
+ if ($debug) {
+ print qq{<p>$F{"field$chart-$row-$col"} | $F{"type$chart-$row-$col"} | $F{"value$chart-$row-$col"}*</p>\n};
+ }
+ $col++;
+
+ }
+ $row++;
+ }
+
+
+# A boolean chart is a way of representing the terms in a logical
+# expression. Bugzilla builds SQL queries depending on how you enter
+# terms into the boolean chart. Boolean charts are represented in
+# urls as tree-tuples of (chart id, row, column). The query form
+# (query.cgi) may contain an arbitrary number of boolean charts where
+# each chart represents a clause in a SQL query.
+#
+# The query form starts out with one boolean chart containing one
+# row and one column. Extra rows can be created by pressing the
+# AND button at the bottom of the chart. Extra columns are created
+# by pressing the OR button at the right end of the chart. Extra
+# charts are created by pressing "Add another boolean chart".
+#
+# Each chart consists of an artibrary number of rows and columns.
+# The terms within a row are ORed together. The expressions represented
+# by each row are ANDed together. The expressions represented by each
+# chart are ANDed together.
+#
+# ----------------------
+# | col2 | col2 | col3 |
+# --------------|------|------|
+# | row1 | a1 | a2 | |
+# |------|------|------|------| => ((a1 OR a2) AND (b1 OR b2 OR b3) AND (c1))
+# | row2 | b1 | b2 | b3 |
+# |------|------|------|------|
+# | row3 | c1 | | |
+# -----------------------------
+#
+# --------
+# | col2 |
+# --------------|
+# | row1 | d1 | => (d1)
+# ---------------
+#
+# Together, these two charts represent a SQL expression like this
+# SELECT blah FROM blah WHERE ( (a1 OR a2)AND(b1 OR b2 OR b3)AND(c1)) AND (d1)
+#
+# The terms within a single row of a boolean chart are all constraints
+# on a single piece of data. If you're looking for a bug that has two
+# different people cc'd on it, then you need to use two boolean charts.
+# This will find bugs with one CC mathing 'foo@blah.org' and and another
+# CC matching 'bar@blah.org'.
+#
+# --------------------------------------------------------------
+# CC | equal to
+# foo@blah.org
+# --------------------------------------------------------------
+# CC | equal to
+# bar@blah.org
+#
+# If you try to do this query by pressing the AND button in the
+# original boolean chart then what you'll get is an expression that
+# looks for a single CC where the login name is both "foo@blah.org",
+# and "bar@blah.org". This is impossible.
+#
+# --------------------------------------------------------------
+# CC | equal to
+# foo@blah.org
+# AND
+# CC | equal to
+# bar@blah.org
+# --------------------------------------------------------------
+
+# $chartid is the number of the current chart whose SQL we're contructing
+# $row is the current row of the current chart
+
+# names for table aliases are constructed using $chartid and $row
+# SELECT blah FROM $table "$table_$chartid_$row" WHERE ....
+
+# $f = field of table in bug db (e.g. bug_id, reporter, etc)
+# $ff = qualified field name (field name prefixed by table)
+# e.g. bugs_activity.bug_id
+# $t = type of query. e.g. "equal to", "changed after", case sensitive substr"
+# $v = value - value the user typed in to the form
+# $q = sanitized version of user input (SqlQuote($v))
+# @supptables = Tables and/or table aliases used in query
+# %suppseen = A hash used to store all the tables in supptables to weed
+# out duplicates.
+# $suppstring = String which is pasted into query containing all table names
+
+ # get a list of field names to verify the user-submitted chart fields against
+ my %chartfields;
+ &::SendSQL("SELECT name FROM fielddefs");
+ while (&::MoreSQLData()) {
+ my ($name) = &::FetchSQLData();
+ $chartfields{$name} = 1;
+ }
+
+ $row = 0;
+ for ($chart=-1 ;
+ $chart < 0 || exists $F{"field$chart-0-0"} ;
+ $chart++) {
+ $chartid = $chart >= 0 ? $chart : "";
+ for ($row = 0 ;
+ exists $F{"field$chart-$row-0"} ;
+ $row++) {
+ my @orlist;
+ for (my $col = 0 ;
+ exists $F{"field$chart-$row-$col"} ;
+ $col++) {
+ $f = $F{"field$chart-$row-$col"} || "noop";
+ $t = $F{"type$chart-$row-$col"} || "noop";
+ $v = $F{"value$chart-$row-$col"};
+ $v = "" if !defined $v;
+ $v = &::trim($v);
+ if ($f eq "noop" || $t eq "noop" || $v eq "") {
+ next;
+ }
+ # chart -1 is generated by other code above, not from the user-
+ # submitted form, so we'll blindly accept any values in chart -1
+ if ((!$chartfields{$f}) && ($chart != -1)) {
+ my $errstr = "Can't use " . &::html_quote($f) . " as a field name. " .
+ "If you think you're getting this in error, please copy the " .
+ "entire URL out of the address bar at the top of your browser " .
+ "window and email it to <109679\@bugzilla.org>";
+ die "Internal error: $errstr" if $chart < 0;
+ return Error($errstr);
+ }
+
+ # This is either from the internal chart (in which case we
+ # already know about it), or it was in %chartfields, so it is
+ # a valid field name, which means that its ok.
+ &::trick_taint($f);
+ $q = &::SqlQuote($v);
+ my $func;
+ $term = undef;
+ foreach my $key (@funcnames) {
+ if ("$f,$t" =~ m/$key/) {
+ my $ref = $funcsbykey{$key};
+ if ($debug) {
+ print "<p>$key ($f , $t ) => ";
+ }
+ $ff = $f;
+ if ($f !~ /\./) {
+ $ff = "bugs.$f";
+ }
+ &$ref;
+ if ($debug) {
+ print "$f , $t , $term</p>";
+ }
+ if ($term) {
+ last;
+ }
+ }
+ }
+ if ($term) {
+ push(@orlist, $term);
+ }
+ else {
+ my $errstr =
+ qq|Cannot seem to handle <code>$F{"field$chart-$row-$col"}</code>
+ and <code>$F{"type$chart-$row-$col"}</code> together|;
+ $chart < 0 ? die "Internal error: $errstr"
+ : &::ThrowCodeError($errstr);
+ }
+ }
+ if (@orlist) {
+ push(@andlist, "(" . join(" OR ", @orlist) . ")");
+ }
+ }
+ }
+ my %suppseen = ("bugs" => 1);
+ my $suppstring = "bugs";
+ foreach my $str (@supptables) {
+ if (!$suppseen{$str}) {
+ if ($str !~ /^(LEFT|INNER) JOIN/i) {
+ $suppstring .= ",";
+ }
+ $suppstring .= " $str";
+ $suppseen{$str} = 1;
+ }
+ }
+ my $query = ("SELECT DISTINCT " . join(', ', @fields) .
+ " FROM $suppstring" .
+ " WHERE " . join(' AND ', (@wherepart, @andlist)));
+
+ $query = &::SelectVisible($query, $::userid, $::usergroupset);
+
+ if ($debug) {
+ print "<p><code>" . &::value_quote($query) . "</code></p>\n";
+ exit;
+ }
+
+ $self->{'sql'} = $query;
+}
+
+###############################################################################
+# Helper functions for the init() method.
+###############################################################################
+sub SqlifyDate {
+ my ($str) = @_;
+ $str = "" if !defined $str;
+ if ($str =~ /^-?(\d+)([dDwWmMyY])$/) { # relative date
+ my ($amount, $unit, $date) = ($1, lc $2, time);
+ my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime($date);
+ if ($unit eq 'w') { # convert weeks to days
+ $amount = 7*$amount + $wday;
+ $unit = 'd';
+ }
+ if ($unit eq 'd') {
+ $date -= $sec + 60*$min + 3600*$hour + 24*3600*$amount;
+ return time2str("%Y-%m-%d %H:%M:%S", $date);
+ }
+ elsif ($unit eq 'y') {
+ return sprintf("%4d-01-01 00:00:00", $year+1900-$amount);
+ }
+ elsif ($unit eq 'm') {
+ $month -= $amount;
+ while ($month<0) { $year--; $month += 12; }
+ return sprintf("%4d-%02d-01 00:00:00", $year+1900, $month+1);
+ }
+ return undef; # should not happen due to regexp at top
+ }
+ my $date = str2time($str);
+ if (!defined($date)) {
+ my $htmlstr = html_quote($str);
+ ThrowUserError("The string <tt>$htmlstr</tt> is not a legal date.");
+ exit;
+ }
+ return time2str("%Y-%m-%d %H:%M:%S", $date);
+}
+
+sub GetByWordList {
+ my ($field, $strs) = (@_);
+ my @list;
+
+ foreach my $w (split(/[\s,]+/, $strs)) {
+ my $word = $w;
+ if ($word ne "") {
+ $word =~ tr/A-Z/a-z/;
+ $word = SqlQuote(quotemeta($word));
+ $word =~ s/^'//;
+ $word =~ s/'$//;
+ $word = '(^|[^a-z0-9])' . $word . '($|[^a-z0-9])';
+ push(@list, "lower($field) regexp '$word'");
+ }
+ }
+
+ return \@list;
+}
+
+# Support for "any/all/nowordssubstr" comparison type ("words as substrings")
+sub GetByWordListSubstr {
+ my ($field, $strs) = (@_);
+ my @list;
+
+ foreach my $word (split(/[\s,]+/, $strs)) {
+ if ($word ne "") {
+ push(@list, "INSTR(LOWER($field), " . lc(SqlQuote($word)) . ")");
+ }
+ }
+
+ return \@list;
+}
+
+sub getSQL {
+ my $self = shift;
+ return $self->{'sql'};
+}
+
+1;
diff --git a/buglist.cgi b/buglist.cgi
index ce824ea57..589719962 100755
--- a/buglist.cgi
+++ b/buglist.cgi
@@ -23,7 +23,6 @@
# Stephan Niemz <st.n@gmx.net>
# Andreas Franke <afranke@mathweb.org>
# Myk Melez <myk@mozilla.org>
-# Michael Schindler <michael@compressconsult.com>
################################################################################
# Script Initialization
@@ -35,31 +34,28 @@ use strict;
use lib qw(.);
-use vars qw( $template $vars );
+use vars qw($template $vars);
+
+use Bugzilla::Search;
# Include the Bugzilla CGI and general utility library.
require "CGI.pl";
-# Shut up misguided -w warnings about "used only once". "use vars" just
-# doesn't work for me.
-sub sillyness {
- my $zz;
- $zz = $::db_name;
- $zz = @::components;
- $zz = @::default_column_list;
- $zz = $::defaultqueryname;
- $zz = @::dontchange;
- $zz = @::legal_keywords;
- $zz = @::legal_platform;
- $zz = @::legal_priority;
- $zz = @::legal_product;
- $zz = @::legal_severity;
- $zz = @::settable_resolution;
- $zz = @::target_milestone;
- $zz = $::unconfirmedstate;
- $zz = $::userid;
- $zz = @::versions;
-};
+use vars qw($db_name
+ @components
+ @default_column_list
+ $defaultqueryname
+ @dontchange
+ @legal_keywords
+ @legal_platform
+ @legal_priority
+ @legal_product
+ @legal_severity
+ @settable_resolution
+ @target_milestone
+ $unconfirmedstate
+ $userid
+ @versions);
if (length($::buffer) == 0) {
$vars->{'title'} = "Parameters Required";
@@ -170,39 +166,6 @@ if ($::FORM{'cmdtype'} eq 'runnamed') {
# Utilities
################################################################################
-sub SqlifyDate {
- my ($str) = @_;
- $str = "" if !defined $str;
- if ($str =~ /^-?(\d+)([dDwWmMyY])$/) { # relative date
- my ($amount, $unit, $date) = ($1, lc $2, time);
- my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime($date);
- if ($unit eq 'w') { # convert weeks to days
- $amount = 7*$amount + $wday;
- $unit = 'd';
- }
- if ($unit eq 'd') {
- $date -= $sec + 60*$min + 3600*$hour + 24*3600*$amount;
- return time2str("%Y-%m-%d %H:%M:%S", $date);
- }
- elsif ($unit eq 'y') {
- return sprintf("%4d-01-01 00:00:00", $year+1900-$amount);
- }
- elsif ($unit eq 'm') {
- $month -= $amount;
- while ($month<0) { $year--; $month += 12; }
- return sprintf("%4d-%02d-01 00:00:00", $year+1900, $month+1);
- }
- return undef; # should not happen due to regexp at top
- }
- my $date = str2time($str);
- if (!defined($date)) {
- my $htmlstr = html_quote($str);
- DisplayError("The string <tt>$htmlstr</tt> is not a legal date.");
- exit;
- }
- return time2str("%Y-%m-%d %H:%M:%S", $date);
-}
-
my @weekday= qw( Sun Mon Tue Wed Thu Fri Sat );
sub DiffDate {
my ($datestr) = @_;
@@ -219,41 +182,6 @@ sub DiffDate {
return $date;
}
-sub GetByWordList {
- my ($field, $strs) = (@_);
- my @list;
-
- foreach my $w (split(/[\s,]+/, $strs)) {
- my $word = $w;
- if ($word ne "") {
- $word =~ tr/A-Z/a-z/;
- $word = SqlQuote(quotemeta($word));
- $word =~ s/^'//;
- $word =~ s/'$//;
- $word = '(^|[^a-z0-9])' . $word . '($|[^a-z0-9])';
- push(@list, "lower($field) regexp '$word'");
- }
- }
-
- return \@list;
-}
-
-#
-# support for "any/all/nowordssubstr" comparison type ("words as substrings")
-#
-sub GetByWordListSubstr {
- my ($field, $strs) = (@_);
- my @list;
-
- foreach my $word (split(/[\s,]+/, $strs)) {
- if ($word ne "") {
- push(@list, "INSTR(LOWER($field), " . lc(SqlQuote($word)) . ")");
- }
- }
-
- return \@list;
-}
-
sub LookupNamedQuery {
my ($name) = @_;
confirm_login();
@@ -307,759 +235,6 @@ sub GetGroupsByGroupSet {
}
-
-################################################################################
-# Query Generation
-################################################################################
-
-sub GenerateSQL {
- my $debug = 0;
- my ($fieldsref, $urlstr) = (@_);
- my @fields;
- my @supptables;
- my @wherepart;
- @fields = @$fieldsref if $fieldsref;
- my %F;
- my %M;
- ParseUrlString($urlstr, \%F, \%M);
- my @specialchart;
- my @andlist;
-
- # First, deal with all the old hard-coded non-chart-based poop.
-
- unshift(@supptables,
- ("profiles map_assigned_to",
- "profiles map_reporter",
- "LEFT JOIN profiles map_qa_contact ON bugs.qa_contact = map_qa_contact.userid"));
- unshift(@wherepart,
- ("bugs.assigned_to = map_assigned_to.userid",
- "bugs.reporter = map_reporter.userid"));
-
- my $minvotes;
- if (defined $F{'votes'}) {
- my $c = trim($F{'votes'});
- if ($c ne "") {
- if ($c !~ /^[0-9]*$/) {
- my $htmlc = html_quote($c);
- DisplayError("The <em>At least ___ votes</em> field must be
- a simple number. You entered <kbd>$htmlc</kbd>,
- which doesn't cut it.");
- exit;
- }
- push(@specialchart, ["votes", "greaterthan", $c - 1]);
- }
- }
-
- if ($M{'bug_id'}) {
- my $type = "anyexact";
- if ($F{'bugidtype'} && $F{'bugidtype'} eq 'exclude') {
- $type = "nowords";
- }
- push(@specialchart, ["bug_id", $type, join(',', @{$M{'bug_id'}})]);
- }
-
-# This is evil. We should never allow a user to directly append SQL to
-# any query without a huge amount of validation. Even then, it would
-# be a bad idea. Beware that uncommenting this will allow someone to
-# peak at virtually anything they want in the bugs database.
-# if (defined $F{'sql'}) {
-# die "Invalid sql: $F{'sql'}" if $F{'sql'} =~ /;/;
-# push(@wherepart, "( $F{'sql'} )");
-# }
-
- my @legal_fields = ("product", "version", "rep_platform", "op_sys",
- "bug_status", "resolution", "priority", "bug_severity",
- "assigned_to", "reporter", "component",
- "target_milestone", "groupset");
-
- foreach my $field (keys %F) {
- if (lsearch(\@legal_fields, $field) != -1) {
- push(@specialchart, [$field, "anyexact",
- join(',', @{$M{$field}})]);
- }
- }
-
- if ($F{'keywords'}) {
- my $t = $F{'keywords_type'};
- if (!$t || $t eq "or") {
- $t = "anywords";
- }
- push(@specialchart, ["keywords", $t, $F{'keywords'}]);
- }
-
- foreach my $id ("1", "2") {
- if (!defined ($F{"email$id"})) {
- next;
- }
- my $email = trim($F{"email$id"});
- if ($email eq "") {
- next;
- }
- my $type = $F{"emailtype$id"};
- if ($type eq "exact") {
- $type = "anyexact";
- foreach my $name (split(',', $email)) {
- $name = trim($name);
- if ($name) {
- DBNameToIdAndCheck($name);
- }
- }
- }
-
- my @clist;
- foreach my $field ("assigned_to", "reporter", "cc", "qa_contact") {
- if ($F{"email$field$id"}) {
- push(@clist, $field, $type, $email);
- }
- }
- if ($F{"emaillongdesc$id"}) {
- my $table = "longdescs_";
- push(@supptables, "longdescs $table");
- push(@wherepart, "$table.bug_id = bugs.bug_id");
- my $ptable = "longdescnames_";
- push(@supptables, "profiles $ptable");
- push(@wherepart, "$table.who = $ptable.userid");
- push(@clist, "$ptable.login_name", $type, $email);
- }
- if (@clist) {
- push(@specialchart, \@clist);
- } else {
- my $htmlemail = html_quote($email);
- DisplayError("You must specify one or more fields in which
- to search for <tt>$htmlemail</tt>.");
- exit;
- }
- }
-
-
- if (defined $F{'changedin'}) {
- my $c = trim($F{'changedin'});
- if ($c ne "") {
- if ($c !~ /^[0-9]*$/) {
- my $htmlc = html_quote($c);
- DisplayError("The <em>changed in last ___ days</em> field
- must be a simple number. You entered
- <kbd>$htmlc</kbd>, which doesn't cut it.");
- exit;
- }
- push(@specialchart, ["changedin",
- "lessthan", $c + 1]);
- }
- }
-
- my $ref = $M{'chfield'};
-
- if (defined $ref) {
- my $which = lsearch($ref, "[Bug creation]");
- if ($which >= 0) {
- splice(@$ref, $which, 1);
- push(@specialchart, ["creation_ts", "greaterthan",
- SqlifyDate($F{'chfieldfrom'})]);
- my $to = $F{'chfieldto'};
- if (defined $to) {
- $to = trim($to);
- if ($to ne "" && $to !~ /^now$/i) {
- push(@specialchart, ["creation_ts", "lessthan",
- SqlifyDate($to)]);
- }
- }
- }
- }
-
-
-
- if (defined $ref && 0 < @$ref) {
- push(@supptables, "bugs_activity actcheck");
-
- my @list;
- foreach my $f (@$ref) {
- push(@list, "\nactcheck.fieldid = " . GetFieldID($f));
- }
- push(@wherepart, "actcheck.bug_id = bugs.bug_id");
- push(@wherepart, "(" . join(' OR ', @list) . ")");
- push(@wherepart, "actcheck.bug_when >= " .
- SqlQuote(SqlifyDate($F{'chfieldfrom'})));
- my $to = $F{'chfieldto'};
- if (defined $to) {
- $to = trim($to);
- if ($to ne "" && $to !~ /^now$/i) {
- push(@wherepart, "actcheck.bug_when <= " .
- SqlQuote(SqlifyDate($to)));
- }
- }
- my $value = $F{'chfieldvalue'};
- if (defined $value) {
- $value = trim($value);
- if ($value ne "") {
- push(@wherepart, "actcheck.added = " .
- SqlQuote($value))
- }
- }
- }
-
-
- foreach my $f ("short_desc", "long_desc", "bug_file_loc",
- "status_whiteboard") {
- if (defined $F{$f}) {
- my $s = trim($F{$f});
- if ($s ne "") {
- my $n = $f;
- my $q = SqlQuote($s);
- my $type = $F{$f . "_type"};
- push(@specialchart, [$f, $type, $s]);
- }
- }
- }
-
- my $chartid;
- # $statusid is used by the code that queries for attachment statuses.
- my $statusid = 0;
- my $f;
- my $ff;
- my $t;
- my $q;
- my $v;
- my $term;
- my %funcsbykey;
- my @funcdefs =
- (
- "^(assigned_to|reporter)," => sub {
- push(@supptables, "profiles map_$f");
- push(@wherepart, "bugs.$f = map_$f.userid");
- $f = "map_$f.login_name";
- },
- "^qa_contact," => sub {
- push(@supptables,
- "LEFT JOIN profiles map_qa_contact ON bugs.qa_contact = map_qa_contact.userid");
- $f = "map_$f.login_name";
- },
-
- "^cc," => sub {
- push(@supptables, "LEFT JOIN cc cc_$chartid ON bugs.bug_id = cc_$chartid.bug_id");
-
- push(@supptables, "LEFT JOIN profiles map_cc_$chartid ON cc_$chartid.who = map_cc_$chartid.userid");
- $f = "map_cc_$chartid.login_name";
- },
-
- "^long_?desc,changedby" => sub {
- my $table = "longdescs_$chartid";
- push(@supptables, "longdescs $table");
- push(@wherepart, "$table.bug_id = bugs.bug_id");
- my $id = DBNameToIdAndCheck($v);
- $term = "$table.who = $id";
- },
- "^long_?desc,changedbefore" => sub {
- my $table = "longdescs_$chartid";
- push(@supptables, "longdescs $table");
- push(@wherepart, "$table.bug_id = bugs.bug_id");
- $term = "$table.bug_when < " . SqlQuote(SqlifyDate($v));
- },
- "^long_?desc,changedafter" => sub {
- my $table = "longdescs_$chartid";
- push(@supptables, "longdescs $table");
- push(@wherepart, "$table.bug_id = bugs.bug_id");
- $term = "$table.bug_when > " . SqlQuote(SqlifyDate($v));
- },
- "^long_?desc," => sub {
- my $table = "longdescs_$chartid";
- push(@supptables, "longdescs $table");
- push(@wherepart, "$table.bug_id = bugs.bug_id");
- $f = "$table.thetext";
- },
- "^attachments\..*," => sub {
- my $table = "attachments_$chartid";
- push(@supptables, "attachments $table");
- push(@wherepart, "bugs.bug_id = $table.bug_id");
- $f =~ m/^attachments\.(.*)$/;
- my $field = $1;
- if ($t eq "changedby") {
- $v = DBNameToIdAndCheck($v);
- $q = SqlQuote($v);
- $field = "submitter_id";
- $t = "equals";
- } elsif ($t eq "changedbefore") {
- $v = SqlifyDate($v);
- $q = SqlQuote($v);
- $field = "creation_ts";
- $t = "lessthan";
- } elsif ($t eq "changedafter") {
- $v = SqlifyDate($v);
- $q = SqlQuote($v);
- $field = "creation_ts";
- $t = "greaterthan";
- }
- if ($field eq "ispatch" && $v ne "0" && $v ne "1") {
- DisplayError("The only legal values for the <em>Attachment is
- patch</em> field are 0 and 1.");
- exit;
- }
- if ($field eq "isobsolete" && $v ne "0" && $v ne "1") {
- DisplayError("The only legal values for the <em>Attachment is
- obsolete</em> field are 0 and 1.");
- exit;
- }
- $f = "$table.$field";
- },
- "^attachstatusdefs.name," => sub {
- # The below has Fun with the names for attachment statuses. This
- # isn't needed for changed* queries, so exclude those - the
- # generic stuff will cope
- return if ($t =~ m/^changed/);
-
- # Searching for "status != 'bar'" wants us to look for an
- # attachment without the 'bar' status, not for an attachment with
- # a status not equal to 'bar' (Which would pick up an attachment
- # with more than one status). We do this by LEFT JOINS, after
- # grabbing the matching attachment status ids.
- # Note that this still won't find bugs with no attachments, since
- # that isn't really what people would expect.
-
- # First, get the attachment status ids, using the other funcs
- # to match the WHERE term.
- # Note that we need to reverse the negated bits for this to work
- # This somewhat abuses the definitions of the various terms -
- # eg, does 'contains all' mean that the status has to contain all
- # those words, or that all those words must be exact matches to
- # statuses, which must all be on a single attachment, or should
- # the match on the status descriptions be a contains match, too?
-
- my $inverted = 0;
- if ($t =~ m/not(.*)/) {
- $t = $1;
- $inverted = 1;
- }
-
- $ref = $funcsbykey{",$t"};
- &$ref;
- SendSQL("SELECT id FROM attachstatusdefs WHERE $term");
-
- my @as_ids;
- while (MoreSQLData()) {
- push @as_ids, FetchOneColumn();
- }
-
- # When searching for multiple statuses within a single boolean chart,
- # we want to match each status record separately. In other words,
- # "status = 'foo' AND status = 'bar'" should match attachments with
- # one status record equal to "foo" and another one equal to "bar",
- # not attachments where the same status record equals both "foo" and
- # "bar" (which is nonsensical). In order to do this we must add an
- # additional counter to the end of the "attachstatuses" table
- # reference.
- ++$statusid;
-
- my $attachtable = "attachments_$chartid";
- my $statustable = "attachstatuses_${chartid}_$statusid";
-
- push(@supptables, "attachments $attachtable");
- my $join = "LEFT JOIN attachstatuses $statustable ON ".
- "($attachtable.attach_id = $statustable.attach_id AND " .
- "$statustable.statusid IN (" . join(",", @as_ids) . "))";
- push(@supptables, $join);
- push(@wherepart, "bugs.bug_id = $attachtable.bug_id");
- if ($inverted) {
- $term = "$statustable.statusid IS NULL";
- } else {
- $term = "$statustable.statusid IS NOT NULL";
- }
- },
- "^changedin," => sub {
- $f = "(to_days(now()) - to_days(bugs.delta_ts))";
- },
-
- "^keywords," => sub {
- GetVersionTable();
- my @list;
- my $table = "keywords_$chartid";
- foreach my $value (split(/[\s,]+/, $v)) {
- if ($value eq '') {
- next;
- }
- my $id = GetKeywordIdFromName($value);
- if ($id) {
- push(@list, "$table.keywordid = $id");
- }
- else {
- my $htmlv = html_quote($v);
- DisplayError(qq|There is no keyword named <code>$htmlv</code>.
- To search for keywords, consult the
- <a href="describekeywords.cgi">list of legal keywords</a>.|);
- exit;
- }
- }
- my $haveawordterm;
- if (@list) {
- $haveawordterm = "(" . join(' OR ', @list) . ")";
- if ($t eq "anywords") {
- $term = $haveawordterm;
- } elsif ($t eq "allwords") {
- $ref = $funcsbykey{",$t"};
- &$ref;
- if ($term && $haveawordterm) {
- $term = "(($term) AND $haveawordterm)";
- }
- }
- }
- if ($term) {
- push(@supptables, "keywords $table");
- push(@wherepart, "$table.bug_id = bugs.bug_id");
- }
- },
-
- "^dependson," => sub {
- my $table = "dependson_" . $chartid;
- push(@supptables, "dependencies $table");
- $ff = "$table.$f";
- $ref = $funcsbykey{",$t"};
- &$ref;
- push(@wherepart, "$table.blocked = bugs.bug_id");
- },
-
- "^blocked," => sub {
- my $table = "blocked_" . $chartid;
- push(@supptables, "dependencies $table");
- $ff = "$table.$f";
- $ref = $funcsbykey{",$t"};
- &$ref;
- push(@wherepart, "$table.dependson = bugs.bug_id");
- },
-
-
- ",equals" => sub {
- $term = "$ff = $q";
- },
- ",notequals" => sub {
- $term = "$ff != $q";
- },
- ",casesubstring" => sub {
- $term = "INSTR($ff, $q)";
- },
- ",(substring|substr)" => sub {
- $term = "INSTR(LOWER($ff), " . lc($q) . ")";
- },
- ",notsubstring" => sub {
- $term = "INSTR(LOWER($ff), " . lc($q) . ") = 0";
- },
- ",regexp" => sub {
- $term = "LOWER($ff) REGEXP $q";
- },
- ",notregexp" => sub {
- $term = "LOWER($ff) NOT REGEXP $q";
- },
- ",lessthan" => sub {
- $term = "$ff < $q";
- },
- ",greaterthan" => sub {
- $term = "$ff > $q";
- },
- ",anyexact" => sub {
- my @list;
- foreach my $w (split(/,/, $v)) {
- if ($w eq "---" && $f !~ /milestone/) {
- $w = "";
- }
- push(@list, "$ff = " . SqlQuote($w));
- }
- $term = join(" OR ", @list);
- },
- ",anywordssubstr" => sub {
- $term = join(" OR ", @{GetByWordListSubstr($ff, $v)});
- },
- ",allwordssubstr" => sub {
- $term = join(" AND ", @{GetByWordListSubstr($ff, $v)});
- },
- ",nowordssubstr" => sub {
- my @list = @{GetByWordListSubstr($ff, $v)};
- if (@list) {
- $term = "NOT (" . join(" OR ", @list) . ")";
- }
- },
- ",anywords" => sub {
- $term = join(" OR ", @{GetByWordList($ff, $v)});
- },
- ",allwords" => sub {
- $term = join(" AND ", @{GetByWordList($ff, $v)});
- },
- ",nowords" => sub {
- my @list = @{GetByWordList($ff, $v)};
- if (@list) {
- $term = "NOT (" . join(" OR ", @list) . ")";
- }
- },
- ",changedbefore" => sub {
- my $table = "act_$chartid";
- my $ftable = "fielddefs_$chartid";
- push(@supptables, "bugs_activity $table");
- push(@supptables, "fielddefs $ftable");
- push(@wherepart, "$table.bug_id = bugs.bug_id");
- push(@wherepart, "$table.fieldid = $ftable.fieldid");
- $term = "($ftable.name = '$f' AND $table.bug_when < $q)";
- },
- ",changedafter" => sub {
- my $table = "act_$chartid";
- my $ftable = "fielddefs_$chartid";
- push(@supptables, "bugs_activity $table");
- push(@supptables, "fielddefs $ftable");
- push(@wherepart, "$table.bug_id = bugs.bug_id");
- push(@wherepart, "$table.fieldid = $ftable.fieldid");
- $term = "($ftable.name = '$f' AND $table.bug_when > $q)";
- },
- ",changedfrom" => sub {
- my $table = "act_$chartid";
- my $ftable = "fielddefs_$chartid";
- push(@supptables, "bugs_activity $table");
- push(@supptables, "fielddefs $ftable");
- push(@wherepart, "$table.bug_id = bugs.bug_id");
- push(@wherepart, "$table.fieldid = $ftable.fieldid");
- $term = "($ftable.name = '$f' AND $table.removed = $q)";
- },
- ",changedto" => sub {
- my $table = "act_$chartid";
- my $ftable = "fielddefs_$chartid";
- push(@supptables, "bugs_activity $table");
- push(@supptables, "fielddefs $ftable");
- push(@wherepart, "$table.bug_id = bugs.bug_id");
- push(@wherepart, "$table.fieldid = $ftable.fieldid");
- $term = "($ftable.name = '$f' AND $table.added = $q)";
- },
- ",changedby" => sub {
- my $table = "act_$chartid";
- my $ftable = "fielddefs_$chartid";
- push(@supptables, "bugs_activity $table");
- push(@supptables, "fielddefs $ftable");
- push(@wherepart, "$table.bug_id = bugs.bug_id");
- push(@wherepart, "$table.fieldid = $ftable.fieldid");
- my $id = DBNameToIdAndCheck($v);
- $term = "($ftable.name = '$f' AND $table.who = $id)";
- },
- );
- my @funcnames;
- while (@funcdefs) {
- my $key = shift(@funcdefs);
- my $value = shift(@funcdefs);
- if ($key =~ /^[^,]*$/) {
- die "All defs in %funcs must have a comma in their name: $key";
- }
- if (exists $funcsbykey{$key}) {
- die "Duplicate key in %funcs: $key";
- }
- $funcsbykey{$key} = $value;
- push(@funcnames, $key);
- }
-
- # first we delete any sign of "Chart #-1" from the HTML form hash
- # since we want to guarantee the user didn't hide something here
- my @badcharts = grep /^(field|type|value)-1-/, (keys %F);
- foreach my $field (@badcharts) {
- delete $F{$field};
- }
-
- # now we take our special chart and stuff it into the form hash
- my $chart = -1;
- my $row = 0;
- foreach my $ref (@specialchart) {
- my $col = 0;
- while (@$ref) {
- $F{"field$chart-$row-$col"} = shift(@$ref);
- $F{"type$chart-$row-$col"} = shift(@$ref);
- $F{"value$chart-$row-$col"} = shift(@$ref);
- if ($debug) {
- print qq{<p>$F{"field$chart-$row-$col"} | $F{"type$chart-$row-$col"} | $F{"value$chart-$row-$col"}*</p>\n};
- }
- $col++;
-
- }
- $row++;
- }
-
-
-# A boolean chart is a way of representing the terms in a logical
-# expression. Bugzilla builds SQL queries depending on how you enter
-# terms into the boolean chart. Boolean charts are represented in
-# urls as tree-tuples of (chart id, row, column). The query form
-# (query.cgi) may contain an arbitrary number of boolean charts where
-# each chart represents a clause in a SQL query.
-#
-# The query form starts out with one boolean chart containing one
-# row and one column. Extra rows can be created by pressing the
-# AND button at the bottom of the chart. Extra columns are created
-# by pressing the OR button at the right end of the chart. Extra
-# charts are created by pressing "Add another boolean chart".
-#
-# Each chart consists of an artibrary number of rows and columns.
-# The terms within a row are ORed together. The expressions represented
-# by each row are ANDed together. The expressions represented by each
-# chart are ANDed together.
-#
-# ----------------------
-# | col2 | col2 | col3 |
-# --------------|------|------|
-# | row1 | a1 | a2 | |
-# |------|------|------|------| => ((a1 OR a2) AND (b1 OR b2 OR b3) AND (c1))
-# | row2 | b1 | b2 | b3 |
-# |------|------|------|------|
-# | row3 | c1 | | |
-# -----------------------------
-#
-# --------
-# | col2 |
-# --------------|
-# | row1 | d1 | => (d1)
-# ---------------
-#
-# Together, these two charts represent a SQL expression like this
-# SELECT blah FROM blah WHERE ( (a1 OR a2)AND(b1 OR b2 OR b3)AND(c1)) AND (d1)
-#
-# The terms within a single row of a boolean chart are all constraints
-# on a single piece of data. If you're looking for a bug that has two
-# different people cc'd on it, then you need to use two boolean charts.
-# This will find bugs with one CC mathing 'foo@blah.org' and and another
-# CC matching 'bar@blah.org'.
-#
-# --------------------------------------------------------------
-# CC | equal to
-# foo@blah.org
-# --------------------------------------------------------------
-# CC | equal to
-# bar@blah.org
-#
-# If you try to do this query by pressing the AND button in the
-# original boolean chart then what you'll get is an expression that
-# looks for a single CC where the login name is both "foo@blah.org",
-# and "bar@blah.org". This is impossible.
-#
-# --------------------------------------------------------------
-# CC | equal to
-# foo@blah.org
-# AND
-# CC | equal to
-# bar@blah.org
-# --------------------------------------------------------------
-
-# $chartid is the number of the current chart whose SQL we're contructing
-# $row is the current row of the current chart
-
-# names for table aliases are constructed using $chartid and $row
-# SELECT blah FROM $table "$table_$chartid_$row" WHERE ....
-
-# $f = field of table in bug db (e.g. bug_id, reporter, etc)
-# $ff = qualified field name (field name prefixed by table)
-# e.g. bugs_activity.bug_id
-# $t = type of query. e.g. "equal to", "changed after", case sensitive substr"
-# $v = value - value the user typed in to the form
-# $q = sanitized version of user input (SqlQuote($v))
-# @supptables = Tables and/or table aliases used in query
-# %suppseen = A hash used to store all the tables in supptables to weed
-# out duplicates.
-# $suppstring = String which is pasted into query containing all table names
-
- # get a list of field names to verify the user-submitted chart fields against
- my %chartfields;
- SendSQL("SELECT name FROM fielddefs");
- while (MoreSQLData()) {
- my ($name) = FetchSQLData();
- $chartfields{$name} = 1;
- }
-
- $row = 0;
- for ($chart=-1 ;
- $chart < 0 || exists $F{"field$chart-0-0"} ;
- $chart++) {
- $chartid = $chart >= 0 ? $chart : "";
- for ($row = 0 ;
- exists $F{"field$chart-$row-0"} ;
- $row++) {
- my @orlist;
- for (my $col = 0 ;
- exists $F{"field$chart-$row-$col"} ;
- $col++) {
- $f = $F{"field$chart-$row-$col"} || "noop";
- $t = $F{"type$chart-$row-$col"} || "noop";
- $v = $F{"value$chart-$row-$col"};
- $v = "" if !defined $v;
- $v = trim($v);
- if ($f eq "noop" || $t eq "noop" || $v eq "") {
- next;
- }
- # chart -1 is generated by other code above, not from the user-
- # submitted form, so we'll blindly accept any values in chart -1
- if ((!$chartfields{$f}) && ($chart != -1)) {
- my $errstr = "Can't use " . html_quote($f) . " as a field name. " .
- "If you think you're getting this in error, please copy the " .
- "entire URL out of the address bar at the top of your browser " .
- "window and email it to <109679\@bugzilla.org>";
- die "Internal error: $errstr" if $chart < 0;
- return Error($errstr);
- }
-
- # This is either from the internal chart (in which case we
- # already know about it), or it was in %chartfields, so it is
- # a valid field name, which means that its ok.
- trick_taint($f);
- $q = SqlQuote($v);
- my $func;
- $term = undef;
- foreach my $key (@funcnames) {
- if ("$f,$t" =~ m/$key/) {
- my $ref = $funcsbykey{$key};
- if ($debug) {
- print "<p>$key ($f , $t ) => ";
- }
- $ff = $f;
- if ($f !~ /\./) {
- $ff = "bugs.$f";
- }
- &$ref;
- if ($debug) {
- print "$f , $t , $term</p>";
- }
- if ($term) {
- last;
- }
- }
- }
- if ($term) {
- push(@orlist, $term);
- }
- else {
- my $errstr =
- qq|Cannot seem to handle <code>$F{"field$chart-$row-$col"}</code>
- and <code>$F{"type$chart-$row-$col"}</code> together|;
- $chart < 0 ? die "Internal error: $errstr"
- : DisplayError($errstr) && exit;
- }
- }
- if (@orlist) {
- push(@andlist, "(" . join(" OR ", @orlist) . ")");
- }
- }
- }
- my %suppseen = ("bugs" => 1);
- my $suppstring = "bugs";
- foreach my $str (@supptables) {
- if (!$suppseen{$str}) {
- if ($str !~ /^(LEFT|INNER) JOIN/i) {
- $suppstring .= ",";
- }
- $suppstring .= " $str";
- $suppseen{$str} = 1;
- }
- }
- my $query = ("SELECT DISTINCT " . join(', ', @fields) .
- " FROM $suppstring" .
- " WHERE " . join(' AND ', (@wherepart, @andlist)));
-
- $query = SelectVisible($query, $::userid, $::usergroupset);
-
- if ($debug) {
- print "<p><code>" . value_quote($query) . "</code></p>\n";
- exit;
- }
- return $query;
-}
-
-
-
################################################################################
# Command Execution
################################################################################
@@ -1312,7 +487,9 @@ if ($dotweak) {
my @selectnames = map($columns->{$_}->{'name'}, @selectcolumns);
# Generate the basic SQL query that will be used to generate the bug list.
-my $query = GenerateSQL(\@selectnames, $::buffer);
+my $search = new Bugzilla::Search('fields' => \@selectnames,
+ 'url' => $::buffer);
+my $query = $search->getSQL();
################################################################################