2009-04-16 Eric Roman <eroman@chromium.org>
[WebKit-https.git] / BugsSite / request.cgi
1 #!/usr/bin/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): Myk Melez <myk@mozilla.org>
22
23 ################################################################################
24 # Script Initialization
25 ################################################################################
26
27 # Make it harder for us to do dangerous things in Perl.
28 use strict;
29
30 # Include the Bugzilla CGI and general utility library.
31 use lib qw(.);
32 require "CGI.pl";
33
34 # Use Bugzilla's Request module which contains utilities for handling requests.
35 use Bugzilla::Flag;
36 use Bugzilla::FlagType;
37
38 # use Bugzilla's User module which contains utilities for handling users.
39 use Bugzilla::User;
40
41 use vars qw($template $vars @legal_product @legal_components %components);
42
43 # Make sure the user is logged in.
44 Bugzilla->login();
45
46 ################################################################################
47 # Main Body Execution
48 ################################################################################
49
50 queue();
51 exit;
52
53 ################################################################################
54 # Functions
55 ################################################################################
56
57 sub queue {
58     my $cgi = Bugzilla->cgi;
59     my $dbh = Bugzilla->dbh;
60     
61     validateStatus($cgi->param('status'));
62     validateGroup($cgi->param('group'));
63     
64     my $attach_join_clause = "flags.attach_id = attachments.attach_id";
65     if (Param("insidergroup") && !UserInGroup(Param("insidergroup"))) {
66         $attach_join_clause .= " AND attachments.isprivate < 1";
67     }
68
69     my $query = 
70     # Select columns describing each flag, the bug/attachment on which
71     # it has been set, who set it, and of whom they are requesting it.
72     " SELECT    flags.id, flagtypes.name,
73                 flags.status,
74                 flags.bug_id, bugs.short_desc,
75                 products.name, components.name,
76                 flags.attach_id, attachments.description,
77                 requesters.realname, requesters.login_name,
78                 requestees.realname, requestees.login_name,
79     " . $dbh->sql_date_format('flags.creation_date', '%Y.%m.%d %H:%i') .
80     # Use the flags and flagtypes tables for information about the flags,
81     # the bugs and attachments tables for target info, the profiles tables
82     # for setter and requestee info, the products/components tables
83     # so we can display product and component names, and the bug_group_map
84     # table to help us weed out secure bugs to which the user should not have
85     # access.
86     "
87       FROM           flags 
88            LEFT JOIN attachments
89                   ON ($attach_join_clause)
90           INNER JOIN flagtypes
91                   ON flags.type_id = flagtypes.id
92           INNER JOIN profiles AS requesters
93                   ON flags.setter_id = requesters.userid
94            LEFT JOIN profiles AS requestees
95                   ON flags.requestee_id  = requestees.userid
96           INNER JOIN bugs
97                   ON flags.bug_id = bugs.bug_id
98           INNER JOIN products
99                   ON bugs.product_id = products.id
100           INNER JOIN components
101                   ON bugs.component_id = components.id
102            LEFT JOIN bug_group_map AS bgmap
103                   ON bgmap.bug_id = bugs.bug_id
104                  AND bgmap.group_id NOT IN (" .
105                      join(', ', (-1, values(%{Bugzilla->user->groups}))) . ")
106            LEFT JOIN cc AS ccmap
107                   ON ccmap.who = $::userid
108                  AND ccmap.bug_id = bugs.bug_id\r
109     " .
110
111     # Weed out bug the user does not have access to
112     " WHERE     ((bgmap.group_id IS NULL) OR
113                  (ccmap.who IS NOT NULL AND cclist_accessible = 1) OR
114                  (bugs.reporter = $::userid AND bugs.reporter_accessible = 1) OR
115                  (bugs.assigned_to = $::userid) " .
116                  (Param('useqacontact') ? "OR
117                  (bugs.qa_contact = $::userid))" : ")");
118     
119     # Non-deleted flags only
120     $query .= " AND flags.is_active = 1 ";
121     
122     # Limit query to pending requests.
123     $query .= " AND flags.status = '?' " unless $cgi->param('status');
124
125     # The set of criteria by which we filter records to display in the queue.
126     my @criteria = ();
127     
128     # A list of columns to exclude from the report because the report conditions
129     # limit the data being displayed to exact matches for those columns.
130     # In other words, if we are only displaying "pending" , we don't
131     # need to display a "status" column in the report because the value for that
132     # column will always be the same.
133     my @excluded_columns = ();
134     
135     # Filter requests by status: "pending", "granted", "denied", "all" 
136     # (which means any), or "fulfilled" (which means "granted" or "denied").
137     if ($cgi->param('status')) {
138         if ($cgi->param('status') eq "+-") {
139             push(@criteria, "flags.status IN ('+', '-')");
140             push(@excluded_columns, 'status') unless $cgi->param('do_union');
141         }
142         elsif ($cgi->param('status') ne "all") {
143             push(@criteria, "flags.status = '" . $cgi->param('status') . "'");
144             push(@excluded_columns, 'status') unless $cgi->param('do_union');
145         }
146     }
147     
148     # Filter results by exact email address of requester or requestee.
149     if (defined $cgi->param('requester') && $cgi->param('requester') ne "") {
150         push(@criteria, $dbh->sql_istrcmp('requesters.login_name',
151                                           SqlQuote($cgi->param('requester'))));
152         push(@excluded_columns, 'requester') unless $cgi->param('do_union');
153     }
154     if (defined $cgi->param('requestee') && $cgi->param('requestee') ne "") {
155         if ($cgi->param('requestee') ne "-") {
156             push(@criteria, $dbh->sql_istrcmp('requestees.login_name',
157                             SqlQuote($cgi->param('requestee'))));
158         }
159         else { push(@criteria, "flags.requestee_id IS NULL") }
160         push(@excluded_columns, 'requestee') unless $cgi->param('do_union');
161     }
162     
163     # Filter results by exact product or component.
164     if (defined $cgi->param('product') && $cgi->param('product') ne "") {
165         my $product_id = get_product_id($cgi->param('product'));
166         if ($product_id) {
167             push(@criteria, "bugs.product_id = $product_id");
168             push(@excluded_columns, 'product') unless $cgi->param('do_union');
169             if (defined $cgi->param('component') && $cgi->param('component') ne "") {
170                 my $component_id = get_component_id($product_id, $cgi->param('component'));
171                 if ($component_id) {
172                     push(@criteria, "bugs.component_id = $component_id");
173                     push(@excluded_columns, 'component') unless $cgi->param('do_union');
174                 }
175                 else { ThrowUserError("component_not_valid", { 'product' => $cgi->param('product'),
176                                                                'name' => $cgi->param('component') }) }
177             }
178         }
179         else { ThrowUserError("product_doesnt_exist", { 'product' => $cgi->param('product') }) }
180     }
181     
182     # Filter results by flag types.
183     my $form_type = $cgi->param('type');
184     if (defined $form_type && !grep($form_type eq $_, ("", "all"))) {
185         # Check if any matching types are for attachments.  If not, don't show
186         # the attachment column in the report.
187         my $types = Bugzilla::FlagType::match({ 'name' => $form_type });
188         my $has_attachment_type = 0;
189         foreach my $type (@$types) {
190             if ($type->{'target_type'} eq "attachment") {
191                 $has_attachment_type = 1;
192                 last;
193             }
194         }
195         if (!$has_attachment_type) { push(@excluded_columns, 'attachment') }
196         
197         push(@criteria, "flagtypes.name = " . SqlQuote($form_type));
198         push(@excluded_columns, 'type') unless $cgi->param('do_union');
199     }
200     
201     # Add the criteria to the query.  We do an intersection by default 
202     # but do a union if the "do_union" URL parameter (for which there is no UI 
203     # because it's an advanced feature that people won't usually want) is true.
204     my $and_or = $cgi->param('do_union') ? " OR " : " AND ";
205     $query .= " AND (" . join($and_or, @criteria) . ") " if scalar(@criteria);
206     
207     # Group the records by flag ID so we don't get multiple rows of data
208     # for each flag.  This is only necessary because of the code that
209     # removes flags on bugs the user is unauthorized to access.
210     $query .= ' ' . $dbh->sql_group_by('flags.id',
211                'flagtypes.name, flags.status, flags.bug_id, bugs.short_desc,
212                 products.name, components.name, flags.attach_id,
213                 attachments.description, requesters.realname,
214                 requesters.login_name, requestees.realname,
215                 requestees.login_name, flags.creation_date,
216                 cclist_accessible, bugs.reporter, bugs.reporter_accessible,
217                 bugs.assigned_to');
218
219     # Group the records, in other words order them by the group column
220     # so the loop in the display template can break them up into separate
221     # tables every time the value in the group column changes.
222
223     my $form_group = $cgi->param('group');
224     $form_group ||= "requestee";
225     if ($form_group eq "requester") {
226         $query .= " ORDER BY requesters.realname, requesters.login_name";
227     }
228     elsif ($form_group eq "requestee") {
229         $query .= " ORDER BY requestees.realname, requestees.login_name";
230     }
231     elsif ($form_group eq "category") {
232         $query .= " ORDER BY products.name, components.name";
233     }
234     elsif ($form_group eq "type") {
235         $query .= " ORDER BY flagtypes.name";
236     }
237
238     # Order the records (within each group).
239     $query .= " , flags.creation_date";
240     
241     # Pass the query to the template for use when debugging this script.
242     $vars->{'query'} = $query;
243     $vars->{'debug'} = $cgi->param('debug') ? 1 : 0;
244     
245     SendSQL($query);
246     my @requests = ();
247     while (MoreSQLData()) {
248         my @data = FetchSQLData();
249         my $request = {
250           'id'              => $data[0] , 
251           'type'            => $data[1] , 
252           'status'          => $data[2] , 
253           'bug_id'          => $data[3] , 
254           'bug_summary'     => $data[4] , 
255           'category'        => "$data[5]: $data[6]" , 
256           'attach_id'       => $data[7] , 
257           'attach_summary'  => $data[8] ,
258           'requester'       => ($data[9] ? "$data[9] <$data[10]>" : $data[10]) , 
259           'requestee'       => ($data[11] ? "$data[11] <$data[12]>" : $data[12]) , 
260           'created'         => $data[13]
261         };
262         push(@requests, $request);
263     }
264
265     # Get a list of request type names to use in the filter form.
266     my @types = ("all");
267     SendSQL("SELECT DISTINCT(name) FROM flagtypes ORDER BY name");
268     push(@types, FetchOneColumn()) while MoreSQLData();
269     
270     # products and components and the function used to modify the components
271     # menu when the products menu changes; used by the template to populate
272     # the menus and keep the components menu consistent with the products menu
273     GetVersionTable();
274     my $selectable = GetSelectableProductHash();
275     $vars->{'products'} = $selectable->{legal_products};
276     $vars->{'components'} = $selectable->{legal_components};
277     $vars->{'components_by_product'} = $selectable->{components_by_product};
278     
279     $vars->{'excluded_columns'} = \@excluded_columns;
280     $vars->{'group_field'} = $form_group;
281     $vars->{'requests'} = \@requests;
282     $vars->{'types'} = \@types;
283
284     # Return the appropriate HTTP response headers.
285     print Bugzilla->cgi->header();
286
287     # Generate and return the UI (HTML page) from the appropriate template.
288     $template->process("request/queue.html.tmpl", $vars)
289       || ThrowTemplateError($template->error());
290 }
291
292 ################################################################################
293 # Data Validation / Security Authorization
294 ################################################################################
295
296 sub validateStatus {
297     my $status = $_[0];
298     return if !defined $status;
299     
300     grep($status eq $_, qw(? +- + - all))
301       || ThrowCodeError("flag_status_invalid",
302                         { status => $status });
303 }
304
305 sub validateGroup {
306     my $group = $_[0];
307     return if !defined $group;
308     
309     grep($group eq $_, qw(requester requestee category type))
310       || ThrowCodeError("request_queue_group_invalid", 
311                         { group => $group });
312 }
313