Rubber-stamped by Brady Eidson.
[webbrowser.git] / BugsSite / query.cgi
blob678940584dd199945228d54a2f9f7a40f8b8fb2f
1 #!/usr/bin/env perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
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/
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.
14 # The Original Code is the Bugzilla Bug Tracking System.
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.
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>
28 use strict;
29 use lib qw(. lib);
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);
43 my $cgi = Bugzilla->cgi;
44 my $dbh = Bugzilla->dbh;
45 my $template = Bugzilla->template;
46 my $vars = {};
47 my $buffer = $cgi->query_string();
49 my $user = Bugzilla->login();
50 my $userid = $user->id;
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)]);
62 if (defined $cgi->cookie('DEFAULTQUERY')) {
63 push(@oldquerycookies, [DEFAULT_QUERY_NAME, 'DEFAULTQUERY',
64 $cgi->cookie('DEFAULTQUERY')]);
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));
83 $dbh->bz_commit_transaction();
85 $cgi->remove_cookie($cookiename);
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));
96 $buffer = "";
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));
107 local our %default;
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;
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")
138 $default{$name} = [];
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;
145 # Iterate over the URL parameters
146 foreach my $name ($buf->param()) {
147 my @values = $buf->param($name);
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;
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];
166 elsif (exists $default{$name}) {
167 $foundone = 1;
168 push (@{$default{$name}}, @values);
171 return $foundone;
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"});
184 if (!scalar(@{$default{'chfieldto'}}) || $default{'chfieldto'}->[0] eq "") {
185 $default{'chfieldto'} = ["Now"];
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);
194 # Create the component, version and milestone lists.
195 my %components;
196 my %versions;
197 my %milestones;
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});
205 my @components = sort(keys %components);
206 my @versions = sort { vers_cmp (lc($a), lc($b)) } keys %versions;
207 my @milestones = sort(keys %milestones);
209 $vars->{'product'} = \@selectable_products;
211 # Create data structures representing each classification
212 if (Bugzilla->params->{'useclassification'}) {
213 $vars->{'classification'} = $user->get_selectable_classifications;
216 # We use 'component_' because 'component' is a Template Toolkit reserved word.
217 $vars->{'component_'} = \@components;
219 $vars->{'version'} = \@versions;
221 if (Bugzilla->params->{'usetargetmilestone'}) {
222 $vars->{'target_milestone'} = \@milestones;
225 $vars->{'have_keywords'} = Bugzilla::Keyword::keyword_count();
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)];
232 my @chfields;
234 push @chfields, "[Bug creation]";
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';
246 push @chfields, $val;
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);
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');
263 # Boolean charts
264 my @fields = Bugzilla->get_fields({ obsolete => 0 });
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))
271 @fields = grep($_->name ne $tt_field, @fields);
275 @fields = sort {lc($a->description) cmp lc($b->description)} @fields;
276 unshift(@fields, { name => "noop", description => "---" });
277 $vars->{'fields'} = \@fields;
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");
288 if (!$cgi->param('field0-0-0')) {
289 $cgi->param(-name => 'field0-0-0', -value => "xyzzy");
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 = '';
305 push(@cols, { field => $cgi->param("field$chart-$row-$col"),
306 type => $cgi->param("type$chart-$row-$col") || 'noop',
307 value => $value });
309 push(@rows, \@cols);
311 push(@charts, {'rows' => \@rows, 'negate' => scalar($cgi->param("negate$chart")) });
314 $default{'charts'} = \@charts;
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));
325 # Sort order
326 my $deforder;
327 my @orders = ('Bug Number', 'Importance', 'Assignee', 'Last Changed');
329 if ($cgi->cookie('LASTORDER')) {
330 $deforder = "Reuse same sort as last time";
331 unshift(@orders, $deforder);
334 if ($cgi->param('order')) { $deforder = $cgi->param('order') }
336 $vars->{'userdefaultquery'} = $userdefaultquery;
337 $vars->{'orders'} = \@orders;
338 $default{'order'} = [$deforder || 'Importance'];
340 if (($cgi->param('query_format') || $cgi->param('format') || "")
341 eq "create-series") {
342 require Bugzilla::Chart;
343 $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
346 $vars->{'known_name'} = $cgi->param('known_name');
349 # Add in the defaults.
350 $vars->{'default'} = \%default;
352 $vars->{'format'} = $cgi->param('format');
353 $vars->{'query_format'} = $cgi->param('query_format');
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';
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");
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'));
380 print $cgi->header($format->{'ctype'});
382 $template->process($format->{'template'}, $vars)
383 || ThrowTemplateError($template->error());