2009-04-16 Eric Roman <eroman@chromium.org>
[WebKit-https.git] / BugsSite / editmilestones.cgi
1 #!/usr/bin/perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
3
4 #
5 # This is a script to edit the target milestones. It is largely a copy of
6 # the editversions.cgi script, since the two fields were set up in a
7 # very similar fashion.
8 #
9 # (basically replace each occurance of 'milestone' with 'version', and
10 # you'll have the original script)
11 #
12 # Matt Masson <matthew@zeroknowledge.com>
13 #
14 # Contributors : Gavin Shelley <bugzilla@chimpychompy.org>
15 #                Frédéric Buclin <LpSolit@gmail.com>
16 #
17
18
19 use strict;
20 use lib ".";
21
22 require "CGI.pl";
23 require "globals.pl";
24
25 use Bugzilla::Constants;
26 use Bugzilla::Config qw(:DEFAULT $datadir);
27 use Bugzilla::User;
28
29 use vars qw($template $vars);
30
31 my $cgi = Bugzilla->cgi;
32
33 # TestProduct:  just returns if the specified product does exists
34 # CheckProduct: same check, optionally  emit an error text
35 # TestMilestone:  just returns if the specified product/version combination exists
36 # CheckMilestone: same check, optionally emit an error text
37
38 sub TestProduct ($)
39 {
40     my $product = shift;
41
42     trick_taint($product);
43
44     # does the product exist?
45     my $dbh = Bugzilla->dbh;
46     my $sth = $dbh->prepare_cached("SELECT name
47                                     FROM products
48                                     WHERE name = ?");
49     $sth->execute($product);
50
51     my ($row) = $sth->fetchrow_array;
52
53     $sth->finish;
54
55     return $row;
56 }
57
58 sub CheckProduct ($)
59 {
60     my $product = shift;
61
62     # do we have a product?
63     unless ($product) {
64         ThrowUserError('product_not_specified');    
65     }
66
67     # Does it exist in the DB?
68     unless (TestProduct $product) {
69         ThrowUserError('product_doesnt_exist',
70                        {'product' => $product});
71     }
72 }
73
74 sub TestMilestone ($$)
75 {
76     my ($product, $milestone) = @_;
77
78     my $dbh = Bugzilla->dbh;
79
80     # does the product exist?
81     my $sth = $dbh->prepare_cached("
82              SELECT products.name, value
83              FROM milestones
84              INNER JOIN products
85                 ON milestones.product_id = products.id
86              WHERE products.name = ?
87                AND value = ?");
88
89     trick_taint($product);
90     trick_taint($milestone);
91
92     $sth->execute($product, $milestone);
93
94     my ($db_milestone) = $sth->fetchrow_array();
95
96     $sth->finish();
97
98     return $db_milestone;
99 }
100
101 sub CheckMilestone ($$)
102 {
103     my ($product, $milestone) = @_;
104
105     # do we have the milestone and product combination?
106     unless ($milestone) {
107         ThrowUserError('milestone_not_specified');
108     }
109
110     CheckProduct($product);
111
112     unless (TestMilestone $product, $milestone) {
113         ThrowUserError('milestone_not_valid',
114                        {'product' => $product,
115                         'milestone' => $milestone});
116     }
117 }
118
119 sub CheckSortkey ($$)
120 {
121     my ($milestone, $sortkey) = @_;
122     # Keep a copy in case detaint_signed() clears the sortkey
123     my $stored_sortkey = $sortkey;
124
125     if (!detaint_signed($sortkey) || $sortkey < -32768 || $sortkey > 32767) {
126         ThrowUserError('milestone_sortkey_invalid',
127                        {'name' => $milestone,
128                         'sortkey' => $stored_sortkey});
129     }
130
131     return $sortkey;
132 }
133
134 #
135 # Preliminary checks:
136 #
137
138 my $user = Bugzilla->login(LOGIN_REQUIRED);
139 my $whoid = $user->id;
140
141 print Bugzilla->cgi->header();
142
143 UserInGroup("editcomponents")
144   || ThrowUserError("auth_failure", {group  => "editcomponents",
145                                      action => "edit",
146                                      object => "milestones"});
147
148 #
149 # often used variables
150 #
151 my $product = trim($cgi->param('product')     || '');
152 my $milestone = trim($cgi->param('milestone') || '');
153 my $sortkey = trim($cgi->param('sortkey')     || '0');
154 my $action  = trim($cgi->param('action')      || '');
155
156 #
157 # product = '' -> Show nice list of milestones
158 #
159
160 unless ($product) {
161
162     my @products = ();
163
164     my $dbh = Bugzilla->dbh;
165
166     my $sth = $dbh->prepare_cached('SELECT products.name, products.description
167                                     FROM products 
168                                     ORDER BY products.name');
169
170     my $data = $dbh->selectall_arrayref($sth);
171
172     foreach my $aref (@$data) {
173
174         my $prod = {};
175
176         my ($name, $description) = @$aref;
177
178         $prod->{'name'} = $name;
179         $prod->{'description'} = $description;
180
181         push(@products, $prod);
182     }
183
184     $vars->{'products'} = \@products;
185     $template->process("admin/milestones/select-product.html.tmpl",
186                        $vars)
187       || ThrowTemplateError($template->error());
188
189     exit;
190 }
191
192
193
194 #
195 # action='' -> Show nice list of milestones
196 #
197
198 unless ($action) {
199
200     CheckProduct($product);
201     my $product_id = get_product_id($product);
202     my @milestones = ();
203
204     my $dbh = Bugzilla->dbh;
205
206     my $sth = $dbh->prepare_cached('SELECT value, sortkey
207                                     FROM milestones
208                                     WHERE product_id = ?
209                                     ORDER BY sortkey, value');
210
211     my $data = $dbh->selectall_arrayref($sth,
212                                         undef,
213                                         $product_id);
214
215     foreach my $aref (@$data) {
216
217         my $milestone = {};
218         my ($name, $sortkey) = @$aref;
219
220         $milestone->{'name'} = $name;
221         $milestone->{'sortkey'} = $sortkey;
222
223         push(@milestones, $milestone);
224     }
225
226     $vars->{'product'} = $product;
227     $vars->{'milestones'} = \@milestones;
228     $template->process("admin/milestones/list.html.tmpl",
229                        $vars)
230       || ThrowTemplateError($template->error());
231
232     exit;
233 }
234
235
236
237
238 #
239 # action='add' -> present form for parameters for new milestone
240 #
241 # (next action will be 'new')
242 #
243
244 if ($action eq 'add') {
245
246     CheckProduct($product);
247     my $product_id = get_product_id($product);
248
249     $vars->{'product'} = $product;
250     $template->process("admin/milestones/create.html.tmpl",
251                        $vars)
252       || ThrowTemplateError($template->error());
253
254     exit;
255 }
256
257
258
259 #
260 # action='new' -> add milestone entered in the 'action=add' screen
261 #
262
263 if ($action eq 'new') {
264
265     CheckProduct($product);
266     my $product_id = get_product_id($product);
267
268     # Cleanups and valididy checks
269     unless ($milestone) {
270         ThrowUserError('milestone_blank_name',
271                        {'name' => $milestone});
272     }
273
274     if (length($milestone) > 20) {
275         ThrowUserError('milestone_name_too_long',
276                        {'name' => $milestone});
277     }
278
279     $sortkey = CheckSortkey($milestone, $sortkey);
280
281     if (TestMilestone($product, $milestone)) {
282         ThrowUserError('milestone_already_exists',
283                        {'name' => $milestone,
284                         'product' => $product});
285     }
286
287     # Add the new milestone
288     my $dbh = Bugzilla->dbh;
289     trick_taint($milestone);
290     $dbh->do('INSERT INTO milestones ( value, product_id, sortkey )
291               VALUES ( ?, ?, ? )',
292              undef,
293              $milestone,
294              $product_id,
295              $sortkey);
296
297     # Make versioncache flush
298     unlink "$datadir/versioncache";
299
300     $vars->{'name'} = $milestone;
301     $vars->{'product'} = $product;
302     $template->process("admin/milestones/created.html.tmpl",
303                        $vars)
304       || ThrowTemplateError($template->error());
305
306     exit;
307 }
308
309
310
311
312 #
313 # action='del' -> ask if user really wants to delete
314 #
315 # (next action would be 'delete')
316 #
317
318 if ($action eq 'del') {
319     CheckMilestone($product, $milestone);
320     my $product_id = get_product_id($product);
321     my $dbh = Bugzilla->dbh;
322
323     $vars->{'default_milestone'} =
324       $dbh->selectrow_array('SELECT defaultmilestone
325                              FROM products WHERE id = ?',
326                              undef, $product_id);
327
328     trick_taint($milestone);
329     $vars->{'name'} = $milestone;
330     $vars->{'product'} = $product;
331
332     # The default milestone cannot be deleted.
333     if ($vars->{'default_milestone'} eq $milestone) {
334         ThrowUserError("milestone_is_default", $vars);
335     }
336
337     $vars->{'bug_count'} =
338       $dbh->selectrow_array("SELECT COUNT(bug_id) FROM bugs
339                              WHERE product_id = ? AND target_milestone = ?",
340                              undef, ($product_id, $milestone)) || 0;
341
342     $template->process("admin/milestones/confirm-delete.html.tmpl", $vars)
343       || ThrowTemplateError($template->error());
344     exit;
345 }
346
347
348
349 #
350 # action='delete' -> really delete the milestone
351 #
352
353 if ($action eq 'delete') {
354     CheckMilestone($product, $milestone);
355     my $product_id = get_product_id($product);
356     my $dbh = Bugzilla->dbh;
357
358     my $default_milestone =
359       $dbh->selectrow_array("SELECT defaultmilestone
360                              FROM products WHERE id = ?",
361                              undef, $product_id);
362
363     trick_taint($milestone);
364     $vars->{'name'} = $milestone;
365     $vars->{'product'} = $product;
366
367     # The default milestone cannot be deleted.
368     if ($milestone eq $default_milestone) {
369         ThrowUserError("milestone_is_default", $vars);
370     }
371
372     # We don't want to delete bugs when deleting a milestone.
373     # Bugs concerned are reassigned to the default milestone.
374     my $bug_ids =
375       $dbh->selectcol_arrayref("SELECT bug_id FROM bugs
376                                 WHERE product_id = ? AND target_milestone = ?",
377                                 undef, ($product_id, $milestone));
378
379     my $nb_bugs = scalar(@$bug_ids);
380     if ($nb_bugs) {
381         my $timestamp = $dbh->selectrow_array("SELECT NOW()");
382         foreach my $bug_id (@$bug_ids) {
383             $dbh->do("UPDATE bugs SET target_milestone = ?,
384                       delta_ts = ? WHERE bug_id = ?",
385                       undef, ($default_milestone, $timestamp, $bug_id));
386             # We have to update the 'bugs_activity' table too.
387             LogActivityEntry($bug_id, 'target_milestone', $milestone,
388                              $default_milestone, $whoid, $timestamp);
389         }
390     }
391
392     $vars->{'bug_count'} = $nb_bugs;
393
394     $dbh->do("DELETE FROM milestones WHERE product_id = ? AND value = ?",
395              undef, ($product_id, $milestone));
396
397     unlink "$datadir/versioncache";
398
399     $template->process("admin/milestones/deleted.html.tmpl", $vars)
400       || ThrowTemplateError($template->error());
401     exit;
402 }
403
404
405
406 #
407 # action='edit' -> present the edit milestone form
408 #
409 # (next action would be 'update')
410 #
411
412 if ($action eq 'edit') {
413
414     CheckMilestone($product, $milestone);
415     my $product_id = get_product_id($product);
416
417     my $dbh = Bugzilla->dbh;
418
419     my $sth = $dbh->prepare_cached('SELECT sortkey
420                                     FROM milestones
421                                     WHERE product_id = ?
422                                     AND value = ?');
423
424     trick_taint($milestone);
425
426     $vars->{'sortkey'} = $dbh->selectrow_array($sth,
427                                                undef,
428                                                $product_id,
429                                                $milestone) || 0;
430
431     $vars->{'name'} = $milestone;
432     $vars->{'product'} = $product;
433
434     $template->process("admin/milestones/edit.html.tmpl",
435                        $vars)
436       || ThrowTemplateError($template->error());
437
438     exit;
439 }
440
441
442
443 #
444 # action='update' -> update the milestone
445 #
446
447 if ($action eq 'update') {
448
449     my $milestoneold = trim($cgi->param('milestoneold') || '');
450     my $sortkeyold = trim($cgi->param('sortkeyold')     || '0');
451
452     CheckMilestone($product, $milestoneold);
453     my $product_id = get_product_id($product);
454
455     if (length($milestone) > 20) {
456         ThrowUserError('milestone_name_too_long',
457                        {'name' => $milestone});
458     }
459
460     my $dbh = Bugzilla->dbh;
461
462     $dbh->bz_lock_tables('bugs WRITE',
463                          'milestones WRITE',
464                          'products WRITE');
465
466     if ($sortkey ne $sortkeyold) {
467         $sortkey = CheckSortkey($milestone, $sortkey);
468
469         trick_taint($milestoneold);
470
471         $dbh->do('UPDATE milestones SET sortkey = ?
472                   WHERE product_id = ?
473                   AND value = ?',
474                  undef,
475                  $sortkey,
476                  $product_id,
477                  $milestoneold);
478
479         unlink "$datadir/versioncache";
480         $vars->{'updated_sortkey'} = 1;
481         $vars->{'sortkey'} = $sortkey;
482     }
483
484     if ($milestone ne $milestoneold) {
485         unless ($milestone) {
486             ThrowUserError('milestone_blank_name');
487         }
488         if (TestMilestone($product, $milestone)) {
489             ThrowUserError('milestone_already_exists',
490                            {'name' => $milestone,
491                             'product' => $product});
492         }
493
494         trick_taint($milestone);
495         trick_taint($milestoneold);
496
497         $dbh->do('UPDATE bugs
498                   SET target_milestone = ?
499                   WHERE target_milestone = ?
500                   AND product_id = ?',
501                  undef,
502                  $milestone,
503                  $milestoneold,
504                  $product_id);
505
506         $dbh->do("UPDATE milestones
507                   SET value = ?
508                   WHERE product_id = ?
509                   AND value = ?",
510                  undef,
511                  $milestone,
512                  $product_id,
513                  $milestoneold);
514
515         $dbh->do("UPDATE products
516                   SET defaultmilestone = ?
517                   WHERE id = ?
518                   AND defaultmilestone = ?",
519                  undef,
520                  $milestone,
521                  $product_id,
522                  $milestoneold);
523
524         unlink "$datadir/versioncache";
525
526         $vars->{'updated_name'} = 1;
527     }
528
529     $dbh->bz_unlock_tables();
530
531     $vars->{'name'} = $milestone;
532     $vars->{'product'} = $product;
533     $template->process("admin/milestones/updated.html.tmpl",
534                        $vars)
535       || ThrowTemplateError($template->error());
536
537     exit;
538 }
539
540
541 #
542 # No valid action found
543 #
544 ThrowUserError('no_valid_action', {'field' => "target_milestone"});