process_bug.cgi 37.3 KB
Newer Older
1 2
#!/usr/bonsaitools/bin/perl -w
# -*- Mode: perl; indent-tabs-mode: nil -*-
terry%netscape.com's avatar
terry%netscape.com committed
3
#
4 5 6 7 8 9 10 11 12 13
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
terry%netscape.com's avatar
terry%netscape.com committed
14
# The Original Code is the Bugzilla Bug Tracking System.
15
#
terry%netscape.com's avatar
terry%netscape.com committed
16
# The Initial Developer of the Original Code is Netscape Communications
17 18 19 20
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
terry%netscape.com's avatar
terry%netscape.com committed
21
# Contributor(s): Terry Weissman <terry@mozilla.org>
22
#                 Dan Mosedale <dmose@mozilla.org>
23
#                 Dave Miller <justdave@syndicomm.com>
terry%netscape.com's avatar
terry%netscape.com committed
24

25 26 27
use diagnostics;
use strict;

28 29 30
my $UserInEditGroupSet = -1;
my $UserInCanConfirmGroupSet = -1;

31
require "CGI.pl";
32
use RelationSet;
33 34 35 36 37

# Shut up misguided -w warnings about "used only once":

use vars %::versions,
    %::components,
38
    %::COOKIE,
39
    %::MFORM,
40 41 42 43
    %::legal_keywords,
    %::legal_opsys,
    %::legal_platform,
    %::legal_priority,
44
    %::target_milestone,
45
    %::legal_severity;
46

47
my $whoid = confirm_login();
48

49 50
my $requiremilestone = 0;

51 52 53 54 55 56 57 58 59 60
######################################################################
# Begin Data/Security Validation
######################################################################

# Create a list of IDs of all bugs being modified in this request.
# This list will either consist of a single bug number from the "id"
# form/URL field or a series of numbers from multiple form/URL fields
# named "id_x" where "x" is the bug number.
my @idlist;
if (defined $::FORM{'id'}) {
61
    push @idlist, $::FORM{'id'};
62
} else {
63 64 65 66
    foreach my $i (keys %::FORM) {
        if ($i =~ /^id_([1-9][0-9]*)/) {
            push @idlist, $1;
        }
67 68 69 70 71 72
    }
}

# For each bug being modified, make sure its ID is a valid bug number 
# representing an existing bug that the user is authorized to access.
foreach my $id (@idlist) {
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
    ValidateBugID($id);
}

# If the user has a bug list and is processing one bug, then after
# we process the bug we are going to show them the next bug on their
# list.  Thus we have to make sure this bug ID is also valid,
# since a malicious cracker might alter their cookies for the purpose
# gaining access to bugs they are not authorized to access.
if ( $::COOKIE{"BUGLIST"} ne "" && defined $::FORM{'id'} ) {
    my @buglist = split( /:/ , $::COOKIE{"BUGLIST"} );
    my $idx = lsearch( \@buglist , $::FORM{"id"} );
    if ($idx < $#buglist) {
        my $nextbugid = $buglist[$idx + 1];
        ValidateBugID($nextbugid);
    }
88 89 90 91 92 93
}

######################################################################
# End Data/Security Validation
######################################################################

94 95
print "Content-type: text/html\n\n";

96 97
PutHeader ("Bug processed");

98 99
GetVersionTable();

100 101 102 103
if ( Param("strictvaluechecks") ) {
    CheckFormFieldDefined(\%::FORM, 'product');
    CheckFormFieldDefined(\%::FORM, 'version');
    CheckFormFieldDefined(\%::FORM, 'component');
104 105 106 107 108

    # check if target milestone is defined - matthew@zeroknowledge.com
    if ( Param("usetargetmilestone") ) {
        CheckFormFieldDefined(\%::FORM, 'target_milestone');
    }
109 110
}

111
if ($::FORM{'product'} ne $::dontchange) {
112 113 114
    if ( Param("strictvaluechecks") ) {
        CheckFormField(\%::FORM, 'product', \@::legal_product);
    }
115
    my $prod = $::FORM{'product'};
116 117 118 119 120 121 122 123 124

    # note that when this script is called from buglist.cgi (rather
    # than show_bug.cgi), it's possible that the product will be changed
    # but that the version and/or component will be set to 
    # "--dont_change--" but still happen to be correct.  in this case,
    # the if statement will incorrectly trigger anyway.  this is a 
    # pretty weird case, and not terribly unreasonable behavior, but 
    # worthy of a comment, perhaps.
    #
125 126
    my $vok = lsearch($::versions{$prod}, $::FORM{'version'}) >= 0;
    my $cok = lsearch($::components{$prod}, $::FORM{'component'}) >= 0;
127 128 129 130 131 132 133 134 135

    my $mok = 1;   # so it won't affect the 'if' statement if milestones aren't used
    if ( Param("usetargetmilestone") ) {
       $mok = lsearch($::target_milestone{$prod}, $::FORM{'target_milestone'}) >= 0;
    }

    if (!$vok || !$cok || !$mok) {
        print "<H1>Changing product means changing version, target milestone and component.</H1>\n";
        print "You have chosen a new product, and now the version, target milestone and/or\n";
136
        print "component fields are not correct.  (Or, possibly, the bug did\n";
137 138
        print "not have a valid target milestone, component or version field in the first place.)\n";
        print "Anyway, please set the version, target milestone and component now.<p>\n";
139 140 141
        print "<form>\n";
        print "<table>\n";
        print "<tr>\n";
142
        print "<td align=right><b>Product:</b></td>\n";
143 144
        print "<td>$prod</td>\n";
        print "</tr><tr>\n";
145 146
        print "<td align=right><b>Version:</b></td>\n";
        print "<td>" . Version_element($::FORM{'version'}, $prod) . "</td>\n";
147
        print "</tr><tr>\n";
148 149 150 151 152 153 154

        if ( Param("usetargetmilestone") ) {
            print "<td align=right><b>Target Milestone:</b></td>\n";
            print "<td>" . Milestone_element($::FORM{'target_milestone'}, $prod) . "</td>\n";
            print "</tr><tr>\n";
        }

155 156
        print "<td align=right><b>Component:</b></td>\n";
        print "<td>" . Component_element($::FORM{'component'}, $prod) . "</td>\n";
157 158 159
        print "</tr>\n";
        print "</table>\n";
        foreach my $i (keys %::FORM) {
160
            if ($i ne 'version' && $i ne 'component' && $i ne 'target_milestone') {
161
                print "<input type=hidden name=$i value=\"" .
162
                value_quote($::FORM{$i}) . "\">\n";
terry%netscape.com's avatar
terry%netscape.com committed
163 164
            }
        }
165
        print "<input type=submit value=Commit>\n";
166 167
        print "</form>\n";
        print "</hr>\n";
168
        print "<a href=query.cgi>Cancel all this and go to the query page.</a>\n";
169
        PutFooter();
170
        exit;
terry%netscape.com's avatar
terry%netscape.com committed
171 172 173 174
    }
}


175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
# Checks that the user is allowed to change the given field.  Actually, right
# now, the rules are pretty simple, and don't look at the field itself very
# much, but that could be enhanced.

my $lastbugid = 0;
my $ownerid;
my $reporterid;
my $qacontactid;

sub CheckCanChangeField {
    my ($f, $bugid, $oldvalue, $newvalue) = (@_);
    if ($f eq "assigned_to" || $f eq "reporter" || $f eq "qa_contact") {
        if ($oldvalue =~ /^\d+$/) {
            if ($oldvalue == 0) {
                $oldvalue = "";
            } else {
                $oldvalue = DBID_to_name($oldvalue);
            }
        }
    }
    if ($oldvalue eq $newvalue) {
        return 1;
    }
198 199 200
    if (trim($oldvalue) eq trim($newvalue)) {
        return 1;
    }
201 202 203
    if ($f =~ /^longdesc/) {
        return 1;
    }
204 205
    if ($f eq "resolution") { # always OK this.  if they really can't,
        return 1;             # it'll flag it when "status" is checked.
206
    }
207 208 209 210 211 212 213 214 215 216 217
    if ($UserInEditGroupSet < 0) {
        $UserInEditGroupSet = UserInGroup("editbugs");
    }
    if ($UserInEditGroupSet) {
        return 1;
    }
    if ($lastbugid != $bugid) {
        SendSQL("SELECT reporter, assigned_to, qa_contact FROM bugs " .
                "WHERE bug_id = $bugid");
        ($reporterid, $ownerid, $qacontactid) = (FetchSQLData());
    }
218 219 220 221 222 223
    # Let reporter change bug status, even if they can't edit bugs.
    # If reporter can't re-open their bug they will just file a duplicate.
    # While we're at it, let them close their own bugs as well.
    if ( ($f eq "bug_status") && ($whoid eq $reporterid) ) {
        return 1;
    }
224 225
    if ($f eq "bug_status" && $newvalue ne $::unconfirmedstate &&
        IsOpenedState($newvalue)) {
226 227 228 229 230 231 232 233 234 235 236 237

        # Hmm.  They are trying to set this bug to some opened state
        # that isn't the UNCONFIRMED state.  Are they in the right
        # group?  Or, has it ever been confirmed?  If not, then this
        # isn't legal.

        if ($UserInCanConfirmGroupSet < 0) {
            $UserInCanConfirmGroupSet = UserInGroup("canconfirm");
        }
        if ($UserInCanConfirmGroupSet) {
            return 1;
        }
238 239 240 241
        SendSQL("SELECT everconfirmed FROM bugs WHERE bug_id = $bugid");
        my $everconfirmed = FetchOneColumn();
        if ($everconfirmed) {
            return 1;
242
        }
243 244 245
    } elsif ($reporterid eq $whoid || $ownerid eq $whoid ||
             $qacontactid eq $whoid) {
        return 1;
246 247 248 249
    }
    SendSQL("UNLOCK TABLES");
    $oldvalue = value_quote($oldvalue);
    $newvalue = value_quote($newvalue);
250
    print PuntTryAgain(qq{
251 252 253 254 255 256
Only the owner or submitter of the bug, or a sufficiently
empowered user, may make that change to the $f field.
<TABLE>
<TR><TH ALIGN="right">Old value:</TH><TD>$oldvalue</TD></TR>
<TR><TH ALIGN="right">New value:</TH><TD>$newvalue</TD></TR>
</TABLE>
257
});
258 259 260 261 262 263 264 265 266
    PutFooter();
    exit();
}


    
    


267
if (defined $::FORM{'id'} && Param('strictvaluechecks')) {
268 269 270 271 272 273 274
    # since this means that we were called from show_bug.cgi, now is a good
    # time to do a whole bunch of error checking that can't easily happen when
    # we've been called from buglist.cgi, because buglist.cgi only tweaks
    # values that have been changed instead of submitting all the new values.
    # (XXX those error checks need to happen too, but implementing them 
    # is more work in the current architecture of this script...)
    #
275 276 277 278 279 280 281 282 283 284 285 286
    CheckFormField(\%::FORM, 'rep_platform', \@::legal_platform);
    CheckFormField(\%::FORM, 'priority', \@::legal_priority);
    CheckFormField(\%::FORM, 'bug_severity', \@::legal_severity);
    CheckFormField(\%::FORM, 'component', 
                   \@{$::components{$::FORM{'product'}}});
    CheckFormFieldDefined(\%::FORM, 'bug_file_loc');
    CheckFormFieldDefined(\%::FORM, 'short_desc');
    CheckFormField(\%::FORM, 'product', \@::legal_product);
    CheckFormField(\%::FORM, 'version', 
                   \@{$::versions{$::FORM{'product'}}});
    CheckFormField(\%::FORM, 'op_sys', \@::legal_opsys);
    CheckFormFieldDefined(\%::FORM, 'longdesclength');
terry%netscape.com's avatar
terry%netscape.com committed
287 288
}

289 290 291 292 293 294 295 296 297 298 299 300
my $action  = '';
if (defined $::FORM{action}) {
  $action  = trim($::FORM{action});
}
if ($action eq Param("move-button-text")) {
  $::FORM{'buglist'} = join (":", @idlist);
  do "move.pl" || die "Error executing move.cgi: $!";
  PutFooter();
  exit;
}


301 302
if (!defined $::FORM{'who'}) {
    $::FORM{'who'} = $::COOKIE{'Bugzilla_login'};
terry%netscape.com's avatar
terry%netscape.com committed
303 304
}

305 306
# the common updates to all bugs in @idlist start here
#
307 308 309 310 311
print "<TITLE>Update Bug " . join(" ", @idlist) . "</TITLE>\n";
if (defined $::FORM{'id'}) {
    navigation_header();
}
print "<HR>\n";
312 313 314 315 316 317 318
$::query = "update bugs\nset";
$::comma = "";
umask(0);

sub DoComma {
    $::query .= "$::comma\n    ";
    $::comma = ",";
terry%netscape.com's avatar
terry%netscape.com committed
319 320
}

321 322 323 324 325 326 327 328 329 330 331 332 333 334
sub DoConfirm {
    if ($UserInEditGroupSet < 0) {
        $UserInEditGroupSet = UserInGroup("editbugs");
    }
    if ($UserInCanConfirmGroupSet < 0) {
        $UserInCanConfirmGroupSet = UserInGroup("canconfirm");
    }
    if ($UserInEditGroupSet || $UserInCanConfirmGroupSet) {
        DoComma();
        $::query .= "everconfirmed = 1";
    }
}


335 336 337 338
sub ChangeStatus {
    my ($str) = (@_);
    if ($str ne $::dontchange) {
        DoComma();
339 340 341 342 343
        if (IsOpenedState($str)) {
            $::query .= "bug_status = IF(everconfirmed = 1, '$str', '$::unconfirmedstate')";
        } else {
            $::query .= "bug_status = '$str'";
        }
344 345 346
        $::FORM{'bug_status'} = $str; # Used later for call to
                                      # CheckCanChangeField to make sure this
                                      # is really kosher.
terry%netscape.com's avatar
terry%netscape.com committed
347 348 349
    }
}

350 351 352 353 354
sub ChangeResolution {
    my ($str) = (@_);
    if ($str ne $::dontchange) {
        DoComma();
        $::query .= "resolution = '$str'";
terry%netscape.com's avatar
terry%netscape.com committed
355 356 357
    }
}

358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
#
# This function checks if there is a comment required for a specific
# function and tests, if the comment was given.
# If comments are required for functions  is defined by params.
#
sub CheckonComment( $ ) {
    my ($function) = (@_);
    
    # Param is 1 if comment should be added !
    my $ret = Param( "commenton" . $function );

    # Allow without comment in case of undefined Params.
    $ret = 0 unless ( defined( $ret ));

    if( $ret ) {
        if (!defined $::FORM{'comment'} || $::FORM{'comment'} =~ /^\s*$/) {
374
            # No comment - sorry, action not allowed !
375 376 377
            PuntTryAgain("You have to specify a <b>comment</b> on this " .
                         "change.  Please give some words " .
                         "on the reason for your change.");
378 379 380 381 382 383 384
        } else {
            $ret = 0;
        }
    }
    return( ! $ret ); # Return val has to be inverted
}

terry%netscape.com's avatar
terry%netscape.com committed
385

386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
my $foundbit = 0;
foreach my $b (grep(/^bit-\d*$/, keys %::FORM)) {
    if (!$foundbit) {
        $foundbit = 1;
        DoComma();
        $::query .= "groupset = 0";
    }
    if ($::FORM{$b}) {
        my $v = substr($b, 4);
        $::query .= "+ $v";     # Carefully written so that the math is
                                # done by MySQL, which can handle 64-bit math,
                                # and not by Perl, which I *think* can not.
    }
}

401
foreach my $field ("rep_platform", "priority", "bug_severity",          
402
                   "summary", "component", "bug_file_loc", "short_desc",
403
                   "product", "version", "op_sys",
404
                   "target_milestone", "status_whiteboard") {
405 406 407
    if (defined $::FORM{$field}) {
        if ($::FORM{$field} ne $::dontchange) {
            DoComma();
408
            $::query .= "$field = " . SqlQuote(trim($::FORM{$field}));
terry%netscape.com's avatar
terry%netscape.com committed
409 410 411 412
        }
    }
}

413

414 415
if (defined $::FORM{'qa_contact'}) {
    my $name = trim($::FORM{'qa_contact'});
terry%netscape.com's avatar
terry%netscape.com committed
416
    if ($name ne $::dontchange) {
417 418 419 420 421 422 423 424 425
        my $id = 0;
        if ($name ne "") {
            $id = DBNameToIdAndCheck($name);
        }
        DoComma();
        $::query .= "qa_contact = $id";
    }
}

426 427 428

ConnectToDatabase();

429 430 431
my $formCcSet = new RelationSet;
my $origCcSet = new RelationSet;
my $origCcString;
432
my $removedCcString = "";
433
my $duplicate = 0;
434 435

# We make sure to check out the CC list before we actually start touching any
436 437 438 439
# bugs.  mergeFromString() ultimately searches the database using a quoted
# form of the data it gets from $::FORM{'cc'}, so anything bogus from a 
# security standpoint should trigger an abort there.
#
440
if (defined $::FORM{'newcc'} && defined $::FORM{'id'}) {
441
    $origCcSet->mergeFromDB("select who from cc where bug_id = $::FORM{'id'}");
442
    $formCcSet->mergeFromDB("select who from cc where bug_id = $::FORM{'id'}");
443
    $origCcString = $origCcSet->toString();  # cache a copy of the string vers
444
    if ((exists $::FORM{'removecc'}) && (exists $::FORM{'cc'})) {
445 446 447 448 449

      # save off the folks removed from the CC list so they can be given to 
      # the processmaill command line so they can be sent mail about it.
      #
      $removedCcString = join (',', @{$::MFORM{'cc'}});
450 451 452
      $formCcSet->removeItemsInArray(@{$::MFORM{'cc'}});
    }
    $formCcSet->mergeFromString($::FORM{'newcc'});
453 454
}

455 456 457
if ( Param('strictvaluechecks') ) {
    CheckFormFieldDefined(\%::FORM, 'knob');
}
458 459 460 461
SWITCH: for ($::FORM{'knob'}) {
    /^none$/ && do {
        last SWITCH;
    };
462 463 464 465 466
    /^confirm$/ && CheckonComment( "confirm" ) && do {
        DoConfirm();
        ChangeStatus('NEW');
        last SWITCH;
    };
467
    /^accept$/ && CheckonComment( "accept" ) && do {
468
        DoConfirm();
469
        ChangeStatus('ASSIGNED');
470 471 472 473 474
        if (Param("musthavemilestoneonaccept")) {
            if (Param("usetargetmilestone")) {
                $requiremilestone = 1;
            }
        }
475 476
        last SWITCH;
    };
477
    /^clearresolution$/ && CheckonComment( "clearresolution" ) && do {
478 479 480
        ChangeResolution('');
        last SWITCH;
    };
481
    /^resolve$/ && CheckonComment( "resolve" ) && do {
482 483 484 485
        ChangeStatus('RESOLVED');
        ChangeResolution($::FORM{'resolution'});
        last SWITCH;
    };
486
    /^reassign$/ && CheckonComment( "reassign" ) && do {
487 488 489
        if ($::FORM{'andconfirm'}) {
            DoConfirm();
        }
490 491
        ChangeStatus('NEW');
        DoComma();
492 493 494
        if ( Param("strictvaluechecks") ) {
          if ( !defined$::FORM{'assigned_to'} ||
               trim($::FORM{'assigned_to'}) eq "") {
495 496 497 498
            PuntTryAgain("You cannot reassign to a bug to nobody.  Unless " .
                         "you intentionally cleared out the " .
                         "\"Reassign bug to\" field, " .
                         Param("browserbugmessage"));
499 500
          }
        }
501 502 503 504
        my $newid = DBNameToIdAndCheck($::FORM{'assigned_to'});
        $::query .= "assigned_to = $newid";
        last SWITCH;
    };
505
    /^reassignbycomponent$/  && CheckonComment( "reassignbycomponent" ) && do {
506
        if ($::FORM{'product'} eq $::dontchange) {
507 508
            PuntTryAgain("You must specify a product to help determine the " .
                         "new owner of these bugs.");
509
        }
510
        if ($::FORM{'component'} eq $::dontchange) {
511 512
            PuntTryAgain("You must specify a component whose owner should " .
                         "get assigned these bugs.");
terry%netscape.com's avatar
terry%netscape.com committed
513
        }
514 515 516
        if ($::FORM{'compconfirm'}) {
            DoConfirm();
        }
517 518
        ChangeStatus('NEW');
        SendSQL("select initialowner from components where program=" .
519 520
                SqlQuote($::FORM{'product'}) . " and value=" .
                SqlQuote($::FORM{'component'}));
521 522
        my $newid = FetchOneColumn();
        my $newname = DBID_to_name($newid);
523 524
        DoComma();
        $::query .= "assigned_to = $newid";
525 526 527 528 529
        if (Param("useqacontact")) {
            SendSQL("select initialqacontact from components where program=" .
                    SqlQuote($::FORM{'product'}) .
                    " and value=" . SqlQuote($::FORM{'component'}));
            my $qacontact = FetchOneColumn();
530
            if (defined $qacontact && $qacontact != 0) {
531
                DoComma();
532
                $::query .= "qa_contact = $qacontact";
533 534
            }
        }
535 536
        last SWITCH;
    };   
537
    /^reopen$/  && CheckonComment( "reopen" ) && do {
538
		SendSQL("SELECT resolution FROM bugs WHERE bug_id = $::FORM{'id'}");
539
        ChangeStatus('REOPENED');
540
        ChangeResolution('');
541 542 543
		if (FetchOneColumn() eq 'DUPLICATE') {
			SendSQL("DELETE FROM duplicates WHERE dupe = $::FORM{'id'}");
		}		
544 545
        last SWITCH;
    };
546
    /^verify$/ && CheckonComment( "verify" ) && do {
547 548 549
        ChangeStatus('VERIFIED');
        last SWITCH;
    };
550
    /^close$/ && CheckonComment( "close" ) && do {
551 552 553
        ChangeStatus('CLOSED');
        last SWITCH;
    };
554
    /^duplicate$/ && CheckonComment( "duplicate" ) && do {
555 556
        ChangeStatus('RESOLVED');
        ChangeResolution('DUPLICATE');
557 558 559
        if ( Param('strictvaluechecks') ) {
            CheckFormFieldDefined(\%::FORM,'dup_id');
        }
560
        my $num = trim($::FORM{'dup_id'});
561 562 563
        SendSQL("SELECT bug_id FROM bugs WHERE bug_id = " . SqlQuote($num));
        $num = FetchOneColumn();
        if (!$num) {
564 565
            PuntTryAgain("You must specify a valid bug number of which this bug " .
                         "is a duplicate.  The bug has not been changed.")
terry%netscape.com's avatar
terry%netscape.com committed
566
        }
567
        if (!defined($::FORM{'id'}) || $num == $::FORM{'id'}) {
568 569 570
            PuntTryAgain("Nice try, $::FORM{'who'}.  But it doesn't really ".
                         "make sense to mark a bug as a duplicate of " .
                         "itself, does it?");
terry%netscape.com's avatar
terry%netscape.com committed
571
        }
572 573 574 575 576 577 578
        my $checkid = trim($::FORM{'id'});
        SendSQL("SELECT bug_id FROM bugs where bug_id = " .  SqlQuote($checkid));
        $checkid = FetchOneColumn();
        if (!$checkid) {
            PuntTryAgain("The bug id $::FORM{'id'} is invalid. Please reload this bug ".
                         "and try again.");
        }
579
        $::FORM{'comment'} .= "\n\n*** This bug has been marked as a duplicate of $num ***";
580
        $duplicate = $num;
581

582 583 584 585
        last SWITCH;
    };
    # default
    print "Unknown action $::FORM{'knob'}!\n";
586
    PutFooter();
587
    exit;
terry%netscape.com's avatar
terry%netscape.com committed
588 589 590
}


591
if ($#idlist < 0) {
592
    PuntTryAgain("You apparently didn't choose any bugs to modify.");
terry%netscape.com's avatar
terry%netscape.com committed
593 594
}

595 596 597 598 599

my @keywordlist;
my %keywordseen;

if ($::FORM{'keywords'}) {
600 601 602 603
    foreach my $keyword (split(/[\s,]+/, $::FORM{'keywords'})) {
        if ($keyword eq '') {
            next;
        }
604
        my $i = GetKeywordIdFromName($keyword);
605
        if (!$i) {
606 607 608 609
            PuntTryAgain("Unknown keyword named <code>$keyword</code>. " .
                         "<P>The legal keyword names are " .
                         "<A HREF=describekeywords.cgi>" .
                         "listed here</A>.");
610 611 612 613 614 615 616 617
        }
        if (!$keywordseen{$i}) {
            push(@keywordlist, $i);
            $keywordseen{$i} = 1;
        }
    }
}

618 619 620
my $keywordaction = $::FORM{'keywordaction'} || "makeexact";

if ($::comma eq "" && 0 == @keywordlist && $keywordaction ne "makeexact") {
621
    if (!defined $::FORM{'comment'} || $::FORM{'comment'} =~ /^\s*$/) {
622 623
        PuntTryAgain("Um, you apparently did not change anything on the " .
                     "selected bugs.");
terry%netscape.com's avatar
terry%netscape.com committed
624 625 626
    }
}

627
my $basequery = $::query;
628
my $delta_ts;
terry%netscape.com's avatar
terry%netscape.com committed
629

630

631 632
sub SnapShotBug {
    my ($id) = (@_);
633
    SendSQL("select delta_ts, " . join(',', @::log_columns) .
634
            " from bugs where bug_id = $id");
635 636
    my @row = FetchSQLData();
    $delta_ts = shift @row;
637

638
    return @row;
terry%netscape.com's avatar
terry%netscape.com committed
639 640 641
}


642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
sub SnapShotDeps {
    my ($i, $target, $me) = (@_);
    SendSQL("select $target from dependencies where $me = $i order by $target");
    my @list;
    while (MoreSQLData()) {
        push(@list, FetchOneColumn());
    }
    return join(',', @list);
}


my $timestamp;

sub LogDependencyActivity {
    my ($i, $oldstr, $target, $me) = (@_);
    my $newstr = SnapShotDeps($i, $target, $me);
    if ($oldstr ne $newstr) {
659 660 661 662
        my $fieldid = GetFieldID($target);
        SendSQL("INSERT INTO bugs_activity " .
                "(bug_id,who,bug_when,fieldid,oldvalue,newvalue) VALUES " .
                "($i,$whoid,$timestamp,$fieldid,'$oldstr','$newstr')");
663 664 665 666 667
        return 1;
    }
    return 0;
}

668
# this loop iterates once for each bug to be processed (eg when this script
669
# is called with multiple bugs selected from buglist.cgi instead of
670 671
# show_bug.cgi).
#
672
foreach my $id (@idlist) {
673
    my %dependencychanged;
674 675
    my $write = "WRITE";        # Might want to make a param to control
                                # whether we do LOW_PRIORITY ...
676 677 678
    SendSQL("LOCK TABLES bugs $write, bugs_activity $write, cc $write, " .
            "profiles $write, dependencies $write, votes $write, " .
            "keywords $write, longdescs $write, fielddefs $write, " .
679
            "keyworddefs READ, groups READ, attachments READ, products READ");
680
    my @oldvalues = SnapShotBug($id);
681
    my %oldhash;
682 683
    my $i = 0;
    foreach my $col (@::log_columns) {
684
        $oldhash{$col} = $oldvalues[$i];
685 686 687 688 689
        if (exists $::FORM{$col}) {
            CheckCanChangeField($col, $id, $oldvalues[$i], $::FORM{$col});
        }
        $i++;
    }
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
    if ($requiremilestone) {
        my $value = $::FORM{'target_milestone'};
        if (!defined $value || $value eq $::dontchange) {
            $value = $oldhash{'target_milestone'};
        }
        SendSQL("SELECT defaultmilestone FROM products WHERE product = " .
                SqlQuote($oldhash{'product'}));
        if ($value eq FetchOneColumn()) {
            SendSQL("UNLOCK TABLES");
            PuntTryAgain("You must determine a target milestone for bug $id " .
                         "if you are going to accept it.  (Part of " .
                         "accepting a bug is giving an estimate of when it " .
                         "will be fixed.)");
        }
    }   
705 706 707 708 709 710 711 712
    if (defined $::FORM{'delta_ts'} && $::FORM{'delta_ts'} ne $delta_ts) {
        print "
<H1>Mid-air collision detected!</H1>
Someone else has made changes to this bug at the same time you were trying to.
The changes made were:
<p>
";
        DumpBugActivity($id, $delta_ts);
713
        my $longdesc = GetLongDescriptionAsHTML($id);
714
        my $longchanged = 0;
715

716 717
        if (length($longdesc) > $::FORM{'longdesclength'}) {
            $longchanged = 1;
718 719 720
            print "<P>Added text to the long description:<blockquote>";
            print substr($longdesc, $::FORM{'longdesclength'});
            print "</blockquote>\n";
721 722 723 724 725 726
        }
        SendSQL("unlock tables");
        print "You have the following choices: <ul>\n";
        $::FORM{'delta_ts'} = $delta_ts;
        print "<li><form method=post>";
        foreach my $i (keys %::FORM) {
727 728 729 730 731 732 733 734
            # Make sure we don't include the username/password fields in the
            # HTML.  If cookies are off, they'll have to reauthenticate after
            # hitting "submit changes anyway".
            # see http://bugzilla.mozilla.org/show_bug.cgi?id=15980
            if ($i !~ /^(Bugzilla|LDAP)_(login|password)$/) {
              my $value = value_quote($::FORM{$i});
              print qq{<input type=hidden name="$i" value="$value">\n};
            }
735 736
        }
        print qq{<input type=submit value="Submit my changes anyway">\n};
cyeh%bluemartini.com's avatar
cyeh%bluemartini.com committed
737 738 739 740
        print " This will cause all of the above changes to be overwritten";
        if ($longchanged) {
            print ", except for the changes to the description";
        }
741
        print qq{.</form>\n<li><a href="show_bug.cgi?id=$id">Throw away my changes, and go revisit bug $id</a></ul>\n};
742
        PutFooter();
743 744 745
        exit;
    }
        
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
    my %deps;
    if (defined $::FORM{'dependson'}) {
        my $me = "blocked";
        my $target = "dependson";
        for (1..2) {
            $deps{$target} = [];
            my %seen;
            foreach my $i (split('[\s,]+', $::FORM{$target})) {
                if ($i eq "") {
                    next;

                }
                SendSQL("select bug_id from bugs where bug_id = " .
                        SqlQuote($i));
                my $comp = FetchOneColumn();
                if ($comp ne $i) {
762
                    PuntTryAgain("$i is not a legal bug number");
763
                }
764 765 766
                if ($id eq $i) {
                    PuntTryAgain("You can't make a bug blocked or dependent on itself.");
                }
767 768 769 770 771 772 773 774 775 776 777 778
                if (!exists $seen{$i}) {
                    push(@{$deps{$target}}, $i);
                    $seen{$i} = 1;
                }
            }
            my @stack = @{$deps{$target}};
            while (@stack) {
                my $i = shift @stack;
                SendSQL("select $target from dependencies where $me = $i");
                while (MoreSQLData()) {
                    my $t = FetchOneColumn();
                    if ($t == $id) {
779 780 781 782
                        PuntTryAgain("Dependency loop detected!<P>" .
                                     "The change you are making to " .
                                     "dependencies has caused a circular " .
                                     "dependency chain.");
783 784 785 786 787 788 789
                    }
                    if (!exists $seen{$t}) {
                        push @stack, $t;
                        $seen{$t} = 1;
                    }
                }
            }
790

791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810
	    if ($me eq 'dependson') {
                my @deps   =  @{$deps{'dependson'}};
                my @blocks =  @{$deps{'blocked'}};
                my @union = ();
                my @isect = ();
                my %union = ();
                my %isect = ();
                foreach my $b (@deps, @blocks) { $union{$b}++ && $isect{$b}++ }
                @union = keys %union;
                @isect = keys %isect;
		if (@isect > 0) {
                    my $both;
                    foreach my $i (@isect) {
                       $both = $both . "#" . $i . " ";	
                    }
                    PuntTryAgain("Dependency loop detected!<P>" .
                                 "This bug can't be both blocked and dependent " .
                                 "on bug "  . $both . "!");
                }
            }
811 812 813 814 815
            my $tmp = $me;
            $me = $target;
            $target = $tmp;
        }
    }
816

817 818 819 820 821 822
    if (@::legal_keywords) {
        # There are three kinds of "keywordsaction": makeexact, add, delete.
        # For makeexact, we delete everything, and then add our things.
        # For add, we delete things we're adding (to make sure we don't
        # end up having them twice), and then we add them.
        # For delete, we just delete things on the list.
823
        my $changed = 0;
824 825
        if ($keywordaction eq "makeexact") {
            SendSQL("DELETE FROM keywords WHERE bug_id = $id");
826
            $changed = 1;
827 828 829 830 831
        }
        foreach my $keyword (@keywordlist) {
            if ($keywordaction ne "makeexact") {
                SendSQL("DELETE FROM keywords
                         WHERE bug_id = $id AND keywordid = $keyword");
832
                $changed = 1;
833 834 835 836
            }
            if ($keywordaction ne "delete") {
                SendSQL("INSERT INTO keywords 
                         (bug_id, keywordid) VALUES ($id, $keyword)");
837 838 839 840 841 842 843 844 845 846 847 848
                $changed = 1;
            }
        }
        if ($changed) {
            SendSQL("SELECT keyworddefs.name 
                     FROM keyworddefs, keywords
                     WHERE keywords.bug_id = $id
                         AND keyworddefs.id = keywords.keywordid
                     ORDER BY keyworddefs.name");
            my @list;
            while (MoreSQLData()) {
                push(@list, FetchOneColumn());
849
            }
850 851 852
            SendSQL("UPDATE bugs SET keywords = " .
                    SqlQuote(join(', ', @list)) .
                    " WHERE bug_id = $id");
853 854 855
        }
    }

856
    my $query = "$basequery\nwhere bug_id = $id";
terry%netscape.com's avatar
terry%netscape.com committed
857
    
858 859 860 861
# print "<PRE>$query</PRE>\n";

    if ($::comma ne "") {
        SendSQL($query);
862 863 864
        SendSQL("select delta_ts from bugs where bug_id = $id");
    } else {
        SendSQL("select now()");
terry%netscape.com's avatar
terry%netscape.com committed
865
    }
866
    $timestamp = FetchOneColumn();
terry%netscape.com's avatar
terry%netscape.com committed
867
    
868 869
    if (defined $::FORM{'comment'}) {
        AppendComment($id, $::FORM{'who'}, $::FORM{'comment'});
terry%netscape.com's avatar
terry%netscape.com committed
870 871
    }
    
872
    if (defined $::FORM{'newcc'} && defined $::FORM{'id'}
873 874 875 876 877 878 879 880 881 882 883 884
        && ! $origCcSet->isEqual($formCcSet) ) {

        # update the database to look like the form
        #
        my @CCDELTAS = $origCcSet->generateSqlDeltas($formCcSet, "cc", 
                                                     "bug_id", $::FORM{'id'},
                                                     "who");
        $CCDELTAS[0] eq "" || SendSQL($CCDELTAS[0]);
        $CCDELTAS[1] eq "" || SendSQL($CCDELTAS[1]);

        my $col = GetFieldID('cc');
        my $origq = SqlQuote($origCcString);
885
        my $newq = SqlQuote($formCcSet->toString());
886 887 888 889 890
        SendSQL("INSERT INTO bugs_activity " . 
                "(bug_id,who,bug_when,fieldid,oldvalue,newvalue) VALUES " . 
                "($id,$whoid,'$timestamp',$col,$origq,$newq)");
    }
  
891 892 893 894 895 896 897 898 899 900 901 902

    if (defined $::FORM{'dependson'}) {
        my $me = "blocked";
        my $target = "dependson";
        for (1..2) {
            SendSQL("select $target from dependencies where $me = $id order by $target");
            my %snapshot;
            my @oldlist;
            while (MoreSQLData()) {
                push(@oldlist, FetchOneColumn());
            }
            my @newlist = sort {$a <=> $b} @{$deps{$target}};
903 904
            @dependencychanged{@oldlist} = 1;
            @dependencychanged{@newlist} = 1;
905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932

            while (0 < @oldlist || 0 < @newlist) {
                if (@oldlist == 0 || (@newlist > 0 &&
                                      $oldlist[0] > $newlist[0])) {
                    $snapshot{$newlist[0]} = SnapShotDeps($newlist[0], $me,
                                                          $target);
                    shift @newlist;
                } elsif (@newlist == 0 || (@oldlist > 0 &&
                                           $newlist[0] > $oldlist[0])) {
                    $snapshot{$oldlist[0]} = SnapShotDeps($oldlist[0], $me,
                                                          $target);
                    shift @oldlist;
                } else {
                    if ($oldlist[0] != $newlist[0]) {
                        die "Error in list comparing code";
                    }
                    shift @oldlist;
                    shift @newlist;
                }
            }
            my @keys = keys(%snapshot);
            if (@keys) {
                my $oldsnap = SnapShotDeps($id, $target, $me);
                SendSQL("delete from dependencies where $me = $id");
                foreach my $i (@{$deps{$target}}) {
                    SendSQL("insert into dependencies ($me, $target) values ($id, $i)");
                }
                foreach my $k (@keys) {
933
                    LogDependencyActivity($k, $snapshot{$k}, $me, $target);
934 935 936 937 938 939 940 941 942 943
                }
                LogDependencyActivity($id, $oldsnap, $target, $me);
            }

            my $tmp = $me;
            $me = $target;
            $target = $tmp;
        }
    }

944 945 946 947
    # get a snapshot of the newly set values out of the database, 
    # and then generate any necessary bug activity entries by seeing 
    # what has changed since before we wrote out the new values.
    #
948
    my @newvalues = SnapShotBug($id);
949

950 951 952 953 954 955
    # for passing to processmail to ensure that when someone is removed
    # from one of these fields, they get notified of that fact (if desired)
    #
    my $origOwner = "";
    my $origQaContact = "";

956
    foreach my $c (@::log_columns) {
957 958
        my $col = $c;           # We modify it, don't want to modify array
                                # values in place.
959 960
        my $old = shift @oldvalues;
        my $new = shift @newvalues;
961 962 963 964 965 966
        if (!defined $old) {
            $old = "";
        }
        if (!defined $new) {
            $new = "";
        }
967
        if ($old ne $new) {
968 969 970 971 972 973 974 975 976 977 978 979 980

            # save off the old value for passing to processmail so the old
            # owner can be notified
            #
            if ($col eq 'assigned_to') {
                $old = ($old) ? DBID_to_name($old) : "";
                $new = ($new) ? DBID_to_name($new) : "";
                $origOwner = $old;
            }

            # ditto for the old qa contact
            #
            if ($col eq 'qa_contact') {
981 982
                $old = ($old) ? DBID_to_name($old) : "";
                $new = ($new) ? DBID_to_name($new) : "";
983
                $origQaContact = $old;
terry%netscape.com's avatar
terry%netscape.com committed
984
            }
985

986
            if ($col eq 'product') {
987
                RemoveVotes($id, 0,
988 989
                            "This bug has been moved to a different product");
            }
990
            $col = GetFieldID($col);
991 992
            $old = SqlQuote($old);
            $new = SqlQuote($new);
993
            my $q = "insert into bugs_activity (bug_id,who,bug_when,fieldid,oldvalue,newvalue) values ($id,$whoid,'$timestamp',$col,$old,$new)";
994
            # puts "<pre>$q</pre>"
995
            SendSQL($q);
terry%netscape.com's avatar
terry%netscape.com committed
996 997 998
        }
    }
    
999
    print "<TABLE BORDER=1><TD><H2>Changes to bug $id submitted</H2>\n";
1000
    SendSQL("unlock tables");
1001

1002
    my @ARGLIST = ();
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
    if ( $removedCcString ne "" ) {
        push @ARGLIST, ("-forcecc", $removedCcString);
    }
    if ( $origOwner ne "" ) {
        push @ARGLIST, ("-forceowner", $origOwner);
    }
    if ( $origQaContact ne "") { 
        push @ARGLIST, ( "-forceqacontact", $origQaContact);
    }
    push @ARGLIST, ($id, $::FORM{'who'});
1013
    system ("./processmail",@ARGLIST);
1014

1015
    print "<TD><A HREF=\"show_bug.cgi?id=$id\">Back To BUG# $id</A></TABLE>\n";
1016

1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
    if ($duplicate) {
        # Check to see if Reporter of this bug is reporter of Dupe 
        SendSQL("SELECT reporter FROM bugs WHERE bug_id = " . SqlQuote($::FORM{'id'}));
        my $reporter = FetchOneColumn();
        SendSQL("SELECT reporter FROM bugs WHERE bug_id = " . SqlQuote($duplicate) . " and reporter = $reporter");
        my $isreporter = FetchOneColumn();
        SendSQL("SELECT who FROM cc WHERE bug_id = " . SqlQuote($duplicate) . " and who = $reporter");
        my $isoncc = FetchOneColumn();
        unless ($isreporter || $isoncc) {
            # The reporter is oblivious to the existance of the new bug... add 'em to the cc (and record activity)
            SendSQL("SELECT who FROM cc WHERE bug_id = " . SqlQuote($duplicate));
            my @dupecc;
            while (MoreSQLData()) {
                push (@dupecc, DBID_to_name(FetchOneColumn()));
            }
            my @newdupecc = @dupecc;
            push (@newdupecc, DBID_to_name($reporter));
            my $ccid = GetFieldID("cc");
            my $whochange = DBNameToIdAndCheck($::FORM{'who'});
            SendSQL("INSERT INTO bugs_activity (bug_id,who,bug_when,fieldid,oldvalue,newvalue) VALUES " .
                    "('$duplicate','$whochange',now(),$ccid,'" . join (",", sort @dupecc) . "','" . join (",", sort @newdupecc) . "')"); 
            SendSQL("INSERT INTO cc (who, bug_id) VALUES ($reporter, " . SqlQuote($duplicate) . ")");
        }
        AppendComment($duplicate, $::FORM{'who'}, "*** Bug $::FORM{'id'} has been marked as a duplicate of this bug. ***");
        if ( Param('strictvaluechecks') ) {
          CheckFormFieldDefined(\%::FORM,'comment');
        }
        SendSQL("INSERT INTO duplicates VALUES ($duplicate, $::FORM{'id'})");
        print "<TABLE BORDER=1><TD><H2>Duplicate notation added to bug $duplicate</H2>\n";
        system("./processmail", $duplicate, $::FORM{'who'});
        print "<TD><A HREF=\"show_bug.cgi?id=$duplicate\">Go To BUG# $duplicate</A></TABLE>\n";
    }

1050
    foreach my $k (keys(%dependencychanged)) {
1051
        print "<TABLE BORDER=1><TD><H2>Checking for dependency changes on bug $k</H2>\n";
1052
        system("./processmail", $k, $::FORM{'who'});
1053 1054 1055
        print "<TD><A HREF=\"show_bug.cgi?id=$k\">Go To BUG# $k</A></TABLE>\n";
    }

terry%netscape.com's avatar
terry%netscape.com committed
1056 1057
}

1058
if (defined $::next_bug) {
1059
    print("<P>The next bug in your list is:\n");
1060 1061
    $::FORM{'id'} = $::next_bug;
    print "<HR>\n";
terry%netscape.com's avatar
terry%netscape.com committed
1062

1063
    navigation_header();
1064
    do "bug_form.pl";
terry%netscape.com's avatar
terry%netscape.com committed
1065
} else {
1066
    navigation_header();
1067
    PutFooter();
terry%netscape.com's avatar
terry%netscape.com committed
1068
}