request.cgi 13.3 KB
Newer Older
1
#!/usr/bin/perl -T
2 3 4
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
#
6 7
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
8

9
use 5.10.1;
10
use strict;
11 12
use warnings;

13
use lib qw(. lib);
14

15
use Bugzilla;
16 17
use Bugzilla::Util;
use Bugzilla::Error;
18 19 20
use Bugzilla::Flag;
use Bugzilla::FlagType;
use Bugzilla::User;
21 22
use Bugzilla::Product;
use Bugzilla::Component;
23 24

# Make sure the user is logged in.
25
my $user = Bugzilla->login();
26 27
my $cgi  = Bugzilla->cgi;

28 29
# Force the script to run against the shadow DB. We already validated credentials.
Bugzilla->switch_to_shadow_db;
30
my $template = Bugzilla->template;
31 32 33 34 35 36
my $action   = $cgi->param('action') || '';
my $format   = $template->get_format(
  'request/queue',
  scalar($cgi->param('format')),
  scalar($cgi->param('ctype'))
);
37

38 39
$cgi->set_dated_content_disp("inline", "requests", $format->{extension});
print $cgi->header($format->{'ctype'});
40

41 42
my $fields;
$fields->{'requester'}->{'type'} = 'single';
43

44
# If the user doesn't restrict their search to requests from the wind
45
# (requestee ne '-'), include the requestee for completion.
46 47
unless (defined $cgi->param('requestee') && $cgi->param('requestee') eq '-') {
  $fields->{'requestee'}->{'type'} = 'single';
48 49
}

50
Bugzilla::User::match_field($fields);
51

52
if ($action eq 'queue') {
53
  queue($format);
54 55
}
else {
56
  my $flagtypes = get_flag_types();
57
  my @types     = ('all', @$flagtypes);
58 59 60 61 62 63 64 65 66

  my $vars = {};
  $vars->{'types'}    = \@types;
  $vars->{'requests'} = {};

  my %components;
  foreach my $prod (@{$user->get_selectable_products}) {
    foreach my $comp (@{$prod->components}) {
      $components{$comp->name} = 1;
67
    }
68 69
  }
  $vars->{'components'} = [sort { $a cmp $b } keys %components];
70

71 72
  $template->process($format->{'template'}, $vars)
    || ThrowTemplateError($template->error());
73
}
74 75 76 77 78 79 80
exit;

################################################################################
# Functions
################################################################################

sub queue {
81 82 83 84 85 86 87 88 89 90 91 92 93
  my $format   = shift;
  my $cgi      = Bugzilla->cgi;
  my $dbh      = Bugzilla->dbh;
  my $template = Bugzilla->template;
  my $user     = Bugzilla->user;
  my $userid   = $user->id;
  my $vars     = {};

  my $status     = validateStatus($cgi->param('status'));
  my $form_group = validateGroup($cgi->param('group'));

  my $query =

94 95 96 97 98 99 100 101
    # Select columns describing each flag, the bug/attachment on which
    # it has been set, who set it, and of whom they are requesting it.
    " SELECT    flags.id, flagtypes.name,
                flags.status,
                flags.bug_id, bugs.short_desc,
                products.name, components.name,
                flags.attach_id, attachments.description,
                requesters.realname, requesters.login_name,
102
                requestees.realname, requestees.login_name, COUNT(privs.group_id),
103
    " . $dbh->sql_date_format('flags.modification_date', '%Y.%m.%d %H:%i') .
104

105 106 107 108
    # Use the flags and flagtypes tables for information about the flags,
    # the bugs and attachments tables for target info, the profiles tables
    # for setter and requestee info, the products/components tables
    # so we can display product and component names, and the bug_group_map
109 110 111 112 113
    # table to help us weed out secure bugs to which the user should not have
    # access.
    "
      FROM           flags 
           LEFT JOIN attachments
114
                  ON flags.attach_id = attachments.attach_id
115
          INNER JOIN flagtypes
116
                  ON flags.type_id = flagtypes.id
117
          INNER JOIN profiles AS requesters
118
                  ON flags.setter_id = requesters.userid
119
           LEFT JOIN profiles AS requestees
120
                  ON flags.requestee_id  = requestees.userid
121
          INNER JOIN bugs
122
                  ON flags.bug_id = bugs.bug_id
123
          INNER JOIN products
124
                  ON bugs.product_id = products.id
125
          INNER JOIN components
126
                  ON bugs.component_id = components.id
127 128
           LEFT JOIN bug_group_map AS privs
                  ON privs.bug_id = bugs.bug_id
129
           LEFT JOIN cc AS ccmap
130 131
                  ON ccmap.who = $userid
                 AND ccmap.bug_id = bugs.bug_id
132 133 134 135
           LEFT JOIN bug_group_map AS bgmap
                  ON bgmap.bug_id = bugs.bug_id
    ";

136 137 138 139 140 141 142 143 144 145 146
  if (Bugzilla->params->{or_groups}) {
    $query .= " AND bgmap.group_id IN (" . $user->groups_as_string . ")";
    $query .= " WHERE     (privs.group_id IS NULL OR bgmap.group_id IS NOT NULL OR";
  }
  else {
    $query .= " AND bgmap.group_id NOT IN (" . $user->groups_as_string . ")";
    $query .= " WHERE     (bgmap.group_id IS NULL OR";
  }

  # Weed out bug the user does not have access to
  $query .= "            (ccmap.who IS NOT NULL AND cclist_accessible = 1) OR
147
                 (bugs.reporter = $userid AND bugs.reporter_accessible = 1) OR
148 149 150 151 152 153 154 155 156
                 (bugs.assigned_to = $userid) " . (
    Bugzilla->params->{'useqacontact'}
    ? "OR
                 (bugs.qa_contact = $userid))"
    : ")"
  );

  unless ($user->is_insider) {
    $query .= " AND (attachments.attach_id IS NULL
157 158
                         OR attachments.isprivate = 0
                         OR attachments.submitter_id = $userid)";
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
  }

  # Limit query to pending requests.
  $query .= " AND flags.status = '?' " unless $status;

  # The set of criteria by which we filter records to display in the queue.
  my @criteria = ();

  # A list of columns to exclude from the report because the report conditions
  # limit the data being displayed to exact matches for those columns.
  # In other words, if we are only displaying "pending" , we don't
  # need to display a "status" column in the report because the value for that
  # column will always be the same.
  my @excluded_columns = ();
  my $do_union         = $cgi->param('do_union');

  # Filter results by exact email address of requester or requestee.
  if (defined $cgi->param('requester') && $cgi->param('requester') ne "") {
    my $requester = $dbh->quote($cgi->param('requester'));
    trick_taint($requester);    # Quoted above
    push(@criteria, $dbh->sql_istrcmp('requesters.login_name', $requester));
    push(@excluded_columns, 'requester') unless $do_union;
  }
  if (defined $cgi->param('requestee') && $cgi->param('requestee') ne "") {
    if ($cgi->param('requestee') ne "-") {
      my $requestee = $dbh->quote($cgi->param('requestee'));
      trick_taint($requestee);    # Quoted above
      push(@criteria, $dbh->sql_istrcmp('requestees.login_name', $requestee));
187
    }
188 189
    else {
      push(@criteria, "flags.requestee_id IS NULL");
190
    }
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
    push(@excluded_columns, 'requestee') unless $do_union;
  }

  # If the user wants requester = foo OR requestee = bar, we have to join
  # these criteria separately as all other criteria use AND.
  if (@criteria == 2 && $do_union) {
    my $union = join(' OR ', @criteria);
    @criteria = ("($union)");
  }

  # Filter requests by status: "pending", "granted", "denied", "all"
  # (which means any), or "fulfilled" (which means "granted" or "denied").
  if ($status) {
    if ($status eq "+-") {
      push(@criteria,         "flags.status IN ('+', '-')");
      push(@excluded_columns, 'status');
207
    }
208 209 210
    elsif ($status ne "all") {
      push(@criteria,         "flags.status = '$status'");
      push(@excluded_columns, 'status');
211
    }
212 213 214 215 216 217 218 219 220 221 222 223 224 225
  }

  # Filter results by exact product or component.
  if (defined $cgi->param('product') && $cgi->param('product') ne "") {
    my $product = Bugzilla::Product->check(scalar $cgi->param('product'));
    push(@criteria,         "bugs.product_id = " . $product->id);
    push(@excluded_columns, 'product');
    if (defined $cgi->param('component') && $cgi->param('component') ne "") {
      my $component
        = Bugzilla::Component->check({
        product => $product, name => scalar $cgi->param('component')
        });
      push(@criteria,         "bugs.component_id = " . $component->id);
      push(@excluded_columns, 'component');
226
    }
227
  }
228

229 230 231
  # Filter results by flag types.
  my $form_type = $cgi->param('type');
  if (defined $form_type && !grep($form_type eq $_, ("", "all"))) {
232

233 234 235 236 237 238 239 240 241 242 243 244
    # Check if any matching types are for attachments.  If not, don't show
    # the attachment column in the report.
    my $has_attachment_type = Bugzilla::FlagType::count(
      {'name' => $form_type, 'target_type' => 'attachment'});

    if (!$has_attachment_type) { push(@excluded_columns, 'attachment') }

    my $quoted_form_type = $dbh->quote($form_type);
    trick_taint($quoted_form_type);    # Already SQL quoted
    push(@criteria,         "flagtypes.name = " . $quoted_form_type);
    push(@excluded_columns, 'type');
  }
245

246
  $query .= ' AND ' . join(' AND ', @criteria) if scalar(@criteria);
247

248 249 250 251 252
  # Group the records by flag ID so we don't get multiple rows of data
  # for each flag.  This is only necessary because of the code that
  # removes flags on bugs the user is unauthorized to access.
  $query .= ' ' . $dbh->sql_group_by(
    'flags.id', 'flagtypes.name, flags.status, flags.bug_id, bugs.short_desc,
253 254 255
                products.name, components.name, flags.attach_id,
                attachments.description, requesters.realname,
                requesters.login_name, requestees.realname,
256
                requestees.login_name, flags.modification_date,
257
                cclist_accessible, bugs.reporter, bugs.reporter_accessible,
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
                bugs.assigned_to'
  );

  # Group the records, in other words order them by the group column
  # so the loop in the display template can break them up into separate
  # tables every time the value in the group column changes.

  $form_group ||= "requestee";
  if ($form_group eq "requester") {
    $query .= " ORDER BY requesters.realname, requesters.login_name";
  }
  elsif ($form_group eq "requestee") {
    $query .= " ORDER BY requestees.realname, requestees.login_name";
  }
  elsif ($form_group eq "category") {
    $query .= " ORDER BY products.name, components.name";
  }
  elsif ($form_group eq "type") {
    $query .= " ORDER BY flagtypes.name";
  }

  # Order the records (within each group).
  $query .= " , flags.modification_date";

  # Pass the query to the template for use when debugging this script.
  $vars->{'query'} = $query;
  $vars->{'debug'} = $cgi->param('debug') ? 1 : 0;

  my $results  = $dbh->selectall_arrayref($query);
  my @requests = ();
  foreach my $result (@$results) {
    my @data    = @$result;
    my $request = {
      'id'             => $data[0],
      'type'           => $data[1],
      'status'         => $data[2],
      'bug_id'         => $data[3],
      'bug_summary'    => $data[4],
      'category'       => "$data[5]: $data[6]",
      'attach_id'      => $data[7],
      'attach_summary' => $data[8],
      'requester'      => ($data[9] ? "$data[9] <$data[10]>" : $data[10]),
      'requestee'      => ($data[11] ? "$data[11] <$data[12]>" : $data[12]),
      'restricted'     => $data[13] ? 1 : 0,
      'created'        => $data[14]
    };
    push(@requests, $request);
  }

  # Get a list of request type names to use in the filter form.
  my @types     = ("all");
  my $flagtypes = get_flag_types();
  push(@types, @$flagtypes);

  $vars->{'excluded_columns'} = \@excluded_columns;
  $vars->{'group_field'}      = $form_group;
  $vars->{'requests'}         = \@requests;
  $vars->{'types'}            = \@types;

  # This code is needed to populate the Product and Component select fields.
  my ($products, %components);
  if (Bugzilla->params->{useclassification}) {
    foreach my $class (@{$user->get_selectable_classifications}) {
      push @$products, @{$user->get_selectable_products($class->id)};
322
    }
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
  }
  else {
    $products = $user->get_selectable_products;
  }

  foreach my $product (@$products) {
    $components{$_->name} = 1 foreach @{$product->components};
  }
  $vars->{'products'}   = $products;
  $vars->{'components'} = [sort keys %components];

  $vars->{'urlquerypart'} = $cgi->canonicalise_query('ctype');

  # Generate and return the UI (HTML page) from the appropriate template.
  $template->process($format->{'template'}, $vars)
    || ThrowTemplateError($template->error());
339 340 341 342 343 344 345
}

################################################################################
# Data Validation / Security Authorization
################################################################################

sub validateStatus {
346 347
  my $status = shift;
  return if !defined $status;
348

349 350 351 352
  grep($status eq $_, qw(? +- + - all))
    || ThrowUserError("flag_status_invalid", {status => $status});
  trick_taint($status);
  return $status;
353 354 355
}

sub validateGroup {
356 357
  my $group = shift;
  return if !defined $group;
358

359 360 361 362
  grep($group eq $_, qw(requester requestee category type))
    || ThrowUserError("request_queue_group_invalid", {group => $group});
  trick_taint($group);
  return $group;
363 364
}

365 366 367
# Returns all flag types which have at least one flag of this type.
# If a flag type is inactive but still has flags, we want it.
sub get_flag_types {
368 369 370
  my $dbh        = Bugzilla->dbh;
  my $flag_types = $dbh->selectcol_arrayref(
    'SELECT DISTINCT name
371 372 373
                                                 FROM flagtypes
                                                WHERE flagtypes.id IN
                                                      (SELECT DISTINCT type_id FROM flags)
374 375 376
                                             ORDER BY name'
  );
  return $flag_types;
377
}