Not reviewed. Chromium rebaselines after http://trac.webkit.org/changeset/72518
[WebKit-https.git] / BugsSite / query.cgi
1 #!/usr/bin/env perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
3 #
4 # The contents of this file are subject to the Mozilla Public
5 # License Version 1.1 (the "License"); you may not use this file
6 # except in compliance with the License. You may obtain a copy of
7 # the License at http://www.mozilla.org/MPL/
8 #
9 # Software distributed under the License is distributed on an "AS
10 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11 # implied. See the License for the specific language governing
12 # rights and limitations under the License.
13 #
14 # The Original Code is the Bugzilla Bug Tracking System.
15 #
16 # The Initial Developer of the Original Code is Netscape Communications
17 # Corporation. Portions created by Netscape are
18 # Copyright (C) 1998 Netscape Communications Corporation. All
19 # Rights Reserved.
20 #
21 # Contributor(s): Terry Weissman <terry@mozilla.org>
22 #                 David Gardiner <david.gardiner@unisa.edu.au>
23 #                 Matthias Radestock <matthias@sorted.org>
24 #                 Gervase Markham <gerv@gerv.net>
25 #                 Byron Jones <bugzilla@glob.com.au>
26 #                 Max Kanat-Alexander <mkanat@bugzilla.org>
27
28 use strict;
29 use lib qw(. lib);
30
31 use Bugzilla;
32 use Bugzilla::Bug;
33 use Bugzilla::Constants;
34 use Bugzilla::Search;
35 use Bugzilla::User;
36 use Bugzilla::Util;
37 use Bugzilla::Error;
38 use Bugzilla::Product;
39 use Bugzilla::Keyword;
40 use Bugzilla::Field;
41 use Bugzilla::Install::Util qw(vers_cmp);
42
43 my $cgi = Bugzilla->cgi;
44 my $dbh = Bugzilla->dbh;
45 my $template = Bugzilla->template;
46 my $vars = {};
47 my $buffer = $cgi->query_string();
48
49 my $user = Bugzilla->login();
50 my $userid = $user->id;
51
52 # Backwards compatibility hack -- if there are any of the old QUERY_*
53 # cookies around, and we are logged in, then move them into the database
54 # and nuke the cookie. This is required for Bugzilla 2.8 and earlier.
55 if ($userid) {
56     my @oldquerycookies;
57     foreach my $i ($cgi->cookie()) {
58         if ($i =~ /^QUERY_(.*)$/) {
59             push(@oldquerycookies, [$1, $i, $cgi->cookie($i)]);
60         }
61     }
62     if (defined $cgi->cookie('DEFAULTQUERY')) {
63         push(@oldquerycookies, [DEFAULT_QUERY_NAME, 'DEFAULTQUERY',
64                                 $cgi->cookie('DEFAULTQUERY')]);
65     }
66     if (@oldquerycookies) {
67         foreach my $ref (@oldquerycookies) {
68             my ($name, $cookiename, $value) = (@$ref);
69             if ($value) {
70                 # If the query name contains invalid characters, don't import.
71                 $name =~ /[<>&]/ && next;
72                 trick_taint($name);
73                 $dbh->bz_start_transaction();
74                 my $query = $dbh->selectrow_array(
75                     "SELECT query FROM namedqueries " .
76                      "WHERE userid = ? AND name = ?",
77                      undef, ($userid, $name));
78                 if (!$query) {
79                     $dbh->do("INSERT INTO namedqueries " .
80                             "(userid, name, query) VALUES " .
81                             "(?, ?, ?)", undef, ($userid, $name, $value));
82                 }
83                 $dbh->bz_commit_transaction();
84             }
85             $cgi->remove_cookie($cookiename);
86         }
87     }
88 }
89
90 if ($cgi->param('nukedefaultquery')) {
91     if ($userid) {
92         $dbh->do("DELETE FROM namedqueries" .
93                  " WHERE userid = ? AND name = ?", 
94                  undef, ($userid, DEFAULT_QUERY_NAME));
95     }
96     $buffer = "";
97 }
98
99 my $userdefaultquery;
100 if ($userid) {
101     $userdefaultquery = $dbh->selectrow_array(
102         "SELECT query FROM namedqueries " .
103          "WHERE userid = ? AND name = ?", 
104          undef, ($userid, DEFAULT_QUERY_NAME));
105 }
106
107 local our %default;
108
109 # We pass the defaults as a hash of references to arrays. For those
110 # Items which are single-valued, the template should only reference [0]
111 # and ignore any multiple values.
112 sub PrefillForm {
113     my ($buf) = (@_);
114     my $cgi = Bugzilla->cgi;
115     $buf = new Bugzilla::CGI($buf);
116     my $foundone = 0;
117
118     # Nothing must be undef, otherwise the template complains.
119     foreach my $name ("bug_status", "resolution", "assigned_to",
120                       "rep_platform", "priority", "bug_severity",
121                       "classification", "product", "reporter", "op_sys",
122                       "component", "version", "chfield", "chfieldfrom",
123                       "chfieldto", "chfieldvalue", "target_milestone",
124                       "email", "emailtype", "emailreporter",
125                       "emailassigned_to", "emailcc", "emailqa_contact",
126                       "emaillongdesc", "content",
127                       "changedin", "votes", "short_desc", "short_desc_type",
128                       "long_desc", "long_desc_type", "bug_file_loc",
129                       "bug_file_loc_type", "status_whiteboard",
130                       "status_whiteboard_type", "bug_id",
131                       "bugidtype", "keywords", "keywords_type",
132                       "deadlinefrom", "deadlineto",
133                       "x_axis_field", "y_axis_field", "z_axis_field",
134                       "chart_format", "cumulate", "x_labels_vertical",
135                       "category", "subcategory", "name", "newcategory",
136                       "newsubcategory", "public", "frequency") 
137     {
138         $default{$name} = [];
139     }
140  
141     # we won't prefill the boolean chart data from this query if
142     # there are any being submitted via params
143     my $prefillcharts = (grep(/^field-/, $cgi->param)) ? 0 : 1;
144  
145     # Iterate over the URL parameters
146     foreach my $name ($buf->param()) {
147         my @values = $buf->param($name);
148
149         # If the name begins with the string 'field', 'type', 'value', or
150         # 'negate', then it is part of the boolean charts. Because
151         # these are built different than the rest of the form, we need
152         # to store these as parameters. We also need to indicate that
153         # we found something so the default query isn't added in if
154         # all we have are boolean chart items.
155         if ($name =~ m/^(?:field|type|value|negate)/) {
156             $cgi->param(-name => $name, -value => $values[0]) if ($prefillcharts);
157             $foundone = 1;
158         }
159         # If the name ends in a number (which it does for the fields which
160         # are part of the email searching), we use the array
161         # positions to show the defaults for that number field.
162         elsif ($name =~ m/^(.+)(\d)$/ && defined($default{$1})) {
163             $foundone = 1;
164             $default{$1}->[$2] = $values[0];
165         }
166         elsif (exists $default{$name}) {
167             $foundone = 1;
168             push (@{$default{$name}}, @values);
169         }
170     }
171     return $foundone;
172 }
173
174 if (!PrefillForm($buffer)) {
175     # Ah-hah, there was no form stuff specified.  Do it again with the
176     # default query.
177     if ($userdefaultquery) {
178         PrefillForm($userdefaultquery);
179     } else {
180         PrefillForm(Bugzilla->params->{"defaultquery"});
181     }
182 }
183
184 if (!scalar(@{$default{'chfieldto'}}) || $default{'chfieldto'}->[0] eq "") {
185     $default{'chfieldto'} = ["Now"];
186 }
187
188 # if using groups for entry, then we don't want people to see products they 
189 # don't have access to. Remove them from the list.
190 my @selectable_products = sort {lc($a->name) cmp lc($b->name)} 
191                                @{$user->get_selectable_products};
192 Bugzilla::Product::preload(\@selectable_products);
193
194 # Create the component, version and milestone lists.
195 my %components;
196 my %versions;
197 my %milestones;
198
199 foreach my $product (@selectable_products) {
200     $components{$_->name} = 1 foreach (@{$product->components});
201     $versions{$_->name}   = 1 foreach (@{$product->versions});
202     $milestones{$_->name} = 1 foreach (@{$product->milestones});
203 }
204
205 my @components = sort(keys %components);
206 my @versions = sort { vers_cmp (lc($a), lc($b)) } keys %versions;
207 my @milestones = sort(keys %milestones);
208
209 $vars->{'product'} = \@selectable_products;
210
211 # Create data structures representing each classification
212 if (Bugzilla->params->{'useclassification'}) {
213     $vars->{'classification'} = $user->get_selectable_classifications;
214 }
215
216 # We use 'component_' because 'component' is a Template Toolkit reserved word.
217 $vars->{'component_'} = \@components;
218
219 $vars->{'version'} = \@versions;
220
221 if (Bugzilla->params->{'usetargetmilestone'}) {
222     $vars->{'target_milestone'} = \@milestones;
223 }
224
225 $vars->{'have_keywords'} = Bugzilla::Keyword::keyword_count();
226
227 my $legal_resolutions = get_legal_field_values('resolution');
228 push(@$legal_resolutions, "---"); # Oy, what a hack.
229 # Another hack - this array contains "" for some reason. See bug 106589.
230 $vars->{'resolution'} = [grep ($_, @$legal_resolutions)];
231
232 my @chfields;
233
234 push @chfields, "[Bug creation]";
235
236 # This is what happens when you have variables whose definition depends
237 # on the DB schema, and then the underlying schema changes...
238 foreach my $val (editable_bug_fields()) {
239     if ($val eq 'classification_id') {
240         $val = 'classification';
241     } elsif ($val eq 'product_id') {
242         $val = 'product';
243     } elsif ($val eq 'component_id') {
244         $val = 'component';
245     }
246     push @chfields, $val;
247 }
248
249 if (Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
250     push @chfields, "work_time";
251 } else {
252     @chfields = grep($_ ne "estimated_time", @chfields);
253     @chfields = grep($_ ne "remaining_time", @chfields);
254 }
255 @chfields = (sort(@chfields));
256 $vars->{'chfield'} = \@chfields;
257 $vars->{'bug_status'} = get_legal_field_values('bug_status');
258 $vars->{'rep_platform'} = get_legal_field_values('rep_platform');
259 $vars->{'op_sys'} = get_legal_field_values('op_sys');
260 $vars->{'priority'} = get_legal_field_values('priority');
261 $vars->{'bug_severity'} = get_legal_field_values('bug_severity');
262
263 # Boolean charts
264 my @fields = Bugzilla->get_fields({ obsolete => 0 });
265
266 # If we're not in the time-tracking group, exclude time-tracking fields.
267 if (!Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
268     foreach my $tt_field (qw(estimated_time remaining_time work_time
269                              percentage_complete deadline))
270     {
271         @fields = grep($_->name ne $tt_field, @fields);
272     }
273 }
274
275 @fields = sort {lc($a->description) cmp lc($b->description)} @fields;
276 unshift(@fields, { name => "noop", description => "---" });
277 $vars->{'fields'} = \@fields;
278
279 # Creating new charts - if the cmd-add value is there, we define the field
280 # value so the code sees it and creates the chart. It will attempt to select
281 # "xyzzy" as the default, and fail. This is the correct behaviour.
282 foreach my $cmd (grep(/^cmd-/, $cgi->param)) {
283     if ($cmd =~ /^cmd-add(\d+)-(\d+)-(\d+)$/) {
284         $cgi->param(-name => "field$1-$2-$3", -value => "xyzzy");
285     }
286 }
287
288 if (!$cgi->param('field0-0-0')) {
289     $cgi->param(-name => 'field0-0-0', -value => "xyzzy");
290 }
291
292 # Create data structure of boolean chart info. It's an array of arrays of
293 # arrays - with the inner arrays having three members - field, type and
294 # value.
295 my @charts;
296 for (my $chart = 0; $cgi->param("field$chart-0-0"); $chart++) {
297     my @rows;
298     for (my $row = 0; $cgi->param("field$chart-$row-0"); $row++) {
299         my @cols;
300         for (my $col = 0; $cgi->param("field$chart-$row-$col"); $col++) {
301             my $value = $cgi->param("value$chart-$row-$col");
302             if (!defined($value)) {
303                 $value = '';
304             }
305             push(@cols, { field => $cgi->param("field$chart-$row-$col"),
306                           type => $cgi->param("type$chart-$row-$col") || 'noop',
307                           value => $value });
308         }
309         push(@rows, \@cols);
310     }
311     push(@charts, {'rows' => \@rows, 'negate' => scalar($cgi->param("negate$chart")) });
312 }
313
314 $default{'charts'} = \@charts;
315
316 # Named queries
317 if ($userid) {
318      $vars->{'namedqueries'} = $dbh->selectcol_arrayref(
319            "SELECT name FROM namedqueries " .
320             "WHERE userid = ? AND name != ? " .
321          "ORDER BY name",
322          undef, ($userid, DEFAULT_QUERY_NAME));
323 }
324
325 # Sort order
326 my $deforder;
327 my @orders = ('Bug Number', 'Importance', 'Assignee', 'Last Changed');
328
329 if ($cgi->cookie('LASTORDER')) {
330     $deforder = "Reuse same sort as last time";
331     unshift(@orders, $deforder);
332 }
333
334 if ($cgi->param('order')) { $deforder = $cgi->param('order') }
335
336 $vars->{'userdefaultquery'} = $userdefaultquery;
337 $vars->{'orders'} = \@orders;
338 $default{'order'} = [$deforder || 'Importance'];
339
340 if (($cgi->param('query_format') || $cgi->param('format') || "")
341     eq "create-series") {
342     require Bugzilla::Chart;
343     $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
344 }
345
346 $vars->{'known_name'} = $cgi->param('known_name');
347
348
349 # Add in the defaults.
350 $vars->{'default'} = \%default;
351
352 $vars->{'format'} = $cgi->param('format');
353 $vars->{'query_format'} = $cgi->param('query_format');
354
355 # Set default page to "specific" if none provided
356 if (!($cgi->param('query_format') || $cgi->param('format'))) {
357     if (defined $cgi->cookie('DEFAULTFORMAT')) {
358         $vars->{'format'} = $cgi->cookie('DEFAULTFORMAT');
359     } else {
360         $vars->{'format'} = 'specific';
361     }
362 }
363
364 # Set cookie to current format as default, but only if the format
365 # one that we should remember.
366 if (defined($vars->{'format'}) && IsValidQueryType($vars->{'format'})) {
367     $cgi->send_cookie(-name => 'DEFAULTFORMAT',
368                       -value => $vars->{'format'},
369                       -expires => "Fri, 01-Jan-2038 00:00:00 GMT");
370 }
371
372 # Generate and return the UI (HTML page) from the appropriate template.
373 # If we submit back to ourselves (for e.g. boolean charts), we need to
374 # preserve format information; hence query_format taking priority over
375 # format.
376 my $format = $template->get_format("search/search", 
377                                    $vars->{'query_format'} || $vars->{'format'}, 
378                                    scalar $cgi->param('ctype'));
379
380 print $cgi->header($format->{'ctype'});
381
382 $template->process($format->{'template'}, $vars)
383   || ThrowTemplateError($template->error());