Rubber-stamped by Brady Eidson.
[webbrowser.git] / BugsSite / request.cgi
blob9b08e59f5f09ddaa67491036f25b7cad78b19589
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): Myk Melez <myk@mozilla.org>
22 # Frédéric Buclin <LpSolit@gmail.com>
24 ################################################################################
25 # Script Initialization
26 ################################################################################
28 # Make it harder for us to do dangerous things in Perl.
29 use strict;
31 use lib qw(. lib);
33 use Bugzilla;
34 use Bugzilla::Util;
35 use Bugzilla::Error;
36 use Bugzilla::Flag;
37 use Bugzilla::FlagType;
38 use Bugzilla::User;
39 use Bugzilla::Product;
40 use Bugzilla::Component;
42 # Make sure the user is logged in.
43 my $user = Bugzilla->login();
44 my $cgi = Bugzilla->cgi;
45 my $dbh = Bugzilla->dbh;
46 my $template = Bugzilla->template;
47 my $action = $cgi->param('action') || '';
49 print $cgi->header();
51 ################################################################################
52 # Main Body Execution
53 ################################################################################
55 my $fields;
56 $fields->{'requester'}->{'type'} = 'single';
57 # If the user doesn't restrict his search to requests from the wind
58 # (requestee ne '-'), include the requestee for completion.
59 unless (defined $cgi->param('requestee')
60 && $cgi->param('requestee') eq '-')
62 $fields->{'requestee'}->{'type'} = 'single';
65 Bugzilla::User::match_field($cgi, $fields);
67 if ($action eq 'queue') {
68 queue();
70 else {
71 my $flagtypes = $dbh->selectcol_arrayref('SELECT DISTINCT(name) FROM flagtypes
72 ORDER BY name');
73 my @types = ('all', @$flagtypes);
75 my $vars = {};
76 $vars->{'products'} = $user->get_selectable_products;
77 $vars->{'types'} = \@types;
78 $vars->{'requests'} = {};
80 my %components;
81 foreach my $prod (@{$vars->{'products'}}) {
82 foreach my $comp (@{$prod->components}) {
83 $components{$comp->name} = 1;
86 $vars->{'components'} = [ sort { $a cmp $b } keys %components ];
88 $template->process('request/queue.html.tmpl', $vars)
89 || ThrowTemplateError($template->error());
91 exit;
93 ################################################################################
94 # Functions
95 ################################################################################
97 sub queue {
98 my $cgi = Bugzilla->cgi;
99 # There are some user privilege checks to do. We do them against the main DB.
100 my $dbh = Bugzilla->dbh;
101 my $template = Bugzilla->template;
102 my $user = Bugzilla->user;
103 my $userid = $user->id;
104 my $vars = {};
106 my $status = validateStatus($cgi->param('status'));
107 my $form_group = validateGroup($cgi->param('group'));
109 my $query =
110 # Select columns describing each flag, the bug/attachment on which
111 # it has been set, who set it, and of whom they are requesting it.
112 " SELECT flags.id, flagtypes.name,
113 flags.status,
114 flags.bug_id, bugs.short_desc,
115 products.name, components.name,
116 flags.attach_id, attachments.description,
117 requesters.realname, requesters.login_name,
118 requestees.realname, requestees.login_name,
119 " . $dbh->sql_date_format('flags.modification_date', '%Y.%m.%d %H:%i') .
120 # Use the flags and flagtypes tables for information about the flags,
121 # the bugs and attachments tables for target info, the profiles tables
122 # for setter and requestee info, the products/components tables
123 # so we can display product and component names, and the bug_group_map
124 # table to help us weed out secure bugs to which the user should not have
125 # access.
127 FROM flags
128 LEFT JOIN attachments
129 ON flags.attach_id = attachments.attach_id
130 INNER JOIN flagtypes
131 ON flags.type_id = flagtypes.id
132 INNER JOIN profiles AS requesters
133 ON flags.setter_id = requesters.userid
134 LEFT JOIN profiles AS requestees
135 ON flags.requestee_id = requestees.userid
136 INNER JOIN bugs
137 ON flags.bug_id = bugs.bug_id
138 INNER JOIN products
139 ON bugs.product_id = products.id
140 INNER JOIN components
141 ON bugs.component_id = components.id
142 LEFT JOIN bug_group_map AS bgmap
143 ON bgmap.bug_id = bugs.bug_id
144 AND bgmap.group_id NOT IN (" .
145 join(', ', (-1, values(%{$user->groups}))) . ")
146 LEFT JOIN cc AS ccmap
147 ON ccmap.who = $userid
148 AND ccmap.bug_id = bugs.bug_id
151 # Weed out bug the user does not have access to
152 " WHERE ((bgmap.group_id IS NULL) OR
153 (ccmap.who IS NOT NULL AND cclist_accessible = 1) OR
154 (bugs.reporter = $userid AND bugs.reporter_accessible = 1) OR
155 (bugs.assigned_to = $userid) " .
156 (Bugzilla->params->{'useqacontact'} ? "OR
157 (bugs.qa_contact = $userid))" : ")");
159 unless ($user->is_insider) {
160 $query .= " AND (attachments.attach_id IS NULL
161 OR attachments.isprivate = 0
162 OR attachments.submitter_id = $userid)";
165 # Limit query to pending requests.
166 $query .= " AND flags.status = '?' " unless $status;
168 # The set of criteria by which we filter records to display in the queue.
169 # We now move to the shadow DB to query the DB.
170 my @criteria = ();
171 $dbh = Bugzilla->switch_to_shadow_db;
173 # A list of columns to exclude from the report because the report conditions
174 # limit the data being displayed to exact matches for those columns.
175 # In other words, if we are only displaying "pending" , we don't
176 # need to display a "status" column in the report because the value for that
177 # column will always be the same.
178 my @excluded_columns = ();
180 # Filter requests by status: "pending", "granted", "denied", "all"
181 # (which means any), or "fulfilled" (which means "granted" or "denied").
182 if ($status) {
183 if ($status eq "+-") {
184 push(@criteria, "flags.status IN ('+', '-')");
185 push(@excluded_columns, 'status') unless $cgi->param('do_union');
187 elsif ($status ne "all") {
188 push(@criteria, "flags.status = '$status'");
189 push(@excluded_columns, 'status') unless $cgi->param('do_union');
193 # Filter results by exact email address of requester or requestee.
194 if (defined $cgi->param('requester') && $cgi->param('requester') ne "") {
195 my $requester = $dbh->quote($cgi->param('requester'));
196 trick_taint($requester); # Quoted above
197 push(@criteria, $dbh->sql_istrcmp('requesters.login_name', $requester));
198 push(@excluded_columns, 'requester') unless $cgi->param('do_union');
200 if (defined $cgi->param('requestee') && $cgi->param('requestee') ne "") {
201 if ($cgi->param('requestee') ne "-") {
202 my $requestee = $dbh->quote($cgi->param('requestee'));
203 trick_taint($requestee); # Quoted above
204 push(@criteria, $dbh->sql_istrcmp('requestees.login_name',
205 $requestee));
207 else { push(@criteria, "flags.requestee_id IS NULL") }
208 push(@excluded_columns, 'requestee') unless $cgi->param('do_union');
211 # Filter results by exact product or component.
212 if (defined $cgi->param('product') && $cgi->param('product') ne "") {
213 my $product = Bugzilla::Product::check_product(scalar $cgi->param('product'));
214 push(@criteria, "bugs.product_id = " . $product->id);
215 push(@excluded_columns, 'product') unless $cgi->param('do_union');
216 if (defined $cgi->param('component') && $cgi->param('component') ne "") {
217 my $component = Bugzilla::Component->check({ product => $product,
218 name => scalar $cgi->param('component') });
219 push(@criteria, "bugs.component_id = " . $component->id);
220 push(@excluded_columns, 'component') unless $cgi->param('do_union');
224 # Filter results by flag types.
225 my $form_type = $cgi->param('type');
226 if (defined $form_type && !grep($form_type eq $_, ("", "all"))) {
227 # Check if any matching types are for attachments. If not, don't show
228 # the attachment column in the report.
229 my $has_attachment_type =
230 Bugzilla::FlagType::count({ 'name' => $form_type,
231 'target_type' => 'attachment' });
233 if (!$has_attachment_type) { push(@excluded_columns, 'attachment') }
235 my $quoted_form_type = $dbh->quote($form_type);
236 trick_taint($quoted_form_type); # Already SQL quoted
237 push(@criteria, "flagtypes.name = " . $quoted_form_type);
238 push(@excluded_columns, 'type') unless $cgi->param('do_union');
241 # Add the criteria to the query. We do an intersection by default
242 # but do a union if the "do_union" URL parameter (for which there is no UI
243 # because it's an advanced feature that people won't usually want) is true.
244 my $and_or = $cgi->param('do_union') ? " OR " : " AND ";
245 $query .= " AND (" . join($and_or, @criteria) . ") " if scalar(@criteria);
247 # Group the records by flag ID so we don't get multiple rows of data
248 # for each flag. This is only necessary because of the code that
249 # removes flags on bugs the user is unauthorized to access.
250 $query .= ' ' . $dbh->sql_group_by('flags.id',
251 'flagtypes.name, flags.status, flags.bug_id, bugs.short_desc,
252 products.name, components.name, flags.attach_id,
253 attachments.description, requesters.realname,
254 requesters.login_name, requestees.realname,
255 requestees.login_name, flags.modification_date,
256 cclist_accessible, bugs.reporter, bugs.reporter_accessible,
257 bugs.assigned_to');
259 # Group the records, in other words order them by the group column
260 # so the loop in the display template can break them up into separate
261 # tables every time the value in the group column changes.
263 $form_group ||= "requestee";
264 if ($form_group eq "requester") {
265 $query .= " ORDER BY requesters.realname, requesters.login_name";
267 elsif ($form_group eq "requestee") {
268 $query .= " ORDER BY requestees.realname, requestees.login_name";
270 elsif ($form_group eq "category") {
271 $query .= " ORDER BY products.name, components.name";
273 elsif ($form_group eq "type") {
274 $query .= " ORDER BY flagtypes.name";
277 # Order the records (within each group).
278 $query .= " , flags.modification_date";
280 # Pass the query to the template for use when debugging this script.
281 $vars->{'query'} = $query;
282 $vars->{'debug'} = $cgi->param('debug') ? 1 : 0;
284 my $results = $dbh->selectall_arrayref($query);
285 my @requests = ();
286 foreach my $result (@$results) {
287 my @data = @$result;
288 my $request = {
289 'id' => $data[0] ,
290 'type' => $data[1] ,
291 'status' => $data[2] ,
292 'bug_id' => $data[3] ,
293 'bug_summary' => $data[4] ,
294 'category' => "$data[5]: $data[6]" ,
295 'attach_id' => $data[7] ,
296 'attach_summary' => $data[8] ,
297 'requester' => ($data[9] ? "$data[9] <$data[10]>" : $data[10]) ,
298 'requestee' => ($data[11] ? "$data[11] <$data[12]>" : $data[12]) ,
299 'created' => $data[13]
301 push(@requests, $request);
304 # Get a list of request type names to use in the filter form.
305 my @types = ("all");
306 my $flagtypes = $dbh->selectcol_arrayref(
307 "SELECT DISTINCT(name) FROM flagtypes ORDER BY name");
308 push(@types, @$flagtypes);
310 # We move back to the main DB to get the list of products the user can see.
311 $dbh = Bugzilla->switch_to_main_db;
313 $vars->{'products'} = $user->get_selectable_products;
314 $vars->{'excluded_columns'} = \@excluded_columns;
315 $vars->{'group_field'} = $form_group;
316 $vars->{'requests'} = \@requests;
317 $vars->{'types'} = \@types;
319 my %components;
320 foreach my $prod (@{$vars->{'products'}}) {
321 foreach my $comp (@{$prod->components}) {
322 $components{$comp->name} = 1;
325 $vars->{'components'} = [ sort { $a cmp $b } keys %components ];
327 # Generate and return the UI (HTML page) from the appropriate template.
328 $template->process("request/queue.html.tmpl", $vars)
329 || ThrowTemplateError($template->error());
332 ################################################################################
333 # Data Validation / Security Authorization
334 ################################################################################
336 sub validateStatus {
337 my $status = shift;
338 return if !defined $status;
340 grep($status eq $_, qw(? +- + - all))
341 || ThrowCodeError("flag_status_invalid",
342 { status => $status });
343 trick_taint($status);
344 return $status;
347 sub validateGroup {
348 my $group = shift;
349 return if !defined $group;
351 grep($group eq $_, qw(requester requestee category type))
352 || ThrowCodeError("request_queue_group_invalid",
353 { group => $group });
354 trick_taint($group);
355 return $group;