You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by rb...@locus.apache.org on 2000/03/10 02:54:39 UTC

cvs commit: apache-site/bugs 1x1.gif CGI.pl CHANGES README addcomponent.cgi ant.jpg backdoor.cgi bug_form.pl bug_status.html buglist.cgi changepassword.cgi checksetup.pl colchange.cgi collectstats.pl createaccount.cgi createattachment.cgi defparams.pl describecomponents.cgi doaddcomponent.cgi doeditcomponents.cgi doeditowners.cgi doeditparams.cgi doeditvotes.cgi editcomponents.cgi editowners.cgi editparams.cgi editproducts.cgi editusers.cgi editversions.cgi enter_bug.cgi globals.pl globals.pl~ help.html helpemailquery.html how_to_mail.html index.html localconfig localconfig~ long_list.cgi new_comment.cgi newquip.html notargetmilestone.html post_bug.cgi process_bug.cgi processmail query.cgi relogin.cgi reports.cgi sanitycheck.cgi show_activity.cgi show_bug.cgi showattachment.cgi showdependencygraph.cgi showdependencytree.cgi showowners.cgi showvotes.cgi votehelp.html whineatnews.pl

rbb         00/03/09 17:54:38

  Added:       bugs     1x1.gif CGI.pl CHANGES README addcomponent.cgi
                        ant.jpg backdoor.cgi bug_form.pl bug_status.html
                        buglist.cgi changepassword.cgi checksetup.pl
                        colchange.cgi collectstats.pl createaccount.cgi
                        createattachment.cgi defparams.pl
                        describecomponents.cgi doaddcomponent.cgi
                        doeditcomponents.cgi doeditowners.cgi
                        doeditparams.cgi doeditvotes.cgi editcomponents.cgi
                        editowners.cgi editparams.cgi editproducts.cgi
                        editusers.cgi editversions.cgi enter_bug.cgi
                        globals.pl globals.pl~ help.html
                        helpemailquery.html how_to_mail.html index.html
                        localconfig localconfig~ long_list.cgi
                        new_comment.cgi newquip.html notargetmilestone.html
                        post_bug.cgi process_bug.cgi processmail query.cgi
                        relogin.cgi reports.cgi sanitycheck.cgi
                        show_activity.cgi show_bug.cgi showattachment.cgi
                        showdependencygraph.cgi showdependencytree.cgi
                        showowners.cgi showvotes.cgi votehelp.html
                        whineatnews.pl
  Log:
  
  
  Revision  Changes    Path
  1.1                  apache-site/bugs/1x1.gif
  
  	<<Binary file>>
  
  
  1.1                  apache-site/bugs/CGI.pl
  
  Index: CGI.pl
  ===================================================================
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  # Contains some global routines used throughout the CGI scripts of Bugzilla.
  
  use diagnostics;
  use strict;
  
  # Shut up misguided -w warnings about "used only once".  For some reason,
  # "use vars" chokes on me when I try it here.
  
  sub CGI_pl_sillyness {
      my $zz;
      $zz = %::FILENAME;
  }
  
  use CGI::Carp qw(fatalsToBrowser);
  
  require 'globals.pl';
  
  sub GeneratePersonInput {
      my ($field, $required, $def_value, $extraJavaScript) = (@_);
      $extraJavaScript ||= "";
      if ($extraJavaScript ne "") {
          $extraJavaScript = "onChange=\"$extraJavaScript\"";
      }
      return "<INPUT NAME=\"$field\" SIZE=32 $extraJavaScript VALUE=\"$def_value\">";
  }
  
  sub GeneratePeopleInput {
      my ($field, $def_value) = (@_);
      return "<INPUT NAME=\"$field\" SIZE=45 VALUE=\"$def_value\">";
  }
  
  
  
  
  # Implementations of several of the below were blatently stolen from CGI.pm,
  # by Lincoln D. Stein.
  
  
  # Get rid of all the %xx encoding and the like from the given URL.
  
  sub url_decode {
      my ($todecode) = (@_);
      $todecode =~ tr/+/ /;       # pluses become spaces
      $todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
      return $todecode;
  }
  
  
  # Quotify a string, suitable for putting into a URL.
  
  sub url_quote {
      my($toencode) = (@_);
      $toencode=~s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg;
      return $toencode;
  }
  
  
  sub ProcessFormFields {
      my ($buffer) = (@_);
      undef %::FORM;
      undef %::MFORM;
  
      my %isnull;
      my $remaining = $buffer;
      while ($remaining ne "") {
  	my $item;
  	if ($remaining =~ /^([^&]*)&(.*)$/) {
  	    $item = $1;
  	    $remaining = $2;
  	} else {
  	    $item = $remaining;
  	    $remaining = "";
  	}
  
  	my $name;
  	my $value;
  	if ($item =~ /^([^=]*)=(.*)$/) {
  	    $name = $1;
  	    $value = url_decode($2);
  	} else {
  	    $name = $item;
  	    $value = "";
  	}
  	if ($value ne "") {
  	    if (defined $::FORM{$name}) {
  		$::FORM{$name} .= $value;
  		my $ref = $::MFORM{$name};
  		push @$ref, $value;
  	    } else {
  		$::FORM{$name} = $value;
  		$::MFORM{$name} = [$value];
  	    }
          } else {
              $isnull{$name} = 1;
          }
      }
      if (defined %isnull) {
          foreach my $name (keys(%isnull)) {
              if (!defined $::FORM{$name}) {
                  $::FORM{$name} = "";
                  $::MFORM{$name} = [];
              }
          }
      }
  }
  
  
  sub ProcessMultipartFormFields {
      my ($boundary) = (@_);
      $boundary =~ s/^-*//;
      my $remaining = $ENV{"CONTENT_LENGTH"};
      my $inheader = 1;
      my $itemname = "";
  #    open(DEBUG, ">debug") || die "Can't open debugging thing";
  #    print DEBUG "Boundary is '$boundary'\n";
      while ($remaining > 0 && ($_ = <STDIN>)) {
          $remaining -= length($_);
  #        print DEBUG "< $_";
          if ($_ =~ m/^-*$boundary/) {
  #            print DEBUG "Entered header\n";
              $inheader = 1;
              $itemname = "";
              next;
          }
  
          if ($inheader) {
              if (m/^\s*$/) {
                  $inheader = 0;
  #                print DEBUG "left header\n";
                  $::FORM{$itemname} = "";
              }
              if (m/^Content-Disposition:\s*form-data\s*;\s*name\s*=\s*"([^\"]+)"/i) {
                  $itemname = $1;
  #                print DEBUG "Found itemname $itemname\n";
                  if (m/;\s*filename\s*=\s*"([^\"]+)"/i) {
                      $::FILENAME{$itemname} = $1;
                  }
              }
              
              next;
          }
          $::FORM{$itemname} .= $_;
      }
      delete $::FORM{""};
      # Get rid of trailing newlines.
      foreach my $i (keys %::FORM) {
          chomp($::FORM{$i});
          $::FORM{$i} =~ s/\r$//;
      }
  }
          
  
  
  
  
  sub FormData {
      my ($field) = (@_);
      return $::FORM{$field};
  }
  
  sub html_quote {
      my ($var) = (@_);
      $var =~ s/\&/\&amp;/g;
      $var =~ s/</\&lt;/g;
      $var =~ s/>/\&gt;/g;
      return $var;
  }
  
  sub value_quote {
      my ($var) = (@_);
      $var =~ s/\&/\&amp;/g;
      $var =~ s/</\&lt;/g;
      $var =~ s/>/\&gt;/g;
      $var =~ s/"/\&quot;/g;
      $var =~ s/\n/\&#010;/g;
      $var =~ s/\r/\&#013;/g;
      return $var;
  }
  
  sub navigation_header {
      if (defined $::COOKIE{"BUGLIST"} && $::COOKIE{"BUGLIST"} ne "" &&
          defined $::FORM{'id'}) {
  	my @bugs = split(/:/, $::COOKIE{"BUGLIST"});
  	my $cur = lsearch(\@bugs, $::FORM{"id"});
  	print "<B>Bug List:</B> (@{[$cur + 1]} of @{[$#bugs + 1]})\n";
  	print "<A HREF=\"show_bug.cgi?id=$bugs[0]\">First</A>\n";
  	print "<A HREF=\"show_bug.cgi?id=$bugs[$#bugs]\">Last</A>\n";
  	if ($cur > 0) {
  	    print "<A HREF=\"show_bug.cgi?id=$bugs[$cur - 1]\">Prev</A>\n";
  	} else {
  	    print "<I><FONT COLOR=\#777777>Prev</FONT></I>\n";
  	}
  	if ($cur < $#bugs) {
  	    $::next_bug = $bugs[$cur + 1];
  	    print "<A HREF=\"show_bug.cgi?id=$::next_bug\">Next</A>\n";
  	} else {
  	    print "<I><FONT COLOR=\#777777>Next</FONT></I>\n";
  	}
      }
      print "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF=query.cgi>Query page</A>\n";
      print "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A HREF=enter_bug.cgi>Enter new bug</A>\n"
  }
  
  
  sub make_options {
      my ($src,$default,$isregexp) = (@_);
      my $last = "";
      my $popup = "";
      my $found = 0;
      $default = "" if !defined $default;
  
      if ($src) {
          foreach my $item (@$src) {
              if ($item eq "-blank-" || $item ne $last) {
                  if ($item eq "-blank-") {
                      $item = "";
                  }
                  $last = $item;
                  if ($isregexp ? $item =~ $default : $default eq $item) {
                      $popup .= "<OPTION SELECTED VALUE=\"$item\">$item";
                      $found = 1;
                  } else {
                      $popup .= "<OPTION VALUE=\"$item\">$item";
                  }
              }
          }
      }
      if (!$found && $default ne "") {
  	$popup .= "<OPTION SELECTED>$default";
      }
      return $popup;
  }
  
  
  sub make_popup {
      my ($name,$src,$default,$listtype,$onchange) = (@_);
      my $popup = "<SELECT NAME=$name";
      if ($listtype > 0) {
          $popup .= " SIZE=5";
          if ($listtype == 2) {
              $popup .= " MULTIPLE";
          }
      }
      if (defined $onchange && $onchange ne "") {
          $popup .= " onchange=$onchange";
      }
      $popup .= ">" . make_options($src, $default,
                                   ($listtype == 2 && $default ne ""));
      $popup .= "</SELECT>";
      return $popup;
  }
  
  
  sub PasswordForLogin {
      my ($login) = (@_);
      SendSQL("select cryptpassword from profiles where login_name = " .
  	    SqlQuote($login));
      my $result = FetchOneColumn();
      if (!defined $result) {
          $result = "";
      }
      return $result;
  }
  
  
  sub quietly_check_login() {
      $::usergroupset = '0';
      my $loginok = 0;
      if (defined $::COOKIE{"Bugzilla_login"} &&
  	defined $::COOKIE{"Bugzilla_logincookie"}) {
          ConnectToDatabase();
          if (!defined $ENV{'REMOTE_HOST'}) {
              $ENV{'REMOTE_HOST'} = $ENV{'REMOTE_ADDR'};
          }
          SendSQL("select profiles.groupset, profiles.login_name, " .
                  "profiles.login_name = " .
  		SqlQuote($::COOKIE{"Bugzilla_login"}) .
  		" and profiles.cryptpassword = logincookies.cryptpassword " .
  		"and logincookies.hostname = " .
  		SqlQuote($ENV{"REMOTE_HOST"}) .
  		" from profiles,logincookies where logincookies.cookie = " .
  		SqlQuote($::COOKIE{"Bugzilla_logincookie"}) .
  		" and profiles.userid = logincookies.userid");
          my @row;
          if (@row = FetchSQLData()) {
              $loginok = $row[2];
              if ($loginok) {
                  $::usergroupset = $row[0];
                  $::COOKIE{"Bugzilla_login"} = $row[1]; # Makes sure case is in
                                                         # canonical form.
              }
          }
      }
      if (!$loginok) {
          delete $::COOKIE{"Bugzilla_login"};
      }
      return $loginok;
  }
  
  
  
  
  sub CheckEmailSyntax {
      my ($addr) = (@_);
      my $match = Param('emailregexp');
      if ($addr !~ /$match/) {
          print "Content-type: text/html\n\n";
  
          PutHeader("Check e-mail syntax");
          print "The e-mail address you entered\n";
          print "(<b>$addr</b>) didn't match our minimal\n";
          print "syntax checking for a legal email address.\n";
          print Param('emailregexpdesc');
          print "<p>Please click <b>back</b> and try again.\n";
          exit;
      }
  }
  
  
  
  sub MailPassword {
      my ($login, $password) = (@_);
      my $urlbase = Param("urlbase");
      my $template = "From: bugzilla-daemon
  To: %s
  Subject: Your bugzilla password.
  
  To use the wonders of bugzilla, you can use the following:
  
   E-mail address: %s
         Password: %s
  
   To change your password, go to:
   ${urlbase}changepassword.cgi
  
   (Your bugzilla and CVS password, if any, are not currently synchronized.
   Top hackers are working around the clock to fix this, as you read this.)
  ";
      my $msg = sprintf($template, $login . Param('emailsuffix'),
                        $login, $password);
  
      open SENDMAIL, "|/usr/lib/sendmail -t";
      print SENDMAIL $msg;
      close SENDMAIL;
  
      print "The password for the e-mail address\n";
      print "$login has been e-mailed to that address.\n";
      print "<p>When the e-mail arrives, you can click <b>Back</b>\n";
      print "and enter your password in the form there.\n";
  }
  
  
  sub confirm_login {
      my ($nexturl) = (@_);
  
  # Uncommenting the next line can help debugging...
  #    print "Content-type: text/plain\n\n";
  
      ConnectToDatabase();
      if (defined $::FORM{"Bugzilla_login"} &&
  	defined $::FORM{"Bugzilla_password"}) {
  
  	my $enteredlogin = $::FORM{"Bugzilla_login"};
          my $enteredpwd = $::FORM{"Bugzilla_password"};
          CheckEmailSyntax($enteredlogin);
  
          my $realcryptpwd  = PasswordForLogin($::FORM{"Bugzilla_login"});
          
          if (defined $::FORM{"PleaseMailAPassword"}) {
  	    my $realpwd;
              if ($realcryptpwd eq "") {
  		$realpwd = InsertNewUser($enteredlogin, "");
              } else {
                  SendSQL("select password from profiles where login_name = " .
  			SqlQuote($enteredlogin));
  		$realpwd = FetchOneColumn();
              }
  	    print "Content-type: text/html\n\n";
  	    PutHeader("<H1>Password has been emailed");
              MailPassword($enteredlogin, $realpwd);
              exit;
          }
  
  	my $enteredcryptpwd = crypt($enteredpwd, substr($realcryptpwd, 0, 2));
          if ($realcryptpwd eq "" || $enteredcryptpwd ne $realcryptpwd) {
              print "Content-type: text/html\n\n";
  	    PutHeader("Login failed");
              print "The username or password you entered is not valid.\n";
              print "Please click <b>Back</b> and try again.\n";
              exit;
          }
          $::COOKIE{"Bugzilla_login"} = $enteredlogin;
          if (!defined $ENV{'REMOTE_HOST'}) {
              $ENV{'REMOTE_HOST'} = $ENV{'REMOTE_ADDR'};
          }
  	SendSQL("insert into logincookies (userid,cryptpassword,hostname) values (@{[DBNameToIdAndCheck($enteredlogin)]}, @{[SqlQuote($realcryptpwd)]}, @{[SqlQuote($ENV{'REMOTE_HOST'})]})");
          SendSQL("select LAST_INSERT_ID()");
          my $logincookie = FetchOneColumn();
  
          $::COOKIE{"Bugzilla_logincookie"} = $logincookie;
          print "Set-Cookie: Bugzilla_login=$enteredlogin ; path=/; expires=Sun, 30-Jun-2029 00:00:00 GMT\n";
          print "Set-Cookie: Bugzilla_logincookie=$logincookie ; path=/; expires=Sun, 30-Jun-2029 00:00:00 GMT\n";
  
          # This next one just cleans out any old bugzilla passwords that may
          # be sitting around in the cookie files, from the bad old days when
          # we actually stored the password there.
          print "Set-Cookie: Bugzilla_password= ; path=/; expires=Sun, 30-Jun-80 00:00:00 GMT\n";
  
      }
  
  
      my $loginok = quietly_check_login();
  
      if ($loginok != 1) {
          print "Content-type: text/html\n\n";
          PutHeader("Login");
          print "I need a legitimate e-mail address and password to continue.\n";
          if (!defined $nexturl || $nexturl eq "") {
  	    # Sets nexturl to be argv0, stripping everything up to and
  	    # including the last slash.
  	    $0 =~ m:[^/]*$:;
  	    $nexturl = $&;
          }
          my $method = "POST";
          if (defined $ENV{"REQUEST_METHOD"} && length($::buffer) > 1) {
              $method = $ENV{"REQUEST_METHOD"};
          }
          print "
  <FORM action=$nexturl method=$method>
  <table>
  <tr>
  <td align=right><b>E-mail address:</b></td>
  <td><input size=35 name=Bugzilla_login></td>
  </tr>
  <tr>
  <td align=right><b>Password:</b></td>
  <td><input type=password size=35 name=Bugzilla_password></td>
  </tr>
  </table>
  ";
          foreach my $i (keys %::FORM) {
              if ($i =~ /^Bugzilla_/) {
                  next;
              }
              print "<input type=hidden name=$i value=\"@{[value_quote($::FORM{$i})]}\">\n";
          }
          print "
  <input type=submit value=Login name=GoAheadAndLogIn><hr>
  If you don't have a password, or have forgotten it, then please fill in the
  e-mail address above and click
   here:<input type=submit value=\"E-mail me a password\"
  name=PleaseMailAPassword>
  </form>\n";
  
          # This seems like as good as time as any to get rid of old
          # crufty junk in the logincookies table.  Get rid of any entry
          # that hasn't been used in a month.
          SendSQL("delete from logincookies where to_days(now()) - to_days(lastused) > 30");
  
          
          exit;
      }
  
      # Update the timestamp on our logincookie, so it'll keep on working.
      SendSQL("update logincookies set lastused = null where cookie = $::COOKIE{'Bugzilla_logincookie'}");
  }
  
  
  sub PutHeader {
      my ($title, $h1, $h2, $extra) = (@_);
  
      if (!defined $h1) {
  	$h1 = $title;
      }
      if (!defined $h2) {
  	$h2 = "";
      }
      if (!defined $extra) {
  	$extra = "";
      }
  
      print "<HTML><HEAD>\n<TITLE>$title</TITLE>\n";
      print Param("headerhtml") . "\n</HEAD>\n";
      print "<BODY   BGCOLOR=\"#FFFFFF\" TEXT=\"#000000\"\n";
      print "LINK=\"#0000EE\" VLINK=\"#551A8B\" ALINK=\"#FF0000\" $extra>\n";
  
      print PerformSubsts(Param("bannerhtml"), undef);
  
      print "<TABLE BORDER=0 CELLPADDING=12 CELLSPACING=0 WIDTH=\"100%\">\n";
      print " <TR>\n";
      print "  <TD>\n";
      print "   <TABLE BORDER=0 CELLPADDING=0 CELLSPACING=2>\n";
      print "    <TR><TD VALIGN=TOP ALIGN=CENTER NOWRAP>\n";
      print "     <FONT SIZE=\"+3\"><B><NOBR>$h1</NOBR></B></FONT>\n";
      print "    </TD></TR><TR><TD VALIGN=TOP ALIGN=CENTER>\n";
      print "     <B>$h2</B>\n";
      print "    </TD></TR>\n";
      print "   </TABLE>\n";
      print "  </TD>\n";
      print "  <TD>\n";
  
      print Param("blurbhtml");
  
      print "</TD></TR></TABLE>\n";
  }
  
  
  sub DumpBugActivity {
      my ($id, $starttime) = (@_);
      my $datepart = "";
      if (defined $starttime) {
          $datepart = "and bugs_activity.bug_when >= $starttime";
      }
      my $query = "
          select bugs_activity.field, bugs_activity.bug_when,
                  bugs_activity.oldvalue, bugs_activity.newvalue,
                  profiles.login_name
          from bugs_activity,profiles
          where bugs_activity.bug_id = $id $datepart
          and profiles.userid = bugs_activity.who
          order by bugs_activity.bug_when";
  
      SendSQL($query);
      
      print "<table border cellpadding=4>\n";
      print "<tr>\n";
      print "    <th>Who</th><th>What</th><th>Old value</th><th>New value</th><th>When</th>\n";
      print "</tr>\n";
      
      my @row;
      while (@row = FetchSQLData()) {
          my ($field,$when,$old,$new,$who) = (@row);
          $old = value_quote($old);
          $new = value_quote($new);
          if ($old eq "") {
              $old = "&nbsp;";
          }
          if ($new eq "") {
              $new = "&nbsp;";
          }
          print "<tr>\n";
          print "<td>$who</td>\n";
          print "<td>$field</td>\n";
          print "<td>$old</td>\n";
          print "<td>$new</td>\n";
          print "<td>$when</td>\n";
          print "</tr>\n";
      }
      print "</table>\n";
  }
  
  
  
  
  
  
  ############# Live code below here (that is, not subroutine defs) #############
  
  
  $| = 1;
  
  # Uncommenting this next line can help debugging.
  # print "Content-type: text/html\n\nHello mom\n";
  
  # foreach my $k (sort(keys %ENV)) {
  #     print "$k $ENV{$k}<br>\n";
  # }
  
  if (defined $ENV{"REQUEST_METHOD"}) {
      if ($ENV{"REQUEST_METHOD"} eq "GET") {
          if (defined $ENV{"QUERY_STRING"}) {
              $::buffer = $ENV{"QUERY_STRING"};
          } else {
              $::buffer = "";
          }
          ProcessFormFields $::buffer;
      } else {
          if ($ENV{"CONTENT_TYPE"} =~
              m@multipart/form-data; boundary=\s*([^; ]+)@) {
              ProcessMultipartFormFields($1);
              $::buffer = "";
          } else {
              read STDIN, $::buffer, $ENV{"CONTENT_LENGTH"} ||
                  die "Couldn't get form data";
              ProcessFormFields $::buffer;
          }
      }
  }
  
  
  if (defined $ENV{"HTTP_COOKIE"}) {
      foreach my $pair (split(/;/, $ENV{"HTTP_COOKIE"})) {
  	$pair = trim($pair);
  	if ($pair =~ /^([^=]*)=(.*)$/) {
  	    $::COOKIE{$1} = $2;
  	} else {
  	    $::COOKIE{$pair} = "";
  	}
      }
  }
  
  1;
  
  
  
  1.1                  apache-site/bugs/CHANGES
  
  Index: CHANGES
  ===================================================================
  This file contains only important changes made to Bugzilla.  If you
  are updating from an older verseion, make sure that you check this file!
  
  For a more complete list of what has changed, use Bonsai
  (http://cvs-mirror.mozilla.org/webtools/bonsai/cvsqueryform.cgi) to
  query the CVS tree.  For example,
  
      http://cvs-mirror.mozilla.org/webtools/bonsai/cvsquery.cgi?module=all&branch=HEAD&branchtype=match&dir=mozilla%2Fwebtools%2Fbugzilla&file=&filetype=match&who=&whotype=match&sortby=Date&hours=2&date=week&mindate=&maxdate=&cvsroot=%2Fcvsroot 
  
  will tell you what has been changed in the last week.
  
  
  10/12/99 The CHANGES file is now obsolete!  There is a new file called
  checksetup.pl.  You should get in the habit of running that file every time
  you update your installation of Bugzilla.  That file will be constantly 
  updated to automatically update your installation to match any code changes.
  If you're curious as to what is going on, changes are commented in that file, 
  at the end.
  
  Many thanks to Holger Schurig <ho...@nikocity.de> for writing this
  script!
  
  
  
  10/11/99 Restructured voting database to add a cached value in each
  bug recording how many total votes that bug has.  While I'm at it, I
  removed the unused "area" field from the bugs database.  It is
  distressing to realize that the bugs table has reached the maximum
  number of indices allowed by MySQL (16), which may make future
  enhancements awkward.
  
  You must feed the following to MySQL:
  
  	alter table bugs drop column area;
  	alter table bugs add column votes mediumint not null, add index (votes);
  
  You then *must* delete the data/versioncache file when you make this
  change, as it contains references to the "area" field.  Deleting it is safe,
  bugzilla will correctly regenerate it.
  
  If you have been using the voting feature at all, then you will then
  need to update the voting cache.  You can do this by visiting the
  sanitycheck.cgi page, and taking it up on its offer to rebuild the
  votes stuff.
  
  
  10/7/99 Added voting ability.  You must run the new script
  "makevotestable.sh".  You must also feed the following to mysql:
  
  	alter table products add column votesperuser smallint not null;
  
  
  
  9/15/99 Apparently, newer alphas of MySQL won't allow you to have
  "when" as a column name.  So, I have had to rename a column in the
  bugs_activity table.  You must feed the below to mysql or you won't
  work at all.
  
  	alter table bugs_activity change column when bug_when datetime not null;
  
  
  8/16/99 Added "OpenVMS" to the list of OS's. Feed this to mysql:
  
  	alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "BeOS", "OpenVMS", "other") not null;
  
  6/22/99 Added an entry to the attachments table to record who the submitter
  was.  Nothing uses this yet, but it still should be recorded.
  
  	alter table attachments add column submitter_id mediumint not null;
  
  You should also run this script to populate the new field:
  
  #!/usr/bonsaitools/bin/perl -w
  use diagnostics;
  use strict;
  require "globals.pl";
  $|=1;
  ConnectToDatabase();
  SendSQL("select bug_id, attach_id from attachments order by bug_id");
  my @list;
  while (MoreSQLData()) {
      my @row = FetchSQLData();
      push(@list, \@row);
  }
  foreach my $ref (@list) {
      my ($bug, $attach) = (@$ref);
      SendSQL("select long_desc from bugs where bug_id = $bug");
      my $comment = FetchOneColumn() . "Created an attachment (id=$attach)";
  
      if ($comment =~ m@-* Additional Comments From ([^ ]*)[- 0-9/:]*\nCreated an attachment \(id=$attach\)@) {
  	print "Found $1\n";
  	SendSQL("select userid from profiles where login_name=" .
  		SqlQuote($1));
  	my $userid = FetchOneColumn();
  	if (defined $userid && $userid > 0) {
  	    SendSQL("update attachments set submitter_id=$userid where attach_id = $attach");
  	}
      } else {
  	print "Bug $bug can't find comment for attachment $attach\n";
      }
  }
  
  
  
  
  
  
  6/14/99 Added "BeOS" to the list of OS's. Feed this to mysql:
  
  	alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "BeOS", "other") not null;
  
  
  5/27/99 Added support for dependency information.  You must run the new
  "makedependenciestable.sh" script.  You can turn off dependencies with the new
  "usedependencies" param, but it defaults to being on.  Also, read very
  carefully the description for the new "webdotbase" param; you will almost
  certainly need to tweak it.
  
  
  5/24/99 Added "Mac System 8.6" and "Neutrino" to the list of OS's.
  Feed this to mysql:
  
  	alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "other") not null;
  
  
  5/12/99 Added a pref to control how much email you get.  This needs a new
  column in the profiles table, so feed the following to mysql:
  
  	alter table profiles add column emailnotification enum("ExcludeSelfChanges", "CConly", "All") not null default "ExcludeSelfChanges";
  
  5/5/99 Added the ability to search by creation date.  To make this perform
  well, you ought to do the following:
  
  	alter table bugs change column creation_ts creation_ts datetime not null, add index (creation_ts);
  
  
  4/30/99 Added a new severity, "blocker".  To get this into your running
  Bugzilla, do the following:
  
  	alter table bugs change column bug_severity bug_severity enum("blocker", "critical", "major", "normal", "minor", "trivial", "enhancement") not null;
  
  
  4/22/99 There was a bug where the long descriptions of bugs had a variety of
  newline characters at the end, depending on the operating system of the browser
  that submitted the text.  This bug has been fixed, so that no further changes
  like that will happen.  But to fix problems that have already crept into your
  database, you can run the following perl script (which is slow and ugly, but
  does work:)
  #!/usr/bonsaitools/bin/perl -w
  use diagnostics;
  use strict;
  require "globals.pl";
  $|=1;
  ConnectToDatabase();
  SendSQL("select bug_id from bugs order by bug_id");
  my @list;
  while (MoreSQLData()) {
      push(@list, FetchOneColumn());
  }
  foreach my $id (@list) {
      if ($id % 50 == 0) {
  	print "\n$id ";
      }
      SendSQL("select long_desc from bugs where bug_id = $id");
      my $comment = FetchOneColumn();
      my $orig = $comment;
      $comment =~ s/\r\n/\n/g;     # Get rid of windows-style line endings.
      $comment =~ s/\r/\n/g;       # Get rid of mac-style line endings.
      if ($comment ne $orig) {
  	SendSQL("update bugs set long_desc = " . SqlQuote($comment) .
  		" where bug_id = $id");
  	print ".";
      } else {
  	print "-";
      }
  }
  
  
  
  4/8/99 Added ability to store patches with bugs.  This requires a new table
  to store the data, so you will need to run the "makeattachmenttable.sh" script.
  
  3/25/99 Unfortunately, the HTML::FromText CPAN module had too many bugs, and
  so I had to roll my own.  We no longer use the HTML::FromText CPAN module.
  
  3/24/99 (This entry has been removed.  It used to say that we required the
  HTML::FromText CPAN module, but that's no longer true.)
  
  3/22/99 Added the ability to query by fields which have changed within a date
  range.  To make this perform a bit better, we need a new index:
  
  	alter table bugs_activity add index (field);
  
  3/10/99 Added 'groups' stuff, where we have different group bits that we can
  put on a person or on a bug.  Some of the group bits control access to bugzilla
  features.  And a person can't access a bug unless he has every group bit set
  that is also set on the bug.  See the comments in makegroupstable.sh for a bit
  more info.
  
  The 'maintainer' param is now used only as an email address for people to send
  complaints to.  The groups table is what is now used to determine permissions.
  
  You will need to run the new script "makegroupstable.sh".  And then you need to
  feed the following lines to MySQL (replace XXX with the login name of the
  maintainer, the person you wish to be all-powerful).
  
  	alter table bugs add column groupset bigint not null;
  	alter table profiles add column groupset bigint not null;
  	update profiles set groupset=0x7fffffffffffffff where login_name = XXX;
  
  
  
  3/8/99 Added params to control how priorities are set in a new bug.  You can
  now choose whether to let submitters of new bugs choose a priority, or whether
  they should just accept the default priority (which is now no longer hardcoded
  to "P2", but is instead a param.)  The default value of the params will cause
  the same behavior as before.
  
  3/3/99 Added a "disallownew" field to the products table.  If non-zero, then
  don't let people file new bugs against this product.  (This is for when a 
  product is retired, but you want to keep the bug reports around for posterity.)
  Feed this to MySQL:
  
  	alter table products add column disallownew tinyint not null;
  
  
  2/8/99 Added FreeBSD to the list of OS's.  Feed this to MySQL:
  
  	alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "OS/2", "other") not null;
  
  
  2/4/99 Added a new column "description" to the components table, and added 
  links to a new page which will use this to describe the components of a 
  given product.  Feed this to MySQL:
  
  	alter table components add column description mediumtext not null;
  
  
  2/3/99 Added a new column "initialqacontact" to the components table that gives
  an initial QA contact field.  It may be empty if you wish the initial qa
  contact to be empty.  If you're not using the QA contact field, you don't need
  to add this column, but you might as well be safe and add it anyway:
  
  	alter table components add column initialqacontact tinytext not null;
  
  
  2/2/99 Added a new column "milestoneurl" to the products table that gives a URL
  which is to describe the currently defined milestones for a product.  If you
  don't use target milestone, you might be able to get away without adding this
  column, but you might as well be safe and add it anyway:
  
  	alter table products add column milestoneurl tinytext not null;
  
  
  1/29/99 Whoops; had a mispelled op_sys.  It was "Mac System 7.1.6"; it should
  be "Mac System 7.6.1".  It turns out I had no bugs with this value set, so I
  could just do the below simple command.  If you have bugs with this value, you
  may need to do something more complicated.
  
  	alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "OSF/1", "Solaris", "SunOS", "OS/2", "other") not null;
  
  
  
  1/20/99 Added new fields: Target Milestone, QA Contact, and Status Whiteboard.
  These fields are all optional in the UI; there are parameters to turn them on.
  However, whether or not you use them, the fields need to be in the DB.  There
  is some code that needs them, even if you don't.
  
  To update your DB to have these fields, send the following to MySQL:
  
          alter table bugs add column target_milestone varchar(20) not null,
                  add column qa_contact mediumint not null,
                  add column status_whiteboard mediumtext not null,
                  add index (target_milestone), add index (qa_contact);
  
  
  
  1/18/99 You can now query by CC.  To make this perform reasonably, the CC table
  needs some indices.  The following MySQL does the necessary stuff:
  
  	alter table cc add index (bug_id), add index (who);
  
  
  1/15/99 The op_sys field can now be queried by (and more easily tweaked).
  To make this perform reasonably, it needs an index.  The following MySQL 
  command will create the necessary index:
  
  	alter table bugs add index (op_sys);
  
  
  12/2/98 The op_sys and rep_platform fields have been tweaked.  op_sys
  is now an enum, rather than having the legal values all hard-coded in
  perl.  rep_platform now no longer allows a value of "X-Windows".
  
  Here's how I ported to the new world.  This ought to work for you too.
  Actually, it's probably overkill.  I had a lot of illegal values for op_sys
  in my tables, from importing bugs from strange places.  If you haven't done 
  anything funky, then much of the below will be a no-op.
  
  First, send the following commands to MySQL to make sure all your values for
  rep_platform and op_sys are legal in the new world..
  
  	update bugs set rep_platform="Sun" where rep_platform="X-Windows" and op_sys like "Solaris%";
  	update bugs set rep_platform="SGI" where rep_platform="X-Windows" and op_sys = "IRIX";
  	update bugs set rep_platform="SGI" where rep_platform="X-Windows" and op_sys = "HP-UX";
  	update bugs set rep_platform="DEC" where rep_platform="X-Windows" and op_sys = "OSF/1";
  	update bugs set rep_platform="PC" where rep_platform="X-Windows" and op_sys = "Linux";
  	update bugs set rep_platform="other" where rep_platform="X-Windows";
  	update bugs set rep_platform="other" where rep_platform="";
  	update bugs set op_sys="Mac System 7" where op_sys="System 7";
  	update bugs set op_sys="Mac System 7.5" where op_sys="System 7.5";
  	update bugs set op_sys="Mac System 8.0" where op_sys="8.0";
  	update bugs set op_sys="OSF/1" where op_sys="Digital Unix 4.0";
  	update bugs set op_sys="IRIX" where op_sys like "IRIX %";
  	update bugs set op_sys="HP-UX" where op_sys like "HP-UX %";
  	update bugs set op_sys="Windows NT" where op_sys like "NT %";
  	update bugs set op_sys="OSF/1" where op_sys like "OSF/1 %";
  	update bugs set op_sys="Solaris" where op_sys like "Solaris %";
  	update bugs set op_sys="SunOS" where op_sys like "SunOS%";
  	update bugs set op_sys="other" where op_sys = "Motif";
  	update bugs set op_sys="other" where op_sys = "Other";
  
  Next, send the following commands to make sure you now have only legal
  entries in your table.  If either of the queries do not come up empty, then
  you have to do more stuff like the above.
  
  	select bug_id,op_sys,rep_platform from bugs where rep_platform not regexp "^(All|DEC|HP|Macintosh|PC|SGI|Sun|X-Windows|Other)$";
  	select bug_id,op_sys,rep_platform from bugs where op_sys not regexp "^(All|Windows 3.1|Windows 95|Windows 98|Windows NT|Mac System 7|Mac System 7.5|Mac System 7.1.6|Mac System 8.0|AIX|BSDI|HP-UX|IRIX|Linux|OSF/1|Solaris|SunOS|other)$";
  
  Finally, once that's all clear, alter the table to make enforce the new legal
  entries:
  
  	alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.1.6", "Mac System 8.0", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "OSF/1", "Solaris", "SunOS", "other") not null, change column rep_platform rep_platform enum("All", "DEC", "HP", "Macintosh", "PC", "SGI", "Sun", "Other");
  
  
  
  
  
  11/20/98 Added searching of CC field.  To better support this, added
  some indexes to the CC table.  You probably want to execute the following
  mysql commands:
  
  	alter table cc add index (bug_id);
  	alter table cc add index (who);
  
  
  10/27/98 security check for legal products in place. bug charts are not
  available as an option if collectstats.pl has never been run. all products 
  get daily stats collected now. README updated: Chart::Base is listed as
  a requirement, instructions for using collectstats.pl included as 
  an optional step. also got silly and added optional quips to bug
  reports. 
  
  10/17/98 modified README installation instructions slightly. 
  
  10/7/98 Added a new table called "products".  Right now, this is used
  only to have a description for each product, and that description is
  only used when initially adding a new bug.  Anyway, you *must* create
  the new table (which you can do by running the new makeproducttable.sh
  script).  If you just leave it empty, things will work much as they
  did before, or you can add descriptions for some or all of your
  products.
  
  
  9/15/98 Everything has been ported to Perl.  NO MORE TCL.  This
  transition should be relatively painless, except for the "params"
  file.  This is the file that contains parameters you've set up on the
  editparams.cgi page.  Before changing to Perl, this was a tcl-syntax
  file, stored in the same directory as the code; after the change to
  Perl, it becomes a perl-syntax file, stored in a subdirectory named
  "data".  See the README file for more details on what version of Perl
  you need.
  
  So, if updating from an older version of Bugzilla, you will need to
  edit data/param, change the email address listed for
  $::param{'maintainer'}, and then go revisit the editparams.cgi page
  and reset all the parameters to your taste.  Fortunately, your old
  params file will still be around, and so you ought to be able to
  cut&paste important bits from there.
  
  Also, note that the "whineatnews" script has changed name (it now has
  an extension of .pl instead of .tcl), so you'll need to change your
  cron job.
  
  And the "comments" file has been moved to the data directory.  Just do
  "cat comments >> data/comments" to restore any old comments that may
  have been lost.
  
  
  
  9/2/98 Changed the way password validation works.  We now keep a
  crypt'd version of the password in the database, and check against
  that.  (This is silly, because we're also keeping the plaintext
  version there, but I have plans...)  Stop passing the plaintext
  password around as a cookie; instead, we have a cookie that references
  a record in a new database table, logincookies.
  
  IMPORTANT: if updating from an older version of Bugzilla, you must run
  the following commands to keep things working:
  
   ./makelogincookiestable.sh
   echo "alter table profiles add column cryptpassword varchar(64);" | mysql bugs
   echo "update profiles set cryptpassword = encrypt(password,substring(rand(),3, 4));" | mysql bugs
  
  
  
  
  1.1                  apache-site/bugs/README
  
  Index: README
  ===================================================================
  This is Bugzilla.  See <http://www.mozilla.org/bugs/>.
  
  
  	==========
  	DISCLAIMER
  	==========
  
     This is not very well packaged code.  It's not packaged at all.  Don't
  come here expecting something you plop in a directory, twiddle a few
  things, and you're off and using it.  Work has to be done to get there.  
  We'd like to get there, but it wasn't clear when that would be, and so we
  decided to let people see it first.
  
  	============
  	INSTALLATION
  	============
  
  0. Introduction
  
     Installation of bugzilla is pretty straight forward, especially if your
  machine already has MySQL and the MySQL-related perl packages installed.
  If those aren't installed yet, then that's the first order of business.  The
  other necessary ingredient is a web server set up to run cgi scripts.
  
  1. Installing the Prerequisites
  
     The software packages necessary for the proper running of bugzilla are:
  
  	1. MySQL database server and the mysql client
  	2. Perl (5.004 or greater)
  	3. DBI Perl module 
  	4. Data::Dumper Perl module
  	5. MySQL related Perl module collection
  	6. TimeDate Perl module collection
  	7. GD perl module (1.18 or greater)
  	8. Chart::Base Perl module (0.99 or greater)
  	9. The web server of your choice
  
     Bugzilla has quite a few prerequisites, but none of them are TCL.
  Previous versions required TCL, but it no longer needed (or used).
  
  1.1. Getting and setting up MySQL database
  
     Visit MySQL homepage at http://www.mysql.org and grab the latest stable
  release of the server.  Both binaries and source are available and which you
  get shouldn't matter.  Be aware that many of the binary versions of MySQL store
  their data files in /var which on many installations (particularly common with
  linux installations) is part of a smaller root partition.  If you decide to
  build from sources you can easily set the dataDir as an option to configure.
  
    If you've installed from source or non-package (RPM, deb, etc.) binaries
  you'll want to make sure to add mysqld to your init scripts so the server
  daemon will come back up whenever your machine reboots.
  
    You also may want to edit those init scripts, to make sure that mysqld will
  accept large packets.  By default, mysqld is set up to only accept packets up
  to 64K long.  This limits the size of attachments you may put on bugs.  If you
  add something like "-O max_allowed_packet=1M" to the command that starts mysqld
  (or safe_mysqld), then you will be able to have attachments up to about 1
  megabyte.
  
  1.2. Perl (5.004 or greater)
  
    Any machine that doesn't have perl on it is a sad machine indeed.  Perl
  for *nix systems can be gotten in source form from http://www.perl.com.
  
    Perl is now a far cry from the the single compiler/interpreter binary it
  once was. It now includes a great many required modules and quite a few other
  support files.  If you're not up to or not inclined to build perl from source,
  you'll want to install it on your machine using some sort of packaging system
  (be it RPM, deb, or what have you) to ensure a sane install.  In the subsequent
  sections you'll be installing quite a few perl modules; this can be quite
  ornery if your perl installation isn't up to snuff.
  
  1.3. DBI Perl module
  
     The DBI module is a generic Perl module used by other database related
  Perl modules.  For our purposes it's required by the MySQL-related
  modules.  As long as your Perl installation was done correctly the DBI
  module should be a breeze.  It's a mixed Perl/C module, but Perl's
  MakeMaker system simplifies the C compilation greatly.
  
    Like almost all Perl modules DBI can be found on the Comprehensive Perl
  Archive Network (CPAN) at http://www.cpan.org .  The CPAN servers have a
  real tendency to bog down, so please use mirrors.  The current location at
  the time of this writing (02/17/99) can be found in Appendix A.
  
    Quality, general Perl module installation instructions can be found on
  the CPAN website, but basically you'll just need to:
  
  	1. Untar the module tarball -- it should create its own directory
  	2. Enter the following commands:
  		perl Makefile.PL
  		make
  		make test
  		make install
  
     If everything went ok that should be all it takes.  For the vast
  majority of perl modules this is all that's required.
  
  1.4 Data::Dumper Perl module
  
     The Data::Dumper module provides data structure persistence for Perl
  (similar to Java's serialization).  It comes with later sub-releases of
  Perl 5.004, but a re-installation just to be sure it's available won't
  hurt anything.
  
     Data::Dumper is used by the MySQL related Perl modules.  It can be
  found on CPAN (link in Appendix A) and can be installed by following the
  same four step make sequence used for the DBI module.
  
  1.5. MySQL related Perl module collection
  
     The Perl/MySQL interface requires a few mutually-dependent perl
  modules.  These modules are grouped together into the the
  Msql-Mysql-modules package.  This package can be found at CPAN (link in
  Appendix A).  After the archive file has been downloaded it should be
  untarred.
  
     The MySQL modules are all build using one make file which is generated
  by running:
  
  	perl Makefile.PL
  
     The MakeMaker process will ask you a few questions about the desired
  compilation target and your MySQL installation.  For many of the questions
  the provided default will be adequate.
  
     When asked if your desired target is the MySQL or mSQL packages
  selected the MySQL related ones.  Later you will be asked if you wish to
  provide backwards compatibility with the older MySQL packages; you must
  answer YES to this question.  The default will be no, and if you select it
  things won't work later.
  
     A host of 'localhost' should be fine and a testing user of 'test' and
  a null password should find itself with sufficient access to run tests on 
  the 'test' database which MySQL created upon installation.  If 'make test' 
  and 'make install' go through without errors you should be ready to go as 
  far as database connectivity is concerned.
  
  1.6. TimeDate Perl module collection
  
     Many of the more common date/time/calendar related Perl modules have
  been grouped into a bundle similar to the MySQL modules bundle. This
  bundle is stored on the CPAN under the name TimeDate.  A (hopefully
  current) link can be found in Appendix A.  The component module we're most
  interested in is the Date::Format module, but installing all of them is
  probably a good idea anyway.  The standard Perl module installation
  instructions should work perfectly for this simple package.
  
  1.7. GD Perl module (1.18 or greater)
  
     The GD library was written by Thomas Boutel a long while ago to
  programatically generate images in C.  Since then it's become almost a
  defacto standard for programatic image construction.  The Perl bindings to
  it found in the GD library are used on a million web pages to generate
  graphs on the fly.  That's what bugzilla will be using it for so you'd
  better install it if you want any of the graphing to work.
    Actually bugzilla uses the Graph module which relies on GD itself, but
  isn't that always the way with OOP.  At any rate, you can find the GD
  library on CPAN (link in Appendix A) and it installs beautifully in the
  usual fashion.
  
  1.8. Chart::Base Perl module (0.99 or greater)
  
     The Chart module provides bugzilla with on-the-fly charting abilities.
  It can be installed in the usual fashion after it has been fetched from
  CPAN where it is found as the Chart-x.x... tarball in a directory to be
  listed in Appendix A.
  
  1.9. HTTP server
  
     You have a freedom of choice here - Apache, Netscape or any other server on
  UNIX would do. You can easily run the web server on a different machine than
  MySQL, but that makes MySQL permissions harder to manage.
  
     You'll want to make sure that your web server will run any file with the
  .cgi extension as a cgi and not just display it.  If you're using apache that
  means uncommenting the following line in the srm.conf file:
  
  	AddHandler cgi-script .cgi
  
     With apache you'll also want to make sure that within the access.conf
  file the line:
  
  	Options ExecCGI
  
  is in the stanza that covers the directories you intend to put the
  bugzilla .html and .cgi files into.
  
  2. Installing the Bugzilla Files
  
     You should untar the bugzilla files into a directory that you're
  willing to make writable by the default web server user (probably
  'nobody').  You may decide to put the files off of the main web space for
  your web server or perhaps off of /usr/local with a symbolic link in the
  web space that points to the bugzilla directory.  At any rate, just dump
  all the files in the same place (optionally omitting the CVS directory if
  it accidentally got tarred up with the rest of bugzilla) and make sure
  you can get at the files in that directory through your web server.
  
     Once all the files are in a web accessible directory, make that
  directory writable by your webserver's user (which may require just
  making it world writable).  
  	
     Lastly, you'll need to set up a symbolic link from /usr/bonsaitools/bin
  to the correct location of your perl executable (probably /usr/bin/perl).  Or,
  you'll have to hack all the .cgi files to change where they look for perl.
  
  3. Setting Up the MySQL database
  
     After you've gotten all the software installed and working you're ready
  to start preparing the database for its life as a the back end to a high
  quality bug tracker.
  
      First, you'll want to fix MySQL permissions.  Bugzilla always logs in as
  user "bugs", with no password.  That needs to work.  MySQL permissions are a
  deep, nasty complicated thing.  I've just turned them off.  If you want to do
  that, too, then the magic is to do run "mysql mysql", and feed it commands like
  this (replace all instances of HOSTNAME with the name of the machine mysql is
  running on):
  
  	DELETE FROM host;
  	DELETE FROM user;
  	INSERT INTO host VALUES ('localhost','%','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y');
  	INSERT INTO host VALUES (HOSTNAME,'%','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y');
  	INSERT INTO user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y');
  	INSERT INTO user VALUES (HOSTNAME,'','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y');
  	INSERT INTO user VALUES (HOSTNAME,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y');
  	INSERT INTO user VALUES ('localhost','','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y');
  
  The number of 'Y' entries to use varies with the version of MySQL; they keep
  adding columns.  The list here should work with version 3.22.23b.
  
  This run of "mysql mysql" may need some extra parameters to deal with whatever
  database permissions were set up previously.  In particular, you might have to say "mysql -uroot mysql", and give it an appropriate password.
  
  For much more information about MySQL permissions, see the MySQL documentation.
  
  After you've tweaked the permissions, run "mysqladmin reload" to make sure that
  the database server knows to look at your new permission list.
  
  Next, you can just run the magic checksetup.pl script.  (Many thanks to Holger
  Schurig <ho...@nikocity.de> for writing this script!)  It will make
  sure things have reasonable permissions, set up the "data" directory, and
  create all the MySQL tables.  Just run:
  
  	./checksetup.pl
  
  The first time you run it, it will create a file called "localconfig" which you
  should examine and perhaps tweak a bit.  Then re-run checksetup.pl and it will
  do the real work.
  
  
  At ths point, you should have a nearly empty copy of the bug tracking setup.
  
  4. Tweaking the Bugzilla->MySQL Connection Data
  
     If you have played with MySQL permissions, rather than just opening it
  wide open as described above, then you may need to tweak the Bugzilla 
  code to connect appropriately.
  
     In order for bugzilla to be able to connect to the MySQL database
  you'll have to tell bugzilla where the database server is, what database
  you're connecting to, and whom to connect as.  Simply open up the
  globals.pl file in the bugzilla directory and find the line that begins
  like:
  
  	$::db = Mysql->Connect("
  
     That line does the actual database connection.  The Connect method
  takes four parameters which are (with appropriate values):
  
  	1. server's host: just use "localhost"
  	2. database name: "bugs" if you're following these directions
  	3. MySQL username: whatever you created for your webserver user
  		probably "nobody"
  	4. Password for the MySQL account in item 3.
  
  Just fill in those values and close up globals.pl
  
  5. Setting up yourself as Maintainer
  
      Start by creating your own bugzilla account.  To do so, just try to "add
  a bug" from the main bugzilla menu (now available from your system through your
  web browser!).  You'll be prompted for logon info, and you should enter your
  email address and then select 'mail me my password'.  When you get the password
  mail, log in with it.  Don't finish entering that new bug.
  
      Now, add yourself to every group.  The magic checksetup.pl script can do
  this for you, if you run it again now.  That script will notice if there's
  exactly one user in the database, and if so, add that person to every group.
  
      If you want to add someone to every group by hand, you can do it by
  typing the appropriate MySQL commands.  Run mysql, and type:
  
  	update profiles set groupset=0x7fffffffffffffff where login_name = 'XXX';
  
  replacing XXX with your Bugzilla email address.
  
  Now, if you go to the query page (off of the bugzilla main menu) where you'll
  now find a 'edit parameters' option which is filled with editable treats.
  
  6. Setting Up the Whining Cron Job (Optional)
  
     By now you've got a fully functional bugzilla, but what good are bugs
  if they're not annoying?  To help make those bugs more annoying you can
  set up bugzilla's automatic whining system.  This can be done by adding
  the following command as a daily crontab entry (for help on that see that
  crontab man page):
  
  	cd <your-bugzilla-directory> ; ./whineatnews.pl
  
  7. Bug Graphs (Optional)
  
     As long as you installed the GD and Graph::Base Perl modules you might
  as well turn on the nifty bugzilla bug reporting graphs.  Just add the
  command:
  	
  	cd <your-bugzilla-directory> ; ./collectstats.pl
  
  as a nightly entry to your crontab and after two days have passed you'll
  be able to view bug graphs from the Bug Reports page.
  
  8. Real security for MySQL
  
  MySQL has "interesting" default security parameters:
          mysqld defaults to running as root
          it defaults to allowing external network connections
          it has a known port number, and is easy to detect
          it defaults to no passwords whatsoever
          it defaults to allowing "File_Priv"
  This means anyone from anywhere on the internet can not only drop the database
  with one SQL command, and they can write as root to the system. 
  
  To see your permissions do:
          > mysql -u root -p
          use mysql;
          show tables;
          select * from user;
          select * from db;
  
  To fix the gaping holes:
          DELETE FROM user WHERE User='';
          UPDATE user SET Password=PASSWORD('new_password') WHERE user='root';
          FLUSH PRIVILEGES;
  
  If you're not running "mit-pthreads" you can use:
          GRANT USAGE ON *.* TO bugs@localhost;
          GRANT ALL ON bugs.* TO bugs@localhost;
          REVOKE DROP ON bugs.* FROM bugs@localhost;
          FLUSH PRIVILEGES;
  
  With "mit-pthreads" you'll need to modify the "globals.pl" Mysql->Connect line
  to specify a specific host name instead of "localhost", and accept external
  connections:
          GRANT USAGE ON *.* TO bugs@bounce.hop.com;
          GRANT ALL ON bugs.* TO bugs@bounce.hop.com;
          REVOKE DROP ON bugs.* FROM bugs@bounce.hop.com;
          FLUSH PRIVILEGES;
  
  Consider also:
  	o Turning off external networking with "--skip-networking",
  	unless you have "mit-pthreads", in which case you can't.  Without
  	networking, MySQL connects with a Unix domain socket.
  
  	o using the --user= option to mysqld to run it as an unprivileged user.
  
          o starting MySQL in a chroot jail
  
          o running the httpd in a jail
  
          o making sure the MySQL passwords are different from the OS
          passwords (MySQL "root" has nothing to do with system "root").
  
          o running MySQL on a separate untrusted machine
  
          o making backups ;-)
  
  
  
  ---------[ Appendices ]-----------------------
  
  Appendix A. Required Software Download Links
  
     All of these sites are current as of February 17, 1999.  Hopefully
  they'll stay current for a while.
  
  MySQL: http://www.mysql.org
  
  Perl: http://www.perl.org
  
  CPAN: http://www.cpan.org
  
  DBI Perl module: ftp://ftp.cpan.org/pub/perl/CPAN/modules/by-module/DBI/
  
  Data::Dumper module:
  	ftp://ftp.cpan.org/pub/perl/CPAN/modules/by-module/Data/
  
  MySQL related Perl modules:
  	ftp://ftp.cpan.org/pub/perl/CPAN/modules/by-module/Mysql/
  
  TimeDate Perl module collection:
  	ftp://ftp.cpan.org/pub/perl/CPAN/modules/by-module/Date/
  
  
  GD Perl module: ftp://ftp.cpan.org/pub/perl/CPAN/modules/by-module/GD/
  
  Chart::Base module:
  	ftp://ftp.cpan.org/pub/perl/CPAN/modules/by-module/Chart/
  
  
  Appendix B. Modifying Your Running System
  
     Bugzilla optimizes database lookups by storing all relatively static
  information in the versioncache file, located in the data/ subdirectory
  under your installation directory (we said before it needs to be writable,
  right?!)
  
     If you make a change to the structural data in your database (the
  versions table for example), or to the "constants" encoded in
  defparams.pl, you will need to remove the cached content from the data
  directory (by doing a "rm data/versioncache"), or your changes won't show
  up!
  
     That file gets automatically regenerated whenever it's more than an
  hour old, so Bugzilla will eventually notice your changes by itself, but
  generally you want it to notice right away, so that you can test things.
  
  
  Appendix C. Upgrading from previous versions of Bugzilla
  
  The developers of Bugzilla are constantly adding new tables, columns and
  fields.  You'll get SQL errors if you just update the code.  The strategy to
  update is to simply always run the checksetup.pl script whenever you upgrade
  your installation of Bugzilla.  If you want to see what has changed, you can
  read the comments in that file, starting from the end.
  
  
  Appendix D. History
  
     This document was originally adapted from the Bonsai installation
  instructions by Terry Weissman <te...@mozilla.org>.
  
     The February 25, 1999 re-write of this page was done by Ry4an Brase
  <ry...@ry4an.org>, with some edits by Terry Weissman, Bryce Nesbitt,
  & Martin Pool (But don't send bug reports to them!  Report them using bugzilla,
  at http://bugzilla.mozilla.org/enter_bug.cgi , project Webtools, component
  Bugzilla).
  
     Comments from people using this document for the first time are especially
  welcomed.
  
  
  
  1.1                  apache-site/bugs/addcomponent.cgi
  
  Index: addcomponent.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Sam Ziegler <sa...@ziegler.org>
  # Terry Weissman <te...@mozilla.org>
  # Mark Hamby <mh...@logicon.com>
  
  # Code derived from editcomponents.cgi, reports.cgi
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  # Shut up misguided -w warnings about "used only once":
  
  use vars @::legal_product;
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  if (!UserInGroup("editcomponents")) {
      print "<H1>Sorry, you aren't a member of the 'editcomponents' group.</H1>\n";
      print "And so, you aren't allowed to add a new component.\n";
      exit;
  }
  
  
  PutHeader("Add Component");
  
  print "This page lets you add a component to bugzilla.\n";
  
  unlink "data/versioncache";
  GetVersionTable();
  
  my $prodcode = "P0";
  
  my $product_popup = make_options (\@::legal_product, $::legal_product[0]);
  
  print "
        <form method=post action=doaddcomponent.cgi>
  
        <TABLE>
        <TR>
                <th align=right>Component:</th>
                <TD><input size=60 name=\"component\" value=\"\"></TD>
        </TR>
        <TR>
                <TH  align=right>Program:</TH>
                <TD><SELECT NAME=\"product\">
                        $product_popup
                        </SELECT></TD>
        </TR>
        <TR>
                <TH  align=right>Description:</TH>
                <TD><input size=60 name=\"description\" value=\"\"></TD>
        </TR>
        <TR>
                <TH  align=right>Initial owner:</TH>
                <TD><input size=60 name=\"initialowner\" value=\"\"></TD>
        </TR>
        ";
  
  if (Param('useqacontact')) {
        print "
                <TR>
                        <TH  align=right>Initial QA contact:</TH>
                        <TD><input size=60 name=\"initialqacontact\" value=\"\"></TD>
                </TR>
                ";
  }
  
  print "
        </table>
        <hr>
        ";
  
  print "<input type=submit value=\"Submit changes\">\n";
  
  print "</form>\n";
  
  print "<p><a href=query.cgi>Skip all this, and go back to the query page</a>\n";
  
  
  
  1.1                  apache-site/bugs/ant.jpg
  
  	<<Binary file>>
  
  
  1.1                  apache-site/bugs/backdoor.cgi
  
  Index: backdoor.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  # Provides a silly 'back-door' mechanism to let me automatically insert
  # bugs from the netscape bugsystem.  Other installations of Bugzilla probably
  # don't need to worry about this file any.
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  # Shut up misguided -w warnings about "used only once":
  
  use vars %::versions;
  
  
  ConnectToDatabase();
  
  print "Content-type: text/plain\n\n";
  
  # while (my ($key,$value) = each %ENV) {
  #     print "$key=$value\n";
  # }
  
  my $host = $ENV{'REMOTE_ADDR'};
  
  SendSQL("select passwd from backdoor where host = '$host'");
  my $passwd = FetchOneColumn();
  if (!defined $passwd || !defined $::FORM{'passwd'} ||
      $passwd ne crypt($::FORM{'passwd'}, substr($passwd, 0, 2))) {
      print "Who are you?\n";
      print "Env:\n";
      while (my ($key,$value) = each %ENV) {
          print "$key=$value\n";
      }
      print "\nForm:\n";
      while (my ($key,$value) = each %::FORM) {
          print "$key=$value\n";
      }
      exit;
  }
  
  
  
  my $prod = $::FORM{'product'};
  my $comp = $::FORM{'component'};
  my $version = $::FORM{'version'};
  
  GetVersionTable();
  
  
  sub Punt {
      my ($label, $value) = (@_);
      my $maintainer = Param("maintainer");
      print "I don't know how to move into Bugzilla a bug with a $label of $value.
  If you really do need to do this, speak to $maintainer and maybe he
  can teach me.";
      exit;
  }
  
  
  # Do remapping of things from BugSplat world to Bugzilla.
  
  if ($prod eq "Communicator") {
      $prod = "Browser";
      $version = "other";
  }
  
      
  # Validate fields, and whine about things that we apparently couldn't remap
  # into something legal.
  
      
  if (!defined $::components{$prod}) {
      Punt("product", $prod);
  }
  if (lsearch($::components{$prod}, $comp) < 0) {
      Punt("component", $comp);
  }
  if (lsearch($::versions{$prod}, $version) < 0) {
      $version = "other";
      if (lsearch($::versions{$prod}, $version) < 0) {
          Punt("version", $version);
      }
  }
  
  
  $::FORM{'product'} = $prod;
  $::FORM{'component'} = $comp;
  $::FORM{'version'} = $version;
  
  
  $::FORM{'long_desc'} =
      "(This bug imported from BugSplat, Netscape's internal bugsystem.  It
  was known there as bug #$::FORM{'bug_id'}
  http://scopus.netscape.com/bugsplat/show_bug.cgi?id=$::FORM{'bug_id'}
  Imported into Bugzilla on " . time2str("%D %H:%M", time()) . ")
  
  " . $::FORM{'long_desc'};
      
  
  $::FORM{'reporter'} =
      DBNameToIdAndCheck("$::FORM{'reporter'}\@netscape.com", 1);
  $::FORM{'assigned_to'} = 
      DBNameToIdAndCheck("$::FORM{'assigned_to'}\@netscape.com", 1);
  if ($::FORM{'qa_contact'} ne "") {
      $::FORM{'qa_contact'} =
          DBNameToIdAndCheck("$::FORM{'qa_contact'}\@netscape.com", 1);
  } else {
      $::FORM{'qa_contact'} = 0;
  }
      
  
  my @list = ('reporter', 'assigned_to', 'product', 'version', 'rep_platform',
              'op_sys', 'bug_status', 'bug_severity', 'priority', 'component',
              'short_desc', 'long_desc', 'creation_ts', 'delta_ts',
              'bug_file_loc', 'qa_contact', 'groupset');
  
  my @vallist;
  foreach my $i (@list) {
      push @vallist, SqlQuote($::FORM{$i});
  }
  
  my $query = "insert into bugs (" .
      join(',', @list) .
      ") values (" .
      join(',', @vallist) .
      ")";
  
  
  SendSQL($query);
  
  SendSQL("select LAST_INSERT_ID()");
  my $zillaid = FetchOneColumn();
  
  foreach my $cc (split(/,/, $::FORM{'cc'})) {
      if ($cc ne "") {
          my $cid = DBNameToIdAndCheck("$cc\@netscape.com", 1);
          SendSQL("insert into cc (bug_id, who) values ($zillaid, $cid)");
      }
  }
  
  print "Created bugzilla bug $zillaid\n";
  system("./processmail $zillaid");
  
  
  
  1.1                  apache-site/bugs/bug_form.pl
  
  Index: bug_form.pl
  ===================================================================
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  # Shut up misguided -w warnings about "used only once".  For some reason,
  # "use vars" chokes on me when I try it here.
  
  sub bug_form_pl_sillyness {
      my $zz;
      $zz = %::FORM;
      $zz = %::components;
      $zz = %::prodmaxvotes;
      $zz = %::versions;
      $zz = @::legal_opsys;
      $zz = @::legal_platform;
      $zz = @::legal_product;
      $zz = @::legal_priority;
      $zz = @::legal_resolution_no_dup;
      $zz = @::legal_severity;
  }
  
  my %knownattachments;
  
  # This routine quoteUrls contains inspirations from the HTML::FromText CPAN
  # module by Gareth Rees <ga...@cre.canon.co.uk>.  It has been heavily hacked,
  # all that is really recognizable from the original is bits of the regular
  # expressions.
  
  sub quoteUrls {
      my $text = shift;		# Take a copy; don't modify in-place.
      return $text unless $text;
  
      my $base = Param('urlbase');
  
      my $protocol = join '|',
      qw(afs cid ftp gopher http https mid news nntp prospero telnet wais);
  
      my %options = ( metachars => 1, @_ );
  
      my $count = 0;
  
      # Now, quote any "#" characters so they won't confuse stuff later
      $text =~ s/#/%#/g;
  
      # Next, find anything that looks like a URL or an email address and
      # pull them out the the text, replacing them with a "##<digits>##
      # marker, and writing them into an array.  All this confusion is
      # necessary so that we don't match on something we've already replaced,
      # which can happen if you do multiple s///g operations.
  
      my @things;
      while ($text =~ s%((mailto:)?([\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+)\b|
                         (\b((?:$protocol):\S+[\w/])))%"##$count##"%exo) {
          my $item = $&;
  
          $item = value_quote($item);
  
          if ($item !~ m/^$protocol:/o && $item !~ /^mailto:/) {
              # We must have grabbed this one because it looks like an email
              # address.
              $item = qq{<A HREF="mailto:$item">$item</A>};
          } else {
              $item = qq{<A HREF="$item">$item</A>};
          }
  
          $things[$count++] = $item;
      }
      while ($text =~ s/\bbug(\s|%\#)*(\d+)/"##$count##"/ei) {
          my $item = $&;
          my $num = $2;
          $item = value_quote($item); # Not really necessary, since we know
                                  # there's no special chars in it.
          $item = qq{<A HREF="${base}show_bug.cgi?id=$num">$item</A>};
          $things[$count++] = $item;
      }
      while ($text =~ s/\*\*\* This bug has been marked as a duplicate of (\d+) \*\*\*/"##$count##"/ei) {
          my $item = $&;
          my $num = $1;
          $item =~ s@\d+@<A HREF="${base}show_bug.cgi?id=$num">$num</A>@;
          $things[$count++] = $item;
      }
      while ($text =~ s/Created an attachment \(id=(\d+)\)/"##$count##"/e) {
          my $item = $&;
          my $num = $1;
          if (exists $knownattachments{$num}) {
              $item = qq{<A HREF="showattachment.cgi?attach_id=$num">$item</A>};
          }
          $things[$count++] = $item;
      }
  
      $text = value_quote($text);
      $text =~ s/\&#010;/\n/g;
  
      # Stuff everything back from the array.
      for (my $i=0 ; $i<$count ; $i++) {
          $text =~ s/##$i##/$things[$i]/e;
      }
  
      # And undo the quoting of "#" characters.
      $text =~ s/%#/#/g;
  
      return $text;
  }
  
  my $loginok = quietly_check_login();
  
  my $id = $::FORM{'id'};
  
  my $query = "
  select
          bugs.bug_id,
          product,
          version,
          rep_platform,
          op_sys,
          bug_status,
          resolution,
          priority,
          bug_severity,
          component,
          assigned_to,
          reporter,
          bug_file_loc,
          short_desc,
  	target_milestone,
  	qa_contact,
  	status_whiteboard,
          date_format(creation_ts,'Y-m-d'),
          groupset,
  	delta_ts,
  	sum(votes.count)
  from bugs left join votes using(bug_id)
  where bugs.bug_id = $id
  and bugs.groupset & $::usergroupset = bugs.groupset
  group by bugs.bug_id";
  
  SendSQL($query);
  my %bug;
  my @row;
  if (@row = FetchSQLData()) {
      my $count = 0;
      foreach my $field ("bug_id", "product", "version", "rep_platform",
  		       "op_sys", "bug_status", "resolution", "priority",
  		       "bug_severity", "component", "assigned_to", "reporter",
  		       "bug_file_loc", "short_desc", "target_milestone",
                         "qa_contact", "status_whiteboard", "creation_ts",
                         "groupset", "delta_ts", "votes") {
  	$bug{$field} = shift @row;
  	if (!defined $bug{$field}) {
  	    $bug{$field} = "";
  	}
  	$count++;
      }
  } else {
      SendSQL("select groupset from bugs where bug_id = $id");
      if (@row = FetchSQLData()) {
          print "<H1>Permission denied.</H1>\n";
          if ($loginok) {
              print "Sorry; you do not have the permissions necessary to see\n";
              print "bug $id.\n";
          } else {
              print "Sorry; bug $id can only be viewed when logged\n";
              print "into an account with the appropriate permissions.  To\n";
              print "see this bug, you must first\n";
              print "<a href=\"show_bug.cgi?id=$id&GoAheadAndLogIn=1\">";
              print "log in</a>.";
          }
      } else {
          print "<H1>Bug not found</H1>\n";
          print "There does not seem to be a bug numbered $id.\n";
      }
      exit;
  }
  
  $bug{'assigned_to'} = DBID_to_name($bug{'assigned_to'});
  $bug{'reporter'} = DBID_to_name($bug{'reporter'});
  $bug{'long_desc'} = GetLongDescription($id);
  my $longdesclength = length($bug{'long_desc'});
  
  
  GetVersionTable();
  
  #
  # These should be read from the database ...
  #
  
  my $resolution_popup = make_options(\@::legal_resolution_no_dup,
  				    $bug{'resolution'});
  my $platform_popup = make_options(\@::legal_platform, $bug{'rep_platform'});
  my $priority_popup = make_options(\@::legal_priority, $bug{'priority'});
  my $sev_popup = make_options(\@::legal_severity, $bug{'bug_severity'});
  
  
  my $component_popup = make_options($::components{$bug{'product'}},
  				   $bug{'component'});
  
  my $cc_element = '<INPUT NAME=cc SIZE=30 VALUE="' .
      ShowCcList($id) . '">';
  
  
  my $URL = $bug{'bug_file_loc'};
  
  if (defined $URL && $URL ne "none" && $URL ne "NULL" && $URL ne "") {
      $URL = "<B><A HREF=\"$URL\">URL:</A></B>";
  } else {
      $URL = "<B>URL:</B>";
  }
  
  print "
  <FORM NAME=changeform METHOD=POST ACTION=\"process_bug.cgi\">
  <INPUT TYPE=HIDDEN NAME=\"delta_ts\" VALUE=\"$bug{'delta_ts'}\">
  <INPUT TYPE=HIDDEN NAME=\"longdesclength\" VALUE=\"$longdesclength\">
  <INPUT TYPE=HIDDEN NAME=\"id\" VALUE=$id>
  <INPUT TYPE=HIDDEN NAME=\"was_assigned_to\" VALUE=\"$bug{'assigned_to'}\">
    <TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0><TR>
      <TD ALIGN=RIGHT><B>Bug#:</B></TD><TD><A HREF=\"show_bug.cgi?id=$bug{'bug_id'}\">$bug{'bug_id'}</A></TD>
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#rep_platform\">Platform:</A></B></TD>
      <TD><SELECT NAME=rep_platform>$platform_popup</SELECT></TD>
      <TD ALIGN=RIGHT><B>Version:</B></TD>
      <TD><SELECT NAME=version>" .
      make_options($::versions{$bug{'product'}}, $bug{'version'}) .
      "</SELECT></TD>
    </TR><TR>
      <TD ALIGN=RIGHT><B>Product:</B></TD>
      <TD><SELECT NAME=product>" .
      make_options(\@::legal_product, $bug{'product'}) .
      "</SELECT></TD>
      <TD ALIGN=RIGHT><B>OS:</B></TD>
      <TD><SELECT NAME=op_sys>" .
      make_options(\@::legal_opsys, $bug{'op_sys'}) .
      "</SELECT><TD ALIGN=RIGHT><B>Reporter:</B></TD><TD>$bug{'reporter'}</TD>
    </TR><TR>
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html\">Status:</A></B></TD>
        <TD>$bug{'bug_status'}</TD>
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#priority\">Priority:</A></B></TD>
        <TD><SELECT NAME=priority>$priority_popup</SELECT></TD>
      <TD ALIGN=RIGHT><B>Cc:</B></TD>
        <TD> $cc_element </TD>
    </TR><TR>
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html\">Resolution:</A></B></TD>
        <TD>$bug{'resolution'}</TD>
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#severity\">Severity:</A></B></TD>
        <TD><SELECT NAME=bug_severity>$sev_popup</SELECT></TD>
      <TD ALIGN=RIGHT><B><A HREF=\"describecomponents.cgi?product=" .
      url_quote($bug{'product'}) . "\">Component:</A></B></TD>
        <TD><SELECT NAME=component>$component_popup</SELECT></TD>
    </TR><TR>
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#assigned_to\">Assigned&nbsp;To:
          </A></B></TD>
        <TD>$bug{'assigned_to'}</TD>";
  
  if (Param("usetargetmilestone")) {
      my $url = "";
      if (defined $::milestoneurl{$bug{'product'}}) {
          $url = $::milestoneurl{$bug{'product'}};
      }
      if ($url eq "") {
          $url = "notargetmilestone.html";
      }
      if ($bug{'target_milestone'} eq "") {
          $bug{'target_milestone'} = " ";
      }
      push(@::legal_target_milestone, " ");
      print "
  <TD ALIGN=RIGHT><A href=\"$url\"><B>Target Milestone:</B></A></TD>
  <TD><SELECT NAME=target_milestone>" .
      make_options(\@::legal_target_milestone,
                   $bug{'target_milestone'}) .
                       "</SELECT></TD>";
  }
  
  print "
  </TR>";
  
  if (Param("useqacontact")) {
      my $name = $bug{'qa_contact'} > 0 ? DBID_to_name($bug{'qa_contact'}) : "";
      print "
    <TR>
      <TD ALIGN=\"RIGHT\"><B>QA Contact:</B>
      <TD COLSPAN=6>
        <INPUT NAME=qa_contact VALUE=\"" .
      value_quote($name) .
      "\" SIZE=60></
    </TR>";
  }
  
  
  print "
    <TR>
      <TD ALIGN=\"RIGHT\">$URL
      <TD COLSPAN=6>
        <INPUT NAME=bug_file_loc VALUE=\"$bug{'bug_file_loc'}\" SIZE=60></TD>
    </TR><TR>
      <TD ALIGN=\"RIGHT\"><B>Summary:</B>
      <TD COLSPAN=6>
        <INPUT NAME=short_desc VALUE=\"" .
      value_quote($bug{'short_desc'}) .
      "\" SIZE=60></TD>
    </TR>";
  
  if (Param("usestatuswhiteboard")) {
      print "
    <TR>
      <TD ALIGN=\"RIGHT\"><B>Status Whiteboard:</B>
      <TD COLSPAN=6>
        <INPUT NAME=status_whiteboard VALUE=\"" .
      value_quote($bug{'status_whiteboard'}) .
      "\" SIZE=60></
    </TR>";
  }
  
  print "<tr><td align=right><B>Attachments:</b></td>\n";
  SendSQL("select attach_id, creation_ts, description from attachments where bug_id = $id");
  while (MoreSQLData()) {
      my ($attachid, $date, $desc) = (FetchSQLData());
      if ($date =~ /^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/) {
          $date = "$3/$4/$2 $5:$6";
      }
      my $link = "showattachment.cgi?attach_id=$attachid";
      $desc = value_quote($desc);
      print qq{<td><a href="$link">$date</a></td><td colspan=4>$desc</td></tr><tr><td></td>};
      $knownattachments{$attachid} = 1;
  }
  print "<td colspan=6><a href=\"createattachment.cgi?id=$id\">Create a new attachment</a> (proposed patch, testcase, etc.)</td></tr></table>\n";
  
  
  sub EmitDependList {
      my ($desc, $myfield, $targetfield) = (@_);
      print "<th align=right>$desc:</th><td>";
      my @list;
      SendSQL("select dependencies.$targetfield, bugs.bug_status
   from dependencies, bugs
   where dependencies.$myfield = $id
     and bugs.bug_id = dependencies.$targetfield
   order by dependencies.$targetfield");
      while (MoreSQLData()) {
          my ($i, $stat) = (FetchSQLData());
          push(@list, $i);
          my $opened = ($stat eq "NEW" || $stat eq "ASSIGNED" ||
                        $stat eq "REOPENED");
          if (!$opened) {
              print "<strike>";
          }
          print qq{<a href="show_bug.cgi?id=$i">$i</a>};
          if (!$opened) {
              print "</strike>";
          }
          print " ";
      }
      print "</td><td><input name=$targetfield value=\"" .
          join(',', @list) . "\"></td>\n";
  }
  
  if (Param("usedependencies")) {
      print "<table><tr>\n";
      EmitDependList("Bugs that bug $id depends on", "blocked", "dependson");
      print qq{
  <td rowspan=2><a href="showdependencytree.cgi?id=$id">Show dependency tree</a>
  };
      if (Param("webdotbase") ne "") {
          print qq{
  <br><a href="showdependencygraph.cgi?id=$id">Show dependency graph</a>
  };
      }
      print "</td></tr><tr>";
      EmitDependList("Bugs depending on bug $id", "dependson", "blocked");
      print "</tr></table>\n";
  }
  
  if ($::prodmaxvotes{$bug{'product'}}) {
      print qq{
  <table><tr>
  <th><a href="votehelp.html">Votes</a> for bug $id:</th><td>
  <a href="showvotes.cgi?bug_id=$id">$bug{'votes'}</a>
  &nbsp;&nbsp;&nbsp;<a href="showvotes.cgi?voteon=$id">Vote for this bug</a>
  </td></tr></table>
  };
  }
  
  print "
  <br>
  <B>Additional Comments:</B>
  <BR>
  <TEXTAREA WRAP=HARD NAME=comment ROWS=5 COLS=80></TEXTAREA><BR>";
  
  
  if ($::usergroupset ne '0') {
      SendSQL("select bit, description, (bit & $bug{'groupset'} != 0) from groups where bit & $::usergroupset != 0 and isbuggroup != 0 order by bit");
      while (MoreSQLData()) {
          my ($bit, $description, $ison) = (FetchSQLData());
          my $check0 = !$ison ? " SELECTED" : "";
          my $check1 = $ison ? " SELECTED" : "";
          print "<select name=bit-$bit><option value=0$check0>\n";
          print "People not in the \"$description\" group can see this bug\n";
          print "<option value=1$check1>\n";
          print "Only people in the \"$description\" group can see this bug\n";
          print "</select><br>\n";
      }
  }
  
  print "<br>
  <INPUT TYPE=radio NAME=knob VALUE=none CHECKED>
          Leave as <b>$bug{'bug_status'} $bug{'resolution'}</b><br>";
  
  
  # knum is which knob number we're generating, in javascript terms.
  
  my $knum = 1;
  
  my $status = $bug{'bug_status'};
  
  if ($status eq "NEW" || $status eq "ASSIGNED" || $status eq "REOPENED") {
      if ($status ne "ASSIGNED") {
          print "<INPUT TYPE=radio NAME=knob VALUE=accept>";
  	print "Accept bug (change status to <b>ASSIGNED</b>)<br>";
          $knum++;
      }
      if ($bug{'resolution'} ne "") {
          print "<INPUT TYPE=radio NAME=knob VALUE=clearresolution>\n";
          print "Clear the resolution (remove the current resolution of\n";
          print "<b>$bug{'resolution'}</b>)<br>\n";
          $knum++;
      }
      print "<INPUT TYPE=radio NAME=knob VALUE=resolve>
          Resolve bug, changing <A HREF=\"bug_status.html\">resolution</A> to
          <SELECT NAME=resolution
            ONCHANGE=\"document.changeform.knob\[$knum\].checked=true\">
            $resolution_popup</SELECT><br>\n";
      $knum++;
      print "<INPUT TYPE=radio NAME=knob VALUE=duplicate>
          Resolve bug, mark it as duplicate of bug # 
          <INPUT NAME=dup_id SIZE=6 ONCHANGE=\"document.changeform.knob\[$knum\].checked=true\"><br>\n";
      $knum++;
      my $assign_element = "<INPUT NAME=\"assigned_to\" SIZE=32 ONCHANGE=\"document.changeform.knob\[$knum\].checked=true\" VALUE=\"$bug{'assigned_to'}\">";
  
      print "<INPUT TYPE=radio NAME=knob VALUE=reassign> 
            <A HREF=\"bug_status.html#assigned_to\">Reassign</A> bug to
            $assign_element
          <br>\n";
      $knum++;
      print "<INPUT TYPE=radio NAME=knob VALUE=reassignbycomponent>
            Reassign bug to owner of selected component<br>\n";
      $knum++;
  } else {
      print "<INPUT TYPE=radio NAME=knob VALUE=reopen> Reopen bug<br>\n";
      $knum++;
      if ($status eq "RESOLVED") {
          print "<INPUT TYPE=radio NAME=knob VALUE=verify>
          Mark bug as <b>VERIFIED</b><br>\n";
          $knum++;
      }
      if ($status ne "CLOSED") {
          print "<INPUT TYPE=radio NAME=knob VALUE=close>
          Mark bug as <b>CLOSED</b><br>\n";
          $knum++;
      }
  }
   
  print "
  <INPUT TYPE=\"submit\" VALUE=\"Commit\">
  <INPUT TYPE=\"reset\" VALUE=\"Reset\">
  <INPUT TYPE=hidden name=form_name VALUE=process_bug>
  <BR>
  <FONT size=\"+1\"><B>
   <A HREF=\"show_activity.cgi?id=$id\">View Bug Activity</A>
   <A HREF=\"long_list.cgi?buglist=$id\">Format For Printing</A>
  </B></FONT><BR>
  </FORM>
  <table><tr><td align=left><B>Description:</B></td><td width=\"100%\">&nbsp;</td>
  <td align=right>Opened:&nbsp;$bug{'creation_ts'}</td></tr></table>
  <HR>
  <PRE>
  ";
  print quoteUrls($bug{'long_desc'}, email=>1, urls=>1);
  print "
  </PRE>
  <HR>\n";
  
  # To add back option of editing the long description, insert after the above
  # long_list.cgi line:
  #  <A HREF=\"edit_desc.cgi?id=$id\">Edit Long Description</A>
  
  navigation_header();
  
  1;
  
  
  
  1.1                  apache-site/bugs/bug_status.html
  
  Index: bug_status.html
  ===================================================================
  <HTML>
  
  <!--
       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.
      
       The Original Code is the Bugzilla Bug Tracking System.
      
       The Initial Developer of the Original Code is Netscape Communications
       Corporation. Portions created by Netscape are
       Copyright (C) 1998 Netscape Communications Corporation. All
       Rights Reserved.
      
       Contributor(s): 
  
       Contributor(s): Terry Weissman <te...@mozilla.org>
  -->
  
  <TITLE>A Bug's Life Cycle</TITLE>
  
  <h1 ALIGN=CENTER>A Bug's Life Cycle</h1>
  
  The <B>status</B> and <B>resolution</B> field define and track the
  life cycle of a bug.
  
  <p>
  <TABLE BORDER=1 CELLPADDING=4>
  
  <TR ALIGN=CENTER VALIGN=TOP>
  <TD WIDTH=50%><H1>STATUS</H1> <TD><H1>RESOLUTION</H1>
  
  <TR VALIGN=TOP>
  <TD>The <B>status</B> field indicates the general health of a bug. Only
  certain status transitions are allowed.
  <TD>The <b>resolution</b> field indicates what happened to this bug.
  
  <TR VALIGN=TOP><TD>
  <DL><DT><B>NEW</B> 
  <DD> This bug has recently been added to the assignee's list of bugs
       and must be processed. Bugs in this state may be accepted, and
       become <B>ASSIGNED</B>, passed on to someone else, and remain
       <B>NEW</B>, or resolved and marked <B>RESOLVED</B>.
  <DT><B>ASSIGNED</B> 
  <DD> This bug is not yet resolved, but is assigned to the proper
       person. From here bugs can be given to another person and become
       <B>NEW</B>, or resolved and become <B>RESOLVED</B>.
  <DT><B>REOPENED</B>
  <DD>This bug was once resolved, but the resolution was deemed
       incorrect.  For example, a <B>WORKSFORME</B> bug is
       <B>REOPENED</B> when more information shows up and the bug is now
       reproducible.  From here bugs are either marked <B>ASSIGNED</B>
       or <B>RESOLVED</B>.
  </DL>
  <TD>
  <DL>
  <DD> No resolution yet. All bugs which are <B>NEW</B> or
       <B>ASSIGNED</B> have the resolution set to blank. All other bugs
       will be marked with one of the following resolutions.
  </DL>
  
  <TR VALIGN=TOP><TD>
  <DL>
  <DT><B>RESOLVED</B> 
  <DD> A resolution has been taken, and it is awaiting verification by
       QA. From here bugs are either re-opened and become
       <B>REOPENED</B>, are marked <B>VERIFIED</B>, or are closed for good
       and marked <B>CLOSED</B>.
  <DT><B>VERIFIED</B>
  <DD> QA has looked at the bug and the resolution and agrees that the
       appropriate resolution has been taken.  Bugs remain in this state
       until the product they were reported against actually ship, at
       which point the become <B>CLOSED</B>.
  <DT><B>CLOSED</B> 
  <DD> The bug is considered dead, the resolution is correct. Any zombie
       bugs who choose to walk the earth again must do so by becoming
       <B>REOPENED</B>.
  </DL>
  
  <TD>
  <DL>
  <DT><B>FIXED</B>
  <DD> A fix for this bug is checked into the tree and tested.
  <DT><B>INVALID</B>
  <DD> The problem described is not a bug 
  <DT><B>WONTFIX</B>
  <DD> The problem described is a bug which will never be fixed.
  <DT><B>LATER</B>
  <DD> The problem described is a bug which will not be fixed in this
       version of the product.
  <DT><B>REMIND</B>
  <DD> The problem described is a bug which will probably not be fixed in this
       version of the product, but might still be.
  <DT><B>DUPLICATE</B>
  <DD> The problem is a duplicate of an existing bug. Marking a bug
       duplicate requires the bug# of the duplicating bug and will at
       least put that bug number in the description field.
  <DT><B>WORKSFORME</B>
  <DD> All attempts at reproducing this bug were futile, reading the
       code produces no clues as to why this behavior would occur. If
       more information appears later, please re-assign the bug, for
       now, file it.
  </DL>
  </TABLE>
  
  <H1>Other Fields</H1>
  
  <table border=1 cellpadding=4><tr><td>
  <a name="severity"><h2>Severity</h2></a>
  
  This field describes the impact of a bug.
  
  <p>
  <p>
   
  <table>
  <tr><th>Blocker</th><td>Blocks development and/or testing work
  <tr><th>Critical</th><td>crashes, loss of data, severe memory leak 
  <tr><th>Major</th><td>major loss of function 
  <tr><th>Minor</th><td>minor loss of function, or other problem where easy workaround is present 
  <tr><th>Trivial</th><td>cosmetic problem like misspelt words or misaligned text 
  <tr><th>Enhancement</th><td>Request for enhancement
  </table> 
  
  </td><td>
  
  <a name="priority"><h2>Priority</h2></a>
  
  This field describes the importance and order in which a bug should be
  fixed.  This field is utilized by the programmers/engineers to
  prioritized their work to be done.  The available priorities are:
  
  <p>
  <p>
  
  <table>
  <tr><th>P1</th><td>Most important
  <tr><th>P2</th><td>
  <tr><th>P3</th><td>
  <tr><th>P4</th><td>
  <tr><th>P5</th><td>Least important
  </table>
  </tr></table>
  
  <a name="rep_platform"><h2>Platform</h2></a>
  This is the hardware platform against which the bug was reported.  Legal
  platforms include:
  
  <UL>
  <LI> All (happens on all platform; cross-platform bug)
  <LI> Macintosh
  <LI> PC
  <LI> Sun
  <LI> HP
  </UL>
  
  <b>Note:</b> Selecting the option "All" does not select bugs assigned against all platforms.  It
  merely selects bugs that <b>occur</b> on all platforms.
  
  <a name="op_sys"><h2>Operating System</h2></a>
  This is the operating system against which the bug was reported.  Legal
  operating systems include:
  
  <UL>
  <LI> All (happens on all operating systems; cross-platform bug)
  <LI> Windows 95
  <LI> Mac System 8.0
  <LI> Linux
  </UL>
  
  Note that the operating system implies the platform, but not always.
  For example, Linux can run on PC and Macintosh and others.
  
  <a name="assigned_to"><h2>Assigned To</h2></a>
  
  This is the person in charge of resolving the bug.  Every time this
  field changes, the status changes to <B>NEW</B> to make it easy to see
  which new bugs have appeared on a person's list.
  
  The default status for queries is set to NEW, ASSIGNED and REOPENED. When
  searching for bugs that have been resolved or verified, remember to set the
  status field appropriately. 
  
  <hr>
  <address><a href="http://home.netscape.com/people/terry/">Terry Weissman &lt;terry@netscape.com&gt;</a></address>
  <!-- hhmts start -->
  Last modified: Tue May 11 21:34:56 1999
  <!-- hhmts end -->
  </body> </html>
  
  
  
  1.1                  apache-site/bugs/buglist.cgi
  
  Index: buglist.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  use Date::Parse;
  
  use vars @::legal_platform,
      @::versions,
      @::legal_product,
      %::MFORM,
      @::components,
      @::legal_severity,
      @::legal_priority,
      @::default_column_list,
      @::legal_resolution_no_dup,
      @::legal_target_milestone;
  
  
  
  ConnectToDatabase();
  
  
  if (!defined $::FORM{'cmdtype'}) {
      # This can happen if there's an old bookmark to a query...
      $::FORM{'cmdtype'} = 'doit';
  }
  
  CMD: for ($::FORM{'cmdtype'}) {
      /^runnamed$/ && do {
          $::buffer = $::COOKIE{"QUERY_" . $::FORM{"namedcmd"}};
          ProcessFormFields($::buffer);
          last CMD;
      };
      /^editnamed$/ && do {
  	my $url = "query.cgi?" . $::COOKIE{"QUERY_" . $::FORM{"namedcmd"}};
          print "Content-type: text/html
  Refresh: 0; URL=$url
  
  <TITLE>What a hack.</TITLE>
  Loading your query named <B>$::FORM{'namedcmd'}</B>...
  ";
          exit;
      };
      /^forgetnamed$/ && do {
          print "Set-Cookie: QUERY_" . $::FORM{'namedcmd'} . "= ; path=/ ; expires=Sun, 30-Jun-2029 00:00:00 GMT
  Content-type: text/html
  
  <HTML>
  <TITLE>Forget what?</TITLE>
  OK, the <B>$::FORM{'namedcmd'}</B> query is gone.
  <P>
  <A HREF=query.cgi>Go back to the query page.</A>
  ";
          exit;
      };
      /^asnamed$/ && do {
          if ($::FORM{'newqueryname'} =~ /^[a-zA-Z0-9_ ]+$/) {
      print "Set-Cookie: QUERY_" . $::FORM{'newqueryname'} . "=$::buffer ; path=/ ; expires=Sun, 30-Jun-2029 00:00:00 GMT
  Content-type: text/html
  
  <HTML>
  <TITLE>OK, done.</TITLE>
  OK, you now have a new query named <B>$::FORM{'newqueryname'}</B>.
  
  <P>
  
  <A HREF=query.cgi>Go back to the query page.</A>
  ";
          } else {
              print "Content-type: text/html
  
  <HTML>
  <TITLE>Picky, picky.</TITLE>
  Query names can only have letters, digits, spaces, or underbars.  You entered 
  \"<B>$::FORM{'newqueryname'}</B>\", which doesn't cut it.
  <P>
  Click the <B>Back</B> button and type in a valid name for this query.
  ";
          }
          exit;
      };
      /^asdefault$/ && do {
          print "Set-Cookie: DEFAULTQUERY=$::buffer ; path=/ ; expires=Sun, 30-Jun-2029 00:00:00 GMT
  Content-type: text/html
  
  <HTML>
  <TITLE>OK, default is set.</TITLE>
  OK, you now have a new default query.  You may also bookmark the result of any
  individual query.
  
  <P><A HREF=query.cgi>Go back to the query page, using the new default.</A>
  ";
          exit;
      };
  }
  
  
  my $serverpush = 0;
  if ($ENV{'HTTP_USER_AGENT'} =~ /Mozilla.[3-9]/ && $ENV{'HTTP_USER_AGENT'} !~ /[Cc]ompatible/ ) {
      # Search for real Netscape 3 and up.  http://www.browsercaps.org used as source of
      # browsers compatbile with server-push.  It's a Netscape hack, incompatbile
      # with MSIE and Lynx (at least).  Even Communicator 4.51 has bugs with it,
      # especially during page reload.
      $serverpush = 1;
  
      print "Content-type: multipart/x-mixed-replace;boundary=thisrandomstring\n\n";
      print "--thisrandomstring\n";
      print "Content-type: text/html\n\n";
      print "<p>Please stand by ... <p>\n";
      # Note! HTML header is complete!
  } else {
      print "Content-type: text/html\n";
      # Note! Don't finish HTML header yet!  Only one newline so far!
  }
  
  sub DefCol {
      my ($name, $k, $t, $s, $q) = (@_);
      
      $::key{$name} = $k;
      $::title{$name} = $t;
      if (defined $s && $s ne "") {
          $::sortkey{$name} = $s;
      }
      if (!defined $q || $q eq "") {
          $q = 0;
      }
      $::needquote{$name} = $q;
  }
  
  DefCol("opendate", "date_format(bugs.creation_ts,'Y-m-d')", "Opened",
         "bugs.creation_ts");
  DefCol("changeddate", "date_format(bugs.delta_ts,'Y-m-d')", "Changed",
         "bugs.delta_ts");
  DefCol("severity", "substring(bugs.bug_severity, 1, 3)", "Sev",
         "bugs.bug_severity");
  DefCol("priority", "substring(bugs.priority, 1, 3)", "Pri", "bugs.priority");
  DefCol("platform", "substring(bugs.rep_platform, 1, 3)", "Plt",
         "bugs.rep_platform");
  DefCol("owner", "assign.login_name", "Owner", "assign.login_name");
  DefCol("reporter", "report.login_name", "Reporter", "report.login_name");
  DefCol("qa_contact", "qacont.login_name", "QAContact", "qacont.login_name");
  DefCol("status", "substring(bugs.bug_status,1,4)", "State", "bugs.bug_status");
  DefCol("resolution", "substring(bugs.resolution,1,4)", "Result",
         "bugs.resolution");
  DefCol("summary", "substring(bugs.short_desc, 1, 60)", "Summary", "", 1);
  DefCol("summaryfull", "bugs.short_desc", "Summary", "", 1);
  DefCol("status_whiteboard", "bugs.status_whiteboard", "StatusSummary", "bugs.status_whiteboard", 1);
  DefCol("component", "substring(bugs.component, 1, 8)", "Comp",
         "bugs.component");
  DefCol("product", "substring(bugs.product, 1, 8)", "Product", "bugs.product");
  DefCol("version", "substring(bugs.version, 1, 5)", "Vers", "bugs.version");
  DefCol("os", "substring(bugs.op_sys, 1, 4)", "OS", "bugs.op_sys");
  DefCol("target_milestone", "bugs.target_milestone", "TargetM",
         "bugs.target_milestone");
  DefCol("votes", "bugs.votes", "Votes", "bugs.votes desc");
  
  my @collist;
  if (defined $::COOKIE{'COLUMNLIST'}) {
      @collist = split(/ /, $::COOKIE{'COLUMNLIST'});
  } else {
      @collist = @::default_column_list;
  }
  
  my $minvotes;
  my $votecolnum;
  if (defined $::FORM{'votes'}) {
      my $c = trim($::FORM{'votes'});
      if ($c ne "") {
          if ($c !~ /^[0-9]*$/) {
              print "\n\n<P>The 'At least ___ votes' field must be a simple ";
              print "number. You entered \"$c\", which doesn't cut it.";
              print "<P>Please click the <B>Back</B> button and try again.\n";
              exit;
          }
          $minvotes = $c;
          if (! (grep {/^votes$/} @collist)) {
              push(@collist, 'votes');
          }
          $votecolnum = lsearch(\@collist, 'votes');
      }
  }
  
  
  my $dotweak = defined $::FORM{'tweak'};
  
  if ($dotweak) {
      confirm_login();
  } else {
      quietly_check_login();
  }
  
  
  my $query = "select bugs.bug_id, bugs.groupset";
  
  foreach my $c (@collist) {
      if (exists $::needquote{$c}) {
          $query .= ",
  \t$::key{$c}";
      }
  }
  
  
  if ($dotweak) {
      $query .= ",
  bugs.product,
  bugs.bug_status";
  }
  
  
  $query .= "
  from   bugs,
         profiles assign,
         profiles report
         left join profiles qacont on bugs.qa_contact = qacont.userid,
         versions projector
  
  where  bugs.assigned_to = assign.userid 
  and    bugs.reporter = report.userid
  and    bugs.product = projector.program
  and    bugs.version = projector.value
  and    bugs.groupset & $::usergroupset = bugs.groupset
  ";
  
  if ((defined $::FORM{'emailcc1'} && $::FORM{'emailcc1'}) ||
      (defined $::FORM{'emailcc2'} && $::FORM{'emailcc2'})) {
  
      # We need to poke into the CC table.  Do weird SQL left join stuff so that
      # we can look in the CC table, but won't reject any bugs that don't have
      # any CC fields.
      $query =~ s/bugs,/bugs left join cc on bugs.bug_id = cc.bug_id left join profiles ccname on cc.who = ccname.userid,/;
  }
  
  if (defined $::FORM{'sql'}) {
    $query .= "and (\n$::FORM{'sql'}\n)"
  } else {
    my @legal_fields = ("bug_id", "product", "version", "rep_platform", "op_sys",
                        "bug_status", "resolution", "priority", "bug_severity",
                        "assigned_to", "reporter", "component",
                        "target_milestone", "groupset");
  
    foreach my $field (keys %::FORM) {
        my $or = "";
        if (lsearch(\@legal_fields, $field) != -1 && $::FORM{$field} ne "") {
            $query .= "\tand (\n";
            if ($field eq "assigned_to" || $field eq "reporter") {
                foreach my $p (split(/,/, $::FORM{$field})) {
                    my $whoid = DBNameToIdAndCheck($p);
                    $query .= "\t\t${or}bugs.$field = $whoid\n";
                    $or = "or ";
                }
            } else {
                my $ref = $::MFORM{$field};
                foreach my $v (@$ref) {
                    if ($v eq "(empty)") {
                        $query .= "\t\t${or}bugs.$field is null\n";
                    } else {
                        if ($v eq "---") {
                            $query .= "\t\t${or}bugs.$field = ''\n";
                        } else {
                            $query .= "\t\t${or}bugs.$field = " . SqlQuote($v) .
                                "\n";
                        }
                    }
                    $or = "or ";
                }
            }
            $query .= "\t)\n";
        }
    }
  }
  
  
  foreach my $id ("1", "2") {
      if (!defined ($::FORM{"email$id"})) {
          next;
      }
      my $email = trim($::FORM{"email$id"});
      if ($email eq "") {
          next;
      }
      my $qemail = SqlQuote($email); 
      my $type = $::FORM{"emailtype$id"};
      my $emailid;
      if ($type eq "exact") {
          $emailid = DBNameToIdAndCheck($email);
      }
  
      my $foundone = 0;
      my $lead= "and (\n";
      foreach my $field ("assigned_to", "reporter", "cc", "qa_contact") {
          my $doit = $::FORM{"email$field$id"};
          if (!$doit) {
              next;
          }
          $foundone = 1;
          my $table;
          if ($field eq "assigned_to") {
              $table = "assign";
          } elsif ($field eq "reporter") {
              $table = "report";
          } elsif ($field eq "qa_contact") {
              $table = "qacont";
          } else {
              $table = "ccname";
          }
          if ($type eq "exact") {
              if ($field eq "cc") {
                  $query .= "\t$lead cc.who = $emailid\n";
              } else {
                  $query .= "\t$lead $field = $emailid\n";
              }
          } elsif ($type eq "regexp") {
              $query .= "\t$lead $table.login_name regexp $qemail\n";
          } elsif ($type eq "notregexp") {
              $query .= "\t$lead $table.login_name not regexp $qemail\n";
          } else {
              $query .= "\t$lead instr($table.login_name, $qemail)\n";
          }
          $lead = " or ";
      }
      if (!$foundone) {
          print "\n\n<P>You must specify one or more fields in which to search for <tt>$email</tt>.\n";
          print "<P>Please click the <B>Back</B> button and try again.\n";
          exit;
      }
      if ($lead eq " or ") {
          $query .= ")\n";
      }
  }
                  
  
  
  
  
  if (defined $::FORM{'changedin'}) {
      my $c = trim($::FORM{'changedin'});
      if ($c ne "") {
          if ($c !~ /^[0-9]*$/) {
              print "\n\n<P>The 'changed in last ___ days' field must be a simple ";
              print "number. You entered \"$c\", which doesn't cut it.";
              print "<P>Please click the <B>Back</B> button and try again.\n";
              exit;
          }
          $query .= "and to_days(now()) - to_days(bugs.delta_ts) <= $c ";
      }
  }
  
  if (defined $minvotes) {
      $query .= "and votes >= $minvotes ";
  }
  
  
  my $ref = $::MFORM{'chfield'};
  
  
  sub SqlifyDate {
      my ($str) = (@_);
      if (!defined $str) {
          $str = "";
      }
      my $date = str2time($str);
      if (!defined $date) {
          print "\n\n<P>The string '<tt>$str</tt>' is not a legal date.\n";
          print "<P>Please click the <B>Back</B> button and try again.\n";
          exit;
      }
      return time2str("'%Y/%m/%d %H:%M:%S'", $date);
  }
  
  
  if (defined $ref) {
      my $which = lsearch($ref, "[Bug creation]");
      if ($which >= 0) {
          splice(@$ref, $which, 1);
          $query .= "and bugs.creation_ts >= " .
              SqlifyDate($::FORM{'chfieldfrom'}) . "\n";
          my $to = $::FORM{'chfieldto'};
          if (defined $to) {
              $to = trim($to);
              if ($to ne "" && $to !~ /^now$/i) {
                  $query .= "and bugs.creation_ts <= " .
                      SqlifyDate($to) . "\n";
              }
          }
      }
  }        
  
  
  
  if (defined $ref && 0 < @$ref) {
      # Do surgery on the query to tell it to patch in the bugs_activity
      # table.
      $query =~ s/bugs,/bugs, bugs_activity,/;
      
      my @list;
      foreach my $f (@$ref) {
          push(@list, "\nbugs_activity.field = " . SqlQuote($f));
      }
      $query .= "and bugs_activity.bug_id = bugs.bug_id and (" .
          join(' or ', @list) . ") ";
      $query .= "and bugs_activity.bug_when >= " .
          SqlifyDate($::FORM{'chfieldfrom'}) . "\n";
      my $to = $::FORM{'chfieldto'};
      if (defined $to) {
          $to = trim($to);
          if ($to ne "" && $to !~ /^now$/i) {
              $query .= "and bugs_activity.bug_when <= " . SqlifyDate($to) . "\n";
          }
      }
      my $value = $::FORM{'chfieldvalue'};
      if (defined $value) {
          $value = trim($value);
          if ($value ne "") {
              $query .= "and bugs_activity.newvalue = " .
                  SqlQuote($value) . "\n";
          }
      }
  }
  
  foreach my $f ("short_desc", "long_desc", "bug_file_loc",
                 "status_whiteboard") {
      if (defined $::FORM{$f}) {
          my $s = trim($::FORM{$f});
          if ($s ne "") {
              $s = SqlQuote($s);
              if ($::FORM{$f . "_type"} eq "regexp") {
                  $query .= "and $f regexp $s\n";
              } elsif ($::FORM{$f . "_type"} eq "notregexp") {
                  $query .= "and $f not regexp $s\n";
              } elsif ($::FORM{$f . "_type"} eq "casesubstring") {
                  $query .= "and instr($f, $s)\n";
              } else {
                  $query .= "and instr(lower($f), lower($s))\n";
              }
          }
      }
  }
  
  
  $query .= "group by bugs.bug_id\n";
  
  if (defined $::FORM{'order'} && $::FORM{'order'} ne "") {
      $query .= "order by ";
      $::FORM{'order'} =~ s/votesum/bugs.votes/; # Silly backwards compatability
                                                 # hack.
      ORDER: for ($::FORM{'order'}) {
          /\./ && do {
              # This (hopefully) already has fieldnames in it, so we're done.
              last ORDER;
          };
          /Number/ && do {
              $::FORM{'order'} = "bugs.bug_id";
              last ORDER;
          };
          /Import/ && do {
              $::FORM{'order'} = "bugs.priority, bugs.bug_severity";
              last ORDER;
          };
          /Assign/ && do {
              $::FORM{'order'} = "assign.login_name, bugs.bug_status, priority, bugs.bug_id";
              last ORDER;
          };
          # DEFAULT
          $::FORM{'order'} = "bugs.bug_status, bugs.priority, assign.login_name, bugs.bug_id";
      }
      $query .= $::FORM{'order'};
  }
  
  
  if (Param('expectbigqueries')) {
      SendSQL("set option SQL_BIG_TABLES=1");
  }
  SendSQL($query);
  
  my $count = 0;
  $::bugl = "";
  sub pnl {
      my ($str) = (@_);
      $::bugl  .= $str;
  }
  
  my $fields = $::buffer;
  $fields =~ s/[&?]order=[^&]*//g;
  $fields =~ s/[&?]cmdtype=[^&]*//g;
  
  
  my $oldorder;
  
  if (defined $::FORM{'order'} && trim($::FORM{'order'}) ne "") {
      $oldorder = url_quote(", $::FORM{'order'}");
  } else {
      $oldorder = "";
  }
  
  if ($dotweak) {
      pnl "<FORM NAME=changeform METHOD=POST ACTION=\"process_bug.cgi\">";
  }
  
  my $tablestart = "<TABLE CELLSPACING=0 CELLPADDING=2>
  <TR ALIGN=LEFT><TH>
  <A HREF=\"buglist.cgi?$fields&order=bugs.bug_id\">ID</A>";
  
  
  foreach my $c (@collist) {
      if (exists $::needquote{$c}) {
          if ($::needquote{$c}) {
              $tablestart .= "<TH WIDTH=100% valign=left>";
          } else {
              $tablestart .= "<TH valign=left>";
          }
          if (defined $::sortkey{$c}) {
              $tablestart .= "<A HREF=\"buglist.cgi?$fields&order=" . url_quote($::sortkey{$c}) . "$oldorder\">$::title{$c}</A>";
          } else {
              $tablestart .= $::title{$c};
          }
      }
  }
  
  $tablestart .= "\n";
  
  
  my @row;
  my %seen;
  my @bugarray;
  my %prodhash;
  my %statushash;
  my $buggroupset = "";
  
  while (@row = FetchSQLData()) {
      my $bug_id = shift @row;
      my $g = shift @row;         # Bug's group set.
      if ($buggroupset eq "") {
          $buggroupset = $g;
      } elsif ($buggroupset ne $g) {
          $buggroupset = "x";     # We only play games with tweaking the
                                  # buggroupset if all the bugs have exactly
                                  # the same group.  If they don't, we leave
                                  # it alone.
      }
      if (!defined $seen{$bug_id}) {
          $seen{$bug_id} = 1;
          $count++;
          if ($count % 200 == 0) {
              # Too big tables take too much browser memory...
              pnl "</TABLE>$tablestart";
          }
          push @bugarray, $bug_id;
          pnl "<TR VALIGN=TOP ALIGN=LEFT><TD>";
          if ($dotweak) {
              pnl "<input type=checkbox name=id_$bug_id>";
          }
          pnl "<A HREF=\"show_bug.cgi?id=$bug_id\">";
          pnl "$bug_id</A> ";
          foreach my $c (@collist) {
              if (!exists $::needquote{$c}) {
                  next;
              }
              my $value = shift @row;
              if (!defined $value) {
                  next;
              }
              if ($::needquote{$c}) {
                  $value = html_quote($value);
              } else {
                  $value = "<nobr>$value</nobr>";
              }
              pnl "<td>$value";
          }
          if ($dotweak) {
              my $value = shift @row;
              $prodhash{$value} = 1;
              $value = shift @row;
              $statushash{$value} = 1;
          }
          pnl "\n";
      }
  }
  my $buglist = join(":", @bugarray);
  
  
  # This is stupid.  We really really need to move the quip list into the DB!
  my $quip;
  if (Param('usequip')){
    if (open (COMMENTS, "<data/comments")) {
      my @cdata;
      while (<COMMENTS>) {
        push @cdata, $_;
      }
      close COMMENTS;
      $quip = $cdata[int(rand($#cdata + 1))];
    }
    $quip ||= "Bugzilla would like to put a random quip here, but nobody has entered any.";
  }
  
   
  # We've done all we can without any output.  If we can server push it is time
  # take down the waiting page and put up the real one.       
  if ($serverpush) {
      print "\n";
      print "--thisrandomstring\n";
      print "Content-type: text/html\n";
      # Note! HTML header not yet closed
  }
  my $toolong = 0;
  if (length($buglist) < 4000) {
      print "Set-Cookie: BUGLIST=$buglist\n\n";
  } else {
      print "Set-Cookie: BUGLIST=\n\n";
      $toolong = 1;
  }
  PutHeader("Bug List");
  
  
  print "
  <CENTER>
  <B>" .  time2str("%a %b %e %T %Z %Y", time()) . "</B>";
  
  if (defined $::FORM{'debug'}) {
      print "<PRE>$query</PRE>\n";
  }
  
  if ($toolong) {
      print "<h2>This list is too long for bugzilla's little mind; the\n";
      print "Next/Prev/First/Last buttons won't appear.</h2>\n";
  }
  
  if (Param('usequip')){
    print "<HR><A HREF=newquip.html><I>$quip</I></A></CENTER>\n";
  }
  print "<HR SIZE=10>$tablestart\n";
  print $::bugl;
  print "</TABLE>\n";
  
  if ($count == 0) {
      print "Zarro Boogs found.\n";
      # I've been asked to explain this ... way back when, when Netscape released
      # version 4.0 of its browser, we had a release party.  Naturally, there
      # had been a big push to try and fix every known bug before the release.
      # Naturally, that hadn't actually happened.  (This is not unique to
      # Netscape or to 4.0; the same thing has happened with every software
      # project I've ever seen.)  Anyway, at the release party, T-shirts were
      # handed out that said something like "Netscape 4.0: Zarro Boogs".
      # Just like the software, the T-shirt had no known bugs.  Uh-huh.
      #
      # So, when you query for a list of bugs, and it gets no results, you
      # can think of this as a friendly reminder.  Of *course* there are bugs
      # matching your query, they just aren't in the bugsystem yet...
  } elsif ($count == 1) {
      print "One bug found.\n";
  } else {
      print "$count bugs found.\n";
  }
  
  if ($dotweak) {
      GetVersionTable();
      print "
  <SCRIPT>
  numelements = document.changeform.elements.length;
  function SetCheckboxes(value) {
      for (var i=0 ; i<numelements ; i++) {
          item = document.changeform.elements\[i\];
          item.checked = value;
      }
  }
  document.write(\" <input type=button value=\\\"Uncheck All\\\" onclick=\\\"SetCheckboxes(false);\\\"> <input type=button value=\\\"Check All\\\" onclick=\\\"SetCheckboxes(true);\\\">\");
  </SCRIPT>";
      my $resolution_popup = make_options(\@::legal_resolution_no_dup, "FIXED");
      my @prod_list = keys %prodhash;
      my @list = @prod_list;
      my @legal_versions;
      my @legal_component;
      if (1 == @prod_list) {
          @legal_versions = @{$::versions{$prod_list[0]}};
          @legal_component = @{$::components{$prod_list[0]}};
      }
      
      my $version_popup = make_options(\@legal_versions, $::dontchange);
      my $platform_popup = make_options(\@::legal_platform, $::dontchange);
      my $priority_popup = make_options(\@::legal_priority, $::dontchange);
      my $sev_popup = make_options(\@::legal_severity, $::dontchange);
      my $component_popup = make_options(\@legal_component, $::dontchange);
      my $product_popup = make_options(\@::legal_product, $::dontchange);
  
  
      print "
  <hr>
  <TABLE>
  <TR>
      <TD ALIGN=RIGHT><B>Product:</B></TD>
      <TD><SELECT NAME=product>$product_popup</SELECT></TD>
      <TD ALIGN=RIGHT><B>Version:</B></TD>
      <TD><SELECT NAME=version>$version_popup</SELECT></TD>
  <TR>
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#rep_platform\">Platform:</A></B></TD>
      <TD><SELECT NAME=rep_platform>$platform_popup</SELECT></TD>
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#priority\">Priority:</A></B></TD>
      <TD><SELECT NAME=priority>$priority_popup</SELECT></TD>
  </TR>
  <TR>
      <TD ALIGN=RIGHT><B>Component:</B></TD>
      <TD><SELECT NAME=component>$component_popup</SELECT></TD>
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#severity\">Severity:</A></B></TD>
      <TD><SELECT NAME=bug_severity>$sev_popup</SELECT></TD>
  </TR>";
  
      if (Param("usetargetmilestone")) {
          push(@::legal_target_milestone, " ");
          my $tfm_popup = make_options(\@::legal_target_milestone,
                                       $::dontchange);
          print "
      <TR>
      <TD ALIGN=RIGHT><B>Target milestone:</B></TD>
      <TD><SELECT NAME=target_milestone>$tfm_popup</SELECT></TD>
      </TR>";
      }
  
      if (Param("useqacontact")) {
          print "
  <TR>
  <TD><B>QA Contact:</B></TD>
  <TD COLSPAN=3><INPUT NAME=qa_contact SIZE=32 VALUE=\"" .
              value_quote($::dontchange) . "\"></TD>
  </TR>";
      }
          
  
      print "
  </TABLE>
  
  <INPUT NAME=multiupdate value=Y TYPE=hidden>
  
  <B>Additional Comments:</B>
  <BR>
  <TEXTAREA WRAP=HARD NAME=comment ROWS=5 COLS=80></TEXTAREA><BR>";
  
  if ($::usergroupset ne '0' && $buggroupset =~ /^\d*$/) {
      SendSQL("select bit, description, (bit & $buggroupset != 0) from groups where bit & $::usergroupset != 0 and isbuggroup != 0 order by bit");
      while (MoreSQLData()) {
          my ($bit, $description, $ison) = (FetchSQLData());
          my $check0 = !$ison ? " SELECTED" : "";
          my $check1 = $ison ? " SELECTED" : "";
          print "<select name=bit-$bit><option value=0$check0>\n";
          print "People not in the \"$description\" group can see these bugs\n";
          print "<option value=1$check1>\n";
          print "Only people in the \"$description\" group can see these bugs\n";
          print "</select><br>\n";
      }
  }
  
  
  
  
      # knum is which knob number we're generating, in javascript terms.
  
      my $knum = 0;
      print "
  <INPUT TYPE=radio NAME=knob VALUE=none CHECKED>
          Do nothing else<br>";
      $knum++;
      print "
  <INPUT TYPE=radio NAME=knob VALUE=accept>
          Accept bugs (change status to <b>ASSIGNED</b>)<br>";
      $knum++;
      if (!defined $statushash{'CLOSED'} &&
          !defined $statushash{'VERIFIED'} &&
          !defined $statushash{'RESOLVED'}) {
          print "
  <INPUT TYPE=radio NAME=knob VALUE=clearresolution>
          Clear the resolution<br>";
          $knum++;
          print "
  <INPUT TYPE=radio NAME=knob VALUE=resolve>
          Resolve bugs, changing <A HREF=\"bug_status.html\">resolution</A> to
          <SELECT NAME=resolution
            ONCHANGE=\"document.changeform.knob\[$knum\].checked=true\">
            $resolution_popup</SELECT><br>";
          $knum++;
      }
      if (!defined $statushash{'NEW'} &&
          !defined $statushash{'ASSIGNED'} &&
          !defined $statushash{'REOPENED'}) {
          print "
  <INPUT TYPE=radio NAME=knob VALUE=reopen> Reopen bugs<br>";
          $knum++;
      }
      my @statuskeys = keys %statushash;
      if (1 == @statuskeys) {
          if (defined $statushash{'RESOLVED'}) {
              print "
  <INPUT TYPE=radio NAME=knob VALUE=verify>
          Mark bugs as <b>VERIFIED</b><br>";
              $knum++;
          }
          if (defined $statushash{'VERIFIED'}) {
              print "
  <INPUT TYPE=radio NAME=knob VALUE=close>
          Mark bugs as <b>CLOSED</b><br>";
              $knum++;
          }
      }
      print "
  <INPUT TYPE=radio NAME=knob VALUE=reassign> 
          <A HREF=\"bug_status.html#assigned_to\">Reassign</A> bugs to
          <INPUT NAME=assigned_to SIZE=32
            ONCHANGE=\"document.changeform.knob\[$knum\].checked=true\"
            VALUE=\"$::COOKIE{'Bugzilla_login'}\"><br>";
      $knum++;
      print "<INPUT TYPE=radio NAME=knob VALUE=reassignbycomponent>
            Reassign bugs to owner of selected component<br>";
      $knum++;
  
      print "
  <p>
  <font size=-1>
  To make changes to a bunch of bugs at once:
  <ol>
  <li> Put check boxes next to the bugs you want to change.
  <li> Adjust above form elements.  (It's <b>always</b> a good idea to add some
       comment explaining what you're doing.)
  <li> Click the below \"Commit\" button.
  </ol></font>
  <INPUT TYPE=SUBMIT VALUE=Commit>
  </FORM><hr>\n";
  }
  
  
  if ($count > 0) {
      print "<FORM METHOD=POST ACTION=\"long_list.cgi\">
  <INPUT TYPE=HIDDEN NAME=buglist VALUE=$buglist>
  <INPUT TYPE=SUBMIT VALUE=\"Long Format\">
  <A HREF=\"query.cgi\">Query Page</A>
  &nbsp;&nbsp;<A HREF=\"enter_bug.cgi\">Enter New Bug</A>
  &nbsp;&nbsp;<A HREF=\"colchange.cgi?$::buffer\">Change columns</A>";
      if (!$dotweak && $count > 1) {
          print "&nbsp;&nbsp;<A HREF=\"buglist.cgi?$fields&tweak=1\">";
          print "Change several bugs at once</A>\n";
      }
      print "</FORM>\n";
  }
  if ($serverpush) {
      print "\n--thisrandomstring--\n";
  }
  
  
  
  1.1                  apache-site/bugs/changepassword.cgi
  
  Index: changepassword.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  require "CGI.pl";
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  if (! defined $::FORM{'pwd1'}) {
      PutHeader("Preferences", "Change your password and<br>other preferences",
                $::COOKIE{'Bugzilla_login'});
  
      my $qacontactpart = "";
      if (Param('useqacontact')) {
          $qacontactpart = ", the current QA Contact";
      }
      my $loginname = SqlQuote($::COOKIE{'Bugzilla_login'});
      SendSQL("select emailnotification,realname from profiles where login_name = " .
              $loginname);
      my ($emailnotification, $realname) = (FetchSQLData());
      $realname = value_quote($realname);
      print qq{
  <form method=post>
  <hr>
  <table>
  <tr>
  <td align=right>Please enter the new password for <b>$::COOKIE{'Bugzilla_login'}</b>:</td>
  <td><input type=password name="pwd1"></td>
  </tr>
  <tr>
  <td align=right>Re-enter your new password:</td>
  <td><input type=password name="pwd2"></td>
  </tr>
  <tr>
  <td align=right>Your real name (optional):</td>
  <td><input size=35 name=realname value="$realname"></td>
  </tr>
  </table>
  <hr>
  <table>
  <tr>
  <td align=right>Bugzilla will send out email notification of changed bugs to 
  the current owner, the submitter of the bug$qacontactpart, and anyone on the
  CC list.  However, you can suppress some of those email notifications.
  On which of these bugs would you like email notification of changes?</td>
  <td><SELECT NAME="emailnotification">
  };
      foreach my $i (["ExcludeSelfChanges", "All qualifying bugs except those which I change"],
                     ["CConly", "Only those bugs which I am listed on the CC line"],
                     ["All", "All qualifying bugs"]) {
          my ($tag, $desc) = (@$i);
          my $selectpart = "";
          if ($tag eq $emailnotification) {
              $selectpart = " SELECTED";
          }
          print qq{<OPTION$selectpart VALUE="$tag">$desc\n};
      }
      print "
  </SELECT>
  </td>
  </tr>
  </table>
  <hr>
  <input type=submit value=Submit>
  </form>
  <hr>
  <a href=\"showvotes.cgi\">Review your votes</a>
  <hr>
  ";
      navigation_header();
      exit;
  }
  
  if ($::FORM{'pwd1'} ne $::FORM{'pwd2'}) {
      print "<H1>Try again.</H1>
  The two passwords you entered did not match.  Please click <b>Back</b> and try again.\n";
      exit;
  }
  
  
  my $pwd = $::FORM{'pwd1'};
  
  
  sub x {
      my $sc="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./";
      return substr($sc, int (rand () * 100000) % (length ($sc) + 1), 1);
  }
  
  if ($pwd ne "") {
      if ($pwd !~ /^[a-zA-Z0-9-_]*$/ || length($pwd) < 3 || length($pwd) > 15) {
          print "<H1>Sorry; we're picky.</H1>
  Please choose a password that is between 3 and 15 characters long, and that
  contains only numbers, letters, hyphens, or underlines.
  <p>
  Please click <b>Back</b> and try again.\n";
          exit;
      }
      
      
  # Generate a random salt.
      
      my $salt  = x() . x();
      
      my $encrypted = crypt($pwd, $salt);
      
      SendSQL("update profiles set password='$pwd',cryptpassword='$encrypted' where login_name=" .
              SqlQuote($::COOKIE{'Bugzilla_login'}));
      
      SendSQL("update logincookies set cryptpassword = '$encrypted' where cookie = $::COOKIE{'Bugzilla_logincookie'}");
  }
  
  
  SendSQL("update profiles set emailnotification='$::FORM{'emailnotification'}' where login_name = " .
          SqlQuote($::COOKIE{'Bugzilla_login'}));
  
  my $newrealname = $::FORM{'realname'};
  
  if ($newrealname ne "") {
      $newrealname = SqlQuote($newrealname);
      SendSQL("update profiles set realname=$newrealname where login_name = " .
              SqlQuote($::COOKIE{'Bugzilla_login'}));
  }
  
  PutHeader("Preferences updated.");
  print "
  Your preferences have been updated.
  <p>";
  navigation_header();
  
  
  
  
  1.1                  apache-site/bugs/checksetup.pl
  
  Index: checksetup.pl
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is mozilla.org code.
  #
  # The Initial Developer of the Original Code is Holger
  # Schurig. Portions created by Holger Schurig are
  # Copyright (C) 1999 Holger Schurig. All
  # Rights Reserved.
  #
  # Contributor(s): Holger Schurig <ho...@nikocity.de>
  #                 Terry Weissman <te...@mozilla.org>
  #
  #
  # Direct any questions on this source code to
  #
  # Holger Schurig <ho...@nikocity.de>
  #
  #
  #
  # Hey, what's this?
  #
  # 'checksetup.pl' is a script that is supposed to run during installation
  # time and also after every upgrade.
  #
  # The goal of this script is to make the installation even more easy.
  # It does so by doing things for you as well as testing for problems
  # early.
  #
  # And you can re-run it whenever you want. Especially after Bugzilla
  # get's updated you SHOULD rerun it. Because then it may update your
  # SQL table definitions so that they are again in sync with the code.
  #
  # So, currently this module does:
  #
  #     - check for required perl modules
  #     - set defaults for local configuration variables
  #     - create and populate the data directory after installation
  #     - set the proper rights for the *.cgi, *.html ... etc files
  #     - check if the code can access MySQL
  #     - creates the database 'bugs' if the database does not exist
  #     - creates the tables inside the database if they don't exist
  #     - automatically changes the table definitions of older BugZilla
  #       installations
  #     - populates the groups
  #     - put the first user into all groups so that the system can
  #       be administrated
  #     - changes already existing SQL tables if you change your local
  #       settings, e.g. when you add a new platform
  #
  # People that install this module locally are not supposed to modify
  # this script. This is done by shifting the user settable stuff intp
  # a local configuration file 'localconfig'. When this file get's
  # changed and 'checkconfig.pl' will be re-run, then the user changes
  # will be reflected back into the database.
  #
  # Developers however have to modify this file at various places. To
  # make this easier, I have added some special comments that one can
  # search for.
  #
  #     To                                               Search for
  #
  #     add/delete local configuration variables         --LOCAL--
  #     check for more prerequired modules               --MODULES--
  #     change the defaults for local configuration vars --LOCAL--
  #     update the assigned file permissions             --CHMOD--
  #     add more MySQL-related checks                    --MYSQL--
  #     change table definitions                         --TABLE--
  #     add more groups                                  --GROUPS--
  #
  # Note: sometimes those special comments occur more then once. For
  # example, --LOCAL-- is at least 3 times in this code!  --TABLE--
  # also is used more than once. So search for every occurence!
  #
  
  
  
  
  
  ###########################################################################
  # Global definitions
  ###########################################################################
  
  use diagnostics;
  use strict;
  
  
  
  #
  # This are the --LOCAL-- variables defined in 'localconfig'
  # 
  
  use vars qw(
      $webservergroup
      $db_host $db_port $db_name $db_user
      @severities @priorities @opsys @platforms
  );
  
  
  
  
  
  ###########################################################################
  # Check required module
  ###########################################################################
  
  #
  # Here we check for --MODULES--
  #
  
  print "Checking perl modules ...\n";
  unless (eval "require 5.004") {
      die "Sorry, you need at least Perl 5.004\n";
  }
  
  unless (eval "require DBI") {
      die "Please install the DBI module. You can do this by running (as root)\n\n",
          "       perl -MCPAN -eshell\n",
          "       install DBI\n";
  }
  
  unless (eval "require Data::Dumper") {
      die "Please install the Data::Dumper module. You can do this by running (as root)\n\n",
          "       perl -MCPAN -eshell\n",
          "       install Data::Dumper\n";
  }
  
  unless (eval "require Mysql") {
      die "Please install the Mysql database driver. You can do this by running (as root)\n\n",
          "       perl -MCPAN -eshell\n",
          "       install Msql-Mysql\n\n",
          "Be sure to enable the Mysql emulation!";
  }
  
  unless (eval "require Date::Parse") {
      die "Please install the Date::Parse module. You can do this by running (as root)\n\n",
          "       perl -MCPAN -eshell\n",
          "       install Date::Parse\n";
  }
  
  # The following two modules are optional:
  my $charts = 0;
  $charts++ if eval "require GD";
  $charts++ if eval "require Chart::Base";
  if ($charts != 2) {
      print "If you you want to see graphical bug dependency charts, you may install\n",
      "the optional libgd and the Perl modules GD and Chart::Base, e.g. by\n",
      "running (as root)\n\n",
      "   perl -MCPAN -eshell\n",
      "   install GD\n",
      "   install Chart::Base\n";
  }
  
  
  
  
  
  ###########################################################################
  # Check and update local configuration
  ###########################################################################
  
  #
  # This is quite tricky. But fun!
  #
  # First we read the file 'localconfig'. And then we check if the variables
  # we need to be defined are defined. If not, localconfig will be amended by
  # the new settings and the user informed to check this. The program then
  # stops.
  #
  # Why do it this way around?
  #
  # Assume we will enhance Bugzilla and eventually more local configuration
  # stuff arises on the horizon.
  #
  # But the file 'localconfig' is not in the Bugzilla CVS or tarfile. It should
  # not be there so that we never overwrite user's local setups accidentally.
  # Now, we need a new variable. We simply add the necessary stuff to checksetup.
  # The user get's the new version of Bugzilla from the CVS, runs checksetup
  # and checksetup finds out "Oh, there is something new". Then it adds some
  # default value to the user's local setup and informs the user to check that
  # to see if that is what the user wants.
  #
  # Cute, ey?
  #
  
  print "Checking user setup ...\n";
  do 'localconfig';
  my $newstuff = "";
  sub LocalVar ($$)
  {
      my ($name, $definition) = @_;
  
      # Is there a cleaner way to test if the variable defined in scalar $name
      # is defined or not?
      my $defined = 0;
      $_ = "\$defined = 1 if defined $name;";
      eval $_;
      return if $defined;
  
      $newstuff .= " " . $name;
      open FILE, '>>localconfig';
      print FILE $definition, "\n\n";
      close FILE;
  }
  
  
  
  #
  # Set up the defaults for the --LOCAL-- variables below:
  #
  
      
  LocalVar('$webservergroup', '
  #
  # This is the group your web server runs on.
  # If you have a windows box, ignore this setting.
  # If you do not wish for checksetup to adjust the permissions of anything,
  # set this to "".
  # If you set this to anything besides "", you will need to run checksetup.pl
  # as root.
  $webservergroup = "nobody";
  ');
  
  
  
  LocalVar('$db_host', '
  #
  # How to access the SQL database:
  #
  $db_host = "localhost";         # where is the database?
  $db_port = 3306;                # which port to use
  $db_name = "bugs";              # name of the MySQL database
  $db_user = "bugs";              # user to attach to the MySQL database
  ');
  
  
  
  LocalVar('@severities', '
  #
  # Which bug and feature-request severities do you want?
  #
  @severities = (
          "blocker",
          "critical",
          "major",
          "normal",
          "minor",
          "trivial",
          "enhancement"
  );
  ');
  
  
  
  LocalVar('@priorities', '
  #
  # Which priorities do you want to assign to bugs and feature-request?
  #
  @priorities = (
          "P1",
          "P2",
          "P3",
          "P4",
          "P5"
  );
  ');
  
  
  
  LocalVar('@opsys', '
  #
  # What operatings systems may your products run on?
  #
  @opsys = (
          "All",
          "Windows 3.1",
          "Windows 95",
          "Windows 98",
          "Windows NT",
          "Mac System 7",
          "Mac System 7.5",
          "Mac System 7.6.1",
          "Mac System 8.0",
          "Mac System 8.5",
          "Mac System 8.6",
          "AIX",
          "BSDI",
          "HP-UX",
          "IRIX",
          "Linux",
          "FreeBSD",
          "OSF/1",
          "Solaris",
          "SunOS",
          "Neutrino",
          "OS/2",
          "BeOS",
          "OpenVMS",
          "other"
  );
  ');
  
  
  
  LocalVar('@platforms', '
  #
  # What hardware platforms may your products run on?
  #
  @platforms = (
          "All",
          "DEC",
          "HP",
          "Macintosh",
          "PC",
          "SGI",
          "Sun",
          "Other"
  );
  ');
  
  
  
  
  if ($newstuff ne "") {
      print "This version of Bugzilla contains some variables that you may \n",
            "to change and adapt to your local settings. Please edit the file\n",
            "'localconfig' and return checksetup.pl\n\n",
            "The following variables are new to localconfig since you last ran\n",
            "checksetup.pl:  $newstuff\n";
      exit;
  }
  
  
  
  
  
  ###########################################################################
  # Check data directory
  ###########################################################################
  
  #
  # Create initial --DATA-- directory and make the initial empty files there:
  #
  
  unless (-d 'data') {
      print "Creating data directory ...\n";
      mkdir 'data', 0770;
      if ($webservergroup eq "") {
          chmod 0777, 'data';
      }
      open FILE, '>>data/comments'; close FILE;
      open FILE, '>>data/nomail'; close FILE;
      open FILE, '>>data/mail'; close FILE;
      chmod 0666, glob('data/*');
  }
  
  
  # Just to be sure ...
  unlink "data/versioncache";
  
  
  
  
  
  ###########################################################################
  # Set proper rights
  ###########################################################################
  
  #
  # Here we use --CHMOD-- and friends to set the file permissions
  #
  # The rationale is that the web server generally runs as nobody and so the cgi
  # scripts should not be writable for nobody, otherwise someone may be possible
  # to change the cgi's when exploiting some security flaw somewhere (not
  # necessarily in Bugzilla!)
  #
  # Also, some *.pl files are executable, some are not.
  #
  # +++ Can anybody tell me what a Windows Perl would do with this code?
  #
  
  if ($webservergroup) {
      mkdir 'shadow', 0770 unless -d 'shadow';
      # Funny! getgrname returns the GID if fed with NAME ...
      my $webservergid = getgrnam($webservergroup);
      chown 0, $webservergid, glob('*');
      chmod 0640, glob('*');
  
      chmod 0750, glob('*.cgi'),
                  'processmail',
                  'whineatnews.pl',
                  'collectstats.pl',
                  'checksetup.pl';
  
      chmod 0770, 'data', 'shadow';
      chmod 0666, glob('data/*');
  }
  
  
  
  
  
  ###########################################################################
  # Check MySQL setup
  ###########################################################################
  
  #
  # Check if we have access to --MYSQL--
  #
  
  # This settings are not yet changeable, because other code depends on
  # the fact that we use MySQL and not, say, PostgreSQL.
  
  my $db_base = 'mysql';
  my $db_pass = '';               # Password to attach to the MySQL database
  
  use DBI;
  
  # get a handle to the low-level DBD driver
  my $drh = DBI->install_driver($db_base)
      or die "Can't connect to the $db_base. Is the database installed and up and running?\n";
  
  # Do we have the database itself?
  my @databases = $drh->func($db_host, $db_port, '_ListDBs');
  unless (grep /^$db_name$/, @databases) {
      print "Creating database $db_name ...\n";
      $drh->func('createdb', $db_name, 'admin')
          or die "The '$db_name' database does not exist. I tried to create the database,\n",
                 "but that didn't work, probably because of access rigths. Read the README\n",
                 "file and the documentation of $db_base to make sure that everything is\n",
                 "set up correctly.\n";
  }
  
  # now get a handle to the database:
  my $connectstring = "dbi:$db_base:$db_name:host=$db_host:port=$db_port";
  my $dbh = DBI->connect($connectstring, $db_user, $db_pass)
      or die "Can't connect to the table '$connectstring'.\n",
             "Have you read Bugzilla's README?  Have you read the doc of '$db_name'?\n";
  
  END { $dbh->disconnect if $dbh }
  
  
  
  
  
  ###########################################################################
  # Table definitions
  ###########################################################################
  
  #
  # The following hash stores all --TABLE-- definitions. This will be used
  # to automatically create those tables that don't exist. The code is
  # safer than the make*.sh shell scripts used to be, because they won't
  # delete existing tables.
  #
  # If you want intentionally do this, yon can always drop a table and re-run
  # checksetup, e.g. like this:
  #
  #    $ mysql bugs
  #    mysql> drop table votes;
  #    mysql> exit;
  #    $ ./checksetup.pl
  #
  # If you change one of those field definitions, then also go below to the
  # next occurence of the string --TABLE-- (near the end of this file) to
  # add the code that updates older installations automatically.
  #
  
  
  my %table;
  
  $table{bugs_activity} = 
     'bug_id mediumint not null,
      who mediumint not null,
      bug_when datetime not null,
      field varchar(64) not null,
      oldvalue tinytext,
      newvalue tinytext,
  
      index (bug_id),
      index (bug_when),
      index (field)';
  
  
  $table{attachments} =
     'attach_id mediumint not null auto_increment primary key,
      bug_id mediumint not null,
      creation_ts timestamp,
      description mediumtext not null,
      mimetype mediumtext not null,
      ispatch tinyint,
      filename mediumtext not null,
      thedata longblob not null,
      submitter_id mediumint not null,
  
      index(bug_id),
      index(creation_ts)';
  
  
  $table{bugs} =
     'bug_id mediumint not null auto_increment primary key,
      groupset bigint not null,
      assigned_to mediumint not null, # This is a comment.
      bug_file_loc text,
      bug_severity enum($severities) not null,
      bug_status enum("NEW", "ASSIGNED", "REOPENED", "RESOLVED", "VERIFIED", "CLOSED") not null,
      creation_ts datetime not null,
      delta_ts timestamp,
      short_desc mediumtext,
      long_desc mediumtext,
      op_sys enum($opsys) not null,
      priority enum($priorities) not null,
      product varchar(64) not null,
      rep_platform enum($platforms),
      reporter mediumint not null,
      version varchar(16) not null,
      component varchar(50) not null,
      resolution enum("", "FIXED", "INVALID", "WONTFIX", "LATER", "REMIND", "DUPLICATE", "WORKSFORME") not null,
      target_milestone varchar(20) not null,
      qa_contact mediumint not null,
      status_whiteboard mediumtext not null,
      votes mediumint not null,
  
      index (assigned_to),
      index (creation_ts),
      index (delta_ts),
      index (bug_severity),
      index (bug_status),
      index (op_sys),
      index (priority),
      index (product),
      index (reporter),
      index (version),
      index (component),
      index (resolution),
      index (target_milestone),
      index (qa_contact),
      index (votes)';
  
  
  $table{cc} =
     'bug_id mediumint not null,
      who mediumint not null,
  
      index(bug_id),
      index(who)';
  
  
  $table{components} =
     'value tinytext,
      program varchar(64),
      initialowner tinytext not null,     # Should arguably be a mediumint!
      initialqacontact tinytext not null, # Should arguably be a mediumint!
      description mediumtext not null';
  
  
  $table{dependencies} =
     'blocked mediumint not null,
      dependson mediumint not null,
  
      index(blocked),
      index(dependson)';
  
  
  # Group bits must be a power of two. Groups are identified by a bit; sets of
  # groups are indicated by or-ing these values together.
  #
  # isbuggroup is nonzero if this is a group that controls access to a set
  # of bugs.  In otherword, the groupset field in the bugs table should only
  # have this group's bit set if isbuggroup is nonzero.
  #
  # User regexp is which email addresses are initially put into this group.
  # This is only used when an email account is created; otherwise, profiles
  # may be individually tweaked to add them in and out of groups.
  
  $table{groups} =
     'bit bigint not null,
      name varchar(255) not null,
      description text not null,
      isbuggroup tinyint not null,
      userregexp tinytext not null,
  
      unique(bit),
      unique(name)';
  
  
  $table{logincookies} =
     'cookie mediumint not null auto_increment primary key,
      userid mediumint not null,
      cryptpassword varchar(64),
      hostname varchar(128),
      lastused timestamp,
  
      index(lastused)';
  
  
  $table{products} =
     'product varchar(64),
      description mediumtext,
      milestoneurl tinytext not null,
      disallownew tinyint not null,
      votesperuser smallint not null';
  
  
  $table{profiles} =
     'userid mediumint not null auto_increment primary key,
      login_name varchar(255) not null,
      password varchar(16),
      cryptpassword varchar(64),
      realname varchar(255),
      groupset bigint not null,
      emailnotification enum("ExcludeSelfChanges", "CConly", "All") not null default "ExcludeSelfChanges",
  
      index(login_name)';
  
  
  $table{versions} =
     'value tinytext,
      program varchar(64)';
  
  
  $table{votes} =
     'who mediumint not null,
      bug_id mediumint not null,
      count smallint not null,
  
      index(who),
      index(bug_id)';
  
  
  
  
  
  ###########################################################################
  # Create tables
  ###########################################################################
  
  # The current DBI::mysql tells me to use this:
  #my @tables = map { $_ =~ s/.*\.//; $_ } $dbh->tables();
  # but that doesn't work on a freshly created database, so I still use
  my @tables = $dbh->func('_ListTables');
  #print 'Tables: ', join " ", @tables, "\n";
  
  # add lines here if you add more --LOCAL-- config vars that end up in the enums:
  
  my $severities = '"' . join('", "', @severities) . '"';
  my $priorities = '"' . join('", "', @priorities) . '"';
  my $opsys      = '"' . join('", "', @opsys)      . '"';
  my $platforms  = '"' . join('", "', @platforms)  . '"';
  
  # go throught our %table hash and create missing tables
  while (my ($tabname, $fielddef) = each %table) {
      next if grep /^$tabname$/, @tables;
      print "Creating table $tabname ...\n";
  
      # add lines here if you add more --LOCAL-- config vars that end up in
      # the enums:
  
      $fielddef =~ s/\$severities/$severities/;
      $fielddef =~ s/\$priorities/$priorities/;
      $fielddef =~ s/\$opsys/$opsys/;
      $fielddef =~ s/\$platforms/$platforms/;
  
      $dbh->do("CREATE TABLE $tabname (\n$fielddef\n)")
          or die "Could not create table '$tabname'. Please check your '$db_base' access.\n";
  }
  
  
  
  
  
  ###########################################################################
  # Populate groups table
  ###########################################################################
  
  #
  # This subroutine checks if a group exist. If not, it will be automatically
  # created with the next available bit set
  #
  
  sub AddGroup ($$)
  {
      my ($name, $desc) = @_;
  
      # does the group exist?
      my $sth = $dbh->prepare("SELECT name FROM groups WHERE name='$name'");
      $sth->execute;
      return if $sth->rows;
      
      # get highest bit number
      $sth = $dbh->prepare("SELECT bit FROM groups ORDER BY bit DESC");
      $sth->execute;
      my @row = $sth->fetchrow_array;
  
      # normalize bits
      my $bit;
      if (defined $row[0]) {
          $bit = $row[0] << 1;
      } else {
          $bit = 1;
      }
  
     
      print "Adding group $name ...\n";
      $sth = $dbh->prepare('INSERT INTO groups
                            (bit, name, description, userregexp)
                            VALUES (?, ?, ?, ?)');
      $sth->execute($bit, $name, $desc, "");
  }
  
  
  #
  # BugZilla uses --GROUPS-- to assign various rights to it's users. 
  #
  
  AddGroup 'tweakparams',      'Can tweak operating parameters';
  AddGroup 'editgroupmembers', 'Can put people in and out of groups that they are members of.';
  AddGroup 'creategroups',     'Can create and destroy groups.';
  AddGroup 'editcomponents',   'Can create, destroy, and edit components.';
  
  
  
  
  
  ###########################################################################
  # Create initial test product if there are no products present.
  ###########################################################################
  
  my $sth = $dbh->prepare("SELECT product FROM products");
  $sth->execute;
  unless ($sth->rows) {
      print "Creating initial dummy product 'TestProduct' ...\n";
      $dbh->do('INSERT INTO products(product, description) VALUES ("TestProduct",
                "This is a test product.  This ought to be blown away and ' .
               'replaced with real stuff in a finished installation of ' .
               'bugzilla.")');
      $dbh->do('INSERT INTO versions (value, program) VALUES ("other", "TestProduct")');
      $dbh->do('INSERT INTO components (value, program, description) VALUES (' .
               '"TestComponent", "TestProduct", ' .
               '"This is a test component in the test product database.  ' .
               'This ought to be blown away and replaced with real stuff in ' .
               'a finished installation of bugzilla.")');
  }
  
  
  
  
  
  ###########################################################################
  # Detect changed local settings
  ###########################################################################
  
  #
  # Check if the enums in the bugs table return the same values that are defined
  # in the various locally changeable variables. If this is true, then alter the
  # table definition.
  #
  
  sub GetFieldDef ($$)
  {
      my ($table, $field) = @_;
      my $sth = $dbh->prepare("SHOW COLUMNS FROM $table");
      $sth->execute;
  
      while (my $ref = $sth->fetchrow_arrayref) {
          next if $$ref[0] ne $field;
          return $ref;
     }
  }
  
  sub CheckEnumField ($$@)
  {
      my ($table, $field, @against) = @_;
  
      my $ref = GetFieldDef($table, $field);
      #print "0: $$ref[0]   1: $$ref[1]   2: $$ref[2]   3: $$ref[3]  4: $$ref[4]\n";
      
      $_ = "enum('" . join("','", @against) . "')";
      if ($$ref[1] ne $_) {
          print "Updating field $field in table $table ...\n";
          $_ .= " NOT NULL" if $$ref[3];
          $dbh->do("ALTER TABLE $table
                    CHANGE $field
                    $field $_");
      }
  }
  
  
  
  #
  # This code changes the enum types of some SQL tables whenever you change
  # some --LOCAL-- variables
  #
  
  CheckEnumField('bugs', 'bug_severity', @severities);
  CheckEnumField('bugs', 'priority',     @priorities);
  CheckEnumField('bugs', 'op_sys',       @opsys);
  CheckEnumField('bugs', 'rep_platform', @platforms);
  
  
  
  
  
  ###########################################################################
  # Promote first user into every group
  ###########################################################################
  
  #
  # Assume you just logged in. Now how can you administrate the system? Just
  # execute checksetup.pl again. If there is only 1 user in bugzilla, then
  # this user is promoted into every group.
  #
  
  $sth = $dbh->prepare("SELECT login_name FROM profiles");
  $sth->execute;
  # when we have exactly one user ...
  if ($sth->rows == 1) {
      my @row = $sth->fetchrow_array;
      print "Putting user $row[0] into every group ...\n";
      # are this enought f's for now?  :-)
      $dbh->do("update profiles set groupset=0xffffffffffff");
  }
  
  
  
  
  
  
  ###########################################################################
  # Update the tables to the current definition
  ###########################################################################
  
  #
  # As time passes, fields in tables get deleted, added, changed and so on.
  # So we need some helper subroutines to make this possible:
  #
  
  sub ChangeFieldType ($$$)
  {
      my ($table, $field, $newtype) = @_;
  
      my $ref = GetFieldDef($table, $field);
      #print "0: $$ref[0]   1: $$ref[1]   2: $$ref[2]   3: $$ref[3]  4: $$ref[4]\n";
  
      if ($$ref[1] ne $newtype) {
          print "Updating field type $field in table $table ...\n";
          $newtype .= " NOT NULL" if $$ref[3];
          $dbh->do("ALTER TABLE $table
                    CHANGE $field
                    $field $newtype");
      }
  }
  
  sub RenameField ($$$)
  {
      my ($table, $field, $newname) = @_;
  
      my $ref = GetFieldDef($table, $field);
      return unless $ref; # already fixed?
      #print "0: $$ref[0]   1: $$ref[1]   2: $$ref[2]   3: $$ref[3]  4: $$ref[4]\n";
  
      if ($$ref[1] ne $newname) {
          print "Updating field $field in table $table ...\n";
          my $type = $$ref[1];
          $type .= " NOT NULL" if $$ref[3];
          $dbh->do("ALTER TABLE $table
                    CHANGE $field
                    $newname $type");
      }
  }
  
  sub AddField ($$$)
  {
      my ($table, $field, $definition) = @_;
  
      my $ref = GetFieldDef($table, $field);
      return if $ref; # already added?
  
      print "Adding new field $field to table $table ...\n";
      $dbh->do("ALTER TABLE $table
                ADD COLUMN $field $definition");
  }
  
  sub DropField ($$)
  {
      my ($table, $field) = @_;
  
      my $ref = GetFieldDef($table, $field);
      return unless $ref; # already dropped?
  
      print "Deleting unused field $field from table $table ...\n";
      $dbh->do("ALTER TABLE $table
                DROP COLUMN $field");
  }
  
  
  
  # 5/12/99 Added a pref to control how much email you get.  This needs a new
  # column in the profiles table, so feed the following to mysql:
  
  AddField('profiles', 'emailnotification', 'enum("ExcludeSelfChanges", "CConly", 
                        "All") not null default "ExcludeSelfChanges"');
  
  
  
  # 6/22/99 Added an entry to the attachments table to record who the
  # submitter was.  Nothing uses this yet, but it still should be recorded.
  
  AddField('attachments', 'submitter_id', 'mediumint not null');
  
  #
  # One could even populate this field automatically, e.g. with
  #
  # unless (GetField('attachments', 'submitter_id') {
  #    AddField ...
  #    populate
  # }
  #
  # For now I was too lazy, so you should read the README :-)
  
  
  
  # 9/15/99 Apparently, newer alphas of MySQL won't allow you to have "when"
  # as a column name.  So, I have had to rename a column in the bugs_activity
  # table.
  
  RenameField ('bugs_activity', 'when', 'bug_when');
  
  
  
  # 10/11/99 Restructured voting database to add a cached value in each bug
  # recording how many total votes that bug has.  While I'm at it, I removed
  # the unused "area" field from the bugs database.  It is distressing to
  # realize that the bugs table has reached the maximum number of indices
  # allowed by MySQL (16), which may make future enhancements awkward.
  
  DropField('bugs', 'area');
  AddField('bugs',     'votes',        'mediumint not null, add index (votes)');
  AddField('products', 'votesperuser', 'mediumint not null');
  
  
  
  # The product name used to be very different in various tables.
  #
  # It was   varchar(16)   in bugs
  #          tinytext      in components
  #          tinytext      in products
  #          tinytext      in versions
  #
  # tinytext is equivalent to varchar(255), which is quite huge, so I change
  # them all to varchar(64).
  
  ChangeFieldType ('bugs',       'product', 'varchar(64)');
  ChangeFieldType ('components', 'program', 'varchar(64)');
  ChangeFieldType ('products',   'product', 'varchar(64)');
  ChangeFieldType ('versions',   'program', 'varchar(64)');
  
  
  
  #
  # If you had to change the --TABLE-- definition in any way, then add your
  # differential change code *** A B O V E *** this comment.
  #
  # That is: if you add a new field, you first search for the first occurence
  # of --TABLE-- and add your field to into the table hash. This new setting
  # would be honored for every new installation. Then add your
  # AddField/DropField/ChangeFieldType/RenameField code above. This would then
  # be honored by everyone who updates his Bugzilla installation.
  #
  
  
  
  1.1                  apache-site/bugs/colchange.cgi
  
  Index: colchange.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  print "Content-type: text/html\n";
  
  # The master list not only says what fields are possible, but what order
  # they get displayed in.
  
  my @masterlist = ("opendate", "changeddate", "severity", "priority",
                    "platform", "owner", "reporter", "status", "resolution",
                    "component", "product", "version", "project", "os", "votes");
  
  if (Param("usetargetmilestone")) {
      push(@masterlist, "target_milestone");
  }
  if (Param("useqacontact")) {
      push(@masterlist, "qa_contact");
  }
  if (Param("usestatuswhiteboard")) {
      push(@masterlist, "status_whiteboard");
  }
  
  
  push(@masterlist, ("summary", "summaryfull"));
  
  
  my @collist;
  if (defined $::FORM{'rememberedquery'}) {
      if (defined $::FORM{'resetit'}) {
          @collist = @::default_column_list;
      } else {
          foreach my $i (@masterlist) {
              if (defined $::FORM{"column_$i"}) {
                  push @collist, $i;
              }
          }
      }
      my $list = join(" ", @collist);
      print "Set-Cookie: COLUMNLIST=$list ; path=/ ; expires=Sun, 30-Jun-2029 00:00:00 GMT\n";
      print "Refresh: 0; URL=buglist.cgi?$::FORM{'rememberedquery'}\n";
      print "\n";
      print "<TITLE>What a hack.</TITLE>\n";
      PutHeader ("Change columns");
      print "Resubmitting your query with new columns...\n";
      exit;
  }
  
  if (defined $::COOKIE{'COLUMNLIST'}) {
      @collist = split(/ /, $::COOKIE{'COLUMNLIST'});
  } else {
      @collist = @::default_column_list;
  }
  
  
  my %desc;
  foreach my $i (@masterlist) {
      $desc{$i} = $i;
  }
  
  $desc{'summary'} = "Summary (first 60 characters)";
  $desc{'summaryfull'} = "Full Summary";
  
  
  print "\n";
  PutHeader ("Change columns");
  print "Check which columns you wish to appear on the list, and then click\n";
  print "on submit.\n";
  print "<p>\n";
  print "<FORM ACTION=colchange.cgi>\n";
  print "<INPUT TYPE=HIDDEN NAME=rememberedquery VALUE=$::buffer>\n";
  
  foreach my $i (@masterlist) {
      my $c;
      if (lsearch(\@collist, $i) >= 0) {
          $c = 'CHECKED';
      } else {
          $c = '';
      }
      print "<INPUT TYPE=checkbox NAME=column_$i $c>$desc{$i}<br>\n";
  }
  print "<P>\n";
  print "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n";
  print "</FORM>\n";
  print "<FORM ACTION=colchange.cgi>\n";
  print "<INPUT TYPE=HIDDEN NAME=rememberedquery VALUE=$::buffer>\n";
  print "<INPUT TYPE=HIDDEN NAME=resetit VALUE=1>\n";
  print "<INPUT TYPE=\"submit\" VALUE=\"Reset to Bugzilla default\">\n";
  print "</FORM>\n";
  
  
  
  1.1                  apache-site/bugs/collectstats.pl
  
  Index: collectstats.pl
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>,
  #                 Harrison Page <ha...@netscape.com>
  
  # Run me out of cron at midnight to collect Bugzilla statistics.
  
  use diagnostics;
  use strict;
  use vars @::legal_product;
  
  require "globals.pl";
  
  ConnectToDatabase();
  GetVersionTable();
  
  my @myproducts;
  push( @myproducts, "-All-", @::legal_product );
  
  foreach (@myproducts) {
      my $dir = "data/mining";
  
      &check_data_dir ($dir);
      &collect_stats ($dir, $_);
  }
  
  sub check_data_dir {
      my $dir = shift;
  
      if (! -d) {
          mkdir $dir, 0777;
          chmod 0777, $dir;
      }
  }
  
  sub collect_stats {
      my $dir = shift;
      my $product = shift;
      my $when = localtime (time);
  
  
      $product =~ s/\//-/gs;
      my $file = join '/', $dir, $product;
      my $exists = -f $file;
  
      if (open DATA, ">>$file") {
          push my @row, &today;
  
          foreach my $status ('NEW', 'ASSIGNED', 'REOPENED') {
  	    if( $product eq "-All-" ) {
                  SendSQL("select count(bug_status) from bugs where bug_status='$status'");
  	    } else {
                  SendSQL("select count(bug_status) from bugs where bug_status='$status' and product='$product'");
  	    }
              push @row, FetchOneColumn();
          }
  
          if (! $exists)
          {
              print DATA <<FIN;
  # Bugzilla daily bug stats
  #
  # do not edit me! this file is generated.
  # 
  # fields: date|new|assigned|reopened
  # product: $product
  # created: $when
  FIN
  	}
  
          print DATA (join '|', @row) . "\n";
          close DATA;
      } else {
          print "$0: $file, $!";
      }
  }
  
  sub today {
      my ($dom, $mon, $year) = (localtime(time))[3, 4, 5];
      return sprintf "%04d%02d%02d", 1900 + $year, ++$mon, $dom;
  }
  
  
  
  
  1.1                  apache-site/bugs/createaccount.cgi
  
  Index: createaccount.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  #                 David Gardiner <da...@unisa.edu.au>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  # Shut up misguided -w warnings about "used only once":
  
  use vars %::FORM;
  
  ConnectToDatabase();
  
  # Clear out the login cookies.  Make people log in again if they create an
  # account; otherwise, they'll probably get confused.
  
  print "Set-Cookie: Bugzilla_login= ; path=/; expires=Sun, 30-Jun-80 00:00:00 GMT
  Set-Cookie: Bugzilla_logincookie= ; path=/; expires=Sun, 30-Jun-80 00:00:00 GMT
  Set-Cookie: Bugzilla_password= ; path=/; expires=Sun, 30-Jun-80 00:00:00 GMT
  Content-type: text/html
  
  ";
  
  
  my $login = $::FORM{'login'};
  my $realname = $::FORM{'realname'};
  if (defined $login) {
      CheckEmailSyntax($login);
      if (DBname_to_id($login) != 0) {
  	PutHeader("Account exists");
  	print "A bugzilla account for the name <tt>$login</tt> already\n";
  	print "exists.  If you have forgotten the password for it, then\n";
  	print "<a href=query.cgi?GoAheadAndLogIn>click here</a> and use\n";
  	print "the <b>E-mail me a password</b> button.\n";
  	exit;
      }
      PutHeader("Account created");
      my $password = InsertNewUser($login, $realname);
      MailPassword($login, $password);
      print "A bugzilla account for <tt>$login</tt> has been created.  The\n";
      print "password has been e-mailed to that address.  When it is\n";
      print "received, you may <a href=query.cgi?GoAheadAndLogIn>click\n";
      print "here</a> and log in.  Or, you can just <a href=\"\">go back to\n";
      print "the top</a>.";
      exit;
  }
  
  PutHeader("Create a new bugzilla account");
  print q{
  To create a bugzilla account, all that you need to do is to enter a
  legitimate e-mail address.  The account will be created, and its
  password will be mailed to you. Optionally you may enter your real name 
  as well.
  
  <FORM method=get>
  <table>
  <tr>
  <td align=right><b>E-mail address:</b></td>
  <td><input size=35 name=login></td>
  </tr>
  <tr>
  <td align=right><b>Real name:</b></td>
  <td><input size=35 name=realname></td>
  </tr>
  </table>
  <input type=submit>
  };
  
  
  
  
  1.1                  apache-site/bugs/createattachment.cgi
  
  Index: createattachment.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  #                 David Gardiner <da...@unisa.edu.au>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  use vars %::COOKIE, %::FILENAME;
  
  sub Punt {
      my ($str) = (@_);
      print "$str<P>Please hit <b>Back</b> and try again.\n";
      exit;
  }
  
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  my $id = $::FORM{'id'};
  
  PutHeader("Create an attachment", "Create attachment", "Bug $id");
  
  
  if (!defined($::FORM{'data'})) {
      print qq{
  <form method=post ENCTYPE="multipart/form-data">
  <input type=hidden name=id value=$id>
  To attach a file to <a href="show_bug.cgi?id=$id">bug $id</a>, place it in a
  file on your local machine, and enter the path to that file here:<br>
  <input type=file name=data size=60>
  <P>
  Please also provide a one-line description of this attachment:<BR>
  <input name=description size=60>
  <BR>
  What kind of file is this?
  <br><input type=radio name=type value=patch>Patch file (text/plain, diffs)
  <br><input type=radio name=type value="text/plain">Plain text (text/plain)
  <br><input type=radio name=type value="text/html">HTML source (text/html)
  <br><input type=radio name=type value="image/gif">GIF Image (image/gif)
  <br><input type=radio name=type value="image/jpeg">JPEG Image (image/jpeg)
  <br><input type=radio name=type value="image/png">PNG Image (image/png)
  <br><input type=radio name=type value="application/octet-stream">Binary file (application/octet-stream)
  <br><input type=radio name=type value="other">Other (enter mime type: <input name=othertype size=30>)
  <P>
  <input type=submit value="Submit">
  </form>
  <P>
  };
  } else {
      if ($::FORM{'data'} eq "" || !defined $::FILENAME{'data'}) {
          Punt("No file was provided, or it was empty.");
      }
      my $desc = trim($::FORM{'description'});
      if ($desc eq "") {
          Punt("You must provide a description of your attachment.");
      }
      my $ispatch = 0;
      my $mimetype = $::FORM{'type'};
      if (!defined $mimetype) {
          Punt("You must select which kind of file you have.");
      }
      $mimetype = trim($mimetype);
      if ($mimetype eq "patch") {
          $mimetype = "text/plain";
          $ispatch = 1;
      }
      if ($mimetype eq "other") {
          $mimetype = $::FORM{'othertype'};
      }
      if ($mimetype !~ m@^(\w|-)+/(\w|-)+$@) {
          Punt("You must select a legal mime type.  '<tt>$mimetype</tt>' simply will not do.");
      }
      SendSQL("insert into attachments (bug_id, filename, description, mimetype, ispatch, submitter_id, thedata) values ($id," .
              SqlQuote($::FILENAME{'data'}) . ", " . SqlQuote($desc) . ", " .
              SqlQuote($mimetype) . ", $ispatch, " .
              DBNameToIdAndCheck($::COOKIE{'Bugzilla_login'}) . ", " .
              SqlQuote($::FORM{'data'}) . ")");
      SendSQL("select LAST_INSERT_ID()");
      my $attachid = FetchOneColumn();
      AppendComment($id, $::COOKIE{"Bugzilla_login"},
                    "Created an attachment (id=$attachid)\n$desc\n");
  
      print "<TABLE BORDER=1><TD><H2>Attachment to bug $id created</H2>\n";
      system("./processmail $id $::COOKIE{'Bugzilla_login'}");
      print "<TD><A HREF=\"show_bug.cgi?id=$id\">Go Back to BUG# $id</A></TABLE>\n";
  }
  
  navigation_header();
  
  
  
  
  1.1                  apache-site/bugs/defparams.pl
  
  Index: defparams.pl
  ===================================================================
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  #                 Dawn Endico <en...@mozilla.org>
  
  
  # This file defines all the parameters that we have a GUI to edit within
  # Bugzilla.
  
  use diagnostics;
  use strict;
  
  # Shut up misguided -w warnings about "used only once".  For some reason,
  # "use vars" chokes on me when I try it here.
  
  sub bug_form_pl_sillyness {
      my $zz;
      $zz = %::param_checker;
      $zz = %::param_desc;
      $zz = %::param_type;
  }
  
  sub WriteParams {
      foreach my $i (@::param_list) {
  	if (!defined $::param{$i}) {
  	    $::param{$i} = $::param_default{$i};
              if (!defined $::param{$i}) {
                  die "No default parameter ever specified for $i";
              }
  	}
      }
      mkdir("data", 0777);
      chmod 0777, "data";
      my $tmpname = "data/params.$$";
      open(FID, ">$tmpname") || die "Can't create $tmpname";
      my $v = $::param{'version'};
      delete $::param{'version'};  # Don't write the version number out to
                                  # the params file.
      print FID GenerateCode('%::param');
      $::param{'version'} = $v;
      print FID "1;\n";
      close FID;
      rename $tmpname, "data/params" || die "Can't rename $tmpname to data/params";
      chmod 0666, "data/params";
  }
      
  
  sub DefParam {
      my ($id, $desc, $type, $default, $checker) = (@_);
      push @::param_list, $id;
      $::param_desc{$id} = $desc;
      $::param_type{$id} = $type;
      $::param_default{$id} = $default;
      if (defined $checker) {
  	$::param_checker{$id} = $checker;
      }
  }
  
  
  sub check_numeric {
      my ($value) = (@_);
      if ($value !~ /^[0-9]+$/) {
  	return "must be a numeric value";
      }
      return "";
  }
      
  
  
  @::param_list = ();
  
  
  
  # OK, here are the definitions themselves.
  #
  # The type of parameters (the third parameter to DefParam) can be one
  # of the following:
  #
  # t -- A short text entry field (suitable for a single line)
  # l -- A long text field (suitable for many lines)
  # b -- A boolean value (either 1 or 0)
  # i -- An integer.
  # defenum -- This param defines an enum that defines a column in one of
  #	     the database tables.  The name of the parameter is of the form
  #	     "tablename.columnname".
  
  DefParam("maintainer",
  	 "The email address of the person who maintains this installation of Bugzilla.",
  	 "t",
           'THE MAINTAINER HAS NOT YET BEEN SET');
  
  DefParam("urlbase",
  	 "The URL that is the common initial leading part of all Bugzilla URLs.",
  	 "t",
  	 "http://cvs-mirror.mozilla.org/webtools/bugzilla/",
  	 \&check_urlbase);
  
  sub check_urlbase {
      my ($url) = (@_);
      if ($url !~ m:^http.*/$:) {
  	return "must be a legal URL, that starts with http and ends with a slash.";
      }
      return "";
  }
  
  DefParam("usequip",
  	"If this is on, Bugzilla displays a silly quip at the beginning of buglists, and lets users add to the list of quips.",
  	"b",
  	1);
  
  DefParam("usedespot",
           "If this is on, then we are using the Despot system to control our database of users.  Bugzilla won't ever write into the user database, it will let the Despot code maintain that.  And Bugzilla will send the user over to Despot URLs if they need to change their password.  Also, in that case, Bugzilla will treat the passwords stored in the database as being crypt'd, not plaintext.",
           "b",
           0);
  
  DefParam("despotbaseurl",
           "The base URL for despot.  Used only if <b>usedespot</b> is turned on, above.",
           "t",
           "http://cvs-mirror.mozilla.org/webtools/despot/despot.cgi",
           \&check_despotbaseurl);
  
  
  sub check_despotbaseurl {
      my ($url) = (@_);
      if ($url !~ /^http.*cgi$/) {
  	return "must be a legal URL, that starts with http and ends with .cgi";
      }
      return "";
  }
  
  
  DefParam("headerhtml",
           "Additional HTML to add to the HEAD area of documents, eg. links to stylesheets.",
           "l",
           '');
  
  
  DefParam("bannerhtml",
           "The html that gets emitted at the head of every Bugzilla page. 
  Anything of the form %<i>word</i>% gets replaced by the defintion of that 
  word (as defined on this page).",
           "l",
           q{<TABLE BGCOLOR="#000000" WIDTH="100%" BORDER=0 CELLPADDING=0 CELLSPACING=0>
  <TR><TD><A HREF="http://www.mozilla.org/"><IMG
   SRC="http://www.mozilla.org/images/mozilla-banner.gif" ALT=""
  BORDER=0 WIDTH=600 HEIGHT=58></A></TD></TR></TABLE>
  <CENTER><FONT SIZE=-1>Bugzilla version %version%
  </FONT></CENTER>});
  
  DefParam("blurbhtml",
           "A blurb that appears as part of the header of every Bugzilla page.  This is a place to put brief warnings, pointers to one or two related pages, etc.",
           "l",
           "This is <B>Bugzilla</B>: the Mozilla bug system.  For more 
  information about what Bugzilla is and what it can do, see 
  <A HREF=\"http://www.mozilla.org/\">mozilla.org</A>'s
  <A HREF=\"http://www.mozilla.org/bugs/\"><B>bug pages</B></A>.");
  
  
  
      
  DefParam("changedmail",
  q{The email that gets sent to people when a bug changes.  Within this
  text, %to% gets replaced by the assigned-to and reported-by people,
  separated by a comma (with duplication removed, if they're the same
  person).  %cc% gets replaced by the list of people on the CC list,
  separated by commas.  %bugid% gets replaced by the bug number.
  %diffs% gets replaced by the diff text from the old version to the new
  version of this bug.  %neworchanged% is either "New" or "Changed",
  depending on whether this mail is reporting a new bug or changes made
  to an existing one.  %summary% gets replaced by the summary of this
  bug.  %<i>anythingelse</i>% gets replaced by the definition of that
  parameter (as defined on this page).},
           "l",
  "From: bugzilla-daemon
  To: %to%
  Cc: %cc%
  Subject: [Bug %bugid%] %neworchanged% - %summary%
  
  %urlbase%show_bug.cgi?id=%bugid%
  
  %diffs%");
  
  
  
  DefParam("whinedays",
           "The number of days that we'll let a bug sit untouched in a NEW state before our cronjob will whine at the owner.",
           "t",
           7,
           \&check_numeric);
  
  
  DefParam("whinemail",
           "The email that gets sent to anyone who has a NEW bug that hasn't been touched for more than <b>whinedays</b>.  Within this text, %email% gets replaced by the offender's email address.  %userid% gets replaced by the offender's bugzilla login (which, in most installations, is the same as the email address.)  %<i>anythingelse</i>% gets replaced by the definition of that parameter (as defined on this page).<p> It is a good idea to make sure this message has a valid From: address, so that if the mail bounces, a real person can know that there are bugs assigned to an invalid address.",
           "l",
           q{From: %maintainer%
  To: %email%
  Subject: Your Bugzilla buglist needs attention.
  
  [This e-mail has been automatically generated.]
  
  You have one or more bugs assigned to you in the Bugzilla 
  bugsystem (%urlbase%) that require
  attention.
  
  All of these bugs are in the NEW state, and have not been touched
  in %whinedays% days or more.  You need to take a look at them, and 
  decide on an initial action.
  
  Generally, this means one of three things:
  
  (1) You decide this bug is really quick to deal with (like, it's INVALID),
      and so you get rid of it immediately.
  (2) You decide the bug doesn't belong to you, and you reassign it to someone
      else.  (Hint: if you don't know who to reassign it to, make sure that
      the Component field seems reasonable, and then use the "Reassign bug to
      owner of selected component" option.)
  (3) You decide the bug belongs to you, but you can't solve it this moment.
      Just use the "Accept bug" command.
  
  To get a list of all NEW bugs, you can use this URL (bookmark it if you like!):
  
      %urlbase%buglist.cgi?bug_status=NEW&assigned_to=%userid%
  
  Or, you can use the general query page, at
  %urlbase%query.cgi.
  
  Appended below are the individual URLs to get to all of your NEW bugs that 
  haven't been touched for a week or more.
  
  You will get this message once a day until you've dealt with these bugs!
  
  });
  
  
  
  DefParam("defaultquery",
   	 "This is the default query that initially comes up when you submit a bug.  It's in URL parameter format, which makes it hard to read.  Sorry!",
  	 "t",
  	 "bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=%22Importance%22");
  
  
  DefParam("letsubmitterchoosepriority",
           "If this is on, then people submitting bugs can choose an initial priority for that bug.  If off, then all bugs initially have the default priority selected above.",
           "b",
           1);
  
  
  sub check_priority {
      my ($value) = (@_);
      GetVersionTable();
      if (lsearch(\@::legal_priority, $value) < 0) {
          return "Must be a legal priority value: one of " .
              join(", ", @::legal_priority);
      }
      return "";
  }
  
  DefParam("defaultpriority",
           "This is the priority that newly entered bugs are set to.",
           "t",
           "P2",
           \&check_priority);
  
  
  DefParam("usetargetmilestone",
  	 "Do you wish to use the Target Milestone field?",
  	 "b",
  	 0);
  
  DefParam("nummilestones",
           "If using Target Milestone, how many milestones do you wish to
            appear?",
           "t",
           10,
           \&check_numeric);
  
  DefParam("curmilestone",
           "If using Target Milestone, Which milestone are we working toward right now?",
           "t",
           1,
           \&check_numeric);
  
  DefParam("useqacontact",
  	 "Do you wish to use the QA Contact field?",
  	 "b",
  	 0);
  
  DefParam("usestatuswhiteboard",
  	 "Do you wish to use the Status Whiteboard field?",
  	 "b",
  	 0);
  
  DefParam("usebrowserinfo",
  	 "Do you want bug reports to be assigned an OS & Platform based on the browser
  	  the user makes the report from?",
  	 "b",
  	 1);
  
  DefParam("usedependencies",
           "Do you wish to use dependencies (allowing you to mark which bugs depend on which other ones)?",
           "b",
           1);
  
  DefParam("webdotbase",
           "This is the URL prefix that is common to all requests for webdot.  The <a href=\"http://www.research.att.com/~north/cgi-bin/webdot.cgi\">webdot package</a> is a very swell thing that generates pictures of graphs.  If you have an installation of bugsplat that hides behind a firewall, then to get graphs to work, you will have to install a copy of webdot behind your firewall, and change this path to match.  Also, webdot has some trouble with software domain names, so you may have to play games and hack the %urlbase% part of this.  If this all seems like too much trouble, you can set this paramater to be the empty string, which will cause the graphing feature to be disabled entirely.",
           "t",
           "http://www.research.att.com/~north/cgi-bin/webdot.cgi/%urlbase%");
  
  DefParam("entryheaderhtml",
           "This is a special header for the bug entry page. The text will be printed after the page header, before the bug entry form. It is meant to be a place to put pointers to intructions on how to enter bugs.",
           "l",
           '');
  
  DefParam("expectbigqueries",
           "If this is on, then we will tell mysql to <tt>set option SQL_BIG_TABLES=1</tt> before doing queries on bugs.  This will be a little slower, but one will not get the error <tt>The table ### is full</tt> for big queries that require a big temporary table.",
           "b",
           0);
  
  DefParam("emailregexp",
           'This defines the regexp to use for legal email addresses.  The default tries to match fully qualified email addresses.  Another popular value to put here is <tt>^[^@, ]$</tt>, which means "local usernames, no @ allowed.',
           "t",
           q:^[^@, ]*@[^@, ]*\\.[^@, ]*$:);
  
  DefParam("emailregexpdesc",
           "This describes in english words what kinds of legal addresses are allowed by the <tt>emailregexp</tt> param.",
           "l",
           "A legal address must contain exactly one '\@', and at least one '.' after the \@, and may not contain any commas or spaces.");
  
  DefParam("emailsuffix",
           "This is a string to append to any email addresses when actually sending mail to that address.  It is useful if you have changed the <tt>emailregexp</tt> param to only allow local usernames, but you want the mail to be delivered to username\@my.local.hostname.",
           "t",
           "");
  
  
  DefParam("voteremovedmail",
  q{This is a mail message to send to anyone who gets a vote removed from a bug for any reason.  %to% gets replaced by a comma-separated list of people who used to be voting for this bug.  %bugid% gets replaced by the bug number.  %reason% gets replaced by a short reason describing why the vote was removed.  %<i>anythingelse</i>% gets replaced by the definition of thatparameter (as defined on this page).},
           "l",
  "From: bugzilla-daemon
  To: %to%
  Subject: [Bug %bugid%] Your vote has been removed from this bug
  
  You used to have a vote on bug %bugid%, but it has been removed.
  
  Reason: %reason%
  
  %urlbase%show_bug.cgi?id=%bugid%
  ");
           
  DefParam("allowbugdeletion",
           q{The pages to edit products and components and versions can delete all associated bugs when you delete a product (or component or version).  Since that is a pretty scary idea, you have to turn on this option before any such deletions will ever happen.},
           "b",
           0);
  
  
  
  1;
  
  
  
  
  1.1                  apache-site/bugs/describecomponents.cgi
  
  Index: describecomponents.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use vars %::FORM;
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  ConnectToDatabase();
  GetVersionTable();
  
  print "Content-type: text/html\n\n";
  
  my $product = $::FORM{'product'};
  if (!defined $product || lsearch(\@::legal_product, $product) < 0) {
  
      PutHeader("Bugzilla component description");
      print "
  <FORM>
  Please specify the product whose components you want described.
  <P>
  Product: <SELECT NAME=product>
  ";
      print make_options(\@::legal_product);
      print "
  </SELECT>
  <P>
  <INPUT TYPE=\"submit\" VALUE=\"Submit\">
  </FORM>
  ";
      exit;
  }
  
  
  PutHeader("Bugzilla component description", "Bugzilla component description",
            $product);
  
  print "
  <TABLE>
  <tr>
  <th align=left>Component</th>
  <th align=left>Default owner</th>
  ";
  
  my $useqacontact = Param("useqacontact");
  
  my $cols = 2;
  if ($useqacontact) {
      print "<th align=left>Default qa contact</th>";
      $cols++;
  }
  
  my $colbut1 = $cols - 1;
  
  print "</tr>";
  
  SendSQL("select value, initialowner, initialqacontact, description from components where program = " . SqlQuote($product) . " order by value");
  
  while (MoreSQLData()) {
      my @row = FetchSQLData();
      my ($component, $initialowner, $initialqacontact, $description) = (@row);
  
      print qq|
  <tr><td colspan=$cols><hr></td></tr>
  <tr><td rowspan=2>$component</td>
  <td><a href="mailto:$initialowner">$initialowner</a></td>
  |;
      if ($useqacontact) {
          print qq|
  <td><a href="mailto:$initialqacontact">$initialqacontact</a></td>
  |;
      }
      print "</tr><tr><td colspan=$colbut1>$description</td></tr>\n";
  }
  
  print "<tr><td colspan=$cols><hr></td></tr></table>\n";
  
  
  
  1.1                  apache-site/bugs/doaddcomponent.cgi
  
  Index: doaddcomponent.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Sam Ziegler <sa...@ziegler.org>
  # Terry Weissman <te...@mozilla.org>
  # Mark Hamby <mh...@logicon.com>
  
  # Code derived from doeditcomponents.cgi
  
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  # foreach my $i (sort(keys %::FORM)) {
  #     print value_quote("$i $::FORM{$i}") . "<BR>\n";
  # }
  
  if (!UserInGroup("editcomponents")) {
      print "<H1>Sorry, you aren't a member of the 'editcomponents' group.</H1>\n";
      print "And so, you aren't allowed to add components.\n";
      exit;
  }
  
  
  PutHeader("Adding new component");
  
  unlink "data/versioncache";
  GetVersionTable();
  
  my $component = trim($::FORM{"component"});
  my $product = trim($::FORM{"product"});
  my $description = trim($::FORM{"description"});
  my $initialowner = trim($::FORM{"initialowner"});
  
  if (!defined $::FORM{"initialqacontact"}) {
      # May not be defined if we're not using this field.
      $::FORM{'initialqacontact'} = "";
  }
  my $initialqacontact = trim($::FORM{"initialqacontact"});
  
  if ($component eq "") {
      print "You must enter a name for the new component.  Please press\n";
      print "<b>Back</b> and try again.\n";
      exit;
  }
  
  # Check to ensure the component doesn't exist already.
  SendSQL("SELECT value FROM components WHERE " .
        "program = " . SqlQuote($product) . " and " .
        "value = " . SqlQuote($component));
  my @row = FetchSQLData();
  if (@row) {
          print "<H1>Component already exists</H1>";
          print "The component '$component' already exists\n";
          print "for product '$product'.<P>\n";
          print "<p><a href=query.cgi>Go back to the query page</a>\n";
          exit;
  }
  
  # Check that the email addresses are legitimate.
  foreach my $addr ($initialowner, $initialqacontact) {
      if ($addr ne "") {
          DBNameToIdAndCheck($addr);
      }
  }
  
  # Add the new component.
  SendSQL("INSERT INTO components ( " .
        "value, program, description, initialowner, initialqacontact" .
        " ) VALUES ( " .
        SqlQuote($component) . "," .
        SqlQuote($product) . "," .
        SqlQuote($description) . "," .
        SqlQuote($initialowner) . "," .
        SqlQuote($initialqacontact) . ")" );
  
  unlink "data/versioncache";
  
  print "OK, done.<p>\n";
  print "<a href=addcomponent.cgi>Edit another new component.</a><p>\n";
  print "<a href=editcomponents.cgi>Edit existing components.</a><p>\n";
  print "<a href=query.cgi>Go back to the query page.</a>\n";
  
  
  
  1.1                  apache-site/bugs/doeditcomponents.cgi
  
  Index: doeditcomponents.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Sam Ziegler <sa...@ziegler.org>
  # Terry Weissman <te...@mozilla.org>
  
  # Code derived from doeditowners.cgi
  
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  
  # Shut up misguided -w warnings about "used only once":
  
  use vars @::legal_product;
  
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  # foreach my $i (sort(keys %::FORM)) {
  #     print value_quote("$i $::FORM{$i}") . "<BR>\n";
  # }
  
  if (!UserInGroup("editcomponents")) {
      print "<H1>Sorry, you aren't a member of the 'editcomponents' group.</H1>\n";
      print "And so, you aren't allowed to edit the owners.\n";
      exit;
  }
  
  
  sub Check {
      my ($code1, $code2) = (@_);
      if ($code1 ne $code2) {
          print "<H1>A race error has occurred.</H1>";
          print "It appears that someone else has been changing the database\n";
          print "while you've been editing it.  I'm afraid you will have to\n";
          print "start all over.  Sorry! <P>\n";
          print "<p><a href=query.cgi>Go back to the query page</a>\n";
          exit;
      }
  }
  
  
  my @cmds;
  
  sub DoOne {
      my ($oldvalue, $field, $where, $checkemail) = (@_);
      if (!defined $::FORM{$field}) {
          print "ERROR -- $field not defined!";
          exit;
      }
      if ($oldvalue ne $::FORM{$field}) {
          my $name = $field;
          $name =~ s/^.*-//;
          my $table = "products";
          if ($field =~ /^P\d+-C\d+-/) {
              $table = "components";
          }
          push @cmds, "update $table set $name=" .
              SqlQuote($::FORM{$field}) . " where $where";
          print "Changed $name for $where <P>";
          if ($checkemail) {
              DBNameToIdAndCheck($::FORM{$field});
          }
      }
  }
              
  
  
  PutHeader("Saving new component info");
  
  unlink "data/versioncache";
  GetVersionTable();
  
  my $prodcode = "P000";
  
  foreach my $product (@::legal_product) {
      SendSQL("select description, milestoneurl, disallownew, votesperuser from products where product='$product'");
      my @row = FetchSQLData();
      if (!@row) {
          next;
      }
      my ($description, $milestoneurl, $disallownew, $votesperuser) = (@row);
      $prodcode++;
      Check($product, $::FORM{"prodcode-$prodcode"});
  
      my $where = "product=" . SqlQuote($product);
      DoOne($description, "$prodcode-description", $where);
      if (Param('usetargetmilestone')) {
          DoOne($milestoneurl, "$prodcode-milestoneurl", $where);
      }
      DoOne($disallownew, "$prodcode-disallownew", $where);
      DoOne($votesperuser, "$prodcode-votesperuser", $where);
  
      SendSQL("select value, initialowner, initialqacontact, description from components where program=" . SqlQuote($product) . " order by value");
      my $c = 0;
      while (my @row = FetchSQLData()) {
          my ($component, $initialowner, $initialqacontact, $description) =
              (@row);
          $c++;
          my $compcode = $prodcode . "-" . "C$c";
  
  	Check($component, $::FORM{"compcode-$compcode"});
  
          my $where = "program=" . SqlQuote($product) . " and value=" .
              SqlQuote($component);
  
          DoOne($initialowner, "$compcode-initialowner", $where, 1);
          if (Param('useqacontact')) {
              DoOne($initialqacontact, "$compcode-initialqacontact", $where,
                    1);
          }
          DoOne($description, "$compcode-description", $where);
      }
  
  }
  
  print "Saving changes.<P>\n";
  
  foreach my $cmd (@cmds) {
      print "$cmd <BR>";
      SendSQL($cmd);
  }
  
  unlink "data/versioncache";
  
  print "OK, done.<p>\n";
  print "<a href=editcomponents.cgi>Edit the components some more.</a><p>\n";
  print "<a href=query.cgi>Go back to the query page.</a>\n";
  
  
  
  1.1                  apache-site/bugs/doeditowners.cgi
  
  Index: doeditowners.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Sam Ziegler <sa...@ziegler.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  if (!UserInGroup("editcomponents")) {
      print "<H1>Sorry, you aren't a member of the 'editcomponents' group.</H1>\n";
      print "And so, you aren't allowed to edit the owners.\n";
      exit;
  }
  
  
  PutHeader("Saving new owners");
  
  SendSQL("select program, value, initialowner from components order by program, value");
  
  my @line;
  
  foreach my $key (keys(%::FORM)) {
      $::FORM{url_decode($key)} = $::FORM{$key};
  }
  
  my @updates;
  my $curIndex = 0;
  
  while (@line = FetchSQLData()) {
      my $curItem = "$line[0]_$line[1]";
      if (exists $::FORM{$curItem}) {
          $::FORM{$curItem} =~ s/\r\n/\n/;
          if ($::FORM{$curItem} ne $line[2]) {
              print "$line[0] : $line[1] is now owned by $::FORM{$curItem}.<BR>\n";
              $updates[$curIndex++] = "update components set initialowner = '$::FORM{$curItem}' where program = '$line[0]' and value = '$line[1]'";
          }
      }
  }
  
  foreach my $update (@updates) {
      SendSQL($update);
  }
  
  print "OK, done.<p>\n";
  print "<a href=editowners.cgi>Edit the owners some more.</a><p>\n";
  print "<a href=query.cgi>Go back to the query page.</a>\n";
  
  
  
  1.1                  apache-site/bugs/doeditparams.cgi
  
  Index: doeditparams.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  require "defparams.pl";
  
  # Shut up misguided -w warnings about "used only once":
  use vars %::param,
      %::param_default,
      @::param_list;
  
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  if (!UserInGroup("tweakparams")) {
      print "<H1>Sorry, you aren't a member of the 'tweakparams' group.</H1>\n";
      print "And so, you aren't allowed to edit the parameters.\n";
      exit;
  }
  
  
  PutHeader("Saving new parameters");
  
  foreach my $i (@::param_list) {
  #    print "Processing $i...<BR>\n";
      if (exists $::FORM{"reset-$i"}) {
          $::FORM{$i} = $::param_default{$i};
      }
      $::FORM{$i} =~ s/\r\n/\n/;     # Get rid of windows-style line endings.
      if ($::FORM{$i} ne Param($i)) {
          if (defined $::param_checker{$i}) {
              my $ref = $::param_checker{$i};
              my $ok = &$ref($::FORM{$i});
              if ($ok ne "") {
                  print "New value for $i is invalid: $ok<p>\n";
                  print "Please hit <b>Back</b> and try again.\n";
                  exit;
              }
          }
          print "Changed $i.<br>\n";
          $::param{$i} = $::FORM{$i}
      }
  }
  
  
  WriteParams();
  
  unlink "data/versioncache";
  
  print "OK, done.<p>\n";
  print "<a href=editparams.cgi>Edit the params some more.</a><p>\n";
  print "<a href=query.cgi>Go back to the query page.</a>\n";
      
  
  
  
  1.1                  apache-site/bugs/doeditvotes.cgi
  
  Index: doeditvotes.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  ConnectToDatabase();
  GetVersionTable();
  
  my $who = DBNameToIdAndCheck($::COOKIE{'Bugzilla_login'});
  
  if ($who ne $::FORM{'who'}) {
      PutHeader("Wrong login.");
      print "The login info got confused.  If you want to adjust the votes\n";
      print "for <tt>$::COOKIE{'Bugzilla_login'}</tt>, then please\n";
      print "<a href=showvotes.cgi?user=$who>click here</a>.<hr>\n";
      navigation_header();
      exit();
  }
  
  my @buglist = grep {/^\d+$/} keys(%::FORM);
  
  if (0 == @buglist) {
      PutHeader("Oops?");
      print "Something got confused.  Please click <b>Back</b> and try again.";
      navigation_header();
      exit();
  }
  
  foreach my $id (@buglist) {
      $::FORM{$id} = trim($::FORM{$id});
      if ($::FORM{$id} !~ /\d+/ || $::FORM{$id} < 0) {
          PutHeader("Numbers only, please");
          print "Only use numeric values for your bug votes.\n";
          print "Please click <b>Back</b> and try again.<hr>\n";
          navigation_header();
          exit();
      }
  }
  
  SendSQL("select bug_id, product from bugs where bug_id = " .
          join(" or bug_id = ", @buglist));
  
  my %prodcount;
  
  while (MoreSQLData()) {
      my ($id, $prod) = (FetchSQLData());
      if (!defined $prodcount{$prod}) {
          $prodcount{$prod} = 0;
      }
      $prodcount{$prod} += $::FORM{$id};
  }
  
  foreach my $prod (keys(%prodcount)) {
      if ($prodcount{$prod} > $::prodmaxvotes{$prod}) {
          PutHeader("Don't overstuff!", "Illegal vote");
          print "You may only use $::prodmaxvotes{$prod} votes for bugs in the\n";
          print "<tt>$prod</tt> product, but you are using $prodcount{$prod}.\n";
          print "Please click <b>Back</b> and try again.<hr>\n";
          navigation_header();
          exit();
      }
  }
  
  my %affected;
  SendSQL("lock tables bugs write, votes write");
  SendSQL("select bug_id from votes where who = $who");
  while (MoreSQLData()) {
      my $id = FetchOneColumn();
      $affected{$id} = 1;
  }
  SendSQL("delete from votes where who = $who");
  foreach my $id (@buglist) {
      if ($::FORM{$id} > 0) {
          SendSQL("insert into votes (who, bug_id, count) values ($who, $id, $::FORM{$id})");
      }
      $affected{$id} = 1;
  }
  foreach my $id (keys %affected) {
      SendSQL("select sum(count) from votes where bug_id = $id");
      my $v = FetchOneColumn();
      $v ||= 0;
      SendSQL("update bugs set votes = $v, delta_ts=delta_ts where bug_id = $id");
  }
  SendSQL("unlock tables");
  
  
  
  PutHeader("Voting tabulated", "Voting tabulated", $::COOKIE{'Bugzilla_login'});
  print "Your votes have been recorded.\n";
  print qq{<p><a href="showvotes.cgi?user=$who">Review your votes</a><hr>\n};
  navigation_header();
  exit();
      
  
  
  
  
  1.1                  apache-site/bugs/editcomponents.cgi
  
  Index: editcomponents.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is mozilla.org code.
  #
  # The Initial Developer of the Original Code is Holger
  # Schurig. Portions created by Holger Schurig are
  # Copyright (C) 1999 Holger Schurig. All
  # Rights Reserved.
  #
  # Contributor(s): Holger Schurig <ho...@nikocity.de>
  #               Terry Weissman <te...@mozilla.org>
  #
  # Direct any questions on this source code to
  #
  # Holger Schurig <ho...@nikocity.de>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  require "globals.pl";
  
  # Shut up misguided -w warnings about "used only once".  For some reason,
  # "use vars" chokes on me when I try it here.
  
  sub sillyness {
      my $zz;
      $zz = $::buffer;
  }
  
  
  my $dobugcounts = (defined $::FORM{'dobugcounts'});
  
  
  
  # TestProduct:    just returns if the specified product does exists
  # CheckProduct:   same check, optionally  emit an error text
  # TestComponent:  just returns if the specified product/component combination exists
  # CheckComponent: same check, optionally emit an error text
  
  sub TestProduct ($)
  {
      my $prod = shift;
  
      # does the product exist?
      SendSQL("SELECT product
               FROM products
               WHERE product=" . SqlQuote($prod));
      return FetchOneColumn();
  }
  
  sub CheckProduct ($)
  {
      my $prod = shift;
  
      # do we have a product?
      unless ($prod) {
          print "Sorry, you haven't specified a product.";
          PutTrailer();
          exit;
      }
  
      unless (TestProduct $prod) {
          print "Sorry, product '$prod' does not exist.";
          PutTrailer();
          exit;
      }
  }
  
  sub TestComponent ($$)
  {
      my ($prod,$comp) = @_;
  
      # does the product exist?
      SendSQL("SELECT program,value
               FROM components
               WHERE program=" . SqlQuote($prod) . " and value=" . SqlQuote($comp));
      return FetchOneColumn();
  }
  
  sub CheckComponent ($$)
  {
      my ($prod,$comp) = @_;
  
      # do we have the component?
      unless ($comp) {
          print "Sorry, you haven't specified a component.";
          PutTrailer();
          exit;
      }
  
      CheckProduct($prod);
  
      unless (TestComponent $prod,$comp) {
          print "Sorry, component '$comp' for product '$prod' does not exist.";
          PutTrailer();
          exit;
      }
  }
  
  
  #
  # Displays the form to edit component parameters
  #
  
  sub EmitFormElements ($$$$$)
  {
      my ($product, $component, $initialowner, $initialqacontact, $description) = @_;
  
      print "  <TH ALIGN=\"right\">Component:</TH>\n";
      print "  <TD><INPUT SIZE=64 MAXLENGTH=255 NAME=\"component\" VALUE=\"" .
          value_quote($component) . "\">\n";
      print "      <INPUT TYPE=HIDDEN NAME=\"product\" VALUE=\"" .
          value_quote($product) . "\"></TD>\n";
  
      print "</TR><TR>\n";
      print "  <TH ALIGN=\"right\">Description:</TH>\n";
      print "  <TD><TEXTAREA ROWS=4 COLS=64 WRAP=VIRTUAL NAME=\"description\">" .
          value_quote($description) . "</TEXTAREA></TD>\n";
  
      print "</TR><TR>\n";
      print "  <TH ALIGN=\"right\">Initial owner:</TH>\n";
      print "  <TD><INPUT TYPE=TEXT SIZE=64 MAXLENGTH=255 NAME=\"initialowner\" VALUE=\"" .
          value_quote($initialowner) . "\"></TD>\n";
  
      if (Param('useqacontact')) {
          print "</TR><TR>\n";
          print "  <TH ALIGN=\"right\">Initial QA contact:</TH>\n";
          print "  <TD><INPUT TYPE=TEXT SIZE=64 MAXLENGTH=255 NAME=\"initialqacontact\" VALUE=\"" .
              value_quote($initialqacontact) . "\"></TD>\n";
      }
  }
  
  
  #
  # Displays a text like "a.", "a or b.", "a, b or c.", "a, b, c or d."
  #
  
  sub PutTrailer (@)
  {
      my (@links) = ("Back to the <A HREF=\"query.cgi\">query page</A>", @_);
  
      my $count = $#links;
      my $num = 0;
      print "<P>\n";
      if (!$dobugcounts) {
          print qq{<a href="editcomponents.cgi?dobugcounts=1&$::buffer">};
          print qq{Redisplay table with bug counts (slower)</a><p>\n};
      }
      foreach (@links) {
          print $_;
          if ($num == $count) {
              print ".\n";
          }
          elsif ($num == $count-1) {
              print " or ";
          }
          else {
              print ", ";
          }
          $num++;
      }
      print "</BODY>\n</HTML>\n";
  }
  
  
  
  
  
  
  
  #
  # Preliminary checks:
  #
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  unless (UserInGroup("editcomponents")) {
      PutHeader("Not allowed");
      print "Sorry, you aren't a member of the 'editcomponents' group.\n";
      print "And so, you aren't allowed to add, modify or delete components.\n";
      PutTrailer();
      exit;
  }
  
  
  #
  # often used variables
  #
  my $product   = trim($::FORM{product}   || '');
  my $component = trim($::FORM{component} || '');
  my $action    = trim($::FORM{action}    || '');
  my $localtrailer;
  if ($product) {
      $localtrailer = "<A HREF=\"editcomponents.cgi?product=" . url_quote($product) . "\">edit</A> more components";
  } else {
      $localtrailer = "<A HREF=\"editcomponents.cgi\">edit</A> more components";
  }
  
  
  
  #
  # product = '' -> Show nice list of products
  #
  
  unless ($product) {
      PutHeader("Select product");
  
      if ($dobugcounts){
          SendSQL("SELECT products.product,products.description,COUNT(bug_id)
               FROM products LEFT JOIN bugs
                 ON products.product=bugs.product
               GROUP BY products.product
               ORDER BY products.product");
      } else {
          SendSQL("SELECT products.product,products.description
               FROM products 
               ORDER BY products.product");
      }
      print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0><TR BGCOLOR=\"#6666FF\">\n";
      print "  <TH ALIGN=\"left\">Edit components of ...</TH>\n";
      print "  <TH ALIGN=\"left\">Description</TH>\n";
      if ($dobugcounts) {
          print "  <TH ALIGN=\"left\">Bugs</TH>\n";
      }
      #print "  <TH ALIGN=\"left\">Edit</TH>\n";
      print "</TR>";
      while ( MoreSQLData() ) {
          my ($product, $description, $bugs) = FetchSQLData();
          $description ||= "<FONT COLOR=\"red\">missing</FONT>";
          print "<TR>\n";
          print "  <TD VALIGN=\"top\"><A HREF=\"editcomponents.cgi?product=", url_quote($product), "\"><B>$product</B></A></TD>\n";
          print "  <TD VALIGN=\"top\">$description</TD>\n";
          if ($dobugcounts) {
              $bugs ||= "none";
              print "  <TD VALIGN=\"top\">$bugs</TD>\n";
          }
          #print "  <TD VALIGN=\"top\"><A HREF=\"editproducts.cgi?action=edit&product=", url_quote($product), "\">Edit</A></TD>\n";
      }
      print "</TR></TABLE>\n";
  
      PutTrailer();
      exit;
  }
  
  
  
  #
  # action='' -> Show nice list of components
  #
  
  unless ($action) {
      PutHeader("Select component");
      CheckProduct($product);
  
      if ($dobugcounts) {
          SendSQL("SELECT value,description,initialowner,initialqacontact,COUNT(bug_id)
               FROM components LEFT JOIN bugs
                 ON components.program=bugs.product AND components.value=bugs.component
               WHERE program=" . SqlQuote($product) . "
               GROUP BY value");
      } else {
          SendSQL("SELECT value,description,initialowner,initialqacontact
               FROM components 
               WHERE program=" . SqlQuote($product) . "
               GROUP BY value");
      }        
      print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0><TR BGCOLOR=\"#6666FF\">\n";
      print "  <TH ALIGN=\"left\">Edit component ...</TH>\n";
      print "  <TH ALIGN=\"left\">Description</TH>\n";
      print "  <TH ALIGN=\"left\">Initial owner</TH>\n";
      print "  <TH ALIGN=\"left\">Initial QA contact</TH>\n"
          if Param('useqacontact');
      print "  <TH ALIGN=\"left\">Bugs</TH>\n"
          if $dobugcounts;
      print "  <TH ALIGN=\"left\">Delete</TH>\n";
      print "</TR>";
      while ( MoreSQLData() ) {
          my ($component,$desc,$initialowner,$initialqacontact, $bugs) = FetchSQLData();
          $desc             ||= "<FONT COLOR=\"red\">missing</FONT>";
          $initialowner     ||= "<FONT COLOR=\"red\">missing</FONT>";
          $initialqacontact ||= "<FONT COLOR=\"red\">none</FONT>";
          print "<TR>\n";
          print "  <TD VALIGN=\"top\"><A HREF=\"editcomponents.cgi?product=", url_quote($product), "&component=", url_quote($component), "&action=edit\"><B>$component</B></A></TD>\n";
          print "  <TD VALIGN=\"top\">$desc</TD>\n";
          print "  <TD VALIGN=\"top\">$initialowner</TD>\n";
          print "  <TD VALIGN=\"top\">$initialqacontact</TD>\n"
                  if Param('useqacontact');
          if ($dobugcounts) {
              $bugs ||= 'none';
              print "  <TD VALIGN=\"top\">$bugs</TD>\n";
          }
          print "  <TD VALIGN=\"top\"><A HREF=\"editcomponents.cgi?product=", url_quote($product), "&component=", url_quote($component), "&action=del\"><B>Delete</B></A></TD>\n";
          print "</TR>";
      }
      print "<TR>\n";
      my $span = 3;
      $span++ if Param('useqacontact');
      $span++ if $dobugcounts;
      print "  <TD VALIGN=\"top\" COLSPAN=$span>Add a new component</TD>\n";
      print "  <TD VALIGN=\"top\" ALIGN=\"middle\"><A HREF=\"editcomponents.cgi?product=", url_quote($product) . "&action=add\">Add</A></TD>\n";
      print "</TR></TABLE>\n";
  
      PutTrailer();
      exit;
  }
  
  
  $dobugcounts = 1;               # Stupid hack to force further PutTrailer()
                                  # calls to not offer a "bug count" option.
  
  
  #
  # action='add' -> present form for parameters for new component
  #
  # (next action will be 'new')
  #
  
  if ($action eq 'add') {
      PutHeader("Add component");
      CheckProduct($product);
  
      #print "This page lets you add a new product to bugzilla.\n";
  
      print "<FORM METHOD=POST ACTION=editcomponents.cgi>\n";
      print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
  
      EmitFormElements($product, '', '', '', '');
  
      print "</TR></TABLE>\n<HR>\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Add\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"new\">\n";
      print "</FORM>";
  
      my $other = $localtrailer;
      $other =~ s/more/other/;
      PutTrailer($other);
      exit;
  }
  
  
  
  #
  # action='new' -> add component entered in the 'action=add' screen
  #
  
  if ($action eq 'new') {
      PutHeader("Adding new product");
      CheckProduct($product);
  
      # Cleanups and valididy checks
  
      unless ($component) {
          print "You must enter a name for the new component. Please press\n";
          print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
      if (TestComponent($product,$component)) {
          print "The component '$component' already exists. Please press\n";
          print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
  
      my $description = trim($::FORM{description} || '');
  
      if ($description eq '') {
          print "You must enter a description for the component '$component'. Please press\n";
          print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
  
      my $initialowner = trim($::FORM{initialowner} || '');
  
      if ($initialowner eq '') {
          print "You must enter an initial owner for the component '$component'. Please press\n";
          print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
      #+++
      #DBNameToIdAndCheck($initialowner, 0);
  
      my $initialqacontact = trim($::FORM{initialqacontact} || '');
  
      if (Param('useqacontact')) {
          if ($initialqacontact eq '') {
              print "You must enter an initial QA contact for the component '$component'. Please press\n";
              print "<b>Back</b> and try again.\n";
              PutTrailer($localtrailer);
              exit;
          }
          #+++
          #DBNameToIdAndCheck($initialqacontact, 0);
      }
  
      # Add the new component
      SendSQL("INSERT INTO components ( " .
            "program, value, description, initialowner, initialqacontact " .
            " ) VALUES ( " .
            SqlQuote($product) . "," .
            SqlQuote($component) . "," .
            SqlQuote($description) . "," .
            SqlQuote($initialowner) . "," .
            SqlQuote($initialqacontact) . ")");
  
      # Make versioncache flush
      unlink "data/versioncache";
  
      print "OK, done.<p>\n";
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # action='del' -> ask if user really wants to delete
  #
  # (next action would be 'delete')
  #
  
  if ($action eq 'del') {
      PutHeader("Delete component");
      CheckComponent($product, $component);
  
      # display some data about the component
      SendSQL("SELECT products.product,products.description,
                  products.milestoneurl,products.disallownew,
                  components.program,components.value,components.initialowner,
                  components.initialqacontact,components.description
               FROM products
               LEFT JOIN components on product=program
               WHERE product=" . SqlQuote($product) . "
                 AND   value=" . SqlQuote($component) );
  
  
      my ($product,$pdesc,$milestoneurl,$disallownew,
          $dummy,$component,$initialowner,$initialqacontact,$cdesc) = FetchSQLData();
  
      $pdesc            ||= "<FONT COLOR=\"red\">missing</FONT>";
      $milestoneurl     ||= "<FONT COLOR=\"red\">missing</FONT>";
      $disallownew        = $disallownew ? 'closed' : 'open';
      $initialowner     ||= "<FONT COLOR=\"red\">missing</FONT>";
      $initialqacontact ||= "<FONT COLOR=\"red\">missing</FONT>";
      $cdesc            ||= "<FONT COLOR=\"red\">missing</FONT>";
      
      print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0><TR BGCOLOR=\"#6666FF\">\n";
      print "  <TH VALIGN=\"top\" ALIGN=\"left\">Part</TH>\n";
      print "  <TH VALIGN=\"top\" ALIGN=\"left\">Value</TH>\n";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Component:</TD>\n";
      print "  <TD VALIGN=\"top\">$component</TD>";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Component description:</TD>\n";
      print "  <TD VALIGN=\"top\">$cdesc</TD>";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Initial owner:</TD>\n";
      print "  <TD VALIGN=\"top\">$initialowner</TD>";
  
      if (Param('useqacontact')) {
          print "</TR><TR>\n";
          print "  <TD VALIGN=\"top\">Initial QA contact:</TD>\n";
          print "  <TD VALIGN=\"top\">$initialqacontact</TD>";
      }
      SendSQL("SELECT count(bug_id),product,component
               FROM bugs
               GROUP BY product
               HAVING product=" . SqlQuote($product) . "
                  AND component=" . SqlQuote($component));
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Component of product:</TD>\n";
      print "  <TD VALIGN=\"top\">$product</TD>\n";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Description:</TD>\n";
      print "  <TD VALIGN=\"top\">$pdesc</TD>\n";
  
      if (Param('usetargetmilestone')) {
           print "</TR><TR>\n";
           print "  <TD VALIGN=\"top\">Milestone URL:</TD>\n";
           print "  <TD VALIGN=\"top\"><A HREF=\"$milestoneurl\">$milestoneurl</A></TD>\n";
      }
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Closed for bugs:</TD>\n";
      print "  <TD VALIGN=\"top\">$disallownew</TD>\n";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Bugs</TD>\n";
      print "  <TD VALIGN=\"top\">";
      my $bugs = FetchOneColumn();
      print $bugs || 'none';
  
  
      print "</TD>\n</TR></TABLE>";
  
      print "<H2>Confirmation</H2>\n";
  
      if ($bugs) {
          if (!Param("allowbugdeletion")) {
              print "Sorry, there are $bugs bugs outstanding for this component. 
  You must reassign those bugs to another component before you can delete this
  one.";
              PutTrailer($localtrailer);
              exit;
          }
          print "<TABLE BORDER=0 CELLPADDING=20 WIDTH=\"70%\" BGCOLOR=\"red\"><TR><TD>\n",
                "There are bugs entered for this component!  When you delete this ",
                "component, <B><BLINK>all</BLINK></B> stored bugs will be deleted, too. ",
                "You could not even see the bug history for this component anymore!\n",
                "</TD></TR></TABLE>\n";
      }
  
      print "<P>Do you really want to delete this component?<P>\n";
  
      print "<FORM METHOD=POST ACTION=editcomponents.cgi>\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Yes, delete\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"delete\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"product\" VALUE=\"" .
          value_quote($product) . "\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"component\" VALUE=\"" .
          value_quote($component) . "\">\n";
      print "</FORM>";
  
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # action='delete' -> really delete the component
  #
  
  if ($action eq 'delete') {
      PutHeader("Deleting component");
      CheckComponent($product,$component);
  
      # lock the tables before we start to change everything:
  
      SendSQL("LOCK TABLES attachments WRITE,
                           bugs WRITE,
                           bugs_activity WRITE,
                           components WRITE,
                           dependencies WRITE");
  
      # According to MySQL doc I cannot do a DELETE x.* FROM x JOIN Y,
      # so I have to iterate over bugs and delete all the indivial entries
      # in bugs_activies and attachments.
  
      SendSQL("SELECT bug_id
               FROM bugs
               WHERE product=" . SqlQuote($product) . "
                 AND component=" . SqlQuote($component));
      while (MoreSQLData()) {
          my $bugid = FetchOneColumn();
  
          my $query = $::db->query("DELETE FROM attachments WHERE bug_id=$bugid")
                  or die "$::db_errstr";
          $query = $::db->query("DELETE FROM bugs_activity WHERE bug_id=$bugid")
                  or die "$::db_errstr";
          $query = $::db->query("DELETE FROM dependencies WHERE blocked=$bugid")
                  or die "$::db_errstr";
      }
      print "Attachments, bug activity and dependencies deleted.<BR>\n";
  
  
      # Deleting the rest is easier:
  
      SendSQL("DELETE FROM bugs
               WHERE product=" . SqlQuote($product) . "
                 AND component=" . SqlQuote($component));
      print "Bugs deleted.<BR>\n";
  
      SendSQL("DELETE FROM components
               WHERE program=" . SqlQuote($product) . "
                 AND value=" . SqlQuote($component));
      print "Components deleted.<P>\n";
      SendSQL("UNLOCK TABLES");
  
      unlink "data/versioncache";
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # action='edit' -> present the edit component form
  #
  # (next action would be 'update')
  #
  
  if ($action eq 'edit') {
      PutHeader("Edit component");
      CheckComponent($product,$component);
  
      # get data of component
      SendSQL("SELECT products.product,products.description,
                  products.milestoneurl,products.disallownew,
                  components.program,components.value,components.initialowner,
                  components.initialqacontact,components.description
               FROM products
               LEFT JOIN components on product=program
               WHERE product=" . SqlQuote($product) . "
                 AND   value=" . SqlQuote($component) );
  
      my ($product,$pdesc,$milestoneurl,$disallownew,
          $dummy,$component,$initialowner,$initialqacontact,$cdesc) = FetchSQLData();
  
      print "<FORM METHOD=POST ACTION=editcomponents.cgi>\n";
      print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
  
      #+++ display product/product description
  
      EmitFormElements($product, $component, $initialowner, $initialqacontact, $cdesc);
  
      print "</TR><TR>\n";
      print "  <TH ALIGN=\"right\">Bugs:</TH>\n";
      print "  <TD>";
      SendSQL("SELECT count(*)
               FROM bugs
               WHERE product=" . SqlQuote($product) .
              " and component=" . SqlQuote($component));
      my $bugs = '';
      $bugs = FetchOneColumn() if MoreSQLData();
      print $bugs || 'none';
  
      print "</TD>\n</TR></TABLE>\n";
  
      print "<INPUT TYPE=HIDDEN NAME=\"componentold\" VALUE=\"" .
          value_quote($component) . "\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"descriptionold\" VALUE=\"" .
          value_quote($cdesc) . "\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"initialownerold\" VALUE=\"" .
          value_quote($initialowner) . "\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"initialqacontactold\" VALUE=\"" .
          value_quote($initialqacontact) . "\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"update\">\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Update\">\n";
  
      print "</FORM>";
  
      my $other = $localtrailer;
      $other =~ s/more/other/;
      PutTrailer($other);
      exit;
  }
  
  
  
  #
  # action='update' -> update the component
  #
  
  if ($action eq 'update') {
      PutHeader("Update component");
  
      my $componentold        = trim($::FORM{componentold}        || '');
      my $description         = trim($::FORM{description}         || '');
      my $descriptionold      = trim($::FORM{descriptionold}      || '');
      my $initialowner        = trim($::FORM{initialowner}        || '');
      my $initialownerold     = trim($::FORM{initialownerold}     || '');
      my $initialqacontact    = trim($::FORM{initialqacontact}    || '');
      my $initialqacontactold = trim($::FORM{initialqacontactold} || '');
  
      CheckComponent($product,$componentold);
  
      # Note that the order of this tests is important. If you change
      # them, be sure to test for WHERE='$component' or WHERE='$componentold'
  
      SendSQL("LOCK TABLES bugs WRITE,
                           components WRITE");
  
      if ($description ne $descriptionold) {
          unless ($description) {
              print "Sorry, I can't delete the description.";
              PutTrailer($localtrailer);
  	    SendSQL("UNLOCK TABLES");
              exit;
          }
          SendSQL("UPDATE components
                   SET description=" . SqlQuote($description) . "
                   WHERE program=" . SqlQuote($product) . "
                     AND value=" . SqlQuote($componentold));
          print "Updated description.<BR>\n";
      }
  
  
      if ($initialowner ne $initialownerold) {
          unless ($initialowner) {
              print "Sorry, I can't delete the initial owner.";
              PutTrailer($localtrailer);
  	    SendSQL("UNLOCK TABLES");
              exit;
          }
          #+++
          #DBNameToIdAndCheck($initialowner, 0);
          SendSQL("UPDATE components
                   SET initialowner=" . SqlQuote($initialowner) . "
                   WHERE program=" . SqlQuote($product) . "
                     AND value=" . SqlQuote($componentold));
          print "Updated initial owner.<BR>\n";
      }
  
      if (Param('useqacontact') && $initialqacontact ne $initialqacontactold) {
          unless ($initialqacontact) {
              print "Sorry, I can't delete the initial QA contact.";
              PutTrailer($localtrailer);
  	    SendSQL("UNLOCK TABLES");
              exit;
          }
          #+++
          #DBNameToIdAndCheck($initialqacontact, 0);
          SendSQL("UPDATE components
                   SET initialqacontact=" . SqlQuote($initialqacontact) . "
                   WHERE program=" . SqlQuote($product) . "
                     AND value=" . SqlQuote($componentold));
          print "Updated initial QA contact.<BR>\n";
      }
  
  
      if ($component ne $componentold) {
          unless ($component) {
              print "Sorry, I can't delete the product name.";
              PutTrailer($localtrailer);
  	    SendSQL("UNLOCK TABLES");
              exit;
          }
          if (TestComponent($product,$component)) {
              print "Sorry, component name '$component' is already in use.";
              PutTrailer($localtrailer);
  	    SendSQL("UNLOCK TABLES");
              exit;
          }
  
          SendSQL("UPDATE bugs
                   SET component=" . SqlQuote($component) . "
                   WHERE component=" . SqlQuote($componentold) . "
                     AND product=" . SqlQuote($product));
          SendSQL("UPDATE components
                   SET value=" . SqlQuote($component) . "
                   WHERE value=" . SqlQuote($componentold) . "
                     AND program=" . SqlQuote($product));
  
          unlink "data/versioncache";
          print "Updated product name.<BR>\n";
      }
      SendSQL("UNLOCK TABLES");
  
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # No valid action found
  #
  
  PutHeader("Error");
  print "I don't have a clue what you want.<BR>\n";
  
  foreach ( sort keys %::FORM) {
      print "$_: $::FORM{$_}<BR>\n";
  }
  
  
  
  1.1                  apache-site/bugs/editowners.cgi
  
  Index: editowners.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Sam Ziegler <sa...@ziegler.org>
  
  # Code derived from editparams.cgi
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  if (!UserInGroup("editcomponents")) {
      print "<H1>Sorry, you aren't a member of the 'editcomponents' group.</H1>\n";
      print "And so, you aren't allowed to edit the owners.\n";
      exit;
  }
  
  
  PutHeader("Edit Component Owners");
  
  print "This lets you edit the owners of the program components of bugzilla.\n";
  
  print "<form method=post action=doeditowners.cgi><table>\n";
  
  my $rowbreak = "<tr><td colspan=2><hr></td></tr>";
  
  SendSQL("select program, value, initialowner from components order by program, value");
  
  my @line;
  my $curProgram = "";
  
  while (@line = FetchSQLData()) {
      if ($line[0] ne $curProgram) {
          print $rowbreak;
          print "<tr><th align=right valign=top>$line[0]:</th><td></td></tr>\n";
          $curProgram = $line[0];
      }
      print "<tr><td valign = top>$line[1]</td><td><input size=80 ";
      print "name=\"$line[0]_$line[1]\" value=\"$line[2]\"></td></tr>\n";
  }
  
  print "</table>\n";
  
  print "<input type=submit value=\"Submit changes\">\n";
  
  print "</form>\n";
  
  print "<p><a href=query.cgi>Skip all this, and go back to the query page</a>\n";
  
  
  
  1.1                  apache-site/bugs/editparams.cgi
  
  Index: editparams.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  require "defparams.pl";
  
  # Shut up misguided -w warnings about "used only once":
  use vars @::param_desc,
      @::param_list;
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  if (!UserInGroup("tweakparams")) {
      print "<H1>Sorry, you aren't a member of the 'tweakparams' group.</H1>\n";
      print "And so, you aren't allowed to edit the parameters.\n";
      exit;
  }
  
  
  PutHeader("Edit parameters");
  
  print "This lets you edit the basic operating parameters of bugzilla.\n";
  print "Be careful!\n";
  print "<p>\n";
  print "Any item you check Reset on will get reset to its default value.\n";
  
  print "<form method=post action=doeditparams.cgi><table>\n";
  
  my $rowbreak = "<tr><td colspan=2><hr></td></tr>";
  print $rowbreak;
  
  foreach my $i (@::param_list) {
      print "<tr><th align=right valign=top>$i:</th><td>$::param_desc{$i}</td></tr>\n";
      print "<tr><td valign=top><input type=checkbox name=reset-$i>Reset</td><td>\n";
      my $value = Param($i);
      SWITCH: for ($::param_type{$i}) {
  	/^t$/ && do {
              print "<input size=80 name=$i value=\"" .
                  value_quote($value) . "\">\n";
              last SWITCH;
  	};
  	/^l$/ && do {
              print "<textarea wrap=hard name=$i rows=10 cols=80>" .
                  value_quote($value) . "</textarea>\n";
              last SWITCH;
  	};
          /^b$/ && do {
              my $on;
              my $off;
              if ($value) {
                  $on = "checked";
                  $off = "";
              } else {
                  $on = "";
                  $off = "checked";
              }
              print "<input type=radio name=$i value=1 $on>On\n";
              print "<input type=radio name=$i value=0 $off>Off\n";
              last SWITCH;
          };
          # DEFAULT
          print "<font color=red><blink>Unknown param type $::param_type{$i}!!!</blink></font>\n";
      }
      print "</td></tr>\n";
      print $rowbreak;
  }
  
  print "<tr><th align=right valign=top>version:</th><td>
  What version of Bugzilla this is.  This can't be modified here, but
  <tt>%version%</tt> can be used as a parameter in places that understand
  such parameters</td></tr>
  <tr><td></td><td>" . Param('version') . "</td></tr>";
  
  print "</table>\n";
  
  print "<input type=reset value=\"Reset form\"><br>\n";
  print "<input type=submit value=\"Submit changes\">\n";
  
  print "</form>\n";
  
  print "<p><a href=query.cgi>Skip all this, and go back to the query page</a>\n";
  
  
  
  1.1                  apache-site/bugs/editproducts.cgi
  
  Index: editproducts.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is mozilla.org code.
  #
  # The Initial Developer of the Original Code is Holger
  # Schurig. Portions created by Holger Schurig are
  # Copyright (C) 1999 Holger Schurig. All
  # Rights Reserved.
  #
  # Contributor(s): Holger Schurig <ho...@nikocity.de>
  #               Terry Weissman <te...@mozilla.org>
  #               Dawn Endico <en...@mozilla.org>
  #
  # Direct any questions on this source code to
  #
  # Holger Schurig <ho...@nikocity.de>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  require "globals.pl";
  
  
  
  
  # TestProduct:  just returns if the specified product does exists
  # CheckProduct: same check, optionally  emit an error text
  
  sub TestProduct ($)
  {
      my $prod = shift;
  
      # does the product exist?
      SendSQL("SELECT product
               FROM products
               WHERE product=" . SqlQuote($prod));
      return FetchOneColumn();
  }
  
  sub CheckProduct ($)
  {
      my $prod = shift;
  
      # do we have a product?
      unless ($prod) {
          print "Sorry, you haven't specified a product.";
          PutTrailer();
          exit;
      }
  
      unless (TestProduct $prod) {
          print "Sorry, product '$prod' does not exist.";
          PutTrailer();
          exit;
      }
  }
  
  
  #
  # Displays the form to edit a products parameters
  #
  
  sub EmitFormElements ($$$$)
  {
      my ($product, $description, $milestoneurl, $disallownew) = @_;
  
      $product = value_quote($product);
      $description = value_quote($description);
  
      print "  <TH ALIGN=\"right\">Product:</TH>\n";
      print "  <TD><INPUT SIZE=64 MAXLENGTH=64 NAME=\"product\" VALUE=\"$product\"></TD>\n";
      print "</TR><TR>\n";
  
      print "  <TH ALIGN=\"right\">Description:</TH>\n";
      print "  <TD><TEXTAREA ROWS=4 COLS=64 WRAP=VIRTUAL NAME=\"description\">$description</TEXTAREA></TD>\n";
  
      if (Param('usetargetmilestone')) {
          $milestoneurl = value_quote($milestoneurl);
          print "</TR><TR>\n";
          print "  <TH ALIGN=\"right\">Milestone URL:</TH>\n";
          print "  <TD><INPUT TYPE=TEXT SIZE=64 MAXLENGTH=255 NAME=\"milestoneurl\" VALUE=\"$milestoneurl\"></TD>\n";
      }
  
      print "</TR><TR>\n";
      print "  <TH ALIGN=\"right\">Closed for bug entry:</TH>\n";
      my $closed = $disallownew ? "CHECKED" : "";
      print "  <TD><INPUT TYPE=CHECKBOX NAME=\"disallownew\" $closed VALUE=\"1\"></TD>\n";
  }
  
  
  #
  # Displays a text like "a.", "a or b.", "a, b or c.", "a, b, c or d."
  #
  
  sub PutTrailer (@)
  {
      my (@links) = ("Back to the <A HREF=\"query.cgi\">query page</A>", @_);
  
      my $count = $#links;
      my $num = 0;
      print "<P>\n";
      foreach (@links) {
          print $_;
          if ($num == $count) {
              print ".\n";
          }
          elsif ($num == $count-1) {
              print " or ";
          }
          else {
              print ", ";
          }
          $num++;
      }
      print "</BODY>\n</HTML>\n";
  }
  
  
  
  
  
  
  
  #
  # Preliminary checks:
  #
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  unless (UserInGroup("editcomponents")) {
      PutHeader("Not allowed");
      print "Sorry, you aren't a member of the 'editcomponents' group.\n";
      print "And so, you aren't allowed to add, modify or delete products.\n";
      PutTrailer();
      exit;
  }
  
  
  
  #
  # often used variables
  #
  my $product = trim($::FORM{product} || '');
  my $action  = trim($::FORM{action}  || '');
  my $localtrailer = "<A HREF=\"editproducts.cgi\">edit</A> more products";
  
  
  
  #
  # action='' -> Show nice list of products
  #
  
  unless ($action) {
      PutHeader("Select product");
  
      SendSQL("SELECT products.product,description,disallownew,COUNT(bug_id)
               FROM products LEFT JOIN bugs
                 ON products.product=bugs.product
               GROUP BY products.product
               ORDER BY products.product");
      print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0><TR BGCOLOR=\"#6666FF\">\n";
      print "  <TH ALIGN=\"left\">Edit product ...</TH>\n";
      print "  <TH ALIGN=\"left\">Description</TH>\n";
      print "  <TH ALIGN=\"left\">Status</TH>\n";
      print "  <TH ALIGN=\"left\">Bugs</TH>\n";
      print "  <TH ALIGN=\"left\">Action</TH>\n";
      print "</TR>";
      while ( MoreSQLData() ) {
          my ($product, $description, $disallownew, $bugs) = FetchSQLData();
          $description ||= "<FONT COLOR=\"red\">missing</FONT>";
          $disallownew = $disallownew ? 'closed' : 'open';
          $bugs        ||= 'none';
          print "<TR>\n";
          print "  <TD VALIGN=\"top\"><A HREF=\"editproducts.cgi?action=edit&product=", url_quote($product), "\"><B>$product</B></A></TD>\n";
          print "  <TD VALIGN=\"top\">$description</TD>\n";
          print "  <TD VALIGN=\"top\">$disallownew</TD>\n";
          print "  <TD VALIGN=\"top\">$bugs</TD>\n";
          print "  <TD VALIGN=\"top\"><A HREF=\"editproducts.cgi?action=del&product=", url_quote($product), "\">Delete</A></TD>\n";
          print "</TR>";
      }
      print "<TR>\n";
      print "  <TD VALIGN=\"top\" COLSPAN=4>Add a new product</TD>\n";
      print "  <TD VALIGN=\"top\" ALIGN=\"middle\"><FONT SIZE =-1><A HREF=\"editproducts.cgi?action=add\">Add</A></FONT></TD>\n";
      print "</TR></TABLE>\n";
  
      PutTrailer();
      exit;
  }
  
  
  
  
  #
  # action='add' -> present form for parameters for new product
  #
  # (next action will be 'new')
  #
  
  if ($action eq 'add') {
      PutHeader("Add product");
  
      #print "This page lets you add a new product to bugzilla.\n";
  
      print "<FORM METHOD=POST ACTION=editproducts.cgi>\n";
      print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
  
      EmitFormElements('', '', '', 0);
  
      print "</TR><TR>\n";
      print "  <TH ALIGN=\"right\">Version:</TH>\n";
      print "  <TD><INPUT SIZE=64 MAXLENGTH=255 NAME=\"version\" VALUE=\"unspecified\"></TD>\n";
  
      print "</TABLE>\n<HR>\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Add\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"new\">\n";
      print "</FORM>";
  
      my $other = $localtrailer;
      $other =~ s/more/other/;
      PutTrailer($other);
      exit;
  }
  
  
  
  #
  # action='new' -> add product entered in the 'action=add' screen
  #
  
  if ($action eq 'new') {
      PutHeader("Adding new product");
  
      # Cleanups and valididy checks
  
      unless ($product) {
          print "You must enter a name for the new product. Please press\n";
          print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
      if (TestProduct($product)) {
          print "The product '$product' already exists. Please press\n";
          print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
  
      my $version = trim($::FORM{version} || '');
  
      if ($version eq '') {
          print "You must enter a version for product '$product'. Please press\n";
          print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
  
      my $description  = trim($::FORM{description}  || '');
      my $milestoneurl = trim($::FORM{milestoneurl} || '');
      my $disallownew = 0;
      $disallownew = 1 if $::FORM{disallownew};
  
      # Add the new product.
      SendSQL("INSERT INTO products ( " .
            "product, description, milestoneurl, disallownew" .
            " ) VALUES ( " .
            SqlQuote($product) . "," .
            SqlQuote($description) . "," .
            SqlQuote($milestoneurl) . "," .
            $disallownew . ")" );
      SendSQL("INSERT INTO versions ( " .
            "value, program" .
            " ) VALUES ( " .
            SqlQuote($version) . "," .
            SqlQuote($product) . ")" );
  
      # Make versioncache flush
      unlink "data/versioncache";
  
      print "OK, done.<p>\n";
      PutTrailer($localtrailer, "<a href=\"editcomponents.cgi?action=add&product=" . url_quote($product) . "\">add</a> components to this new product.");
      exit;
  }
  
  
  
  #
  # action='del' -> ask if user really wants to delete
  #
  # (next action would be 'delete')
  #
  
  if ($action eq 'del') {
      PutHeader("Delete product");
      CheckProduct($product);
  
      # display some data about the product
      SendSQL("SELECT description, milestoneurl, disallownew
               FROM products
               WHERE product=" . SqlQuote($product));
      my ($description, $milestoneurl, $disallownew) = FetchSQLData();
      $description ||= "<FONT COLOR=\"red\">description missing</FONT>";
      $disallownew = $disallownew ? 'closed' : 'open';
      
      print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0>\n";
      print "<TR BGCOLOR=\"#6666FF\">\n";
      print "  <TH VALIGN=\"top\" ALIGN=\"left\">Part</TH>\n";
      print "  <TH VALIGN=\"top\" ALIGN=\"left\">Value</TH>\n";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Product:</TD>\n";
      print "  <TD VALIGN=\"top\">$product</TD>\n";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Description:</TD>\n";
      print "  <TD VALIGN=\"top\">$description</TD>\n";
  
      if (Param('usetargetmilestone')) {
          print "</TR><TR>\n";
          print "  <TD VALIGN=\"top\">Milestone URL:</TD>\n";
          print "  <TD VALIGN=\"top\"><A HREF=\"$milestoneurl\">$milestoneurl</A></TD>\n";
      }
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Closed for bugs:</TD>\n";
      print "  <TD VALIGN=\"top\">$disallownew</TD>\n";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Components:</TD>\n";
      print "  <TD VALIGN=\"top\">";
      SendSQL("SELECT value,description
               FROM components
               WHERE program=" . SqlQuote($product));
      if (MoreSQLData()) {
          print "<table>";
          while ( MoreSQLData() ) {
              my ($component, $description) = FetchSQLData();
              $description ||= "<FONT COLOR=\"red\">description missing</FONT>";
              print "<tr><th align=right valign=top>$component:</th>";
              print "<td valign=top>$description</td></tr>\n";
          }
          print "</table>\n";
      } else {
          print "<FONT COLOR=\"red\">missing</FONT>";
      }
  
      print "</TD>\n</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Versions:</TD>\n";
      print "  <TD VALIGN=\"top\">";
      SendSQL("SELECT value
               FROM versions
               WHERE program=" . SqlQuote($product) . "
               ORDER BY value");
      if (MoreSQLData()) {
          my $br = 0;
          while ( MoreSQLData() ) {
              my ($version) = FetchSQLData();
              print "<BR>" if $br;
              print $version;
              $br = 1;
          }
      } else {
          print "<FONT COLOR=\"red\">missing</FONT>";
      }
  
  
      print "</TD>\n</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Bugs:</TD>\n";
      print "  <TD VALIGN=\"top\">";
      SendSQL("SELECT count(bug_id),product
               FROM bugs
               GROUP BY product
               HAVING product=" . SqlQuote($product));
      my $bugs = FetchOneColumn();
      print $bugs || 'none';
  
  
      print "</TD>\n</TR></TABLE>";
  
      print "<H2>Confirmation</H2>\n";
  
      if ($bugs) {
          if (!Param("allowbugdeletion")) {
              print "Sorry, there are $bugs bugs outstanding for this product.
  You must reassign those bugs to another product before you can delete this
  one.";
              PutTrailer($localtrailer);
              exit;
          }
          print "<TABLE BORDER=0 CELLPADDING=20 WIDTH=\"70%\" BGCOLOR=\"red\"><TR><TD>\n",
                "There are bugs entered for this product!  When you delete this ",
                "product, <B><BLINK>all</BLINK><B> stored bugs will be deleted, too. ",
                "You could not even see a bug history anymore!\n",
                "</TD></TR></TABLE>\n";
      }
  
      print "<P>Do you really want to delete this product?<P>\n";
      print "<FORM METHOD=POST ACTION=editproducts.cgi>\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Yes, delete\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"delete\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"product\" VALUE=\"" .
          value_quote($product) . "\">\n";
      print "</FORM>";
  
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # action='delete' -> really delete the product
  #
  
  if ($action eq 'delete') {
      PutHeader("Deleting product");
      CheckProduct($product);
  
      # lock the tables before we start to change everything:
  
      SendSQL("LOCK TABLES attachments WRITE,
                           bugs WRITE,
                           bugs_activity WRITE,
                           components WRITE,
                           dependencies WRITE,
                           versions WRITE,
                           products WRITE");
  
      # According to MySQL doc I cannot do a DELETE x.* FROM x JOIN Y,
      # so I have to iterate over bugs and delete all the indivial entries
      # in bugs_activies and attachments.
  
      SendSQL("SELECT bug_id
               FROM bugs
               WHERE product=" . SqlQuote($product));
      while (MoreSQLData()) {
          my $bugid = FetchOneColumn();
  
          my $query = $::db->query("DELETE FROM attachments WHERE bug_id=$bugid")
                  or die "$::db_errstr";
          $query = $::db->query("DELETE FROM bugs_activity WHERE bug_id=$bugid")
                  or die "$::db_errstr";
          $query = $::db->query("DELETE FROM dependencies WHERE blocked=$bugid")
                  or die "$::db_errstr";
      }
      print "Attachments, bug activity and dependencies deleted.<BR>\n";
  
  
      # Deleting the rest is easier:
  
      SendSQL("DELETE FROM bugs
               WHERE product=" . SqlQuote($product));
      print "Bugs deleted.<BR>\n";
  
      SendSQL("DELETE FROM components
               WHERE program=" . SqlQuote($product));
      print "Components deleted.<BR>\n";
  
      SendSQL("DELETE FROM versions
               WHERE program=" . SqlQuote($product));
      print "Versions deleted.<P>\n";
  
      SendSQL("DELETE FROM products
               WHERE product=" . SqlQuote($product));
      print "Product '$product' deleted.<BR>\n";
      SendSQL("UNLOCK TABLES");
  
      unlink "data/versioncache";
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # action='edit' -> present the edit products from
  #
  # (next action would be 'update')
  #
  
  if ($action eq 'edit') {
      PutHeader("Edit product");
      CheckProduct($product);
  
      # get data of product
      SendSQL("SELECT description,milestoneurl,disallownew
               FROM products
               WHERE product=" . SqlQuote($product));
      my ($description, $milestoneurl, $disallownew) = FetchSQLData();
  
      print "<FORM METHOD=POST ACTION=editproducts.cgi>\n";
      print "<TABLE  BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
  
      EmitFormElements($product, $description, $milestoneurl, $disallownew);
      
      print "</TR><TR VALIGN=top>\n";
      print "  <TH ALIGN=\"right\"><A HREF=\"editcomponents.cgi?product=", url_quote($product), "\">Edit components:</A></TH>\n";
      print "  <TD>";
      SendSQL("SELECT value,description
               FROM components
               WHERE program=" . SqlQuote($product));
      if (MoreSQLData()) {
          print "<table>";
          while ( MoreSQLData() ) {
              my ($component, $description) = FetchSQLData();
              $description ||= "<FONT COLOR=\"red\">description missing</FONT>";
              print "<tr><th align=right valign=top>$component:</th>";
              print "<td valign=top>$description</td></tr>\n";
          }
          print "</table>\n";
      } else {
          print "<FONT COLOR=\"red\">missing</FONT>";
      }
  
  
      print "</TD>\n</TR><TR>\n";
      print "  <TH ALIGN=\"right\" VALIGN=\"top\"><A HREF=\"editversions.cgi?product=", url_quote($product), "\">Edit versions:</A></TH>\n";
      print "  <TD>";
      SendSQL("SELECT value
               FROM versions
               WHERE program=" . SqlQuote($product) . "
               ORDER BY value");
      if (MoreSQLData()) {
          my $br = 0;
          while ( MoreSQLData() ) {
              my ($version) = FetchSQLData();
              print "<BR>" if $br;
              print $version;
              $br = 1;
          }
      } else {
          print "<FONT COLOR=\"red\">missing</FONT>";
      }
  
  
      print "</TD>\n</TR><TR>\n";
      print "  <TH ALIGN=\"right\">Bugs:</TH>\n";
      print "  <TD>";
      SendSQL("SELECT count(bug_id),product
               FROM bugs
               GROUP BY product
               HAVING product=" . SqlQuote($product));
      my $bugs = '';
      $bugs = FetchOneColumn() if MoreSQLData();
      print $bugs || 'none';
  
      print "</TD>\n</TR></TABLE>\n";
  
      print "<INPUT TYPE=HIDDEN NAME=\"productold\" VALUE=\"" .
          value_quote($product) . "\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"descriptionold\" VALUE=\"" .
          value_quote($description) . "\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"milestoneurlold\" VALUE=\"" .
          value_quote($milestoneurl) . "\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"disallownewold\" VALUE=\"$disallownew\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"update\">\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Update\">\n";
  
      print "</FORM>";
  
      my $x = $localtrailer;
      $x =~ s/more/other/;
      PutTrailer($x);
      exit;
  }
  
  
  
  #
  # action='update' -> update the product
  #
  
  if ($action eq 'update') {
      PutHeader("Update product");
  
      my $productold      = trim($::FORM{productold}      || '');
      my $description     = trim($::FORM{description}     || '');
      my $descriptionold  = trim($::FORM{descriptionold}  || '');
      my $disallownew     = trim($::FORM{disallownew}     || '');
      my $disallownewold  = trim($::FORM{disallownewold}  || '');
      my $milestoneurl    = trim($::FORM{milestoneurl}    || '');
      my $milestoneurlold = trim($::FORM{milestoneurlold} || '');
  
      CheckProduct($productold);
  
      # Note that the order of this tests is important. If you change
      # them, be sure to test for WHERE='$product' or WHERE='$productold'
  
      SendSQL("LOCK TABLES bugs WRITE,
                           components WRITE,
                           products WRITE,
                           versions WRITE");
  
      if ($disallownew != $disallownewold) {
          $disallownew ||= 0;
          SendSQL("UPDATE products
                   SET disallownew=$disallownew
                   WHERE product=" . SqlQuote($productold));
          print "Updated bug submit status.<BR>\n";
      }
  
      if ($description ne $descriptionold) {
          unless ($description) {
              print "Sorry, I can't delete the description.";
              PutTrailer($localtrailer);
              SendSQL("UNLOCK TABLES");
              exit;
          }
          SendSQL("UPDATE products
                   SET description=" . SqlQuote($description) . "
                   WHERE product=" . SqlQuote($productold));
          print "Updated description.<BR>\n";
      }
  
      if (Param('usetargetmilestone') && $milestoneurl ne $milestoneurlold) {
          SendSQL("UPDATE products
                   SET milestoneurl=" . SqlQuote($milestoneurl) . "
                   WHERE product=" . SqlQuote($productold));
          print "Updated mile stone URL.<BR>\n";
      }
  
  
      if ($product ne $productold) {
          unless ($product) {
              print "Sorry, I can't delete the product name.";
              PutTrailer($localtrailer);
              SendSQL("UNLOCK TABLES");
              exit;
          }
          if (TestProduct($product)) {
              print "Sorry, product name '$product' is already in use.";
              PutTrailer($localtrailer);
              SendSQL("UNLOCK TABLES");
              exit;
          }
  
          SendSQL("UPDATE bugs
                   SET product=" . SqlQuote($product) . "
                   WHERE product=" . SqlQuote($productold));
          SendSQL("UPDATE components
                   SET program=" . SqlQuote($product) . "
                   WHERE program=" . SqlQuote($productold));
          SendSQL("UPDATE products
                   SET product=" . SqlQuote($product) . "
                   WHERE product=" . SqlQuote($productold));
          SendSQL("UPDATE versions
                   SET program='$product'
                   WHERE program=" . SqlQuote($productold));
  
          unlink "data/versioncache";
          print "Updated product name.<BR>\n";
      }
      SendSQL("UNLOCK TABLES");
  
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # No valid action found
  #
  
  PutHeader("Error");
  print "I don't have a clue what you want.<BR>\n";
  
  foreach ( sort keys %::FORM) {
      print "$_: $::FORM{$_}<BR>\n";
  }
  
  
  
  1.1                  apache-site/bugs/editusers.cgi
  
  Index: editusers.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is mozilla.org code.
  #
  # The Initial Developer of the Original Code is Holger
  # Schurig. Portions created by Holger Schurig are
  # Copyright (C) 1999 Holger Schurig. All
  # Rights Reserved.
  #
  # Contributor(s): Holger Schurig <ho...@nikocity.de>
  #
  #
  # Direct any questions on this source code to
  #
  # Holger Schurig <ho...@nikocity.de>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  require "globals.pl";
  
  
  
  
  
  # TestUser:  just returns if the specified user does exists
  # CheckUser: same check, optionally  emit an error text
  
  sub TestUser ($)
  {
      my $user = shift;
  
      # does the product exist?
      SendSQL("SELECT login_name
  	     FROM profiles
  	     WHERE login_name=" . SqlQuote($user));
      return FetchOneColumn();
  }
  
  sub CheckUser ($)
  {
      my $user = shift;
  
      # do we have a product?
      unless ($user) {
  	print "Sorry, you haven't specified a user.";
          PutTrailer();
  	exit;
      }
  
      unless (TestUser $user) {
  	print "Sorry, user '$user' does not exist.";
          PutTrailer();
  	exit;
      }
  }
  
  
  
  #
  # Displays the form to edit a user parameters
  #
  
  sub EmitFormElements ($$$$)
  {
      my ($user, $password, $realname, $groupset) = @_;
  
      print "  <TH ALIGN=\"right\">Login name:</TH>\n";
      print "  <TD><INPUT SIZE=64 MAXLENGTH=255 NAME=\"user\" VALUE=\"$user\"></TD>\n";
  
      print "</TR><TR>\n";
      print "  <TH ALIGN=\"right\">Real name:</TH>\n";
      print "  <TD><INPUT SIZE=64 MAXLENGTH=255 NAME=\"realname\" VALUE=\"$realname\"></TD>\n";
  
      print "</TR><TR>\n";
      print "  <TH ALIGN=\"right\">Password:</TH>\n";
      print "  <TD><INPUT SIZE=16 MAXLENGTH=16 NAME=\"password\" VALUE=\"$password\"></TD>\n";
  
  
      SendSQL("SELECT bit,name,description
  	     FROM groups
  	     ORDER BY name");
      while (MoreSQLData()) {
  	my($bit,$name,$description) = FetchSQLData();
  	print "</TR><TR>\n";
          $bit = $bit+0; # this strange construct coverts a string to a number
  	print "  <TH ALIGN=\"right\">", ucfirst($name), ":</TH>\n";
  	my $checked = ($groupset & $bit) ? "CHECKED" : "";
  	print "  <TD><INPUT TYPE=CHECKBOX NAME=\"bit_$name\" $checked VALUE=\"$bit\"> $description</TD>\n";
      }
  
  }
  
  
  
  #
  # Displays a text like "a.", "a or b.", "a, b or c.", "a, b, c or d."
  #
  
  sub PutTrailer (@)
  {
      my (@links) = ("Back to the <A HREF=\"index.html\">index</A>", @_);
  
      my $count = $#links;
      my $num = 0;
      print "<P>\n";
      foreach (@links) {
  	print $_;
  	if ($num == $count) {
  	    print ".\n";
  	}
  	elsif ($num == $count-1) {
  	    print " or ";
  	}
  	else {
  	    print ", ";
  	}
  	$num++;
      }
      print "</BODY></HTML>\n";
  }
  
  
  
  #
  # Preliminary checks:
  #
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  unless (UserInGroup("tweakparams")) {
      PutHeader("Not allowed");
      print "Sorry, you aren't a member of the 'tweakparams' group.\n";
      print "And so, you aren't allowed to add, modify or delete users.\n";
      PutTrailer();
      exit;
  }
  
  
  
  #
  # often used variables
  #
  my $user    = trim($::FORM{user}   || '');
  my $action  = trim($::FORM{action} || '');
  my $localtrailer = "<A HREF=\"editusers.cgi\">edit</A> more users";
  
  
  
  #
  # action='' -> Show nice list of users
  #
  
  unless ($action) {
      PutHeader("Select user");
  
      SendSQL("SELECT login_name,realname
  	     FROM profiles
  	     ORDER BY login_name");
      my $count = 0;
      my $header = "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0><TR BGCOLOR=\"#6666FF\">
  <TH ALIGN=\"left\">Edit user ...</TH>
  <TH ALIGN=\"left\">Real name</TH>
  <TH ALIGN=\"left\">Action</TH>\n
  </TR>";
      print $header;
      while ( MoreSQLData() ) {
          $count++;
          if ($count % 100 == 0) {
              print "</table>$header";
          }
  	my ($user, $realname) = FetchSQLData();
  	$realname ||= "<FONT COLOR=\"red\">missing</FONT>";
  	print "<TR>\n";
  	print "  <TD VALIGN=\"top\"><A HREF=\"editusers.cgi?action=edit&user=", url_quote($user), "\"><B>$user</B></A></TD>\n";
  	print "  <TD VALIGN=\"top\">$realname</TD>\n";
  	print "  <TD VALIGN=\"top\"><A HREF=\"editusers.cgi?action=del&user=", url_quote($user), "\">Delete</A></TD>\n";
  	print "</TR>";
      }
      print "<TR>\n";
      print "  <TD VALIGN=\"top\" COLSPAN=2>Add a new user</TD>\n";
      print "  <TD VALIGN=\"top\" ALIGN=\"middle\"><FONT SIZE =-1><A HREF=\"editusers.cgi?action=add\">Add</A></FONT></TD>\n";
      print "</TR></TABLE>\n";
  
      PutTrailer();
      exit;
  }
  
  
  
  
  #
  # action='add' -> present form for parameters for new user
  #
  # (next action will be 'new')
  #
  
  if ($action eq 'add') {
      PutHeader("Add user");
  
      #print "This page lets you add a new product to bugzilla.\n";
  
      print "<FORM METHOD=POST ACTION=editusers.cgi>\n";
      print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
  
      EmitFormElements('', '', '', 0);
  
      print "</TR></TABLE>\n<HR>\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Add\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"new\">\n";
      print "</FORM>";
  
      my $other = $localtrailer;
      $other =~ s/more/other/;
      PutTrailer($other);
      exit;
  }
  
  
  
  #
  # action='new' -> add user entered in the 'action=add' screen
  #
  
  if ($action eq 'new') {
      PutHeader("Adding new user");
  
      # Cleanups and valididy checks
      my $realname = trim($::FORM{realname} || '');
      my $password = trim($::FORM{password} || '');
  
      unless ($user) {
          print "You must enter a name for the new user. Please press\n";
          print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
      unless ($user =~ /^[^\@]+\@[^\@]+$/) {
          print "The user name entered must be a valid e-mail address. Please press\n";
          print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
      if (TestUser($user)) {
  	print "The user '$user' does already exist. Please press\n";
  	print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
  	exit;
      }
      if ($password !~ /^[a-zA-Z0-9-_]*$/ || length($password) < 3 || length($password) > 16) {
          print "The new user must have a password. The password must be between ",
  	      "3 and 16 characters long and must contain only numbers, letters, ",
  	      "hyphens and underlines. Press <b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
  
      my $bits = 0;
      foreach (keys %::FORM) {
  	next unless /^bit_/;
  	#print "$_=$::FORM{$_}<br>\n";
  	$bits |= $::FORM{$_};
      }
      
  
      sub x {
  	my $sc="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./";
  	return substr($sc, int (rand () * 100000) % (length ($sc) + 1), 1);
      }
  
      my $salt = x() . x();
      my $cryptpassword = crypt($password, $salt);
  
      # Add the new user
      SendSQL("INSERT INTO profiles ( " .
            "login_name, password, cryptpassword, realname, groupset" .
            " ) VALUES ( " .
            SqlQuote($user) . "," .
            SqlQuote($password) . "," .
            SqlQuote($cryptpassword) . "," .
            SqlQuote($realname) . "," .
            $bits . ")" );
  
      #+++ send e-mail away
  
      print "OK, done.<p>\n";
      PutTrailer($localtrailer,
  	"<a href=\"editusers.cgi?action=add\">add</a> another user.");
      exit;
  
  }
  
  
  
  #
  # action='del' -> ask if user really wants to delete
  #
  # (next action would be 'delete')
  #
  
  if ($action eq 'del') {
      PutHeader("Delete user");
      CheckUser($user);
  
      # display some data about the product
      SendSQL("SELECT realname, groupset, emailnotification, login_name
  	     FROM profiles
  	     WHERE login_name=" . SqlQuote($user));
      my ($realname, $groupset, $emailnotification) = FetchSQLData();
      $realname ||= "<FONT COLOR=\"red\">missing</FONT>";
      
      print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0>\n";
      print "<TR BGCOLOR=\"#6666FF\">\n";
      print "  <TH VALIGN=\"top\" ALIGN=\"left\">Part</TH>\n";
      print "  <TH VALIGN=\"top\" ALIGN=\"left\">Value</TH>\n";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Login name:</TD>\n";
      print "  <TD VALIGN=\"top\">$user</TD>\n";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Real name:</TD>\n";
      print "  <TD VALIGN=\"top\">$realname</TD>\n";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">E-Mail notification:</TD>\n";
      print "  <TD VALIGN=\"top\">$emailnotification</TD>\n";
  
      print "</TR><TR>\n";
      print "  <TD VALIGN=\"top\">Group set:</TD>\n";
      print "  <TD VALIGN=\"top\">";
      SendSQL("SELECT bit, name
  	     FROM groups
  	     ORDER BY name");
      my $found = 0;
      while ( MoreSQLData() ) {
  	my ($bit,$name) = FetchSQLData();
  	if ($bit & $groupset) {
  	    print "<br>\n" if $found;
  	    print ucfirst $name;
  	    $found = 1;
  	}
      }
      print "none" unless $found;
      print "</TD>\n</TR>";
  
  
      # Check if the user is an initialowner
      my $nodelete = '';
  
      SendSQL("SELECT program, value
  	     FROM components
  	     WHERE initialowner=" . SqlQuote($user));
      $found = 0;
      while (MoreSQLData()) {
  	if ($found) {
  	    print "<BR>\n";
  	} else {
  	    print "<TR>\n";
  	    print "  <TD VALIGN=\"top\">Initial owner:</TD>\n";
  	    print "  <TD VALIGN=\"top\">";
  	}
  	my ($product, $component) = FetchSQLData();
  	print "<a href=\"editcomponents.cgi?product=", url_quote($product),
  		"&component=", url_quote($component),
  		"&action=edit\">$product: $component</a>";
  	$found    = 1;
  	$nodelete = 'initial bug owner';
      }
      print "</TD>\n</TR>" if $found;
  
  
      # Check if the user is an initialqacontact
  
      SendSQL("SELECT program, value
  	     FROM components
  	     WHERE initialqacontact=" . SqlQuote($user));
      $found = 0;
      while (MoreSQLData()) {
  	if ($found) {
  	    print "<BR>\n";
  	} else {
  	    print "<TR>\n";
  	    print "  <TD VALIGN=\"top\">Initial QA contact:</TD>\n";
  	    print "  <TD VALIGN=\"top\">";
  	}
  	my ($product, $component) = FetchSQLData();
  	print "<a href=\"editcomponents.cgi?product=", url_quote($product),
  		"&component=", url_quote($component),
  		"&action=edit\">$product: $component</a>";
  	$found    = 1;
  	$nodelete = 'initial QA contact';
      }
      print "</TD>\n</TR>" if $found;
  
      print "</TABLE>\n";
  
  
      if ($nodelete) {
  	print "<P>You can't delete this user because '$user' is an $nodelete ",
  	      "for at least one product.";
  	PutTrailer($localtrailer);
  	exit;
      }
  
  
      print "<H2>Confirmation</H2>\n";
      print "<P>Do you really want to delete this user?<P>\n";
  
      print "<FORM METHOD=POST ACTION=editusers.cgi>\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Yes, delete\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"delete\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"user\" VALUE=\"$user\">\n";
      print "</FORM>";
  
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # action='delete' -> really delete the user
  #
  
  if ($action eq 'delete') {
      PutHeader("Deleting user");
      CheckUser($user);
  
      SendSQL("SELECT userid
  	     FROM profiles
  	     WHERE login_name=" . SqlQuote($user));
      my $userid = FetchOneColumn();
  
      SendSQL("DELETE FROM profiles
  	     WHERE login_name=" . SqlQuote($user));
      SendSQL("DELETE FROM logincookies
  	     WHERE userid=" . $userid);
      print "User deleted.<BR>\n";
  
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # action='edit' -> present the user edit from
  #
  # (next action would be 'update')
  #
  
  if ($action eq 'edit') {
      PutHeader("Edit user");
      CheckUser($user);
  
      # get data of user
      SendSQL("SELECT password, realname, groupset, emailnotification
  	     FROM profiles
  	     WHERE login_name=" . SqlQuote($user));
      my ($password, $realname, $groupset, $emailnotification) = FetchSQLData();
  
      print "<FORM METHOD=POST ACTION=editusers.cgi>\n";
      print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
  
      EmitFormElements($user, $password, $realname, $groupset);
      
      print "</TR></TABLE>\n";
  
      print "<INPUT TYPE=HIDDEN NAME=\"userold\" VALUE=\"$user\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"passwordold\" VALUE=\"$password\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"realnameold\" VALUE=\"$realname\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"groupsetold\" VALUE=\"$groupset\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"emailnotificationold\" VALUE=\"$emailnotification\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"update\">\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Update\">\n";
  
      print "</FORM>";
  
      my $x = $localtrailer;
      $x =~ s/more/other/;
      PutTrailer($x);
      exit;
  }
  
  #
  # action='update' -> update the user
  #
  
  if ($action eq 'update') {
      PutHeader("Update User");
  
      my $userold               = trim($::FORM{userold}              || '');
      my $realname              = trim($::FORM{realname}             || '');
      my $realnameold           = trim($::FORM{realnameold}          || '');
      my $password              = trim($::FORM{password}             || '');
      my $passwordold           = trim($::FORM{passwordold}          || '');
      my $emailnotification     = trim($::FORM{emailnotification}    || '');
      my $emailnotificationold  = trim($::FORM{emailnotificationold} || '');
      my $groupsetold           = trim($::FORM{groupsetold}          || '');
  
      my $groupset = 0;
      foreach (keys %::FORM) {
  	next unless /^bit_/;
  	#print "$_=$::FORM{$_}<br>\n";
  	$groupset |= $::FORM{$_};
      }
  
      CheckUser($userold);
  
      # Note that the order of this tests is important. If you change
      # them, be sure to test for WHERE='$product' or WHERE='$productold'
  
      if ($groupset != $groupsetold) {
          SendSQL("UPDATE profiles
  		 SET groupset=" . $groupset . "
  		 WHERE login_name=" . SqlQuote($userold));
  	print "Updated permissions.\n";
      }
  
  =for me
  
      if ($emailnotification ne $emailnotificationold) {
          SendSQL("UPDATE profiles
  		 SET emailnotification=" . $emailnotification . "
  		 WHERE login_name=" . SqlQuote($userold));
  	print "Updated email notification.<BR>\n";
      }
  
  =cut
  
      if ($password ne $passwordold) {
          SendSQL("UPDATE profiles
  		 SET password=" . SqlQuote($password) . "
  		 WHERE login_name=" . SqlQuote($userold));
  	print "Updated password.<BR>\n";
      }
      if ($realname ne $realnameold) {
          SendSQL("UPDATE profiles
  		 SET realname=" . SqlQuote($realname) . "
  		 WHERE login_name=" . SqlQuote($userold));
  	print "Updated real name.<BR>\n";
      }
      if ($user ne $userold) {
  	unless ($user) {
  	    print "Sorry, I can't delete the user's name.";
              PutTrailer($localtrailer);
  	    exit;
          }
  	if (TestUser($user)) {
  	    print "Sorry, user name '$user' is already in use.";
              PutTrailer($localtrailer);
  	    exit;
          }
  
          SendSQL("UPDATE profiles
  		 SET login_name=" . SqlQuote($user) . "
  		 WHERE login_name=" . SqlQuote($userold));
  
  	print "Updated user's name.<BR>\n";
      }
  
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # No valid action found
  #
  
  PutHeader("Error");
  print "I don't have a clue what you want.<BR>\n";
  
  foreach ( sort keys %::FORM) {
      print "$_: $::FORM{$_}<BR>\n";
  }
  
  
  
  1.1                  apache-site/bugs/editversions.cgi
  
  Index: editversions.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is mozilla.org code.
  #
  # The Initial Developer of the Original Code is Holger
  # Schurig. Portions created by Holger Schurig are
  # Copyright (C) 1999 Holger Schurig. All
  # Rights Reserved.
  #
  # Contributor(s): Holger Schurig <ho...@nikocity.de>
  #               Terry Weissman <te...@mozilla.org>
  #
  #
  # Direct any questions on this source code to
  #
  # Holger Schurig <ho...@nikocity.de>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  require "globals.pl";
  
  
  
  
  # TestProduct:  just returns if the specified product does exists
  # CheckProduct: same check, optionally  emit an error text
  # TestVersion:  just returns if the specified product/version combination exists
  # CheckVersion: same check, optionally emit an error text
  
  sub TestProduct ($)
  {
      my $prod = shift;
  
      # does the product exist?
      SendSQL("SELECT product
               FROM products
               WHERE product=" . SqlQuote($prod));
      return FetchOneColumn();
  }
  
  sub CheckProduct ($)
  {
      my $prod = shift;
  
      # do we have a product?
      unless ($prod) {
          print "Sorry, you haven't specified a product.";
          PutTrailer();
          exit;
      }
  
      unless (TestProduct $prod) {
          print "Sorry, product '$prod' does not exist.";
          PutTrailer();
          exit;
      }
  }
  
  sub TestVersion ($$)
  {
      my ($prod,$ver) = @_;
  
      # does the product exist?
      SendSQL("SELECT program,value
               FROM versions
               WHERE program=" . SqlQuote($prod) . " and value=" . SqlQuote($ver));
      return FetchOneColumn();
  }
  
  sub CheckVersion ($$)
  {
      my ($prod,$ver) = @_;
  
      # do we have the version?
      unless ($ver) {
          print "Sorry, you haven't specified a version.";
          PutTrailer();
          exit;
      }
  
      CheckProduct($prod);
  
      unless (TestVersion $prod,$ver) {
          print "Sorry, version '$ver' for product '$prod' does not exist.";
          PutTrailer();
          exit;
      }
  }
  
  
  #
  # Displays the form to edit a version
  #
  
  sub EmitFormElements ($$)
  {
      my ($product, $version) = @_;
  
      print "  <TH ALIGN=\"right\">Version:</TH>\n";
      print "  <TD><INPUT SIZE=64 MAXLENGTH=64 NAME=\"version\" VALUE=\"" .
          value_quote($version) . "\">\n";
      print "      <INPUT TYPE=HIDDEN NAME=\"product\" VALUE=\"" .
          value_quote($product) . "\"></TD>\n";
  }
  
  
  #
  # Displays a text like "a.", "a or b.", "a, b or c.", "a, b, c or d."
  #
  
  sub PutTrailer (@)
  {
      my (@links) = ("Back to the <A HREF=\"query.cgi\">query page</A>", @_);
  
      my $count = $#links;
      my $num = 0;
      print "<P>\n";
      foreach (@links) {
          print $_;
          if ($num == $count) {
              print ".\n";
          }
          elsif ($num == $count-1) {
              print " or ";
          }
          else {
              print ", ";
          }
          $num++;
      }
      print "</BODY>\n</HTML>\n";
  }
  
  
  
  
  
  
  
  #
  # Preliminary checks:
  #
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  unless (UserInGroup("editcomponents")) {
      PutHeader("Not allowed");
      print "Sorry, you aren't a member of the 'editcomponents' group.\n";
      print "And so, you aren't allowed to add, modify or delete versions.\n";
      PutTrailer();
      exit;
  }
  
  
  #
  # often used variables
  #
  my $product = trim($::FORM{product} || '');
  my $version = trim($::FORM{version} || '');
  my $action  = trim($::FORM{action}  || '');
  my $localtrailer;
  if ($version) {
      $localtrailer = "<A HREF=\"editversions.cgi?product=" . url_quote($product) . "\">edit</A> more versions";
  } else {
      $localtrailer = "<A HREF=\"editversions.cgi\">edit</A> more versions";
  }
  
  
  
  #
  # product = '' -> Show nice list of versions
  #
  
  unless ($product) {
      PutHeader("Select product");
  
      SendSQL("SELECT products.product,products.description,'xyzzy'
               FROM products 
               GROUP BY products.product
               ORDER BY products.product");
      print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0><TR BGCOLOR=\"#6666FF\">\n";
      print "  <TH ALIGN=\"left\">Edit versions of ...</TH>\n";
      print "  <TH ALIGN=\"left\">Description</TH>\n";
      print "  <TH ALIGN=\"left\">Bugs</TH>\n";
      #print "  <TH ALIGN=\"left\">Edit</TH>\n";
      print "</TR>";
      while ( MoreSQLData() ) {
          my ($product, $description, $bugs) = FetchSQLData();
          $description ||= "<FONT COLOR=\"red\">missing</FONT>";
          $bugs ||= "none";
          print "<TR>\n";
          print "  <TD VALIGN=\"top\"><A HREF=\"editversions.cgi?product=", url_quote($product), "\"><B>$product</B></A></TD>\n";
          print "  <TD VALIGN=\"top\">$description</TD>\n";
          print "  <TD VALIGN=\"top\">$bugs</TD>\n";
          #print "  <TD VALIGN=\"top\"><A HREF=\"editversions.cgi?action=edit&product=", url_quote($product), "\">Edit</A></TD>\n";
      }
      print "</TR></TABLE>\n";
  
      PutTrailer();
      exit;
  }
  
  
  
  #
  # action='' -> Show nice list of versions
  #
  
  unless ($action) {
      PutHeader("Select version");
      CheckProduct($product);
  
  =for me
  
      # Das geht nicht wie vermutet. Ich bekomme nicht alle Versionen
      # angezeigt!  Schade. Ich w�rde gerne sehen, wieviel Bugs pro
      # Version angegeben sind ...
  
      SendSQL("SELECT value,program,COUNT(bug_id)
               FROM versions LEFT JOIN bugs
                 ON program=product AND value=version
               WHERE program=" . SqlQuote($product) . "
               GROUP BY value");
  
  =cut
  
      SendSQL("SELECT value,program
               FROM versions 
               WHERE program=" . SqlQuote($product) . "
               ORDER BY value");
  
      print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0><TR BGCOLOR=\"#6666FF\">\n";
      print "  <TH ALIGN=\"left\">Edit version ...</TH>\n";
      #print "  <TH ALIGN=\"left\">Bugs</TH>\n";
      print "  <TH ALIGN=\"left\">Action</TH>\n";
      print "</TR>";
      while ( MoreSQLData() ) {
          my ($version,$dummy,$bugs) = FetchSQLData();
          $bugs ||= 'none';
          print "<TR>\n";
          print "  <TD VALIGN=\"top\"><A HREF=\"editversions.cgi?product=", url_quote($product), "&version=", url_quote($version), "&action=edit\"><B>$version</B></A></TD>\n";
          #print "  <TD VALIGN=\"top\">$bugs</TD>\n";
          print "  <TD VALIGN=\"top\"><A HREF=\"editversions.cgi?product=", url_quote($product), "&version=", url_quote($version), "&action=del\"><B>Delete</B></A></TD>\n";
          print "</TR>";
      }
      print "<TR>\n";
      print "  <TD VALIGN=\"top\">Add a new version</TD>\n";
      print "  <TD VALIGN=\"top\" ALIGN=\"middle\"><A HREF=\"editversions.cgi?product=", url_quote($product) . "&action=add\">Add</A></TD>\n";
      print "</TR></TABLE>\n";
  
      PutTrailer();
      exit;
  }
  
  
  
  
  #
  # action='add' -> present form for parameters for new version
  #
  # (next action will be 'new')
  #
  
  if ($action eq 'add') {
      PutHeader("Add version");
      CheckProduct($product);
  
      #print "This page lets you add a new version to a bugzilla-tracked product.\n";
  
      print "<FORM METHOD=POST ACTION=editversions.cgi>\n";
      print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
  
      EmitFormElements($product, $version);
  
      print "</TABLE>\n<HR>\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Add\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"new\">\n";
      print "</FORM>";
  
      my $other = $localtrailer;
      $other =~ s/more/other/;
      PutTrailer($other);
      exit;
  }
  
  
  
  #
  # action='new' -> add version entered in the 'action=add' screen
  #
  
  if ($action eq 'new') {
      PutHeader("Adding new version");
      CheckProduct($product);
  
      # Cleanups and valididy checks
  
      unless ($version) {
          print "You must enter a text for the new version. Please press\n";
          print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
      if (TestVersion($product,$version)) {
          print "The version '$version' already exists. Please press\n";
          print "<b>Back</b> and try again.\n";
          PutTrailer($localtrailer);
          exit;
      }
  
      # Add the new version
      SendSQL("INSERT INTO versions ( " .
            "value, program" .
            " ) VALUES ( " .
            SqlQuote($version) . "," .
            SqlQuote($product) . ")");
  
      # Make versioncache flush
      unlink "data/versioncache";
  
      print "OK, done.<p>\n";
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  
  #
  # action='del' -> ask if user really wants to delete
  #
  # (next action would be 'delete')
  #
  
  if ($action eq 'del') {
      PutHeader("Delete version");
      CheckVersion($product, $version);
  
      SendSQL("SELECT count(bug_id),product,version
               FROM bugs
               GROUP BY product,version
               HAVING product=" . SqlQuote($product) . "
                  AND version=" . SqlQuote($version));
      my $bugs = FetchOneColumn();
  
      print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0>\n";
      print "<TR BGCOLOR=\"#6666FF\">\n";
      print "  <TH VALIGN=\"top\" ALIGN=\"left\">Part</TH>\n";
      print "  <TH VALIGN=\"top\" ALIGN=\"left\">Value</TH>\n";
  
      print "</TR><TR>\n";
      print "  <TH ALIGN=\"left\" VALIGN=\"top\">Product:</TH>\n";
      print "  <TD VALIGN=\"top\">$product</TD>\n";
      print "</TR><TR>\n";
      print "  <TH ALIGN=\"left\" VALIGN=\"top\">Version:</TH>\n";
      print "  <TD VALIGN=\"top\">$version</TD>\n";
      print "</TR><TR>\n";
      print "  <TH ALIGN=\"left\" VALIGN=\"top\">Bugs:</TH>\n";
      print "  <TD VALIGN=\"top\">", $bugs || 'none' , "</TD>\n";
      print "</TR></TABLE>\n";
  
      print "<H2>Confirmation</H2>\n";
  
      if ($bugs) {
          if (!Param("allowbugdeletion")) {
              print "Sorry, there are $bugs bugs outstanding for this version.
  You must reassign those bugs to another version before you can delete this
  one.";
              PutTrailer($localtrailer);
              exit;
          }
          print "<TABLE BORDER=0 CELLPADDING=20 WIDTH=\"70%\" BGCOLOR=\"red\"><TR><TD>\n",
                "There are bugs entered for this version!  When you delete this ",
                "version, <B><BLINK>all</BLINK></B> stored bugs will be deleted, too. ",
                "You could not even see the bug history for this version anymore!\n",
                "</TD></TR></TABLE>\n";
      }
  
      print "<P>Do you really want to delete this version?<P>\n";
      print "<FORM METHOD=POST ACTION=editversions.cgi>\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Yes, delete\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"delete\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"product\" VALUE=\"" .
          value_quote($product) . "\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"version\" VALUE=\"" .
          value_quote($version) . "\">\n";
      print "</FORM>";
  
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # action='delete' -> really delete the version
  #
  
  if ($action eq 'delete') {
      PutHeader("Deleting version");
      CheckVersion($product,$version);
  
      # lock the tables before we start to change everything:
  
      SendSQL("LOCK TABLES attachments WRITE,
                           bugs WRITE,
                           bugs_activity WRITE,
                           versions WRITE,
                           dependencies WRITE");
  
      # According to MySQL doc I cannot do a DELETE x.* FROM x JOIN Y,
      # so I have to iterate over bugs and delete all the indivial entries
      # in bugs_activies and attachments.
  
      SendSQL("SELECT bug_id
               FROM bugs
               WHERE product=" . SqlQuote($product) . "
                 AND version=" . SqlQuote($version));
      while (MoreSQLData()) {
          my $bugid = FetchOneColumn();
  
          my $query = $::db->query("DELETE FROM attachments WHERE bug_id=$bugid")
                  or die "$::db_errstr";
          $query = $::db->query("DELETE FROM bugs_activity WHERE bug_id=$bugid")
                  or die "$::db_errstr";
          $query = $::db->query("DELETE FROM dependencies WHERE blocked=$bugid")
                  or die "$::db_errstr";
      }
      print "Attachments, bug activity and dependencies deleted.<BR>\n";
  
  
      # Deleting the rest is easier:
  
      SendSQL("DELETE FROM bugs
               WHERE product=" . SqlQuote($product) . "
                 AND version=" . SqlQuote($version));
      print "Bugs deleted.<BR>\n";
  
      SendSQL("DELETE FROM versions
               WHERE program=" . SqlQuote($product) . "
                 AND value=" . SqlQuote($version));
      print "Version deleted.<P>\n";
      SendSQL("UNLOCK TABLES");
  
      unlink "data/versioncache";
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # action='edit' -> present the edit version form
  #
  # (next action would be 'update')
  #
  
  if ($action eq 'edit') {
      PutHeader("Edit version");
      CheckVersion($product,$version);
  
      print "<FORM METHOD=POST ACTION=editversions.cgi>\n";
      print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
  
      EmitFormElements($product, $version);
  
      print "</TR></TABLE>\n";
  
      print "<INPUT TYPE=HIDDEN NAME=\"versionold\" VALUE=\"" .
          value_quote($version) . "\">\n";
      print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"update\">\n";
      print "<INPUT TYPE=SUBMIT VALUE=\"Update\">\n";
  
      print "</FORM>";
  
      my $other = $localtrailer;
      $other =~ s/more/other/;
      PutTrailer($other);
      exit;
  }
  
  
  
  #
  # action='update' -> update the version
  #
  
  if ($action eq 'update') {
      PutHeader("Update version");
  
      my $versionold = trim($::FORM{versionold} || '');
  
      CheckVersion($product,$versionold);
  
      # Note that the order of this tests is important. If you change
      # them, be sure to test for WHERE='$version' or WHERE='$versionold'
  
      SendSQL("LOCK TABLES bugs WRITE,
                           versions WRITE");
  
      if ($version ne $versionold) {
          unless ($version) {
              print "Sorry, I can't delete the version text.";
              PutTrailer($localtrailer);
  	    SendSQL("UNLOCK TABLES");
              exit;
          }
          if (TestVersion($product,$version)) {
              print "Sorry, version '$version' is already in use.";
              PutTrailer($localtrailer);
  	    SendSQL("UNLOCK TABLES");
              exit;
          }
          SendSQL("UPDATE bugs
                   SET version=" . SqlQuote($version) . "
                   WHERE version=" . SqlQuote($versionold) . "
                     AND product=" . SqlQuote($product));
          SendSQL("UPDATE versions
                   SET value=" . SqlQuote($version) . "
                   WHERE program=" . SqlQuote($product) . "
                     AND value=" . SqlQuote($versionold));
          unlink "data/versioncache";
          print "Updated version.<BR>\n";
      }
      SendSQL("UNLOCK TABLES");
  
      PutTrailer($localtrailer);
      exit;
  }
  
  
  
  #
  # No valid action found
  #
  
  PutHeader("Error");
  print "I don't have a clue what you want.<BR>\n";
  
  foreach ( sort keys %::FORM) {
      print "$_: $::FORM{$_}<BR>\n";
  }
  
  
  
  1.1                  apache-site/bugs/enter_bug.cgi
  
  Index: enter_bug.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  # Shut up misguided -w warnings about "used only once":
  use vars @::legal_platform,
      @::legal_severity,
      @::legal_opsys,
      @::legal_priority;
  
  
  if (!defined $::FORM{'product'}) {
      GetVersionTable();
      my @prodlist = keys %::versions;
      if ($#prodlist != 0) {
          print "Content-type: text/html\n\n";
          PutHeader("Enter Bug");
          
          print "<H2>First, you must pick a product on which to enter\n";
          print "a bug.</H2>\n";
          print "<table>";
          foreach my $p (sort (@prodlist)) {
              if (defined $::proddesc{$p} && $::proddesc{$p} eq '0') {
                  # Special hack.  If we stuffed a "0" into proddesc, that means
                  # that disallownew was set for this bug, and so we don't want
                  # to allow people to specify that product here.
                  next;
              }
              print "<tr><th align=right valign=top><a href=\"enter_bug.cgi?product=" . url_quote($p) . "\">$p</a>:</th>\n";
              if (defined $::proddesc{$p}) {
                  print "<td valign=top>$::proddesc{$p}</td>\n";
              }
              print "</tr>";
          }
          print "</table>\n";
          exit;
      }
      $::FORM{'product'} = $prodlist[0];
  }
  
  my $product = $::FORM{'product'};
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  sub formvalue {
      my ($name, $default) = (@_);
      if (exists $::FORM{$name}) {
          return $::FORM{$name};
      }
      if (defined $default) {
          return $default;
      }
      return "";
  }
  
  sub pickplatform {
      my $value = formvalue("rep_platform");
      if ($value ne "") {
          return $value;
      }
      if ( Param('usebrowserinfo') ) {
          for ($ENV{'HTTP_USER_AGENT'}) {
              /Mozilla.*\(Windows/ && do {return "PC";};
              /Mozilla.*\(Macintosh/ && do {return "Macintosh";};
              /Mozilla.*\(Win/ && do {return "PC";};
              /Mozilla.*Linux.*86/ && do {return "PC";};
              /Mozilla.*Linux.*alpha/ && do {return "DEC";};
              /Mozilla.*OSF/ && do {return "DEC";};
              /Mozilla.*HP-UX/ && do {return "HP";};
              /Mozilla.*IRIX/ && do {return "SGI";};
              /Mozilla.*(SunOS|Solaris)/ && do {return "Sun";};
          }
      }
      # default
      return "Other";
  }
  
  
  
  sub pickversion {
      my $version = formvalue('version');
  
      if ( Param('usebrowserinfo') ) {
          if ($version eq "") {
              if ($ENV{'HTTP_USER_AGENT'} =~ m@Mozilla[ /]([^ ]*)@) {
                  $version = $1;
              }
          }
      }
      
      if (lsearch($::versions{$product}, $version) >= 0) {
          return $version;
      } else {
          if (defined $::COOKIE{"VERSION-$product"}) {
              if (lsearch($::versions{$product},
                          $::COOKIE{"VERSION-$product"}) >= 0) {
                  return $::COOKIE{"VERSION-$product"};
              }
          }
      }
      return $::versions{$product}->[0];
  }
  
  
  sub pickcomponent {
      my $result =formvalue('component');
      if ($result ne "" && lsearch($::components{$product}, $result) < 0) {
          $result = "";
      }
      return $result;
  }
  
  
  sub pickos {
      if (formvalue('op_sys') ne "") {
          return formvalue('op_sys');
      }
      if ( Param('usebrowserinfo') ) {
          for ($ENV{'HTTP_USER_AGENT'}) {
              /Mozilla.*\(.*;.*; IRIX.*\)/    && do {return "IRIX";};
              /Mozilla.*\(.*;.*; 32bit.*\)/   && do {return "Windows 95";};
              /Mozilla.*\(.*;.*; 16bit.*\)/   && do {return "Windows 3.1";};
              /Mozilla.*\(.*;.*; 68K.*\)/     && do {return "Mac System 8.5";};
              /Mozilla.*\(.*;.*; PPC.*\)/     && do {return "Mac System 8.5";};
              /Mozilla.*\(.*;.*; OSF.*\)/     && do {return "OSF/1";};
              /Mozilla.*\(.*;.*; Linux.*\)/   && do {return "Linux";};
              /Mozilla.*\(.*;.*; SunOS 5.*\)/ && do {return "Solaris";};
              /Mozilla.*\(.*;.*; SunOS.*\)/   && do {return "SunOS";};
              /Mozilla.*\(.*;.*; SunOS.*\)/   && do {return "SunOS";};
              /Mozilla.*\(.*;.*; BSD\/OS.*\)/ && do {return "BSDI";};
              /Mozilla.*\(Win16.*\)/          && do {return "Windows 3.1";};
              /Mozilla.*\(Win95.*\)/          && do {return "Windows 95";};
              /Mozilla.*\(WinNT.*\)/          && do {return "Windows NT";};
          }
      }
      # default
      return "other";
  }
  
  
  GetVersionTable();
  
  my $assign_element = GeneratePersonInput('assigned_to', 1,
                                           formvalue('assigned_to'));
  my $cc_element = GeneratePeopleInput('cc', formvalue('cc'));
  
  
  my $priority = Param('defaultpriority');
  
  my $priority_popup = make_popup('priority', \@::legal_priority,
                                  formvalue('priority', $priority), 0);
  my $sev_popup = make_popup('bug_severity', \@::legal_severity,
                             formvalue('bug_severity', 'normal'), 0);
  my $platform_popup = make_popup('rep_platform', \@::legal_platform,
                                  pickplatform(), 0);
  my $opsys_popup = make_popup('op_sys', \@::legal_opsys, pickos(), 0);
  
  my $component_popup = make_popup('component', $::components{$product},
                                   formvalue('component'), 1);
  
  PutHeader ("Enter Bug");
  
  print "
  <FORM METHOD=POST ACTION=\"post_bug.cgi\">
  <INPUT TYPE=HIDDEN NAME=bug_status VALUE=NEW>
  <INPUT TYPE=HIDDEN NAME=reporter VALUE=\"$::COOKIE{'Bugzilla_login'}\">
  <INPUT TYPE=HIDDEN NAME=product VALUE=\""  . value_quote($product) . "\">
    <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0>";
  
  if (Param("entryheaderhtml")){
    print "
    <TR>
      <td></td>
      <td colspan=3>" .
    Param("entryheaderhtml") . "\n" .
    " </td> 
    </TR>
    <TR><td><br></td></TR>";
  }
  
  print "
    <TR>
      <td ALIGN=right valign=top><B>Reporter:</B></td>
      <td valign=top>$::COOKIE{'Bugzilla_login'}</td>
      <td ALIGN=right valign=top><B>Product:</B></td>
      <td valign=top>$product</td>
    </TR>
    <TR>
      <td ALIGN=right valign=top><B>Version:</B></td>
      <td>" . Version_element(pickversion(), $product) . "</td>
      <td align=right valign=top><b><a href=\"describecomponents.cgi?product=" .
      url_quote($product) . "\">Component:</a></b></td>
      <td>$component_popup</td>
    </TR>
    <tr><td>&nbsp<td> <td> <td> <td> <td> </tr>
    <TR>
      <td align=right><B><A HREF=\"bug_status.html#rep_platform\">Platform:</A></B></td>
      <TD>$platform_popup</TD>
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#op_sys\">OS:</A></B></TD>
      <TD>$opsys_popup</TD>
      <td align=right valign=top></td>
      <td rowspan=3></td>
      <td></td>
    </TR>
    <TR>";
  if (Param('letsubmitterchoosepriority')) {
      print "
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#priority\">Priority</A>:</B></TD>
      <TD>$priority_popup</TD>";
  } else {
      print '<INPUT TYPE=HIDDEN NAME=priority VALUE="' .
          value_quote($priority) . '">';
  }
  print "
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#severity\">Severity</A>:</B></TD>
      <TD>$sev_popup</TD>
      <td></td>
      <td></td>
    </TR>
    <tr><td>&nbsp<td> <td> <td> <td> <td> </tr>
    <tr>
      <TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#assigned_to\">Assigned To:</A></B></TD>
      <TD colspan=5>$assign_element
      (Leave blank to assign to default component owner)</td>
    </tr>
    <tr>
      <TD ALIGN=RIGHT><B>Cc:</B></TD>
      <TD colspan=5>$cc_element</TD>
    </tr>
    <tr><td>&nbsp<td> <td> <td> <td> <td> </tr>
    <TR>
      <TD ALIGN=RIGHT><B>URL:</B>
      <TD COLSPAN=5>
        <INPUT NAME=bug_file_loc SIZE=60 value=\"" .
      value_quote(formvalue('bug_file_loc')) .
      "\"></TD>
    </TR>
    <TR>
      <TD ALIGN=RIGHT><B>Summary:</B>
      <TD COLSPAN=5>
        <INPUT NAME=short_desc SIZE=60 value=\"" .
      value_quote(formvalue('short_desc')) .
      "\"></TD>
    </TR>
    <tr><td align=right valign=top><B>Description:</b></td>
  <!--  </tr> <tr> -->
      <td colspan=5><TEXTAREA WRAP=HARD NAME=comment ROWS=10 COLS=80>" .
      value_quote(formvalue('comment')) .
      "</TEXTAREA><BR></td>
    </tr>
    <tr>
      <td></td>
      <td colspan=5>
         <INPUT TYPE=\"submit\" VALUE=\"    Commit    \">
         &nbsp;&nbsp;&nbsp;&nbsp;
         <INPUT TYPE=\"reset\" VALUE=\"Reset\">
         &nbsp;&nbsp;&nbsp;&nbsp;
         <INPUT TYPE=\"submit\" NAME=maketemplate VALUE=\"Remember values as bookmarkable template\">
      </td>
    </tr>";
  
  if ( Param('usebrowserinfo') ) {
      print "
    <tr>
      <td></td>
      <td colspan=3>
       <br>
       Some fields initialized from your user-agent, 
       <b>$ENV{'HTTP_USER_AGENT'}</b>.  If you think it got it wrong, 
       please tell " . Param('maintainer') . " what it should have been.
      </td>
    </tr>";
  }
  print "
    </TABLE>
    <INPUT TYPE=hidden name=form_name VALUE=enter_bug>
  </FORM>\n";
  
  print "</BODY></HTML>\n";
  
  
  
  
  1.1                  apache-site/bugs/globals.pl
  
  Index: globals.pl
  ===================================================================
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  # Contains some global variables and routines used throughout bugzilla.
  
  use diagnostics;
  use strict;
  
  # Shut up misguided -w warnings about "used only once".  For some reason,
  # "use vars" chokes on me when I try it here.
  
  sub globals_pl_sillyness {
      my $zz;
      $zz = @main::chooseone;
      $zz = @main::db_errstr;
      $zz = @main::default_column_list;
      $zz = @main::dontchange;
      $zz = @main::legal_bug_status;
      $zz = @main::legal_components;
      $zz = @main::legal_opsys;
      $zz = @main::legal_platform;
      $zz = @main::legal_priority;
      $zz = @main::legal_product;
      $zz = @main::legal_severity;
      $zz = @main::legal_target_milestone;
      $zz = @main::legal_versions;
      $zz = @main::milestoneurl;
      $zz = @main::prodmaxvotes;
  }
  
  use Mysql;
  
  use Date::Format;               # For time2str().
  # use Carp;                       # for confess
  
  # Contains the version string for the current running Bugzilla.
  $::param{'version'} = '2.8';
  
  $::dontchange = "--do_not_change--";
  $::chooseone = "--Choose_one:--";
  
  sub ConnectToDatabase {
      if (!defined $::db) {
  	$::db = Mysql->Connect("localhost", "httpd_bugs", "bugs", "")
              || die "Can't connect to database server.";
      }
  }
  
  sub SendSQL {
      my ($str) = (@_);
      $::currentquery = $::db->query($str)
  	|| die "$str: $::db_errstr";
  }
  
  sub MoreSQLData {
      if (defined @::fetchahead) {
  	return 1;
      }
      if (@::fetchahead = $::currentquery->fetchrow()) {
  	return 1;
      }
      return 0;
  }
  
  sub FetchSQLData {
      if (defined @::fetchahead) {
  	my @result = @::fetchahead;
  	undef @::fetchahead;
  	return @result;
      }
      return $::currentquery->fetchrow();
  }
  
  
  sub FetchOneColumn {
      my @row = FetchSQLData();
      return $row[0];
  }
  
      
  
  @::default_column_list = ("severity", "priority", "platform", "owner",
                            "status", "resolution", "summary");
  
  sub AppendComment {
      my ($bugid,$who,$comment) = (@_);
      $comment =~ s/\r\n/\n/g;     # Get rid of windows-style line endings.
      $comment =~ s/\r/\n/g;       # Get rid of mac-style line endings.
      if ($comment =~ /^\s*$/) {  # Nothin' but whitespace.
          return;
      }
      SendSQL("select long_desc from bugs where bug_id = $bugid");
      
      my $desc = FetchOneColumn();
      my $now = time2str("%D %H:%M", time());
      $desc .= "\n\n------- Additional Comments From $who  $now -------\n";
      $desc .= $comment;
      SendSQL("update bugs set long_desc=" . SqlQuote($desc) .
              " where bug_id=$bugid");
  }
  
  sub lsearch {
      my ($list,$item) = (@_);
      my $count = 0;
      foreach my $i (@$list) {
          if ($i eq $item) {
              return $count;
          }
          $count++;
      }
      return -1;
  }
  
  sub Product_element {
      my ($prod,$onchange) = (@_);
      return make_popup("product", keys %::versions, $prod, 1, $onchange);
  }
  
  sub Component_element {
      my ($comp,$prod,$onchange) = (@_);
      my $componentlist;
      if (! defined $::components{$prod}) {
          $componentlist = [];
      } else {
          $componentlist = $::components{$prod};
      }
      my $defcomponent;
      if ($comp ne "" && lsearch($componentlist, $comp) >= 0) {
          $defcomponent = $comp;
      } else {
          $defcomponent = $componentlist->[0];
      }
      return make_popup("component", $componentlist, $defcomponent, 1, "");
  }
  
  sub Version_element {
      my ($vers, $prod, $onchange) = (@_);
      my $versionlist;
      if (!defined $::versions{$prod}) {
          $versionlist = [];
      } else {
          $versionlist = $::versions{$prod};
      }
      my $defversion = $versionlist->[0];
      if (lsearch($versionlist,$vers) >= 0) {
          $defversion = $vers;
      }
      return make_popup("version", $versionlist, $defversion, 1, $onchange);
  }
          
  
  
  # Generate a string which, when later interpreted by the Perl compiler, will
  # be the same as the given string.
  
  sub PerlQuote {
      my ($str) = (@_);
      return SqlQuote($str);
      
  # The below was my first attempt, but I think just using SqlQuote makes more 
  # sense...
  #     $result = "'";
  #     $length = length($str);
  #     for (my $i=0 ; $i<$length ; $i++) {
  #         my $c = substr($str, $i, 1);
  #         if ($c eq "'" || $c eq '\\') {
  #             $result .= '\\';
  #         }
  #         $result .= $c;
  #     }
  #     $result .= "'";
  #     return $result;
  }
  
  
  # Given the name of a global variable, generate Perl code that, if later
  # executed, would restore the variable to its current value.
  
  sub GenerateCode {
      my ($name) = (@_);
      my $result = $name . " = ";
      if ($name =~ /^\$/) {
          my $value = eval($name);
          if (ref($value) eq "ARRAY") {
              $result .= "[" . GenerateArrayCode($value) . "]";
          } else {
              $result .= PerlQuote(eval($name));
          }
      } elsif ($name =~ /^@/) {
          my @value = eval($name);
          $result .= "(" . GenerateArrayCode(\@value) . ")";
      } elsif ($name =~ '%') {
          $result = "";
          foreach my $k (sort { uc($a) cmp uc($b)} eval("keys $name")) {
              $result .= GenerateCode("\$" . substr($name, 1) .
                                      "{'" . $k . "'}");
          }
          return $result;
      } else {
          die "Can't do $name -- unacceptable variable type.";
      }
      $result .= ";\n";
      return $result;
  }
  
  sub GenerateArrayCode {
      my ($ref) = (@_);
      my @list;
      foreach my $i (@$ref) {
          push @list, PerlQuote($i);
      }
      return join(',', @list);
  }
  
  
  
  sub GenerateVersionTable {
      ConnectToDatabase();
      SendSQL("select value, program from versions order by value");
      my @line;
      my %varray;
      my %carray;
      while (@line = FetchSQLData()) {
          my ($v,$p1) = (@line);
          if (!defined $::versions{$p1}) {
              $::versions{$p1} = [];
          }
          push @{$::versions{$p1}}, $v;
          $varray{$v} = 1;
      }
      SendSQL("select value, program from components order by value");
      while (@line = FetchSQLData()) {
          my ($c,$p) = (@line);
          if (!defined $::components{$p}) {
              $::components{$p} = [];
          }
          my $ref = $::components{$p};
          push @$ref, $c;
          $carray{$c} = 1;
      }
  
      my $dotargetmilestone = Param("usetargetmilestone");
  
      my $mpart = $dotargetmilestone ? ", milestoneurl" : "";
      SendSQL("select product, description, votesperuser, disallownew$mpart from products");
      while (@line = FetchSQLData()) {
          my ($p, $d, $votesperuser, $dis, $u) = (@line);
          $::proddesc{$p} = $d;
          if ($dis) {
              # Special hack.  Stomp on the description and make it "0" if we're
              # not supposed to allow new bugs against this product.  This is
              # checked for in enter_bug.cgi.
              $::proddesc{$p} = "0";
          }
          if ($dotargetmilestone) {
              $::milestoneurl{$p} = $u;
          }
          $::prodmaxvotes{$p} = $votesperuser;
      }
              
  
      my $cols = LearnAboutColumns("bugs");
      
      @::log_columns = @{$cols->{"-list-"}};
      foreach my $i ("bug_id", "creation_ts", "delta_ts", "long_desc") {
          my $w = lsearch(\@::log_columns, $i);
          if ($w >= 0) {
              splice(@::log_columns, $w, 1);
          }
      }
      @::log_columns = (sort(@::log_columns));
  
      @::legal_priority = SplitEnumType($cols->{"priority,type"});
      @::legal_severity = SplitEnumType($cols->{"bug_severity,type"});
      @::legal_platform = SplitEnumType($cols->{"rep_platform,type"});
      @::legal_opsys = SplitEnumType($cols->{"op_sys,type"});
      @::legal_bug_status = SplitEnumType($cols->{"bug_status,type"});
      @::legal_resolution = SplitEnumType($cols->{"resolution,type"});
      @::legal_resolution_no_dup = @::legal_resolution;
      my $w = lsearch(\@::legal_resolution_no_dup, "DUPLICATE");
      if ($w >= 0) {
          splice(@::legal_resolution_no_dup, $w, 1);
      }
  
      my @list = sort { uc($a) cmp uc($b)} keys(%::versions);
      @::legal_product = @list;
      mkdir("data", 0777);
      chmod 0777, "data";
      my $tmpname = "data/versioncache.$$";
      open(FID, ">$tmpname") || die "Can't create $tmpname";
  
      print FID GenerateCode('@::log_columns');
      print FID GenerateCode('%::versions');
  
      foreach my $i (@list) {
          if (!defined $::components{$i}) {
              $::components{$i} = "";
          }
      }
      @::legal_versions = sort {uc($a) cmp uc($b)} keys(%varray);
      print FID GenerateCode('@::legal_versions');
      print FID GenerateCode('%::components');
      @::legal_components = sort {uc($a) cmp uc($b)} keys(%carray);
      print FID GenerateCode('@::legal_components');
      foreach my $i('product', 'priority', 'severity', 'platform', 'opsys',
                    'bug_status', 'resolution', 'resolution_no_dup') {
          print FID GenerateCode('@::legal_' . $i);
      }
      print FID GenerateCode('%::proddesc');
      print FID GenerateCode('%::prodmaxvotes');
  
      if ($dotargetmilestone) {
          my $last = Param("nummilestones");
          my $i;
          for ($i=1 ; $i<=$last ; $i++) {
              push(@::legal_target_milestone, "M$i");
          }
          print FID GenerateCode('@::legal_target_milestone');
          print FID GenerateCode('%::milestoneurl');
      }
      print FID "1;\n";
      close FID;
      rename $tmpname, "data/versioncache" || die "Can't rename $tmpname to versioncache";
      chmod 0666, "data/versioncache";
  }
  
  
  
  # Returns the modification time of a file.
  
  sub ModTime {
      my ($filename) = (@_);
      my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
          $atime,$mtime,$ctime,$blksize,$blocks)
          = stat($filename);
      return $mtime;
  }
  
  
  
  # This proc must be called before using legal_product or the versions array.
  
  sub GetVersionTable {
      my $mtime = ModTime("data/versioncache");
      if (!defined $mtime || $mtime eq "") {
          $mtime = 0;
      }
      if (time() - $mtime > 3600) {
          GenerateVersionTable();
      }
      require 'data/versioncache';
      if (!defined %::versions) {
          GenerateVersionTable();
          do 'data/versioncache';
  
          if (!defined %::versions) {
              die "Can't generate file data/versioncache";
          }
      }
  }
  
  
  sub InsertNewUser {
      my ($username, $realname) = (@_);
      my $password = "";
      for (my $i=0 ; $i<8 ; $i++) {
          $password .= substr("abcdefghijklmnopqrstuvwxyz", int(rand(26)), 1);
      }
      SendSQL("select bit, userregexp from groups where userregexp != ''");
      my $groupset = "0";
      while (MoreSQLData()) {
          my @row = FetchSQLData();
          if ($username =~ m/$row[1]/) {
              $groupset .= "+ $row[0]"; # Silly hack to let MySQL do the math,
                                        # not Perl, since we're dealing with 64
                                        # bit ints here, and I don't *think* Perl
                                        # does that.
          }
      }
              
      $username = SqlQuote($username);
      $realname = SqlQuote($realname);
      SendSQL("insert into profiles (login_name, realname, password, cryptpassword, groupset) values ($username, $realname, '$password', encrypt('$password'), $groupset)");
      return $password;
  }
  
  
  sub DBID_to_name {
      my ($id) = (@_);
      if (!defined $::cachedNameArray{$id}) {
          SendSQL("select login_name from profiles where userid = $id");
          my $r = FetchOneColumn();
          if ($r eq "") {
              $r = "__UNKNOWN__";
          }
          $::cachedNameArray{$id} = $r;
      }
      return $::cachedNameArray{$id};
  }
  
  sub DBname_to_id {
      my ($name) = (@_);
      SendSQL("select userid from profiles where login_name = @{[SqlQuote($name)]}");
      my $r = FetchOneColumn();
      if (!defined $r || $r eq "") {
          return 0;
      }
      return $r;
  }
  
  
  sub DBNameToIdAndCheck {
      my ($name, $forceok) = (@_);
      my $result = DBname_to_id($name);
      if ($result > 0) {
          return $result;
      }
      if ($forceok) {
          InsertNewUser($name, "");
          $result = DBname_to_id($name);
          if ($result > 0) {
              return $result;
          }
          print "Yikes; couldn't create user $name.  Please report problem to " .
              Param("maintainer") ."\n";
      } else {
          print "The name <TT>$name</TT> is not a valid username.  Please hit\n";
          print "the <B>Back</B> button and try again.\n";
      }
      exit(0);
  }
  
  sub GetLongDescription {
      my ($id) = (@_);
      SendSQL("select long_desc from bugs where bug_id = $id");
      return FetchOneColumn();
  }
  
  
  sub ShowCcList {
      my ($num) = (@_);
      my @ccids;
      my @row;
      SendSQL("select who from cc where bug_id = $num");
      while (@row = FetchSQLData()) {
          push(@ccids, $row[0]);
      }
      my @result = ();
      foreach my $i (@ccids) {
          push @result, DBID_to_name($i);
      }
  
      return join(',', @result);
  }
  
  
  
  # Fills in a hashtable with info about the columns for the given table in the
  # database.  The hashtable has the following entries:
  #   -list-  the list of column names
  #   <name>,type  the type for the given name
  
  sub LearnAboutColumns {
      my ($table) = (@_);
      my %a;
      SendSQL("show columns from $table");
      my @list = ();
      my @row;
      while (@row = FetchSQLData()) {
          my ($name,$type) = (@row);
          $a{"$name,type"} = $type;
          push @list, $name;
      }
      $a{"-list-"} = \@list;
      return \%a;
  }
  
  
  
  # If the above returned a enum type, take that type and parse it into the
  # list of values.  Assumes that enums don't ever contain an apostrophe!
  
  sub SplitEnumType {
      my ($str) = (@_);
      my @result = ();
      if ($str =~ /^enum\((.*)\)$/) {
          my $guts = $1 . ",";
          while ($guts =~ /^\'([^\']*)\',(.*)$/) {
              push @result, $1;
              $guts = $2;
  	}
      }
      return @result;
  }
  
  
  # This routine is largely copied from Mysql.pm.
  
  sub SqlQuote {
      my ($str) = (@_);
  #     if (!defined $str) {
  #         confess("Undefined passed to SqlQuote");
  #     }
      $str =~ s/([\\\'])/\\$1/g;
      $str =~ s/\0/\\0/g;
      return "'$str'";
  }
  
  
  
  sub UserInGroup {
      my ($groupname) = (@_);
      if ($::usergroupset eq "0") {
          return 0;
      }
      ConnectToDatabase();
      SendSQL("select (bit & $::usergroupset) != 0 from groups where name = " . SqlQuote($groupname));
      my $bit = FetchOneColumn();
      if ($bit) {
          return 1;
      }
      return 0;
  }
  
  
  sub RemoveVotes {
      my ($id, $reason) = (@_);
      ConnectToDatabase();
      SendSQL("select profiles.login_name from votes, profiles where votes.bug_id = $id and profiles.userid = votes.who");
      my @list;
      while (MoreSQLData()) {
          push(@list, FetchOneColumn());
      }
      if (0 < @list) {
          if (open(SENDMAIL, "|/usr/lib/sendmail -t")) {
              my %substs;
              $substs{"to"} = join(',', @list);
              $substs{"bugid"} = $id;
              $substs{"reason"} = $reason;
              print SENDMAIL PerformSubsts(Param("voteremovedmail"), \%substs);
              close SENDMAIL;
          }
          SendSQL("delete from votes where bug_id = $id");
          SendSQL("update bugs set votes = 0, delta_ts=delta_ts where bug_id = $id");
      }
  }
  
  
  
  sub Param {
      my ($value) = (@_);
      if (defined $::param{$value}) {
          return $::param{$value};
      }
      # Um, maybe we haven't sourced in the params at all yet.
      if (stat("data/params")) {
          # Write down and restore the version # here.  That way, we get around
          # anyone who maliciously tries to tweak the version number by editing
          # the params file.  Not to mention that in 2.0, there was a bug that
          # wrote the version number out to the params file...
          my $v = $::param{'version'};
          require "data/params";
          $::param{'version'} = $v;
      }
      if (defined $::param{$value}) {
          return $::param{$value};
      }
      # Well, that didn't help.  Maybe it's a new param, and the user
      # hasn't defined anything for it.  Try and load a default value
      # for it.
      require "defparams.pl";
      WriteParams();
      if (defined $::param{$value}) {
          return $::param{$value};
      }
      # We're pimped.
      die "Can't find param named $value";
  }
  
      
  sub PerformSubsts {
      my ($str, $substs) = (@_);
      $str =~ s/%([a-z]*)%/(defined $substs->{$1} ? $substs->{$1} : Param($1))/eg;
      return $str;
  }
  
  
  # Trim whitespace from front and back.
  
  sub trim {
      ($_) = (@_);
      s/^\s+//g;
      s/\s+$//g;
      return $_;
  }
  
  1;
  
  
  
  1.1                  apache-site/bugs/globals.pl~
  
  Index: globals.pl~
  ===================================================================
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  # Contains some global variables and routines used throughout bugzilla.
  
  use diagnostics;
  use strict;
  
  # Shut up misguided -w warnings about "used only once".  For some reason,
  # "use vars" chokes on me when I try it here.
  
  sub globals_pl_sillyness {
      my $zz;
      $zz = @main::chooseone;
      $zz = @main::db_errstr;
      $zz = @main::default_column_list;
      $zz = @main::dontchange;
      $zz = @main::legal_bug_status;
      $zz = @main::legal_components;
      $zz = @main::legal_opsys;
      $zz = @main::legal_platform;
      $zz = @main::legal_priority;
      $zz = @main::legal_product;
      $zz = @main::legal_severity;
      $zz = @main::legal_target_milestone;
      $zz = @main::legal_versions;
      $zz = @main::milestoneurl;
      $zz = @main::prodmaxvotes;
  }
  
  use Mysql;
  
  use Date::Format;               # For time2str().
  # use Carp;                       # for confess
  
  # Contains the version string for the current running Bugzilla.
  $::param{'version'} = '2.8';
  
  $::dontchange = "--do_not_change--";
  $::chooseone = "--Choose_one:--";
  
  sub ConnectToDatabase {
      if (!defined $::db) {
  	$::db = Mysql->Connect("localhost", "bugs", "bugs", "")
              || die "Can't connect to database server.";
      }
  }
  
  sub SendSQL {
      my ($str) = (@_);
      $::currentquery = $::db->query($str)
  	|| die "$str: $::db_errstr";
  }
  
  sub MoreSQLData {
      if (defined @::fetchahead) {
  	return 1;
      }
      if (@::fetchahead = $::currentquery->fetchrow()) {
  	return 1;
      }
      return 0;
  }
  
  sub FetchSQLData {
      if (defined @::fetchahead) {
  	my @result = @::fetchahead;
  	undef @::fetchahead;
  	return @result;
      }
      return $::currentquery->fetchrow();
  }
  
  
  sub FetchOneColumn {
      my @row = FetchSQLData();
      return $row[0];
  }
  
      
  
  @::default_column_list = ("severity", "priority", "platform", "owner",
                            "status", "resolution", "summary");
  
  sub AppendComment {
      my ($bugid,$who,$comment) = (@_);
      $comment =~ s/\r\n/\n/g;     # Get rid of windows-style line endings.
      $comment =~ s/\r/\n/g;       # Get rid of mac-style line endings.
      if ($comment =~ /^\s*$/) {  # Nothin' but whitespace.
          return;
      }
      SendSQL("select long_desc from bugs where bug_id = $bugid");
      
      my $desc = FetchOneColumn();
      my $now = time2str("%D %H:%M", time());
      $desc .= "\n\n------- Additional Comments From $who  $now -------\n";
      $desc .= $comment;
      SendSQL("update bugs set long_desc=" . SqlQuote($desc) .
              " where bug_id=$bugid");
  }
  
  sub lsearch {
      my ($list,$item) = (@_);
      my $count = 0;
      foreach my $i (@$list) {
          if ($i eq $item) {
              return $count;
          }
          $count++;
      }
      return -1;
  }
  
  sub Product_element {
      my ($prod,$onchange) = (@_);
      return make_popup("product", keys %::versions, $prod, 1, $onchange);
  }
  
  sub Component_element {
      my ($comp,$prod,$onchange) = (@_);
      my $componentlist;
      if (! defined $::components{$prod}) {
          $componentlist = [];
      } else {
          $componentlist = $::components{$prod};
      }
      my $defcomponent;
      if ($comp ne "" && lsearch($componentlist, $comp) >= 0) {
          $defcomponent = $comp;
      } else {
          $defcomponent = $componentlist->[0];
      }
      return make_popup("component", $componentlist, $defcomponent, 1, "");
  }
  
  sub Version_element {
      my ($vers, $prod, $onchange) = (@_);
      my $versionlist;
      if (!defined $::versions{$prod}) {
          $versionlist = [];
      } else {
          $versionlist = $::versions{$prod};
      }
      my $defversion = $versionlist->[0];
      if (lsearch($versionlist,$vers) >= 0) {
          $defversion = $vers;
      }
      return make_popup("version", $versionlist, $defversion, 1, $onchange);
  }
          
  
  
  # Generate a string which, when later interpreted by the Perl compiler, will
  # be the same as the given string.
  
  sub PerlQuote {
      my ($str) = (@_);
      return SqlQuote($str);
      
  # The below was my first attempt, but I think just using SqlQuote makes more 
  # sense...
  #     $result = "'";
  #     $length = length($str);
  #     for (my $i=0 ; $i<$length ; $i++) {
  #         my $c = substr($str, $i, 1);
  #         if ($c eq "'" || $c eq '\\') {
  #             $result .= '\\';
  #         }
  #         $result .= $c;
  #     }
  #     $result .= "'";
  #     return $result;
  }
  
  
  # Given the name of a global variable, generate Perl code that, if later
  # executed, would restore the variable to its current value.
  
  sub GenerateCode {
      my ($name) = (@_);
      my $result = $name . " = ";
      if ($name =~ /^\$/) {
          my $value = eval($name);
          if (ref($value) eq "ARRAY") {
              $result .= "[" . GenerateArrayCode($value) . "]";
          } else {
              $result .= PerlQuote(eval($name));
          }
      } elsif ($name =~ /^@/) {
          my @value = eval($name);
          $result .= "(" . GenerateArrayCode(\@value) . ")";
      } elsif ($name =~ '%') {
          $result = "";
          foreach my $k (sort { uc($a) cmp uc($b)} eval("keys $name")) {
              $result .= GenerateCode("\$" . substr($name, 1) .
                                      "{'" . $k . "'}");
          }
          return $result;
      } else {
          die "Can't do $name -- unacceptable variable type.";
      }
      $result .= ";\n";
      return $result;
  }
  
  sub GenerateArrayCode {
      my ($ref) = (@_);
      my @list;
      foreach my $i (@$ref) {
          push @list, PerlQuote($i);
      }
      return join(',', @list);
  }
  
  
  
  sub GenerateVersionTable {
      ConnectToDatabase();
      SendSQL("select value, program from versions order by value");
      my @line;
      my %varray;
      my %carray;
      while (@line = FetchSQLData()) {
          my ($v,$p1) = (@line);
          if (!defined $::versions{$p1}) {
              $::versions{$p1} = [];
          }
          push @{$::versions{$p1}}, $v;
          $varray{$v} = 1;
      }
      SendSQL("select value, program from components order by value");
      while (@line = FetchSQLData()) {
          my ($c,$p) = (@line);
          if (!defined $::components{$p}) {
              $::components{$p} = [];
          }
          my $ref = $::components{$p};
          push @$ref, $c;
          $carray{$c} = 1;
      }
  
      my $dotargetmilestone = Param("usetargetmilestone");
  
      my $mpart = $dotargetmilestone ? ", milestoneurl" : "";
      SendSQL("select product, description, votesperuser, disallownew$mpart from products");
      while (@line = FetchSQLData()) {
          my ($p, $d, $votesperuser, $dis, $u) = (@line);
          $::proddesc{$p} = $d;
          if ($dis) {
              # Special hack.  Stomp on the description and make it "0" if we're
              # not supposed to allow new bugs against this product.  This is
              # checked for in enter_bug.cgi.
              $::proddesc{$p} = "0";
          }
          if ($dotargetmilestone) {
              $::milestoneurl{$p} = $u;
          }
          $::prodmaxvotes{$p} = $votesperuser;
      }
              
  
      my $cols = LearnAboutColumns("bugs");
      
      @::log_columns = @{$cols->{"-list-"}};
      foreach my $i ("bug_id", "creation_ts", "delta_ts", "long_desc") {
          my $w = lsearch(\@::log_columns, $i);
          if ($w >= 0) {
              splice(@::log_columns, $w, 1);
          }
      }
      @::log_columns = (sort(@::log_columns));
  
      @::legal_priority = SplitEnumType($cols->{"priority,type"});
      @::legal_severity = SplitEnumType($cols->{"bug_severity,type"});
      @::legal_platform = SplitEnumType($cols->{"rep_platform,type"});
      @::legal_opsys = SplitEnumType($cols->{"op_sys,type"});
      @::legal_bug_status = SplitEnumType($cols->{"bug_status,type"});
      @::legal_resolution = SplitEnumType($cols->{"resolution,type"});
      @::legal_resolution_no_dup = @::legal_resolution;
      my $w = lsearch(\@::legal_resolution_no_dup, "DUPLICATE");
      if ($w >= 0) {
          splice(@::legal_resolution_no_dup, $w, 1);
      }
  
      my @list = sort { uc($a) cmp uc($b)} keys(%::versions);
      @::legal_product = @list;
      mkdir("data", 0777);
      chmod 0777, "data";
      my $tmpname = "data/versioncache.$$";
      open(FID, ">$tmpname") || die "Can't create $tmpname";
  
      print FID GenerateCode('@::log_columns');
      print FID GenerateCode('%::versions');
  
      foreach my $i (@list) {
          if (!defined $::components{$i}) {
              $::components{$i} = "";
          }
      }
      @::legal_versions = sort {uc($a) cmp uc($b)} keys(%varray);
      print FID GenerateCode('@::legal_versions');
      print FID GenerateCode('%::components');
      @::legal_components = sort {uc($a) cmp uc($b)} keys(%carray);
      print FID GenerateCode('@::legal_components');
      foreach my $i('product', 'priority', 'severity', 'platform', 'opsys',
                    'bug_status', 'resolution', 'resolution_no_dup') {
          print FID GenerateCode('@::legal_' . $i);
      }
      print FID GenerateCode('%::proddesc');
      print FID GenerateCode('%::prodmaxvotes');
  
      if ($dotargetmilestone) {
          my $last = Param("nummilestones");
          my $i;
          for ($i=1 ; $i<=$last ; $i++) {
              push(@::legal_target_milestone, "M$i");
          }
          print FID GenerateCode('@::legal_target_milestone');
          print FID GenerateCode('%::milestoneurl');
      }
      print FID "1;\n";
      close FID;
      rename $tmpname, "data/versioncache" || die "Can't rename $tmpname to versioncache";
      chmod 0666, "data/versioncache";
  }
  
  
  
  # Returns the modification time of a file.
  
  sub ModTime {
      my ($filename) = (@_);
      my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
          $atime,$mtime,$ctime,$blksize,$blocks)
          = stat($filename);
      return $mtime;
  }
  
  
  
  # This proc must be called before using legal_product or the versions array.
  
  sub GetVersionTable {
      my $mtime = ModTime("data/versioncache");
      if (!defined $mtime || $mtime eq "") {
          $mtime = 0;
      }
      if (time() - $mtime > 3600) {
          GenerateVersionTable();
      }
      require 'data/versioncache';
      if (!defined %::versions) {
          GenerateVersionTable();
          do 'data/versioncache';
  
          if (!defined %::versions) {
              die "Can't generate file data/versioncache";
          }
      }
  }
  
  
  sub InsertNewUser {
      my ($username, $realname) = (@_);
      my $password = "";
      for (my $i=0 ; $i<8 ; $i++) {
          $password .= substr("abcdefghijklmnopqrstuvwxyz", int(rand(26)), 1);
      }
      SendSQL("select bit, userregexp from groups where userregexp != ''");
      my $groupset = "0";
      while (MoreSQLData()) {
          my @row = FetchSQLData();
          if ($username =~ m/$row[1]/) {
              $groupset .= "+ $row[0]"; # Silly hack to let MySQL do the math,
                                        # not Perl, since we're dealing with 64
                                        # bit ints here, and I don't *think* Perl
                                        # does that.
          }
      }
              
      $username = SqlQuote($username);
      $realname = SqlQuote($realname);
      SendSQL("insert into profiles (login_name, realname, password, cryptpassword, groupset) values ($username, $realname, '$password', encrypt('$password'), $groupset)");
      return $password;
  }
  
  
  sub DBID_to_name {
      my ($id) = (@_);
      if (!defined $::cachedNameArray{$id}) {
          SendSQL("select login_name from profiles where userid = $id");
          my $r = FetchOneColumn();
          if ($r eq "") {
              $r = "__UNKNOWN__";
          }
          $::cachedNameArray{$id} = $r;
      }
      return $::cachedNameArray{$id};
  }
  
  sub DBname_to_id {
      my ($name) = (@_);
      SendSQL("select userid from profiles where login_name = @{[SqlQuote($name)]}");
      my $r = FetchOneColumn();
      if (!defined $r || $r eq "") {
          return 0;
      }
      return $r;
  }
  
  
  sub DBNameToIdAndCheck {
      my ($name, $forceok) = (@_);
      my $result = DBname_to_id($name);
      if ($result > 0) {
          return $result;
      }
      if ($forceok) {
          InsertNewUser($name, "");
          $result = DBname_to_id($name);
          if ($result > 0) {
              return $result;
          }
          print "Yikes; couldn't create user $name.  Please report problem to " .
              Param("maintainer") ."\n";
      } else {
          print "The name <TT>$name</TT> is not a valid username.  Please hit\n";
          print "the <B>Back</B> button and try again.\n";
      }
      exit(0);
  }
  
  sub GetLongDescription {
      my ($id) = (@_);
      SendSQL("select long_desc from bugs where bug_id = $id");
      return FetchOneColumn();
  }
  
  
  sub ShowCcList {
      my ($num) = (@_);
      my @ccids;
      my @row;
      SendSQL("select who from cc where bug_id = $num");
      while (@row = FetchSQLData()) {
          push(@ccids, $row[0]);
      }
      my @result = ();
      foreach my $i (@ccids) {
          push @result, DBID_to_name($i);
      }
  
      return join(',', @result);
  }
  
  
  
  # Fills in a hashtable with info about the columns for the given table in the
  # database.  The hashtable has the following entries:
  #   -list-  the list of column names
  #   <name>,type  the type for the given name
  
  sub LearnAboutColumns {
      my ($table) = (@_);
      my %a;
      SendSQL("show columns from $table");
      my @list = ();
      my @row;
      while (@row = FetchSQLData()) {
          my ($name,$type) = (@row);
          $a{"$name,type"} = $type;
          push @list, $name;
      }
      $a{"-list-"} = \@list;
      return \%a;
  }
  
  
  
  # If the above returned a enum type, take that type and parse it into the
  # list of values.  Assumes that enums don't ever contain an apostrophe!
  
  sub SplitEnumType {
      my ($str) = (@_);
      my @result = ();
      if ($str =~ /^enum\((.*)\)$/) {
          my $guts = $1 . ",";
          while ($guts =~ /^\'([^\']*)\',(.*)$/) {
              push @result, $1;
              $guts = $2;
  	}
      }
      return @result;
  }
  
  
  # This routine is largely copied from Mysql.pm.
  
  sub SqlQuote {
      my ($str) = (@_);
  #     if (!defined $str) {
  #         confess("Undefined passed to SqlQuote");
  #     }
      $str =~ s/([\\\'])/\\$1/g;
      $str =~ s/\0/\\0/g;
      return "'$str'";
  }
  
  
  
  sub UserInGroup {
      my ($groupname) = (@_);
      if ($::usergroupset eq "0") {
          return 0;
      }
      ConnectToDatabase();
      SendSQL("select (bit & $::usergroupset) != 0 from groups where name = " . SqlQuote($groupname));
      my $bit = FetchOneColumn();
      if ($bit) {
          return 1;
      }
      return 0;
  }
  
  
  sub RemoveVotes {
      my ($id, $reason) = (@_);
      ConnectToDatabase();
      SendSQL("select profiles.login_name from votes, profiles where votes.bug_id = $id and profiles.userid = votes.who");
      my @list;
      while (MoreSQLData()) {
          push(@list, FetchOneColumn());
      }
      if (0 < @list) {
          if (open(SENDMAIL, "|/usr/lib/sendmail -t")) {
              my %substs;
              $substs{"to"} = join(',', @list);
              $substs{"bugid"} = $id;
              $substs{"reason"} = $reason;
              print SENDMAIL PerformSubsts(Param("voteremovedmail"), \%substs);
              close SENDMAIL;
          }
          SendSQL("delete from votes where bug_id = $id");
          SendSQL("update bugs set votes = 0, delta_ts=delta_ts where bug_id = $id");
      }
  }
  
  
  
  sub Param {
      my ($value) = (@_);
      if (defined $::param{$value}) {
          return $::param{$value};
      }
      # Um, maybe we haven't sourced in the params at all yet.
      if (stat("data/params")) {
          # Write down and restore the version # here.  That way, we get around
          # anyone who maliciously tries to tweak the version number by editing
          # the params file.  Not to mention that in 2.0, there was a bug that
          # wrote the version number out to the params file...
          my $v = $::param{'version'};
          require "data/params";
          $::param{'version'} = $v;
      }
      if (defined $::param{$value}) {
          return $::param{$value};
      }
      # Well, that didn't help.  Maybe it's a new param, and the user
      # hasn't defined anything for it.  Try and load a default value
      # for it.
      require "defparams.pl";
      WriteParams();
      if (defined $::param{$value}) {
          return $::param{$value};
      }
      # We're pimped.
      die "Can't find param named $value";
  }
  
      
  sub PerformSubsts {
      my ($str, $substs) = (@_);
      $str =~ s/%([a-z]*)%/(defined $substs->{$1} ? $substs->{$1} : Param($1))/eg;
      return $str;
  }
  
  
  # Trim whitespace from front and back.
  
  sub trim {
      ($_) = (@_);
      s/^\s+//g;
      s/\s+$//g;
      return $_;
  }
  
  1;
  
  
  
  1.1                  apache-site/bugs/help.html
  
  Index: help.html
  ===================================================================
  <HTML>
  <!--
       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.
      
       The Original Code is the Bugzilla Bug Tracking System.
      
       The Initial Developer of the Original Code is Netscape Communications
       Corporation. Portions created by Netscape are
       Copyright (C) 1998 Netscape Communications Corporation. All
       Rights Reserved.
      
       Contributor(s): 
  
       Contributor(s): Terry Weissman <te...@mozilla.org>
  -->
  
  <TITLE>Clue</TITLE>
  <H1>A Clue</H1>
  This form will allow you to call up a subset of the bug list.
  You should be able to add the URL of the resulting list to
  your bookmark file in order to preserve queries.
  <p>
  The way the query works, if you have nothing checked in a box,
  then all values for that field are legal, for example if you checked nothing
  in any of the boxes, you would get the entire bug list.
  <p>
  The default value of this form should correspond roughly to a "personal"
  bug list.
  <HR>
  <H2>Running queries not supported by the pretty boxes</H2>
  There is a hacky way to do some searches that aren't supported by the
  form.  The buglist script will build queries based on the URL, so
  you can add other criteria.
  <P>
  For example, if you wanted to see all bugs reported against the X platform
  and assigned to jwz, you could ask for all bugs assign to jwz, then
  edit the URL in the "Location" box, adding the clause "&rep_platform=X-Windows"
  to the URL.
  <P>
  Here is a list of some of the field names you could use for additional
  unsupported searches ...
  
  <PRE>
  version
  rep_platform
  op_sys
  reporter area
  bug_file_loc
  short_desc
  </PRE>
  <HR>
  <H1>Browser Notes</H1>
  <P>Bugzilla uses several non-standard Netscape extentions, but this does not seem
  to case any problem with other browsers.  The lynx browser does work, but lynx
  seems to cache results of a .cgi.  You'll sometimes need to press CONTROL-R to reload
  the screen to see an update.
  
  
  
  
  1.1                  apache-site/bugs/helpemailquery.html
  
  Index: helpemailquery.html
  ===================================================================
  <html> <head>
  <title>Help on searching by email address.</title>
  </head>
  
  <body>
  <h1>Help on searching by email address.</h1>
  
  This used to be simpler, but not very powerful.  Now it's really
  powerful and useful, but it may not be obvious how to use it...
  
  <p>
  
  To search for bugs associated with an email address:
  
  <ul>
    <li> Type a portion of an email address into the text field.
    <li> Select which fields of the bug you expect that address to be in
         the bugs you're looking for.
  </ul>
  
  <p>
  
  You can look for up to two different email addresses; if you specify
  both, then only bugs which match both will show up.  This is useful to
  find bugs that were, for example, created by Ralph and assigned to
  Fred.
  
  <p>
  
  You can also use the drop down menus to specify whether you want to
  match addresses by doing a substring match, by using regular
  expressions, or by exactly matching a fully specified email address.
  
  
  
  </body> </html>
  
  
  
  1.1                  apache-site/bugs/how_to_mail.html
  
  Index: how_to_mail.html
  ===================================================================
  <HTML>
  
  <!--
       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.
      
       The Original Code is the Bugzilla Bug Tracking System.
      
       The Initial Developer of the Original Code is Netscape Communications
       Corporation. Portions created by Netscape are
       Copyright (C) 1998 Netscape Communications Corporation. All
       Rights Reserved.
      
       Contributor(s): 
  
       Contributor(s): Terry Weissman <te...@mozilla.org>
  -->
  
  
  <TITLE>How to Mail to bugzilla</TITLE>
  
  <H1>THIS DOESN'T WORK RIGHT NOW.  Coming someday.</H1>
  
  Mailing to "bugzilla" will be piped through a script which examines
  your message, stripping out control lines, and passing the rest of the
  message in as the description of a new bug.  The control lines look like: <P>
  
  <PRE>
  @FIELD-LABEL VALUE
          LABEL           Legal Values
          Priority        critical major normal minor trivial
          Type            BUG RFE
          Product         Cheddar
          Platform        PC X-Windows Macintosh All
          Area            CODE JAVA TEST BUILD UI PERF
          Version version 2.0b1 2.0b2 2.0b2 2.0b4 2.1a0 2.1a1 2.1b0 2.1b1 2.1b2
          OS              Windows 3.1 Windows 95 Windows NT System 7 System 7.5
                          AIX BSDI HP-UX IRIX Linux OSF/1 Solaris SunOS other
          Summary         -anything-
          URL             -anything-
          Assign          someone in eng
  
  
  and
  
  @description
          This tells the bug parse to stop looking for control lines,
          allowing the bug description to contain lines which start with @
  </PRE>
  
  There are default values for all these fields.  If you don't specify a
  Summary, the subject of the mail message is used. <P>
  
  If you specify an illegal value, the default value is used, the
  bug is assigned to you, and the answerback message will describe
  the error. <P>
  
  After the bug is posted, you will get mail verifying the posting
  and informing you of the bug number if you wish to fix any
  mistakes made by the auto-processor. <P>
  
  EXAMPLE: <P>
  
  
  <PRE>
      % Mail bugzilla
      Subject: WinFE crashes with GPF when I pour beer on my keyboard
      @priority critical
      @platform PC
      @assign troy
  
      After the beer bash I emptied the rest of the keg onto my keyboard
      and my sharp build of Navigator got a GPF.
      .
  
  </PRE>
  
  
  
  1.1                  apache-site/bugs/index.html
  
  Index: index.html
  ===================================================================
  <html>
  <head>
  <!-- $Id: index.html,v 1.1 2000/03/10 01:54:33 rbb Exp $ -->
  <!-- Copyright 2000, Apache Software Foundation -->
  <!-- Content head element begins -->
  <title>The Apache HTTPd Project: Bugs Page</title>
  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
  <!-- link rel="stylesheet" href=/style.css --!>
  <body bgcolor="#FFFFFF">
  <table width="100%" border="0">
  <tr> 
  <td> 
  <p>
  <a href="/index.html"><img src="images/apache_sub.gif" alt="The Apache HTTPd Project" border="0"></a> 
  </p>
  </td>
  </tr>
  </table>
  <br>
  <table width="100%" border="0" cellpadding="10" cellspacing="0">
  <tr valign="top"> 
  <td width="20%"> 
  <p>
  <span class="navheading">Essentials:</span><br>
  <span class="navitem">
  <a href="/index.html">Front Page</a><br>
  <a href="/docs/">Documentation</a><br>
  <a href="/docs/misc/FAQ.html">Frequently Asked Questions (FAQ)</a><br>
  <p>
  <a href="/search.html">Search the site</a><br>
  </span>
  </p>
  </td><td>
  <h1>Bugzilla - HTTPd Apache 1.x </h1>
  <hr>
  <p>Report a bug in the 1.x (1.3) stable tree:</p>
  <p>
  <a href="/bug_report.html">When is it a bug and how to report it</a>
  </p>
  <p>
  Direct interface to <a href="http://bugs.apache.org">GNATs</a>.
  </p>
  <hr>
  <h2>Bugzilla - HTTPd Apache 2 <font color="red">alpha</font></h2>
  <hr>
  <p>Report a bug in the Apache 2.0 experimental tree:</p>
  <p>
  <a href="/bug_report.html">When is it a bug and how to report it</a>
  </p>
  <p>You can also <a href="query.cgi">query</a> existing bug reports or
  <a href="reports.cgi">get 2.0 summary reports</a> or 
  <FORM METHOD=GET ACTION="show_bug.cgi">
  <INPUT TYPE=SUBMIT VALUE="Lookup"> a 2.0 bug with a known # <INPUT NAME=id
  SIZE=6></FORM>
  
  <h2>Bugzilla Accounts <font color="red">2.0 alpha only</font></h2>
  <p><a href="createaccount.cgi">Open a new Bugzilla account</a></p>
  <p><a href="relogin.cgi">Forget the currently stored login</a></p>
  <p><a href="changepassword.cgi">Change password or user preferences</a></p>
  
  </td>
  </tr>
  </table>
  
  <br>
  
  <table width="100%" border="0" cellpadding="10" cellspacing="0">
  <tr>
  <td>
  
  <p class="fineprint">
  Copyright &copy;1999 The Apache Software Foundation<br>
  <a href="../legal.html">Legal Stuff They Make Us Say</a><br>
  <a href="../contact.html">Contact Information</a></td>
  </p>
  
  </tr>
  </table>
  
  </body>
  </html>
  
  
  
  1.1                  apache-site/bugs/localconfig
  
  Index: localconfig
  ===================================================================
  
  #
  # This is the group your web server runs on.
  # If you have a windows box, ignore this setting.
  # If you do not wish for checksetup to adjust the permissions of anything,
  # set this to "".
  # If you set this to anything besides "", you will need to run checksetup.pl
  # as root.
  $webservergroup = "httpd";
  
  
  
  #
  # How to access the SQL database:
  #
  $db_host = "localhost";         # where is the database?
  $db_port = 3306;                # which port to use
  $db_name = "httpd_bugs";              # name of the MySQL database
  $db_user = "bugs";              # user to attach to the MySQL database
  
  
  
  #
  # Which bug and feature-request severities do you want?
  #
  @severities = ("", "critical", "serious", "non-critical");
  @priorities = ("", "high", "medium", "low");
  
  
  # What operatings systems may your products run on?
  #
  @opsys = (
          "All",
          "Windows",
          "NetBSD",
          "FreeBSD",
          "Lynx",
          "Sequent",
          "Domain",
          "Unixware",
          "AIX",
          "BSDI",
          "HP-UX",
          "IRIX",
          "Linux",
          "OSF/1",
          "Solaris",
          "OS/2",
          "BeOS",
          "Convex",
          "DGUX",
          "Dynix",
          "MachTen",
          "NCR",
          "Next",
          "QNX",
          "Uxp-ds",
          "other"
  );
  
  
  
  
  #
  # What hardware platforms may your products run on?
  #
  @platforms = (
          "All",
          "DEC",
          "HP",
          "Macintosh",
          "PC",
          "SGI",
          "Sun",
          "Other"
  );
  
  
  
  
  
  1.1                  apache-site/bugs/localconfig~
  
  Index: localconfig~
  ===================================================================
  
  #
  # This is the group your web server runs on.
  # If you have a windows box, ignore this setting.
  # If you do not wish for checksetup to adjust the permissions of anything,
  # set this to "".
  # If you set this to anything besides "", you will need to run checksetup.pl
  # as root.
  $webservergroup = "httpd";
  
  
  
  #
  # How to access the SQL database:
  #
  $db_host = "localhost";         # where is the database?
  $db_port = 3306;                # which port to use
  $db_name = "httpd_bugs";              # name of the MySQL database
  $db_user = "bugs";              # user to attach to the MySQL database
  
  
  
  #
  # Which bug and feature-request severities do you want?
  #
  @severities = (
          "blocker",
          "critical",
          "major",
          "normal",
          "minor",
          "trivial",
          "enhancement"
  );
  
  
  
  #
  # Which priorities do you want to assign to bugs and feature-request?
  #
  @priorities = (
          "P1",
          "P2",
          "P3",
          "P4",
          "P5"
  );
  
  
  
  #
  # What operatings systems may your products run on?
  #
  @opsys = (
          "All",
          "Windows 3.1",
          "Windows 95",
          "Windows 98",
          "Windows NT",
          "Mac System 7",
          "Mac System 7.5",
          "Mac System 7.6.1",
          "Mac System 8.0",
          "Mac System 8.5",
          "Mac System 8.6",
          "AIX",
          "BSDI",
          "HP-UX",
          "IRIX",
          "Linux",
          "FreeBSD",
          "OSF/1",
          "Solaris",
          "SunOS",
          "Neutrino",
          "OS/2",
          "BeOS",
          "OpenVMS",
          "other"
  );
  
  
  
  #
  # What hardware platforms may your products run on?
  #
  @platforms = (
          "All",
          "DEC",
          "HP",
          "Macintosh",
          "PC",
          "SGI",
          "Sun",
          "Other"
  );
  
  
  
  
  
  1.1                  apache-site/bugs/long_list.cgi
  
  Index: long_list.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  # Shut up misguided -w warnings about "used only once".  "use vars" just
  # doesn't work for me.
  
  sub sillyness {
      my $zz;
      $zz = $::usergroupset;
      $zz = %::FORM;
  }
  
  print "Content-type: text/html\n\n";
  PutHeader ("Full Text Bug Listing");
  
  ConnectToDatabase();
  quietly_check_login();
  
  my $generic_query  = "
  select
    bugs.bug_id,
    bugs.product,
    bugs.version,
    bugs.rep_platform,
    bugs.op_sys,
    bugs.bug_status,
    bugs.bug_severity,
    bugs.priority,
    bugs.resolution,
    assign.login_name,
    report.login_name,
    bugs.component,
    bugs.bug_file_loc,
    bugs.short_desc,
    bugs.target_milestone,
    bugs.qa_contact,
    bugs.status_whiteboard
  from bugs,profiles assign,profiles report
  where assign.userid = bugs.assigned_to and report.userid = bugs.reporter and
  bugs.groupset & $::usergroupset = bugs.groupset and";
  
  $::FORM{'buglist'} = "" unless exists $::FORM{'buglist'};
  foreach my $bug (split(/:/, $::FORM{'buglist'})) {
      SendSQL("$generic_query bugs.bug_id = $bug");
  
      my @row;
      if (@row = FetchSQLData()) {
          my ($id, $product, $version, $platform, $opsys, $status, $severity,
              $priority, $resolution, $assigned, $reporter, $component, $url,
              $shortdesc, $target_milestone, $qa_contact,
              $status_whiteboard) = (@row);
          print "<IMG SRC=\"1x1.gif\" WIDTH=1 HEIGHT=80 ALIGN=LEFT>\n";
          print "<TABLE WIDTH=100%>\n";
          print "<TD COLSPAN=4><TR><DIV ALIGN=CENTER><B><FONT =\"+3\">" .
              html_quote($shortdesc) .
                  "</B></FONT></DIV>\n";
          print "<TR><TD><B>Bug#:</B> <A HREF=\"show_bug.cgi?id=$id\">$id</A>\n";
          print "<TD><B>Product:</B> $product\n";
          print "<TD><B>Version:</B> $version\n";
          print "<TD><B>Platform:</B> $platform\n";
          print "<TR><TD><B>OS/Version:</B> $opsys\n";
          print "<TD><B>Status:</B> $status\n";
          print "<TD><B>Severity:</B> $severity\n";
          print "<TD><B>Priority:</B> $priority\n";
          print "<TR><TD><B>Resolution:</B> $resolution</TD>\n";
          print "<TD><B>Assigned To:</B> $assigned\n";
          print "<TD><B>Reported By:</B> $reporter\n";
          if (Param("useqacontact")) {
              my $name = "";
              if ($qa_contact > 0) {
                  $name = DBID_to_name($qa_contact);
              }
              print "<TD><B>QA Contact:</B> $name\n";
          }
          print "<TR><TD><B>Component:</B> $component\n";
          if (Param("usetargetmilestone")) {
              print "<TD><B>Target milestone:</B>$target_milestone\n";
          }
          print "<TR><TD COLSPAN=6><B>URL:</B>&nbsp;";
  	print "<A HREF=\"" . $url . "\">" .  html_quote($url) . "</A>\n"; 
          print "<TR><TD COLSPAN=6><B>Summary:</B> " . html_quote($shortdesc) . "\n";
          if (Param("usestatuswhiteboard")) {
              print "<TR><TD COLSPAN=6><B>Status Whiteboard:" .
                  html_quote($status_whiteboard) . "\n";
          }
          print "<TR><TD><B>Description:</B>\n</TABLE>\n";
          print "<PRE>" . html_quote(GetLongDescription($bug)) . "</PRE>\n";
          print "<HR>\n";
      }
  }
  
  
  
  1.1                  apache-site/bugs/new_comment.cgi
  
  Index: new_comment.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  if ($ENV{'REQUEST_METHOD'} eq "GET") { $buffer = $ENV{'QUERY_STRING'}; }
  else { read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); }
  # Split the name-value pairs
  @pairs = split(/&/, $buffer);
  foreach $pair (@pairs)
  {
      ($name, $value) = split(/=/, $pair);
  
      $value =~ tr/+/ /;
      $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
      $FORM{$name} = $value;
  }
  open(COMMENTS, ">>data/comments");
  $c=$FORM{"comment"};
  print COMMENTS $FORM{"comment"} . "\n";
  close(COMMENTS);
  print "Content-type: text/html\n\n";
  print "<TITLE>The Word Of Confirmation</TITLE>";
  print "<H1>Done</H1>";
  print $c;
  
  
  
  1.1                  apache-site/bugs/newquip.html
  
  Index: newquip.html
  ===================================================================
  <HTML>
  
  <!--
       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.
      
       The Original Code is the Bugzilla Bug Tracking System.
      
       The Initial Developer of the Original Code is Netscape Communications
       Corporation. Portions created by Netscape are
       Copyright (C) 1998 Netscape Communications Corporation. All
       Rights Reserved.
      
       Contributor(s): 
  
       Contributor(s): Terry Weissman <te...@mozilla.org>
  -->
  
  <TITLE>I'm So Pretty and Witty And Wise</TITLE>
  <H2>Add your own clever headline.</h2>
  The buglist picks a random quip for the headline, and 
  you can extend the quip list.  Type in something clever or
  funny or boring and bonk on the button.
  <HR>
  <FORM METHOD=POST ACTION="new_comment.cgi">
  <INPUT SIZE=80 NAME="comment"><BR>
  <INPUT TYPE="submit" VALUE="Add This Quip"></FORM>
  </HR>
  For the impatient, you can
  <A HREF="data/comments">view the whole quip list</A>.
  
  
  
  1.1                  apache-site/bugs/notargetmilestone.html
  
  Index: notargetmilestone.html
  ===================================================================
  <html> <head>
  <title>No target milestones.</title>
  </head>
  
  <body>
  <h1>No target milestones.</h1>
  
  No target milestones have been defined for this product.  You can set
  the Target Milestone field to things, but there is not currently any
  agreed definition of what the milestones are.
  
  
  
  1.1                  apache-site/bugs/post_bug.cgi
  
  Index: post_bug.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  # Shut up misguided -w warnings about "used only once".  For some reason,
  # "use vars" chokes on me when I try it here.
  
  sub sillyness {
      my $zz;
      $zz = $::buffer;
      $zz = %::COOKIE;
  }
  confirm_login();
  
  print "Set-Cookie: PLATFORM=$::FORM{'product'} ; path=/ ; expires=Sun, 30-Jun-2029 00:00:00 GMT\n";
  print "Set-Cookie: VERSION-$::FORM{'product'}=$::FORM{'version'} ; path=/ ; expires=Sun, 30-Jun-2029 00:00:00 GMT\n";
  print "Content-type: text/html\n\n";
  
  if (defined $::FORM{'maketemplate'}) {
      print "<TITLE>Bookmarks are your friend.</TITLE>\n";
      print "<H1>Template constructed.</H1>\n";
      
      my $url = "enter_bug.cgi?$::buffer";
  
      print "If you put a bookmark <a href=\"$url\">to this link</a>, it will\n";
      print "bring up the submit-a-new-bug page with the fields initialized\n";
      print "as you've requested.\n";
      exit;
  }
  
  PutHeader("Posting Bug -- Please wait", "Posting Bug", "One moment please...");
  
  umask 0;
  ConnectToDatabase();
  
  if (!defined $::FORM{'component'} || $::FORM{'component'} eq "") {
      print "You must choose a component that corresponds to this bug.  If\n";
      print "necessary, just guess.  But please hit the <B>Back</B> button\n";
      print "and choose a component.\n";
      exit 0
  }
      
  
  if (!defined $::FORM{'short_desc'} || trim($::FORM{'short_desc'}) eq "") {
      print "You must enter a summary for this bug.  Please hit the\n";
      print "<B>Back</B> button and try again.\n";
      exit;
  }
  
  
  my $forceAssignedOK = 0;
  if ($::FORM{'assigned_to'} eq "") {
      SendSQL("select initialowner from components where program=" .
              SqlQuote($::FORM{'product'}) .
              " and value=" . SqlQuote($::FORM{'component'}));
      $::FORM{'assigned_to'} = FetchOneColumn();
      $forceAssignedOK = 1;
  }
  
  $::FORM{'assigned_to'} = DBNameToIdAndCheck($::FORM{'assigned_to'}, $forceAssignedOK);
  $::FORM{'reporter'} = DBNameToIdAndCheck($::FORM{'reporter'});
  
  
  my @bug_fields = ("reporter", "product", "version", "rep_platform",
                    "bug_severity", "priority", "op_sys", "assigned_to",
                    "bug_status", "bug_file_loc", "short_desc", "component",
                    "status_whiteboard", "target_milestone");
  
  if (Param("useqacontact")) {
      SendSQL("select initialqacontact from components where program=" .
              SqlQuote($::FORM{'product'}) .
              " and value=" . SqlQuote($::FORM{'component'}));
      my $qacontact = FetchOneColumn();
      if (defined $qacontact && $qacontact ne "") {
          $::FORM{'qa_contact'} = DBNameToIdAndCheck($qacontact, 1);
          push(@bug_fields, "qa_contact");
      }
  }
  
  
  
  my @used_fields;
  foreach my $f (@bug_fields) {
      if (exists $::FORM{$f}) {
          push (@used_fields, $f);
      }
  }
  
  my $query = "insert into bugs (\n" . join(",\n", @used_fields) . ",
  creation_ts, long_desc )
  values (
  ";
  
  
  foreach my $field (@used_fields) {
      $query .= SqlQuote($::FORM{$field}) . ",\n";
  }
  
  my $comment = $::FORM{'comment'};
  $comment =~ s/\r\n/\n/g;     # Get rid of windows-style line endings.
  $comment =~ s/\r/\n/g;       # Get rid of mac-style line endings.
  $comment = trim($comment);
  
  $query .= "now(), " . SqlQuote($comment) . " )\n";
  
  
  my %ccids;
  
  
  if (defined $::FORM{'cc'}) {
      foreach my $person (split(/[ ,]/, $::FORM{'cc'})) {
          if ($person ne "") {
              $ccids{DBNameToIdAndCheck($person)} = 1;
          }
      }
  }
  
  
  # print "<PRE>$query</PRE>\n";
  
  SendSQL($query);
  
  SendSQL("select LAST_INSERT_ID()");
  my $id = FetchOneColumn();
  
  foreach my $person (keys %ccids) {
      SendSQL("insert into cc (bug_id, who) values ($id, $person)");
  }
  
  print "<TABLE BORDER=1><TD><H2>Bug $id posted</H2>\n";
  system("./processmail $id $::COOKIE{'Bugzilla_login'}");
  print "<TD><A HREF=\"show_bug.cgi?id=$id\">Back To BUG# $id</A></TABLE>\n";
  
  print "<BR><A HREF=\"createattachment.cgi?id=$id\">Attach a file to this bug</a>\n";
  
  navigation_header();
  
  exit;
  
  
  
  1.1                  apache-site/bugs/process_bug.cgi
  
  Index: process_bug.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  # Shut up misguided -w warnings about "used only once":
  
  use vars %::versions,
      %::components,
      %::COOKIE;
  
  confirm_login();
  
  print "Content-type: text/html\n\n";
  
  PutHeader ("Bug processed");
  
  GetVersionTable();
  
  if ($::FORM{'product'} ne $::dontchange) {
      my $prod = $::FORM{'product'};
      my $vok = lsearch($::versions{$prod}, $::FORM{'version'}) >= 0;
      my $cok = lsearch($::components{$prod}, $::FORM{'component'}) >= 0;
      if (!$vok || !$cok) {
          print "<H1>Changing product means changing version and component.</H1>\n";
          print "You have chosen a new product, and now the version and/or\n";
          print "component fields are not correct.  (Or, possibly, the bug did\n";
          print "not have a valid component or version field in the first place.)\n";
          print "Anyway, please set the version and component now.<p>\n";
          print "<form>\n";
          print "<table>\n";
          print "<tr>\n";
          print "<td align=right><b>Product:</b></td>\n";
          print "<td>$prod</td>\n";
          print "</tr><tr>\n";
          print "<td align=right><b>Version:</b></td>\n";
          print "<td>" . Version_element($::FORM{'version'}, $prod) . "</td>\n";
          print "</tr><tr>\n";
          print "<td align=right><b>Component:</b></td>\n";
          print "<td>" . Component_element($::FORM{'component'}, $prod) . "</td>\n";
          print "</tr>\n";
          print "</table>\n";
          foreach my $i (keys %::FORM) {
              if ($i ne 'version' && $i ne 'component') {
                  print "<input type=hidden name=$i value=\"" .
                  value_quote($::FORM{$i}) . "\">\n";
              }
          }
          print "<input type=submit value=Commit>\n";
          print "</form>\n";
          print "</hr>\n";
          print "<a href=query.cgi>Cancel all this and go to the query page.</a>\n";
          exit;
      }
  }
  
  
  my @idlist;
  if (defined $::FORM{'id'}) {
      push @idlist, $::FORM{'id'};
  } else {
      foreach my $i (keys %::FORM) {
          if ($i =~ /^id_/) {
              push @idlist, substr($i, 3);
          }
      }
  }
  
  if (!defined $::FORM{'who'}) {
      $::FORM{'who'} = $::COOKIE{'Bugzilla_login'};
  }
  
  print "<TITLE>Update Bug " . join(" ", @idlist) . "</TITLE>\n";
  if (defined $::FORM{'id'}) {
      navigation_header();
  }
  print "<HR>\n";
  $::query = "update bugs\nset";
  $::comma = "";
  umask(0);
  
  sub DoComma {
      $::query .= "$::comma\n    ";
      $::comma = ",";
  }
  
  sub ChangeStatus {
      my ($str) = (@_);
      if ($str ne $::dontchange) {
          DoComma();
          $::query .= "bug_status = '$str'";
      }
  }
  
  sub ChangeResolution {
      my ($str) = (@_);
      if ($str ne $::dontchange) {
          DoComma();
          $::query .= "resolution = '$str'";
      }
  }
  
  
  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.
      }
  }
  
  
  foreach my $field ("rep_platform", "priority", "bug_severity", "url",
                     "summary", "component", "bug_file_loc", "short_desc",
                     "product", "version", "component", "op_sys",
                     "target_milestone", "status_whiteboard") {
      if (defined $::FORM{$field}) {
          if ($::FORM{$field} ne $::dontchange) {
              DoComma();
              $::query .= "$field = " . SqlQuote(trim($::FORM{$field}));
          }
      }
  }
  
  
  if (defined $::FORM{'qa_contact'}) {
      my $name = trim($::FORM{'qa_contact'});
      if ($name ne $::dontchange) {
          my $id = 0;
          if ($name ne "") {
              $id = DBNameToIdAndCheck($name);
          }
          DoComma();
          $::query .= "qa_contact = $id";
      }
  }
  
  
  ConnectToDatabase();
  
  SWITCH: for ($::FORM{'knob'}) {
      /^none$/ && do {
          last SWITCH;
      };
      /^accept$/ && do {
          ChangeStatus('ASSIGNED');
          last SWITCH;
      };
      /^clearresolution$/ && do {
          ChangeResolution('');
          last SWITCH;
      };
      /^resolve$/ && do {
          ChangeStatus('RESOLVED');
          ChangeResolution($::FORM{'resolution'});
          last SWITCH;
      };
      /^reassign$/ && do {
          ChangeStatus('NEW');
          DoComma();
          my $newid = DBNameToIdAndCheck($::FORM{'assigned_to'});
          $::query .= "assigned_to = $newid";
          last SWITCH;
      };
      /^reassignbycomponent$/ && do {
          if ($::FORM{'component'} eq $::dontchange) {
              print "You must specify a component whose owner should get\n";
              print "assigned these bugs.\n";
              exit 0
          }
          ChangeStatus('NEW');
          SendSQL("select initialowner from components where program=" .
                  SqlQuote($::FORM{'product'}) . " and value=" .
                  SqlQuote($::FORM{'component'}));
          my $newname = FetchOneColumn();
          my $newid = DBNameToIdAndCheck($newname, 1);
          DoComma();
          $::query .= "assigned_to = $newid";
          last SWITCH;
      };   
      /^reopen$/ && do {
          ChangeStatus('REOPENED');
          last SWITCH;
      };
      /^verify$/ && do {
          ChangeStatus('VERIFIED');
          last SWITCH;
      };
      /^close$/ && do {
          ChangeStatus('CLOSED');
          last SWITCH;
      };
      /^duplicate$/ && do {
          ChangeStatus('RESOLVED');
          ChangeResolution('DUPLICATE');
          my $num = trim($::FORM{'dup_id'});
          if ($num !~ /^[0-9]*$/) {
              print "You must specify a bug number of which this bug is a\n";
              print "duplicate.  The bug has not been changed.\n";
              exit;
          }
          if ($::FORM{'dup_id'} == $::FORM{'id'}) {
              print "Nice try, $::FORM{'who'}.  But it doesn't really make sense to mark a\n";
              print "bug as a duplicate of itself, does it?\n";
              exit;
          }
          AppendComment($::FORM{'dup_id'}, $::FORM{'who'}, "*** Bug $::FORM{'id'} has been marked as a duplicate of this bug. ***");
          $::FORM{'comment'} .= "\n\n*** This bug has been marked as a duplicate of $::FORM{'dup_id'} ***";
  
          print "<TABLE BORDER=1><TD><H2>Notation added to bug $::FORM{'dup_id'}</H2>\n";
          system("./processmail $::FORM{'dup_id'} $::FORM{'who'}");
          print "<TD><A HREF=\"show_bug.cgi?id=$::FORM{'dup_id'}\">Go To BUG# $::FORM{'dup_id'}</A></TABLE>\n";
  
          last SWITCH;
      };
      # default
      print "Unknown action $::FORM{'knob'}!\n";
      exit;
  }
  
  
  if ($#idlist < 0) {
      print "You apparently didn't choose any bugs to modify.\n";
      print "<p>Click <b>Back</b> and try again.\n";
      exit;
  }
  
  if ($::comma eq "") {
      if (!defined $::FORM{'comment'} || $::FORM{'comment'} =~ /^\s*$/) {
          print "Um, you apparently did not change anything on the selected\n";
          print "bugs. <p>Click <b>Back</b> and try again.\n";
          exit
      }
  }
  
  my $basequery = $::query;
  my $delta_ts;
  
  
  sub SnapShotBug {
      my ($id) = (@_);
      SendSQL("select delta_ts, " . join(',', @::log_columns) .
              " from bugs where bug_id = $id");
      my @row = FetchSQLData();
      $delta_ts = shift @row;
  
      return @row;
  }
  
  
  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 $whoid = DBNameToIdAndCheck($::FORM{'who'});
  my $timestamp;
  
  sub LogDependencyActivity {
      my ($i, $oldstr, $target, $me) = (@_);
      my $newstr = SnapShotDeps($i, $target, $me);
      if ($oldstr ne $newstr) {
          SendSQL("insert into bugs_activity (bug_id,who,bug_when,field,oldvalue,newvalue) values ($i,$whoid,$timestamp,'$target','$oldstr','$newstr')");
          return 1;
      }
      return 0;
  }
  
  
  foreach my $id (@idlist) {
      my %dependencychanged;
      SendSQL("lock tables bugs write, bugs_activity write, cc write, profiles write, dependencies write, votes write");
      my @oldvalues = SnapShotBug($id);
  
      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);
          my $longdesc = GetLongDescription($id);
          my $longchanged = 0;
          if (length($longdesc) > $::FORM{'longdesclength'}) {
              $longchanged = 1;
              print "<P>Added text to the long description:<blockquote><pre>";
              print html_quote(substr($longdesc, $::FORM{'longdesclength'}));
              print "</pre></blockquote>\n";
          }
          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) {
              my $value = value_quote($::FORM{$i});
              print qq{<input type=hidden name="$i" value="$value">\n};
          }
          print qq{<input type=submit value="Submit my changes anyway">\n};
          print " This will cause all of the above changes to be overwritten";
          if ($longchanged) {
              print ", except for the changes to the description";
          }
          print qq{.</form>\n<li><a href="show_bug.cgi?id=$id">Throw away my changes, and go revisit bug $id</a></ul>\n};
          navigation_header();
          exit;
      }
          
      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) {
                      print "<H1>$i is not a legal bug number</H1>\n";
                      print "<p>Click <b>Back</b> and try again.\n";
                      exit;
                  }
                  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) {
                          print "<H1>Dependency loop detected!</H1>\n";
                          print "The change you are making to dependencies\n";
                          print "has caused a circular dependency chain.\n";
                          print "<p>Click <b>Back</b> and try again.\n";
                          exit;
                      }
                      if (!exists $seen{$t}) {
                          push @stack, $t;
                          $seen{$t} = 1;
                      }
                  }
              }
                          
  
              my $tmp = $me;
              $me = $target;
              $target = $tmp;
          }
      }
  
      my $query = "$basequery\nwhere bug_id = $id";
      
  # print "<PRE>$query</PRE>\n";
  
      if ($::comma ne "") {
          SendSQL($query);
      }
      
      if (defined $::FORM{'comment'}) {
          AppendComment($id, $::FORM{'who'}, $::FORM{'comment'});
      }
      
      if (defined $::FORM{'cc'} && ShowCcList($id) ne $::FORM{'cc'}) {
          my %ccids;
          foreach my $person (split(/[ ,]/, $::FORM{'cc'})) {
              if ($person ne "") {
                  my $cid = DBNameToIdAndCheck($person);
                  $ccids{$cid} = 1;
              }
          }
          
          SendSQL("delete from cc where bug_id = $id");
          foreach my $ccid (keys %ccids) {
              SendSQL("insert into cc (bug_id, who) values ($id, $ccid)");
          }
      }
  
      SendSQL("select delta_ts from bugs where bug_id = $id");
      $timestamp = FetchOneColumn();
  
      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}};
              @dependencychanged{@oldlist} = 1;
              @dependencychanged{@newlist} = 1;
  
              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) {
                      LogDependencyActivity($k, $snapshot{$k}, $me, $target);
                  }
                  LogDependencyActivity($id, $oldsnap, $target, $me);
              }
  
              my $tmp = $me;
              $me = $target;
              $target = $tmp;
          }
      }
  
  
      my @newvalues = SnapShotBug($id);
      foreach my $col (@::log_columns) {
          my $old = shift @oldvalues;
          my $new = shift @newvalues;
          if (!defined $old) {
              $old = "";
          }
          if (!defined $new) {
              $new = "";
          }
          if ($old ne $new) {
              if ($col eq 'assigned_to' || $col eq 'qa_contact') {
                  $old = DBID_to_name($old) if $old != 0;
                  $new = DBID_to_name($new) if $new != 0;
              }
              if ($col eq 'product') {
                  RemoveVotes($id,
                              "This bug has been moved to a different product");
              }
              $col = SqlQuote($col);
              $old = SqlQuote($old);
              $new = SqlQuote($new);
              my $q = "insert into bugs_activity (bug_id,who,bug_when,field,oldvalue,newvalue) values ($id,$whoid,$timestamp,$col,$old,$new)";
              # puts "<pre>$q</pre>"
              SendSQL($q);
          }
      }
      
      print "<TABLE BORDER=1><TD><H2>Changes to bug $id submitted</H2>\n";
      SendSQL("unlock tables");
      system("./processmail $id $::FORM{'who'}");
      print "<TD><A HREF=\"show_bug.cgi?id=$id\">Back To BUG# $id</A></TABLE>\n";
  
      foreach my $k (keys(%dependencychanged)) {
          print "<TABLE BORDER=1><TD><H2>Checking for dependency changes on bug $k</H2>\n";
          system("./processmail $k $::FORM{'who'}");
          print "<TD><A HREF=\"show_bug.cgi?id=$k\">Go To BUG# $k</A></TABLE>\n";
      }
  
  }
  
  if (defined $::next_bug) {
      print("<P>The next bug in your list is:\n");
      $::FORM{'id'} = $::next_bug;
      print "<HR>\n";
  
      navigation_header();
      do "bug_form.pl";
  } else {
      navigation_header();
  }
  
  
  
  1.1                  apache-site/bugs/processmail
  
  Index: processmail
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>,
  # Bryce Nesbitt <br...@nextbus.com>
  
  # To recreate the shadow database,  run "processmail regenerate" .
  
  use diagnostics;
  use strict;
  
  require "globals.pl";
  
  $| = 1;
  
  umask(0);
  
  $::lockcount = 0;
  my $regenerate = 0;
  my $nametoexclude = "";
  
  sub Lock {
      if ($::lockcount <= 0) {
          $::lockcount = 0;
          if (!open(LOCKFID, ">>data/maillock")) {
              mkdir "data", 0777;
              chmod 0777, "data";
              open(LOCKFID, ">>data/maillock") || die "Can't open lockfile.";
          }
          my $val = flock(LOCKFID,2);
          if (!$val) { # '2' is magic 'exclusive lock' const.
              print "Lock failed: $val\n";
          }
          chmod 0666, "data/maillock";
      }
      $::lockcount++;
  }
  
  sub Unlock {
      $::lockcount--;
      if ($::lockcount <= 0) {
          flock(LOCKFID,8);       # '8' is magic 'unlock' const.
          close LOCKFID;
      }
  }
  
  sub FileSize {
      my ($filename) = (@_);
      my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
          $atime,$mtime,$ctime,$blksize,$blocks)
          = stat($filename);
      if (defined $size) {
          return $size;
      }
      return -1;
  }
  
  
  
  sub Different {
      my ($file1, $file2) = (@_);
      my $size1 = FileSize($file1);
      my $size2 = FileSize($file2);
      if ($size1 != $size2) {
          return 1;
      }
      open(FID1, "<$file1") || die "Can't open $file1";
      open(FID2, "<$file2") || die "Can't open $file2";
      my $d1; 
      my $d2; 
      if (read(FID1, $d1, $size1) ne $size1) {
          die "Can't read $size1 bytes from $file1";
      }
      if (read(FID2, $d2, $size2) ne $size2) {
          die "Can't read $size2 bytes from $file2";
      }
      close FID1;
      close FID2;
      return ($d1 ne $d2);
  }
  
  
  sub DescCC {
      my ($cclist) = (@_);
      if (scalar(@$cclist) <= 0) {
          return "";
      }
      return "Cc: " . join(", ", @$cclist) . "\n";
  }
  
  
  sub DescDependencies {
      my ($id) = (@_);
      if (!Param("usedependencies")) {
          return "";
      }
      my $result = "";
      my $me = "blocked";
      my $target = "dependson";
      my $title = "BugsThisDependsOn";
      for (1..2) {
          SendSQL("select $target from dependencies where $me = $id order by $target");
          my @list;
          while (MoreSQLData()) {
              push(@list, FetchOneColumn());
          }
          if (@list) {
              my @verbose;
              my $count = 0;
              foreach my $i (@list) {
                  SendSQL("select bug_status, resolution from bugs where bug_id = $i");
                  my ($bug_status, $resolution) = (FetchSQLData());
                  my $desc;
                  if ($bug_status eq "NEW" || $bug_status eq "ASSIGNED" ||
                      $bug_status eq "REOPENED") {
                      $desc = "";
                  } else {
                      $desc = "[$resolution]";
                  }
                  push(@verbose, $i . "$desc");
                  $count++;
              }
              if ($count > 5) {
                  $result .= "$title: Big list (more than 5) has been omitted\n";
              } else {
                  $result .= "$title: " . join(', ', @verbose) . "\n";
              }
          }
          my $tmp = $me;
          $me = $target;
          $target = $tmp;
          $title = "OtherBugsDependingOnThis";
      }
      return $result;
  }
  
  
  
  sub GetBugText {
      my ($id) = (@_);
      undef %::bug;
      
      my @collist = ("bug_id", "product", "version", "rep_platform", "op_sys",
                     "bug_status", "resolution", "priority", "bug_severity",
                     "assigned_to", "reporter", "bug_file_loc",
                     "short_desc", "component", "qa_contact", "target_milestone",
                     "status_whiteboard", "groupset");
  
      my $query = "select " . join(", ", @collist) .
          " from bugs where bug_id = $id";
  
      SendSQL($query);
  
      my @row;
      if (!(@row = FetchSQLData())) {
          return "";
      }
      foreach my $field (@collist) {
          $::bug{$field} = shift @row;
          if (!defined $::bug{$field}) {
              $::bug{$field} = "";
          }
      }
  
      $::bug{'assigned_to'} = DBID_to_name($::bug{'assigned_to'});
      $::bug{'reporter'} = DBID_to_name($::bug{'reporter'});
      my $qa_contact = "";
      my $target_milestone = "";
      my $status_whiteboard = "";
      if (Param('useqacontact') && $::bug{'qa_contact'} > 0) {
          $::bug{'qa_contact'} = DBID_to_name($::bug{'qa_contact'});
          $qa_contact = "QAContact: $::bug{'qa_contact'}\n";
      } else {
          $::bug{'qa_contact'} = "";
      }
      if (Param('usetargetmilestone') && $::bug{'target_milestone'} ne "") {
          $target_milestone = "TargetMilestone: $::bug{'target_milestone'}\n";
      }
      if (Param('usestatuswhiteboard') && $::bug{'status_whiteboard'} ne "") {
          $status_whiteboard = "StatusWhiteboard: $::bug{'status_whiteboard'}\n";
      }
  
      $::bug{'long_desc'} = GetLongDescription($id);
  
      my @cclist;
      @cclist = split(/,/, ShowCcList($id));
      my @voterlist;
      SendSQL("select profiles.login_name from votes, profiles where votes.bug_id = $id and profiles.userid = votes.who");
      while (MoreSQLData()) {
          my $v = FetchOneColumn();
          push(@voterlist, $v);
      }
      $::bug{'cclist'} = join(',', @cclist);
      $::bug{'voterlist'} = join(',', @voterlist);
  
  
      return "Bug\#: $id
  Product: $::bug{'product'}
  Version: $::bug{'version'}
  Platform: $::bug{'rep_platform'}
  OS/Version: $::bug{'op_sys'}
  Status: $::bug{'bug_status'}   
  Resolution: $::bug{'resolution'}
  Severity: $::bug{'bug_severity'}
  Priority: $::bug{'priority'}
  Component: $::bug{'component'}
  AssignedTo: $::bug{'assigned_to'}                            
  ReportedBy: $::bug{'reporter'}               
  $qa_contact$target_milestone${status_whiteboard}URL: $::bug{'bug_file_loc'}
  " . DescCC(\@cclist) . "Summary: $::bug{'short_desc'}
  " . DescDependencies($id) . "
  $::bug{'long_desc'}
  ";
  
  }
  
  
  my $didexclude = 0;
  my %seen;
  sub fixaddresses {
      my ($field, $list) = (@_);
      my @result;
      foreach my $i (@$list) {
          if (!defined $i || $i eq "") {
              next;
          }
          SendSQL("select emailnotification, groupset & $::bug{'groupset'} from profiles where login_name = " .
                  SqlQuote($i));
          my ($emailnotification, $groupset) = (FetchSQLData());
          if ($groupset ne $::bug{'groupset'}) {
              next;
          }
          if ($emailnotification eq "CConly") {
              if ($field ne "cc") {
                  next;
              }
          }
          if ($emailnotification eq "ExcludeSelfChanges" &&
             (lc($i) eq $nametoexclude)) {
              $didexclude = 1;
              next;
          }
          
          if (!defined $::nomail{$i} && !defined $seen{$i}) {
              push(@result, $i . Param('emailsuffix'));
              $seen{$i} = 1;
          }
      }
      return join(", ",  @result);
  }
  
  
  sub Log {
      my ($str) = (@_);
      Lock();
      open(FID, ">>data/maillog") || die "Can't write to data/maillog";
      print FID time2str("%D %H:%M", time()) . ": $str\n";
      close FID;
      Unlock();
  }
      
  sub ProcessOneBug {
      my $i = $_[0];
      my $old = "shadow/$i";
      my $new = "shadow/$i.tmp.$$";
      my $diffs = "shadow/$i.diffs.$$";
      my $verb = "Changed";
      if (!stat($old)) {
          mkdir "shadow", 0777;
          chmod 0777, "shadow";
          open(OLD, ">$old") || die "Couldn't create null $old";
          close OLD;
          $verb = "New";
      }
      my $text = GetBugText($i);
      if ($text eq "") {
          die "Couldn't find bug $i.";
      }
      open(FID, ">$new") || die "Couldn't create $new";
      print FID $text;
      close FID;
      if (Different($old, $new)) {
          system("diff -c -b $old $new > $diffs");
          my $tolist = fixaddresses("to",
                                    [$::bug{'assigned_to'}, $::bug{'reporter'},
                                     $::bug{'qa_contact'}]);
          my @combinedcc;
          foreach my $v (split(/,/, "$::bug{'cclist'},$::bug{'voterlist'}")) {
              push @combinedcc, $v;
          }
          my $cclist = fixaddresses("cc", \@combinedcc);
          my $logstr = "Bug $i $verb";
          if ($tolist ne "" || $cclist ne "") {
              my %substs;
  
              $substs{"to"} = $tolist;
              $substs{"cc"} = $cclist;
              $substs{"bugid"} = $i;
              $substs{"diffs"} = "";
              open(DIFFS, "<$diffs") || die "Can't open $diffs";
              while (<DIFFS>) {
                  $substs{"diffs"} .= $_;
              }
              close DIFFS;
              $substs{"neworchanged"} = $verb;
              $substs{"summary"} = $::bug{'short_desc'};
              my $msg = PerformSubsts(Param("changedmail"), \%substs);
  
              if (!$regenerate) {
                  # Note: fixaddresses may result in a Cc: only.  This seems
                  # harmless.
                  open(SENDMAIL, "|/usr/lib/sendmail -t") ||
                      die "Can't open sendmail";
                  print SENDMAIL $msg;
                  close SENDMAIL;
                  $logstr = "$logstr; mail sent to $tolist, $cclist";
                  print "<B>Email sent to:</B> $tolist $cclist\n";
                  if ($didexclude) {
                      print "<B>Excluding:</B> $nametoexclude (<a href=changepassword.cgi>change your preferences</a> if you wish not to be excluded)\n";
                  }
              }
          }
          unlink($diffs);
          Log($logstr);
      }
      rename($new, $old) || die "Can't rename $new to $old";
      chmod 0666, $old;
      if ($regenerate) {
          print "$i ";
      }
      %seen = ();
  }
  
  # Code starts here
  
  ConnectToDatabase();
  Lock();
  
  if (open(FID, "<data/nomail")) {
      while (<FID>) {
          $::nomail{trim($_)} = 1;
      }
      close FID;
  }
  
  if (($#ARGV < 0) || ($#ARGV > 1)) {
      print "Usage error: processmail {bugid} {nametoexclude}\nOr: processmail regenerate\n";
      exit;
  }
  
  # To recreate the shadow database,  run "processmail regenerate" .
  if ($ARGV[0] eq "regenerate") {
      $regenerate = 1;
      shift @ARGV;
      SendSQL("select bug_id from bugs order by bug_id");
      my @regenerate_list;
      while (my @row = FetchSQLData()) {
          push @regenerate_list, $row[0];
      }
      foreach my $i (@regenerate_list) {
          ProcessOneBug($i);
          Unlock();
          Lock();
      }
      print("\n");
      exit;
  }
  
  if ($#ARGV == 1) {
      $nametoexclude = lc($ARGV[1]);
  }
  
  if ($ARGV[0] eq "rescanall") {
      print "<br> Collecting bug ids...\n";
      SendSQL("select bug_id from bugs where to_days(now()) - to_days(delta_ts) <= 2 order by bug_id");
      my @list;
      while (my @row = FetchSQLData()) {
          push @list, $row[0];
      }
      foreach my $id (@list) {
          $ARGV[0] = $id;
          print "<br> Doing bug $id\n";
          ProcessOneBug($ARGV[0]);
      }
  } else {
      ProcessOneBug($ARGV[0]);
  }
  
  exit;
  
  
  
  1.1                  apache-site/bugs/query.cgi
  
  Index: query.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  #                 David Gardiner <da...@unisa.edu.au>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  # Shut up misguided -w warnings about "used only once":
  
  use vars @::legal_resolution,
    @::legal_product,
    @::legal_bug_status,
    @::legal_priority,
    @::legal_opsys,
    @::legal_platform,
    @::legal_components,
    @::legal_versions,
    @::legal_severity,
    @::legal_target_milestone,
    @::log_columns,
    %::versions,
    %::components,
    %::FORM;
  
  
  if (defined $::FORM{"GoAheadAndLogIn"}) {
      # We got here from a login page, probably from relogin.cgi.  We better
      # make sure the password is legit.
      confirm_login();
  }
  
  if (!defined $::COOKIE{"DEFAULTQUERY"}) {
      $::COOKIE{"DEFAULTQUERY"} = Param("defaultquery");
  }
  
  if (!defined $::buffer || $::buffer eq "") {
      $::buffer = $::COOKIE{"DEFAULTQUERY"};
  }
  
  use vars qw(%default);
  my %type;
  
  foreach my $name ("bug_status", "resolution", "assigned_to", "rep_platform",
                    "priority", "bug_severity", "product", "reporter", "op_sys",
                    "component", "version", "chfield", "chfieldfrom",
                    "chfieldto", "chfieldvalue",
                    "email1", "emailtype1", "emailreporter1",
                    "emailassigned_to1", "emailcc1", "emailqa_contact1",
                    "email2", "emailtype2", "emailreporter2",
                    "emailassigned_to2", "emailcc2", "emailqa_contact2",
                    "changedin", "votes", "short_desc", "short_desc_type",
                    "long_desc", "long_desc_type", "bug_file_loc",
                    "bug_file_loc_type", "status_whiteboard",
                    "status_whiteboard_type") {
      $default{$name} = "";
      $type{$name} = 0;
  }
  
  
  foreach my $item (split(/\&/, $::buffer)) {
      my @el = split(/=/, $item);
      my $name = $el[0];
      my $value;
      if ($#el > 0) {
          $value = url_decode($el[1]);
      } else {
          $value = "";
      }
      if (defined $default{$name}) {
          if ($default{$name} ne "") {
              $default{$name} .= "|$value";
              $type{$name} = 1;
          } else {
              $default{$name} = $value;
          }
      }
  }
                    
  
  if ($default{'chfieldto'} eq "") {
      $default{'chfieldto'} = "Now";
  }
  
  
  
  my $namelist = "";
  
  foreach my $i (sort (keys %::COOKIE)) {
      if ($i =~ /^QUERY_/) {
          if ($::COOKIE{$i} ne "") {
              my $name = substr($i, 6); 
              $namelist .= "<OPTION>$name";
          }
      }
  }
  
  print "Set-Cookie: BUGLIST=
  Content-type: text/html\n\n";
  
  GetVersionTable();
  
  sub GenerateEmailInput {
      my ($id) = (@_);
      my $defstr = value_quote($default{"email$id"});
      my $deftype = $default{"emailtype$id"};
      if ($deftype eq "") {
          $deftype = "substring";
      }
      my $assignedto = ($default{"emailassigned_to$id"} eq "1") ? "checked" : "";
      my $reporter = ($default{"emailreporter$id"} eq "1") ? "checked" : "";
      my $cc = ($default{"emailcc$id"} eq "1") ? "checked" : "";
  
      if ($assignedto eq "" && $reporter eq "" && $cc eq "") {
          if ($id eq "1") {
              $assignedto = "checked";
          } else {
              $reporter = "checked";
          }
      }
  
      my $qapart = "";
      if (Param("useqacontact")) {
          my $qacontact =
              ($default{"emailqa_contact$id"} eq "1") ? "checked" : "";
          $qapart = qq|
  <tr>
  <td></td>
  <td>
  <input type="checkbox" name="emailqa_contact$id" value=1 $qacontact>QA Contact
  </td>
  </tr>
  |;
      }
  
      return qq|
  <table border=1 cellspacing=0 cellpadding=0>
  <tr><td>
  <table cellspacing=0 cellpadding=0>
  <tr>
  <td rowspan=2 valign=top><a href="helpemailquery.html">Email:</a>
  <input name="email$id" size="30" value="$defstr">&nbsp;matching as
  <SELECT NAME=emailtype$id>
  <OPTION VALUE="regexp">regexp
  <OPTION VALUE="notregexp">not regexp
  <OPTION SELECTED VALUE="substring">substring
  <OPTION VALUE="exact">exact
  </SELECT>
  </td>
  <td>
  <input type="checkbox" name="emailassigned_to$id" value=1 $assignedto>Assigned To
  </td>
  </tr>
  <tr>
  <td>
  <input type="checkbox" name="emailreporter$id" value=1 $reporter>Reporter
  </td>
  </tr>$qapart
  <tr>
  <td align=right>(Will match any of the selected fields)</td>
  <td>
  <input type="checkbox" name="emailcc$id" value=1 $cc>CC &nbsp;&nbsp;
  </td>
  </tr>
  </table>
  </table>
  |;
  }
  
  
              
  
  
  my $emailinput1 = GenerateEmailInput(1);
  my $emailinput2 = GenerateEmailInput(2);
  
  
  # javascript
      
  my $jscript = << 'ENDSCRIPT';
  <script language="Javascript1.2" type="text/javascript">
  <!--
  function array_push(str)
  {
     this[this.length] = str;
     return this;
  }
  Array.prototype.push = array_push;
  
  var cpts = new Array();
  var vers = new Array();
  var agt=navigator.userAgent.toLowerCase();
  ENDSCRIPT
  
  
  my $p;
  my $v;
  my $c;
  my $i = 0;
  my $j = 0;
  
  foreach $c (@::legal_components) {
      $jscript .= "cpts['$c'] = new Array();\n";
  }
  
  foreach $v (@::legal_versions) {
      $jscript .= "vers['$v'] = new Array();\n";
  }
  
  
  for $p (@::legal_product) {
      if ($::components{$p}) {
          foreach $c (@{$::components{$p}}) {
              $jscript .= "cpts['$c'].push('$p');\n";
          }
      }
  
      if ($::versions{$p}) {
          foreach $v (@{$::versions{$p}}) {
              $jscript .= "vers['$v'].push('$p');\n";
          }
      }
  }
  
  $i = 0;
  $jscript .= q{
  
  // Only display versions/components valid for selected product(s)
  
  function selectProduct(f) {
      var agt=navigator.userAgent.toLowerCase();
      // Netscape 4.04 and 4.05 also choke with an "undefined"
      // error.  if someone can figure out how to "define" the
      // whatever, we'll remove this hack.  in the mean time, we'll
      // assume that v4.00-4.03 also die, so we'll disable the neat
      // javascript stuff for Netscape 4.05 and earlier.
      var agtver = parseFloat(navigator.appVersion);
      if (agtver <= 4.05 ) return;
  
      var cnt = 0;
      var i;
      var j;
      for (i=0 ; i<f.product.length ; i++) {
          if (f.product[i].selected) {
              cnt++;
          }
      }
      var doall = (cnt == f.product.length || cnt == 0);
  
      var csel = new Array();
      for (i=0 ; i<f.component.length ; i++) {
          if (f.component[i].selected) {
              csel[f.component[i].value] = 1;
          }
      }
  
      f.component.options.length = 0;
  
      for (c in cpts) {
          var doit = doall;
          for (i=0 ; !doit && i<f.product.length ; i++) {
              if (f.product[i].selected) {
                  var p = f.product[i].value;
                  for (j in cpts[c]) {
                      var p2 = cpts[c][j];
                      if (p2 == p) {
                          doit = true;
                          break;
                      }
                  }
              }
          }
          if (doit) {
              var l = f.component.length;
              f.component[l] = new Option(c, c);
              if (csel[c]) {
                  f.component[l].selected = true;
              }
          }
      }
  
      var vsel = new Array();
      for (i=0 ; i<f.version.length ; i++) {
          if (f.version[i].selected) {
              vsel[f.version[i].value] = 1;
          }
      }
  
      f.version.options.length = 0;
  
      for (v in vers) {
          var doit = doall;
          for (i=0 ; !doit && i<f.product.length ; i++) {
              if (f.product[i].selected) {
                  var p = f.product[i].value;
                  for (j in vers[v]) {
                      var p2 = vers[v][j];
                      if (p2 == p) {
                          doit = true;
                          break;
                      }
                  }
              }
          }
          if (doit) {
              var l = f.version.length;
              f.version[l] = new Option(v, v);
              if (vsel[v]) {
                  f.version[l].selected = true;
              }
          }
      }
  
  
  
  
  }
  // -->
  </script>
  
  };
  
  
  
  # Muck the "legal product" list so that the default one is always first (and
  # is therefore visibly selected.
  
  # Commented out, until we actually have enough products for this to matter.
  
  # set w [lsearch $legal_product $default{"product"}]
  # if {$w >= 0} {
  #    set legal_product [concat $default{"product"} [lreplace $legal_product $w $w]]
  # }
  
  PutHeader("Bugzilla Query Page", "Query Page", "",
            q{onLoad="selectProduct(document.forms[0]);"});
  
  push @::legal_resolution, "---"; # Oy, what a hack.
  push @::legal_target_milestone, "---"; # Oy, what a hack.
  
  print $jscript;
  
  my @logfields = ("[Bug creation]", @::log_columns);
  
  print "
  <FORM METHOD=GET ACTION=\"buglist.cgi\">
  
  <table>
  <tr>
  <th align=left><A HREF=\"bug_status.html\">Status</a>:</th>
  <th align=left><A HREF=\"bug_status.html\">Resolution</a>:</th>
  <th align=left><A HREF=\"bug_status.html#rep_platform\">Platform</a>:</th>
  <th align=left><A HREF=\"bug_status.html#op_sys\">OpSys</a>:</th>
  <th align=left><A HREF=\"bug_status.html#priority\">Priority</a>:</th>
  <th align=left><A HREF=\"bug_status.html#severity\">Severity</a>:</th>
  </tr>
  <tr>
  <td align=left valign=top>
  <SELECT NAME=\"bug_status\" MULTIPLE SIZE=7>
  @{[make_options(\@::legal_bug_status, $default{'bug_status'}, $type{'bug_status'})]}
  </SELECT>
  </td>
  <td align=left valign=top>
  <SELECT NAME=\"resolution\" MULTIPLE SIZE=7>
  @{[make_options(\@::legal_resolution, $default{'resolution'}, $type{'resolution'})]}
  </SELECT>
  </td>
  <td align=left valign=top>
  <SELECT NAME=\"rep_platform\" MULTIPLE SIZE=7>
  @{[make_options(\@::legal_platform, $default{'rep_platform'}, $type{'rep_platform'})]}
  </SELECT>
  </td>
  <td align=left valign=top>
  <SELECT NAME=\"op_sys\" MULTIPLE SIZE=7>
  @{[make_options(\@::legal_opsys, $default{'op_sys'}, $type{'op_sys'})]}
  </SELECT>
  </td>
  <td align=left valign=top>
  <SELECT NAME=\"priority\" MULTIPLE SIZE=7>
  @{[make_options(\@::legal_priority, $default{'priority'}, $type{'priority'})]}
  </SELECT>
  </td>
  <td align=left valign=top>
  <SELECT NAME=\"bug_severity\" MULTIPLE SIZE=7>
  @{[make_options(\@::legal_severity, $default{'bug_severity'}, $type{'bug_severity'})]}
  </SELECT>
  </tr>
  </table>
  
  <p>
  
  <table>
  <tr><td colspan=2>
  $emailinput1<p>
  </td></tr><tr><td colspan=2>
  $emailinput2<p>
  </td></tr>
  <tr>
  <td>
  Changed in the <NOBR>last <INPUT NAME=changedin SIZE=2 VALUE=\"$default{'changedin'}\"> days.</NOBR>
  </td>
  <td align=right>
  At <NOBR>least <INPUT NAME=votes SIZE=3 VALUE=\"$default{'votes'}\"> votes.</NOBR>
  </tr>
  </table>
  
  
  <table>
  <tr>
  <td rowspan=2 align=right>Where the field(s)
  </td><td rowspan=2>
  <SELECT NAME=\"chfield\" MULTIPLE SIZE=4>
  @{[make_options(\@logfields, $default{'chfield'}, $type{'chfield'})]}
  </SELECT>
  </td><td rowspan=2>
  changed.
  </td><td>
  <nobr>dates <INPUT NAME=chfieldfrom SIZE=10 VALUE=\"$default{'chfieldfrom'}\"></nobr>
  <nobr>to <INPUT NAME=chfieldto SIZE=10 VALUE=\"$default{'chfieldto'}\"></nobr>
  </td>
  </tr>
  <tr>
  <td>changed to value <nobr><INPUT NAME=chfieldvalue SIZE=10> (optional)</nobr>
  </td>
  </table>
  
  
  <P>
  
  <table>
  <tr>
  <TH ALIGN=LEFT VALIGN=BOTTOM>Program:</th>
  <TH ALIGN=LEFT VALIGN=BOTTOM>Version:</th>
  <TH ALIGN=LEFT VALIGN=BOTTOM><A HREF=describecomponents.cgi>Component:</a></th>
  ";
  
  if (Param("usetargetmilestone")) {
      print "<TH ALIGN=LEFT VALIGN=BOTTOM>Target Milestone:</th>";
  }
  
  print "
  </tr>
  <tr>
  
  <td align=left valign=top>
  <SELECT NAME=\"product\" MULTIPLE SIZE=5 onChange=\"selectProduct(this.form);\">
  @{[make_options(\@::legal_product, $default{'product'}, $type{'product'})]}
  </SELECT>
  </td>
  
  <td align=left valign=top>
  <SELECT NAME=\"version\" MULTIPLE SIZE=5>
  @{[make_options(\@::legal_versions, $default{'version'}, $type{'version'})]}
  </SELECT>
  </td>
  
  <td align=left valign=top>
  <SELECT NAME=\"component\" MULTIPLE SIZE=5>
  @{[make_options(\@::legal_components, $default{'component'}, $type{'component'})]}
  </SELECT>
  </td>";
  
  if (Param("usetargetmilestone")) {
      print "
  <td align=left valign=top>
  <SELECT NAME=\"target_milestone\" MULTIPLE SIZE=5>
  @{[make_options(\@::legal_target_milestone, $default{'component'}, $type{'component'})]}
  </SELECT>
  </td>";
  }
  
  
  sub StringSearch {
      my ($desc, $name) = (@_);
      my $type = $name . "_type";
      my $def = value_quote($default{$name});
      print qq{<tr>
  <td align=right>$desc:</td>
  <td><input name=$name size=30 value="$def"></td>
  <td><SELECT NAME=$type>
  };
      if ($default{$type} eq "") {
          $default{$type} = "substring";
      }
      foreach my $i (["substring", "case-insensitive substring"],
                     ["casesubstring", "case-sensitive substring"],
                     ["regexp", "regular expression"],
                     ["notregexp", "not ( regular expression )"]) {
          my ($n, $d) = (@$i);
          my $sel = "";
          if ($default{$type} eq $n) {
              $sel = " SELECTED";
          }
          print qq{<OPTION VALUE="$n"$sel>$d\n};
      }
      print "</SELECT></TD>
  </tr>
  ";
  }
  
  print "
  </tr>
  </table>
  
  <table border=0>
  ";
  
  StringSearch("Summary", "short_desc");
  StringSearch("Description", "long_desc");
  StringSearch("URL", "bug_file_loc");
  
  if (Param("usestatuswhiteboard")) {
      StringSearch("Status whiteboard", "status_whiteboard");
  }
  
  print "
  </table>
  <p>
  
  
  
  <BR>
  <INPUT TYPE=radio NAME=cmdtype VALUE=doit CHECKED> Run this query
  <BR>
  ";
  
  if ($namelist ne "") {
      print "
  <table cellspacing=0 cellpadding=0><tr>
  <td><INPUT TYPE=radio NAME=cmdtype VALUE=editnamed> Load the remembered query:</td>
  <td rowspan=3><select name=namedcmd>$namelist</select>
  </tr><tr>
  <td><INPUT TYPE=radio NAME=cmdtype VALUE=runnamed> Run the remembered query:</td>
  </tr><tr>
  <td><INPUT TYPE=radio NAME=cmdtype VALUE=forgetnamed> Forget the remembered query:</td>
  </tr></table>"
  }
  
  print "
  <INPUT TYPE=radio NAME=cmdtype VALUE=asdefault> Remember this as the default query
  <BR>
  <INPUT TYPE=radio NAME=cmdtype VALUE=asnamed> Remember this query, and name it:
  <INPUT TYPE=text NAME=newqueryname>
  <BR>
  
  <NOBR><B>Sort By:</B>
  <SELECT NAME=\"order\">
    <OPTION>Bug Number
    <OPTION SELECTED>\"Importance\"
    <OPTION>Assignee
  </SELECT></NOBR>
  <INPUT TYPE=\"submit\" VALUE=\"Submit query\">
  <INPUT TYPE=\"reset\" VALUE=\"Reset back to the default query\">
  <INPUT TYPE=hidden name=form_name VALUE=query>
  <BR>Give me a <A HREF=\"help.html\">clue</A> about how to use this form.
  </FORM>
  
  ";
  
  
  quietly_check_login();
  
  if (UserInGroup("tweakparams")) {
      print "<a href=editparams.cgi>Edit Bugzilla operating parameters</a><br>\n";
  }
  if (UserInGroup("editcomponents")) {
      print "<a href=editproducts.cgi>Edit Bugzilla products and components</a><br>\n";
  }
  if (defined $::COOKIE{"Bugzilla_login"}) {
      print "<a href=relogin.cgi>Log in as someone besides <b>$::COOKIE{'Bugzilla_login'}</b></a><br>\n";
  }
  print "<a href=changepassword.cgi>Change your password or preferences.</a><br>\n";
  print "<a href=\"enter_bug.cgi\">Create a new bug.</a><br>\n";
  print "<a href=\"createaccount.cgi\">Open a new Bugzilla account</a><br>\n";
  print "<a href=\"reports.cgi\">Bug reports</a><br>\n";
  
  
  
  1.1                  apache-site/bugs/relogin.cgi
  
  Index: relogin.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  
  print "Set-Cookie: Bugzilla_login= ; path=/; expires=Sun, 30-Jun-80 00:00:00 GMT
  Set-Cookie: Bugzilla_logincookie= ; path=/; expires=Sun, 30-Jun-80 00:00:00 GMT
  Set-Cookie: Bugzilla_password= ; path=/; expires=Sun, 30-Jun-80 00:00:00 GMT
  Content-type: text/html
  
  ";
  PutHeader ("Relogin");
  
  print "<B>Your login has been forgotten</B>.</P>
  The cookie that was remembering your login is now gone.  The next time you
  do an action that requires a login, you will be prompted for it.
  <p>
  ";
  
  navigation_header();
  
  exit;
  
  
  
  
  1.1                  apache-site/bugs/reports.cgi
  
  Index: reports.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Harrison Page <ha...@netscape.com>,
  # Terry Weissman <te...@mozilla.org>,
  # Dawn Endico <en...@mozilla.org>
  # Bryce Nesbitt <br...@nextbus.COM>,
  #    Added -All- report, change "nobanner" to "banner" (it is strange to have a
  #    list with 2 positive and 1 negative choice), default links on, add show
  #    sql comment.
  
  use diagnostics;
  use strict;
  eval "use Chart::Lines";
  
  require "CGI.pl";
  require "globals.pl";
  
  use vars @::legal_product;
  
  my $dir = "data/mining";
  my $week = 60 * 60 * 24 * 7;
  my @status = qw (NEW ASSIGNED REOPENED);
  my %bugsperperson;
  
  # while this looks odd/redundant, it allows us to name
  # functions differently than the value passed in
  
  my %reports = 
  	( 
  	"most_doomed" => \&most_doomed,
  	"most_doomed_for_milestone" => \&most_doomed_for_milestone,
  	"most_recently_doomed" => \&most_recently_doomed,
  	"show_chart" => \&show_chart,
  	);
  
  print "Content-type: text/html\n\n";
  
  # If we're here for the first time, give a banner.  Else respect the banner flag.
  if ( (!defined $::FORM{'product'}) || ($::FORM{'banner'})  )
          {
          PutHeader ("Bug Reports")
          }
  else
          {
  	print("<html><head><title>Bug Reports</title></head><body bgcolor=\"#FFFFFF\">");
          }
  
  ConnectToDatabase();
  GetVersionTable();
  
  my @myproducts;
  push( @myproducts, "-All-", @::legal_product );
  
  $::FORM{'output'} = $::FORM{'output'} || "most_doomed"; # a reasonable default
  
  if (! defined $::FORM{'product'})
  	{
  	&choose_product;
  	}
  else
  	{
  	# we want to be careful about what subroutines 
  	# can be called from outside. modify %reports
  	# accordingly when a new report type is added
  
  	if (! exists $reports{$::FORM{'output'}})
  		{
  		$::FORM{'output'} = "most_doomed"; # a reasonable default
  		}
  	
  	my $f = $reports{$::FORM{'output'}};
  
  	if (! defined $f)
  		{
  		print "start over, your form data was all messed up.<p>\n";
  		foreach (keys %::FORM)
  			{
  			print "<font color=blue>$_</font> : " . 
  				($::FORM{$_} ? $::FORM{$_} : "undef") . "<br>\n";
  			}
  		exit;
  		}
  
  	&{$f};
  	}
  
  print <<FIN;
  <p>
  </body>
  </html>
  FIN
  
  ##################################
  # user came in with no form data #
  ##################################
  
  sub choose_product
  	{
  	my $product_popup = make_options (\@myproducts, $myproducts[0]);
  	my $charts = defined $Chart::Lines::VERSION && -d $dir ? "<option value=\"show_chart\">Bug Charts" : "";
  	# get rid of warning:
  	$Chart::Lines::VERSION = $Chart::Lines::VERSION;
  
  	print <<FIN;
  <center>
  <h1>Welcome to the Bugzilla Query Kitchen</h1>
  <form method=get action=reports.cgi>
  <table border=1 cellpadding=5>
  <tr>
  <td align=center><b>Product:</b></td>
  <td align=center>
  <select name="product">
  $product_popup
  </select>
  </td>
  </tr>
  <tr>
  <td align=center><b>Output:</b></td>
  <td align=center>
  <select name="output">
  <option value="most_doomed">Bug Counts
  FIN
          if (Param('usetargetmilestone')) {
              print "<option value=\"most_doomed_for_milestone\">Most Doomed";
              }
          print "<option value=\"most_recently_doomed\">Most Recently Doomed";
  	print <<FIN;
  $charts
  </select>
  <tr>
  <td align=center><b>Switches:</b></td>
  <td align=left>
  <input type=checkbox name=links checked value=1>&nbsp;Links to Bugs<br>
  <input type=checkbox name=banner checked value=1>&nbsp;Banner<br>
  FIN
  	if (Param('usequip')) {
  	    print "<input type=checkbox name=quip value=1>&nbsp;Quip<br>";
  	} else {
              print "<input type=hidden name=quip value=0>";
          }
  	print <<FIN;
  </td>
  </tr>
  <tr>
  <td colspan=2 align=center>
  <input type=submit value=Continue>
  </td>
  </tr>
  </table>
  </form>
  <p>
  FIN
  #Add this above to get a control for showing the SQL query:
  #<input type=checkbox name=showsql value=1>&nbsp;Show SQL<br>
  	}
  
  sub most_doomed
  	{
  	my $when = localtime (time);
  
  	print <<FIN;
  <center>
  <h1>
  Bug Report for $::FORM{'product'}
  </h1>
  $when<p>
  FIN
  
  # Build up $query string
  	my $query;
  	$query = <<FIN;
  select 
  	bugs.bug_id, bugs.assigned_to, bugs.bug_severity,
  	bugs.bug_status, bugs.product, 
  	assign.login_name,
  	report.login_name,
  	unix_timestamp(date_format(bugs.creation_ts, '%Y-%m-%d %h:%m:%s'))
  
  from   bugs,
         profiles assign,
         profiles report,
         versions projector
  where  bugs.assigned_to = assign.userid
  and    bugs.reporter = report.userid
  FIN
  
  	if( $::FORM{'product'} ne "-All-" ) {
  		$query .= "and    bugs.product='$::FORM{'product'}'";
  	}
  
  	$query .= <<FIN;
  and 	 
  	( 
  	bugs.bug_status = 'NEW' or 
  	bugs.bug_status = 'ASSIGNED' or 
  	bugs.bug_status = 'REOPENED'
  	)
  FIN
  # End build up $query string
  
  	print "<font color=purple><tt>$query</tt></font><p>\n" 
  		unless (! exists $::FORM{'showsql'});
  
  	SendSQL ($query);
  	
  	my $c = 0;
  
  	my $quip = "Summary";
  	my $bugs_count = 0;
  	my $bugs_new_this_week = 0;
  	my $bugs_reopened = 0;
  	my %bugs_owners;
  	my %bugs_summary;
  	my %bugs_status;
  	my %bugs_totals;
  	my %bugs_lookup;
  
  	#############################
  	# suck contents of database # 
  	#############################
  
  	while (my ($bid, $a, $sev, $st, $prod, $who, $rep, $ts) = FetchSQLData())
  		{
  		next if (exists $bugs_lookup{$bid});
  		
  		$bugs_lookup{$bid} ++;
  		$bugs_owners{$who} ++;
  		$bugs_new_this_week ++ if (time - $ts <= $week);
  		$bugs_status{$st} ++;
  		$bugs_count ++;
  		
  		push @{$bugs_summary{$who}{$st}}, $bid;
  		
  		$bugs_totals{$who}{$st} ++;
  		}
  
  	if ($::FORM{'quip'})
  		{
  		if (open (COMMENTS, "<data/comments")) 
  			{
      	my @cdata;
  			while (<COMMENTS>) 
  				{
  				push @cdata, $_;
  				}
  			close COMMENTS;
  			$quip = "<i>" . $cdata[int(rand($#cdata + 1))] . "</i>";
  			}
  		} 
  
  	#########################
  	# start painting report #
  	#########################
  
          $bugs_status{'NEW'}      ||= '0';
          $bugs_status{'ASSIGNED'} ||= '0';
          $bugs_status{'REOPENED'} ||= '0';
  	print <<FIN;
  <h1>$quip</h1>
  <table border=1 cellpadding=5>
  <tr>
  <td align=right><b>New Bugs This Week</b></td>
  <td align=center>$bugs_new_this_week</td>
  </tr>
  
  <tr>
  <td align=right><b>Bugs Marked New</b></td>
  <td align=center>$bugs_status{'NEW'}</td>
  </tr>
  
  <tr>
  <td align=right><b>Bugs Marked Assigned</b></td>
  <td align=center>$bugs_status{'ASSIGNED'}</td>
  </tr>
  
  <tr>
  <td align=right><b>Bugs Marked Reopened</b></td>
  <td align=center>$bugs_status{'REOPENED'}</td>
  </tr>
  
  <tr>
  <td align=right><b>Total Bugs</b></td>
  <td align=center>$bugs_count</td>
  </tr>
  
  </table>
  <p>
  FIN
  
  	if ($bugs_count == 0)
  		{
  		print "No bugs found!\n";
  		exit;
  		}
  	
  	print <<FIN;
  <h1>Bug Count by Engineer</h1>
  <table border=3 cellpadding=5>
  <tr>
  <td align=center bgcolor="#DDDDDD"><b>Owner</b></td>
  <td align=center bgcolor="#DDDDDD"><b>New</b></td>
  <td align=center bgcolor="#DDDDDD"><b>Assigned</b></td>
  <td align=center bgcolor="#DDDDDD"><b>Reopened</b></td>
  <td align=center bgcolor="#DDDDDD"><b>Total</b></td>
  </tr>
  FIN
  
  	foreach my $who (sort keys %bugs_summary)
  		{
  		my $bugz = 0;
  	 	print <<FIN;
  <tr>
  <td align=left><tt>$who</tt></td>
  FIN
  		
  		foreach my $st (@status)
  			{
  			$bugs_totals{$who}{$st} += 0;
  			print <<FIN;
  <td align=center>$bugs_totals{$who}{$st}
  FIN
  			$bugz += $#{$bugs_summary{$who}{$st}} + 1;
  			}
  		
  		print <<FIN;
  <td align=center>$bugz</td>
  </tr>
  FIN
  		}
  	
  	print <<FIN;
  </table>
  <p>
  FIN
  
  	###############################
  	# individual bugs by engineer #
  	###############################
  
  	print <<FIN;
  <h1>Individual Bugs by Engineer</h1>
  <table border=1 cellpadding=5>
  <tr>
  <td align=center bgcolor="#DDDDDD"><b>Owner</b></td>
  <td align=center bgcolor="#DDDDDD"><b>New</b></td>
  <td align=center bgcolor="#DDDDDD"><b>Assigned</b></td>
  <td align=center bgcolor="#DDDDDD"><b>Reopened</b></td>
  </tr>
  FIN
  
  	foreach my $who (sort keys %bugs_summary)
  		{
  		print <<FIN;
  <tr>
  <td align=left><tt>$who</tt></td>
  FIN
  
  		foreach my $st (@status)
  			{
  			my @l;
  
  			foreach (sort { $a <=> $b } @{$bugs_summary{$who}{$st}})
  				{
  				if ($::FORM{'links'})
  					{
  					push @l, "<a href=\"show_bug.cgi?id=$_\">$_</a>\n"; 
  					}
  				else
  					{
  					push @l, $_;
  					}
  				}
  				
  			my $bugz = join ' ', @l;
  			$bugz = "&nbsp;" unless ($bugz);
  			
  			print <<FIN
  <td align=left>$bugz</td>
  FIN
  			}
  
  		print <<FIN;
  </tr>
  FIN
  		}
  
  	print <<FIN;
  </table>
  <p>
  FIN
  	}
  
  sub is_legal_product
  	{
  	my $product = shift;
  	return grep { $_ eq $product} @myproducts;
  	}
  
  sub show_chart
  	{
    my $when = localtime (time);
  
  	if (! is_legal_product ($::FORM{'product'}))
  		{
  		&die_politely ("Unknown product: $::FORM{'product'}");
  		}
  
    print <<FIN;
  <center>
  FIN
  	
  	my @dates;
  	my @open; my @assigned; my @reopened;
  
          my $prodname = $::FORM{'product'};
  
          $prodname =~ s/\//-/gs;
  
          my $file = join '/', $dir, $prodname;
  	my $image = "$file.gif";
  
  	if (! open FILE, $file)
  		{
  		&die_politely ("The tool which gathers bug counts has not been run yet.");
  		}
  	
  	while (<FILE>)
  		{
  		chomp;
  		next if ($_ =~ /^#/ or ! $_);
  		my ($date, $open, $assigned, $reopened) = split /\|/, $_;
  		my ($yy, $mm, $dd) = $date =~ /^\d{2}(\d{2})(\d{2})(\d{2})$/;
  
  		push @dates, "$mm/$dd/$yy";
  		push @open, $open;
  		push @assigned, $assigned;
  		push @reopened, $reopened;
  		}
  	
  	close FILE;
  
  	if ($#dates < 1)
  		{
  		&die_politely ("We don't have enough data points to make a graph (yet)");
  		}
  	
  	my $img = Chart::Lines->new (800, 600);
  	my @labels = qw (New Assigned Reopened);
  	my @when;
  	my $i = 0;
  	my @data;
  
  	push @data, \@dates;
  	push @data, \@open;
  	push @data, \@assigned;
  	push @data, \@reopened;
  
      my $MAXTICKS = 20;      # Try not to show any more x ticks than this.
      my $skip = 1;
      if (@dates > $MAXTICKS) {
          $skip = int((@dates + $MAXTICKS - 1) / $MAXTICKS);
      }
  
  	my %settings =
  		(
  		"title" => "Bug Charts for $::FORM{'product'}",
  		"x_label" => "Dates",
  		"y_label" => "Bug Count",
  		"legend_labels" => \@labels,
          "skip_x_ticks" => $skip,
  		);
  	
  	$img->set (%settings);
  	
  	open IMAGE, ">$image" or die "$image: $!";
  	$img->gif (*IMAGE, \@data);
  	close IMAGE;
  
  	print <<FIN;
  <img src="$image">
  <br clear=left>
  <br>
  FIN
  	}
  
  sub die_politely
  	{
  	my $msg = shift;
  
  	print <<FIN;
  <p>
  <table border=1 cellpadding=10>
  <tr>
  <td align=center>
  <font color=blue>Sorry, but ...</font>
  <p>
  There is no graph available for <b>$::FORM{'product'}</b><p>
  
  <font size=-1>
  $msg
  <p>
  </font>
  </td>
  </tr>
  </table>
  <p>
  FIN
  	
  	exit;
  	}
  
  sub bybugs {
     $bugsperperson{$a} <=> $bugsperperson{$b}
     }
  
  sub most_doomed_for_milestone
  	{
  	my $when = localtime (time);
          my $ms = "M" . Param("curmilestone");
          my $quip = "Summary";
  
  	print "<center>\n<h1>";
          if( $::FORM{'product'} ne "-All-" ) {
              print "Most Doomed for $ms ($::FORM{'product'})";
          } else {
              print "Most Doomed for $ms";
              }
          print "</h1>\n$when<p>\n";
  
  	#########################
  	# start painting report #
  	#########################
  
  	if ($::FORM{'quip'})
                  {
                  if (open (COMMENTS, "<data/comments"))
                          {
                          my @cdata;
                          while (<COMMENTS>)
                                  {
                                  push @cdata, $_;
                                  }
                          close COMMENTS;
                          $quip = "<i>" . $cdata[int(rand($#cdata + 1))] . "</i>";                        }
                  }
  
  
          # Build up $query string
  	my $query;
  	$query = "select distinct assigned_to from bugs where target_milestone=\"$ms\"";
  	if( $::FORM{'product'} ne "-All-" ) {
  		$query .= "and    bugs.product='$::FORM{'product'}'";
  	}
  	$query .= <<FIN;
  and 	 
  	( 
  	bugs.bug_status = 'NEW' or 
  	bugs.bug_status = 'ASSIGNED' or 
  	bugs.bug_status = 'REOPENED'
  	)
  FIN
  # End build up $query string
  
          SendSQL ($query);
          my @people = ();
          while (my ($person) = FetchSQLData())
              {
              push @people, $person;
              }
  
          #############################
          # suck contents of database # 
          #############################
          my $person = "";
          my $bugtotal = 0;
          foreach $person (@people)
                  {
                  my $query = "select count(bug_id) from bugs,profiles where target_milestone=\"$ms\" and userid=assigned_to and userid=\"$person\"";
  	        if( $::FORM{'product'} ne "-All-" ) {
                      $query .= "and    bugs.product='$::FORM{'product'}'";
                      }
  	        $query .= <<FIN;
  and 	 
  	( 
  	bugs.bug_status = 'NEW' or 
  	bugs.bug_status = 'ASSIGNED' or 
  	bugs.bug_status = 'REOPENED'
  	)
  FIN
                  SendSQL ($query);
  	        my $bugcount = FetchSQLData();
                  $bugsperperson{$person} = $bugcount;
                  $bugtotal += $bugcount;
                  }
  
  #       sort people by the number of bugs they have assigned to this milestone
          @people = sort bybugs @people;
          my $totalpeople = @people;
                  
          print "<TABLE>\n";
          print "<TR><TD COLSPAN=2>\n";
          print "$totalpeople engineers have $bugtotal $ms bugs and features.\n";
          print "</TD></TR>\n";
  
          while (@people)
                  {
                  $person = pop @people;
                  print "<TR><TD>\n";
                  SendSQL("select login_name from profiles where userid=$person");
                  my $login_name= FetchSQLData();
                  print("<A HREF=\"buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&target_milestone=$ms&assigned_to=$login_name\">\n");
                  print("$bugsperperson{$person}  bugs and features");
                  print("</A>");
                  print(" for \n");
                  print("<A HREF=\"mailto:$login_name\">");
                  print("$login_name");
                  print("</A>\n");
                  print("</TD><TD>\n");
  
                  $person = pop @people;
                  if ($person) {
                      SendSQL("select login_name from profiles where userid=$person");
                      my $login_name= FetchSQLData();
                      print("<A HREF=\"buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&target_milestone=$ms&assigned_to=$login_name\">\n");
                      print("$bugsperperson{$person}  bugs and features");
                      print("</A>");
                      print(" for \n");
                      print("<A HREF=\"mailto:$login_name\">");
                      print("$login_name");
                      print("</A>\n");
                      print("</TD></TR>\n\n");
                      }
                  }
          print "</TABLE>\n";
  
          }
  
  
  
  sub most_recently_doomed
  	{
  	my $when = localtime (time);
          my $ms = "M" . Param("curmilestone");
          my $quip = "Summary";
  
  	print "<center>\n<h1>";
          if( $::FORM{'product'} ne "-All-" ) {
              print "Most Recently Doomed ($::FORM{'product'})";
          } else {
              print "Most Recently Doomed";
              }
          print "</h1>\n$when<p>\n";
  
  	#########################
  	# start painting report #
  	#########################
  
  	if ($::FORM{'quip'})
                  {
                  if (open (COMMENTS, "<data/comments"))
                          {
                          my @cdata;
                          while (<COMMENTS>)
                                  {
                                  push @cdata, $_;
                                  }
                          close COMMENTS;
                          $quip = "<i>" . $cdata[int(rand($#cdata + 1))] . "</i>";                        }
                  }
  
  
          # Build up $query string
  	my $query;
          $query = "select distinct assigned_to from bugs where bugs.bug_status='NEW' and target_milestone='' and bug_severity!='enhancement' and status_whiteboard='' and (product='Browser' or product='MailNews')";
  	if( $::FORM{'product'} ne "-All-" ) {
  		$query .= "and    bugs.product='$::FORM{'product'}'";
  	}
  
  # End build up $query string
  
          SendSQL ($query);
          my @people = ();
          while (my ($person) = FetchSQLData())
              {
              push @people, $person;
              }
  
          #############################
          # suck contents of database # 
          #############################
          my $person = "";
          my $bugtotal = 0;
          foreach $person (@people)
                  {
                  my $query = "select count(bug_id) from bugs,profiles where bugs.bug_status='NEW' and userid=assigned_to and userid='$person' and target_milestone='' and bug_severity!='enhancement' and status_whiteboard='' and (product='Browser' or product='MailNews')";
  	        if( $::FORM{'product'} ne "-All-" ) {
                      $query .= "and    bugs.product='$::FORM{'product'}'";
                      }
                  SendSQL ($query);
  	        my $bugcount = FetchSQLData();
                  $bugsperperson{$person} = $bugcount;
                  $bugtotal += $bugcount;
                  }
  
  #       sort people by the number of bugs they have assigned to this milestone
          @people = sort bybugs @people;
          my $totalpeople = @people;
          
          if ($totalpeople > 20) {
              splice @people, 0, $totalpeople-20;
              }
                  
          print "<TABLE>\n";
          print "<TR><TD COLSPAN=2>\n";
          print "$totalpeople engineers have $bugtotal untouched new bugs.\n";
          if ($totalpeople > 20) {
              print "These are the 20 most doomed.";
              }
          print "</TD></TR>\n";
  
          while (@people)
                  {
                  $person = pop @people;
                  print "<TR><TD>\n";
                  SendSQL("select login_name from profiles where userid=$person");
                  my $login_name= FetchSQLData();
                  print("<A HREF=\"buglist.cgi?bug_status=NEW&email1=$login_name&emailtype1=substring&emailassigned_to1=1&product=Browser&product=MailNews&target_milestone=---&status_whiteboard=.&status_whiteboard_type=notregexp&bug_severity=blocker&bug_severity=critical&bug_severity=major&bug_severity=normal&bug_severity=minor&bug_severity=trivial\">\n"); 
                  print("$bugsperperson{$person}  bugs");
                  print("</A>");
                  print(" for \n");
                  print("<A HREF=\"mailto:$login_name\">");
                  print("$login_name");
                  print("</A>\n");
                  print("</TD><TD>\n");
  
                  $person = pop @people;
                  if ($person) {
                      SendSQL("select login_name from profiles where userid=$person");
                      my $login_name= FetchSQLData();
                      print("<A HREF=\"buglist.cgi?bug_status=NEW&email1=$login_name&emailtype1=substring&emailassigned_to1=1&product=Browser&product=MailNews&target_milestone=---&status_whiteboard=.&status_whiteboard_type=notregexp&bug_severity=blocker&bug_severity=critical&bug_severity=major&bug_severity=normal&bug_severity=minor&bug_severity=trivial\">\n"); 
                      print("$bugsperperson{$person}  bugs");
                      print("</A>");
                      print(" for \n");
                      print("<A HREF=\"mailto:$login_name\">");
                      print("$login_name");
                      print("</A>\n");
                      print("</TD></TR>\n\n");
                      }
                  }
          print "</TABLE>\n";
  
          }
  
  
  
  1.1                  apache-site/bugs/sanitycheck.cgi
  
  Index: sanitycheck.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  use vars %::FORM;
  
  print "Content-type: text/html\n";
  print "\n";
  
  ConnectToDatabase();
  
  my $offervotecacherebuild = 0;
  
  sub Status {
      my ($str) = (@_);
      print "$str <P>\n";
  }
  
  sub Alert {
      my ($str) = (@_);
      Status("<font color=red>$str</font>");
  }
  
  sub BugLink {
      my ($id) = (@_);
      return "<a href='show_bug.cgi?id=$id'>$id</a>";
  }
  
  sub AlertBadVoteCache {
      my ($id) = (@_);
      Alert("Bad vote cache for bug " . BugLink($id));
      $offervotecacherebuild = 1;
  }
  
  
  my @row;
  my @checklist;
  
  PutHeader("Bugzilla Sanity Check");
  
  if (exists $::FORM{'rebuildvotecache'}) {
      Status("OK, now rebuilding vote cache.");
      SendSQL("lock tables bugs write, votes read");
      SendSQL("update bugs set votes = 0, delta_ts=delta_ts");
      SendSQL("select bug_id, sum(count) from votes group by bug_id");
      my %votes;
      while (@row = FetchSQLData()) {
          my ($id, $v) = (@row);
          $votes{$id} = $v;
      }
      foreach my $id (keys %votes) {
          SendSQL("update bugs set votes = $votes{$id}, delta_ts=delta_ts where bug_id = $id");
      }
      SendSQL("unlock tables");
      Status("Vote cache has been rebuilt.");
  }
  
  print "OK, now running sanity checks.<P>\n";
  
  Status("Checking groups");
  SendSQL("select bit from groups where bit != pow(2, round(log(bit) / log(2)))");
  while (my $bit = FetchOneColumn()) {
      Alert("Illegal bit number found in group table: $bit");
  }
      
  SendSQL("select sum(bit) from groups where isbuggroup != 0");
  my $buggroupset = FetchOneColumn();
  if (!defined $buggroupset || $buggroupset eq "") {
      $buggroupset = 0;
  }
  SendSQL("select bug_id, groupset from bugs where groupset & $buggroupset != groupset");
  while (@row = FetchSQLData()) {
      Alert("Bad groupset $row[1] found in bug " . BugLink($row[0]));
  }
  
  
  
  
  Status("Checking version/products");
  
  SendSQL("select distinct product, version from bugs");
  while (@row = FetchSQLData()) {
      my @copy = @row;
      push(@checklist, \@copy);
  }
  
  foreach my $ref (@checklist) {
      my ($product, $version) = (@$ref);
      SendSQL("select count(*) from versions where program = '$product' and value = '$version'");
      if (FetchOneColumn() != 1) {
          Alert("Bug(s) found with invalid product/version: $product/$version");
      }
  }
  
  
  Status("Checking components/products");
  
  @checklist = ();
  SendSQL("select distinct product, component from bugs");
  while (@row = FetchSQLData()) {
      my @copy = @row;
      push(@checklist, \@copy);
  }
  
  foreach my $ref (@checklist) {
      my ($product, $component) = (@$ref);
      SendSQL("select count(*) from components where program = '$product' and value = '$component'");
      if (FetchOneColumn() != 1) {
          Alert("Bug(s) found with invalid product/component: $product/$component");
      }
  }
  
  
  Status("Checking profile ids...");
  
  SendSQL("select userid,login_name from profiles");
  
  my %profid;
  
  while (@row = FetchSQLData()) {
      my ($id, $email) = (@row);
      if ($email =~ /^[^@, ]*@[^@, ]*\.[^@, ]*$/) {
          $profid{$id} = 1;
      } else {
          Alert "Bad profile id $id &lt;$email&gt;."
      }
  }
  
  
  undef $profid{0};
  
  
  Status("Checking reporter/assigned_to/qa_contact ids");
  SendSQL("select bug_id,reporter,assigned_to,qa_contact,votes from bugs");
  
  my %votes;
  my %bugid;
  
  while (@row = FetchSQLData()) {
      my($id, $reporter, $assigned_to, $qa_contact, $v) = (@row);
      $bugid{$id} = 1;
      if (!defined $profid{$reporter}) {
          Alert("Bad reporter $reporter in " . BugLink($id));
      }
      if (!defined $profid{$assigned_to}) {
          Alert("Bad assigned_to $assigned_to in" . BugLink($id));
      }
      if ($qa_contact != 0 && !defined $profid{$qa_contact}) {
          Alert("Bad qa_contact $qa_contact in" . BugLink($id));
      }
      if ($v != 0) {
          $votes{$id} = $v;
      }
  }
  
  Status("Checking cached vote counts");
  SendSQL("select bug_id, sum(count) from votes group by bug_id");
  
  while (@row = FetchSQLData()) {
      my ($id, $v) = (@row);
      if ($v <= 0) {
          Alert("Bad vote sum for bug $id");
      } else {
          if (!defined $votes{$id} || $votes{$id} != $v) {
              AlertBadVoteCache($id);
          }
          delete $votes{$id};
      }
  }
  foreach my $id (keys %votes) {
      AlertBadVoteCache($id);
  }
  
  if ($offervotecacherebuild) {
      print qq{<a href="sanitycheck.cgi?rebuildvotecache=1">Click here to rebuild the vote cache</a><p>\n};
  }
  
  
  Status("Checking CC table");
  
  SendSQL("select bug_id,who from cc");
  while (@row = FetchSQLData()) {
      my ($id, $cc) = (@row);
      if (!defined $profid{$cc}) {
          Alert("Bad cc $cc in " . BugLink($id));
      }
  }
  
  
  Status("Checking activity table");
  
  SendSQL("select bug_id,who from bugs_activity");
  
  while (@row = FetchSQLData()) {
      my ($id, $who) = (@row);
      if (!defined $bugid{$id}) {
          Alert("Bad bugid " . BugLink($id));
      }
      if (!defined $profid{$who}) {
          Alert("Bad who $who in " . BugLink($id));
      }
  }
  
  
  Status("Checking dependency table");
  
  SendSQL("select blocked, dependson from dependencies");
  while (@row = FetchSQLData()) {
      my ($blocked, $dependson) = (@row);
      if (!defined $bugid{$blocked}) {
          Alert("Bad blocked " . BugLink($blocked));
      }
      if (!defined $bugid{$dependson}) {
          Alert("Bad dependson " . BugLink($dependson));
      }
  }
  
  
  
  Status("Sanity check completed.");
  
  
  
  1.1                  apache-site/bugs/show_activity.cgi
  
  Index: show_activity.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  print "Content-type: text/html\n\n";
  
  PutHeader("Changes made to bug $::FORM{'id'}", "Activity log",
            "Bug $::FORM{'id'}");
  
  ConnectToDatabase();
  
  DumpBugActivity($::FORM{'id'});
  
  print "<hr><a href=show_bug.cgi?id=$::FORM{'id'}>Back to bug $::FORM{'id'}</a>\n";
  
  
  
  1.1                  apache-site/bugs/show_bug.cgi
  
  Index: show_bug.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  ConnectToDatabase();
  
  if ($::FORM{'GoAheadAndLogIn'}) {
      confirm_login();
  }
  
  print "Content-type: text/html\n";
  print "\n";
  
  if (!defined $::FORM{'id'} || $::FORM{'id'} !~ /^\s*\d+\s*$/) {
      PutHeader("Search by bug number");
      print "<FORM METHOD=GET ACTION=\"show_bug.cgi\">\n";
      print "You may find a single bug by entering its bug id here: \n";
      print "<INPUT NAME=id>\n";
      print "<INPUT TYPE=\"submit\" VALUE=\"Show Me This Bug\">\n";
      print "</FORM>\n";
      exit;
  }
  
  GetVersionTable();
  
  PutHeader("Bugzilla bug $::FORM{'id'}", "Bugzilla Bug", $::FORM{'id'});
  navigation_header();
  
  print "<HR>\n";
  
  $! = 0;
  do "bug_form.pl" || die "Error doing bug_form.pl: $!";
  print "</BODY>";
  print "</HTML>\n";
  
  
  
  
  1.1                  apache-site/bugs/showattachment.cgi
  
  Index: showattachment.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  ConnectToDatabase();
  
  my @row;
  if (defined $::FORM{'attach_id'}) {
      SendSQL("select mimetype, thedata from attachments where attach_id = $::FORM{'attach_id'}");
      @row = FetchSQLData();
  }
  if (!@row) {
      print "Content-type: text/html\n\n";
      PutHeader("Bad ID");
      print "Please hit back and try again.\n";
      exit;
  }
  print qq{Content-type: $row[0]\n\n$row[1]};
  
      
  
  
  
  1.1                  apache-site/bugs/showdependencygraph.cgi
  
  Index: showdependencygraph.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  my $id = $::FORM{'id'};
  my $urlbase = Param("urlbase");
  
  my %seen;
  my %edgesdone;
  
  sub AddLink {
      my ($blocked, $dependson) = (@_);
      my $key = "$blocked,$dependson";
      if (!exists $edgesdone{$key}) {
          $edgesdone{$key} = 1;
          print DOT "$blocked -> $dependson\n";
          $seen{$blocked} = 1;
          $seen{$dependson} = 1;
      }
  }
  
  
  print "Content-type: text/html\n\n";
  PutHeader("Dependency graph", "Dependency graph", $id);
  
  $::FORM{'rankdir'} = "LR" if !defined $::FORM{'rankdir'};
  
  
  if (defined $id) {
      ConnectToDatabase();
      quietly_check_login();
      $::usergroupset = $::usergroupset; # More warning suppression silliness.
  
      mkdir("data/webdot", 0777);
  
      my $filename = "data/webdot/$$.dot";
      open(DOT, ">$filename") || die "Can't create $filename";
      print DOT "digraph G {";
      print DOT qq{
  graph [URL="${urlbase}query.cgi", rankdir=$::FORM{'rankdir'}, size="64,64"]
  node [URL="${urlbase}show_bug.cgi?id=\\N", style=filled, color=lightgrey]
  };
      my %baselist;
  
      foreach my $i (split('[\s,]+', $::FORM{'id'})) {
          $i = trim($i);
          if ($i ne "") {
              $baselist{$i} = 1;
          }
      }
      my @basearray = keys(%baselist);
  
      if ($::FORM{'doall'}) {
          SendSQL("select blocked, dependson from dependencies");
          
          while (MoreSQLData()) {
              my ($blocked, $dependson) = (FetchSQLData());
              AddLink($blocked, $dependson);
          }
      } else {
          my @stack = @basearray;
          while (@stack) {
              my $id = shift @stack;
              SendSQL("select blocked, dependson from dependencies where blocked = $id or dependson = $id");
              while (MoreSQLData()) {
                  my ($blocked, $dependson) = (FetchSQLData());
                  if ($blocked != $id && !exists $seen{$blocked}) {
                      push @stack, $blocked;
                  }
                  if ($dependson != $id && !exists $seen{$dependson}) {
                      push @stack, $dependson;
                  }
                  AddLink($blocked, $dependson);
              }
          }
      }
  
      foreach my $k (@basearray) {
          $seen{$k} = 1;
      }
      foreach my $k (keys(%seen)) {
          my $summary = "";
          my $stat;
          if ($::FORM{'showsummary'}) {
              SendSQL("select bug_status, short_desc from bugs where bug_id = $k and bugs.groupset & $::usergroupset = bugs.groupset");
              ($stat, $summary) = (FetchSQLData());
              $stat = "NEW" if !defined $stat;
              $summary = "" if !defined $summary;
          } else {
              SendSQL("select bug_status from bugs where bug_id = $k");
              $stat = FetchOneColumn();
          }
          my @params;
  #        print DOT "$k [URL" . qq{="${urlbase}show_bug.cgi?id=$k"};
          if ($summary ne "") {
              $summary =~ s/([\\\"])/\\$1/g;
              push(@params, qq{label="$k\\n$summary"});
          }
          if (exists $baselist{$k}) {
              push(@params, "shape=box");
          }
          my $opened = ($stat eq "NEW" || $stat eq "ASSIGNED" ||
                        $stat eq "REOPENED");
          if ($opened) {
              push(@params, "color=green");
          }
          if (@params) {
              print DOT "$k [" . join(',', @params) . "]\n";
          } else {
              print DOT "$k\n";
          }
      }
  
  
      print DOT "}\n";
      close DOT;
      chmod 0777, $filename;
      
      my $url = PerformSubsts(Param("webdotbase")) . $filename;
  
      print qq{<a href="$url.map"> <img src="$url.gif" ismap> </a><hr>\n};
  
      # Cleanup any old .dot files created from previous runs.
      my $since = time() - 24 * 60 * 60;
      foreach my $f (glob("data/webdot/*.dot")) {
          if (ModTime($f) < $since) {
              unlink $f;
          }
      }
  } else {
      $::FORM{'id'} = "";
      $::FORM{'doall'} = 0;
      $::FORM{'showsummary'} = 0;
  }    
  
  print "
  <form>
  <table>
  <tr>
  <th align=right>Bug numbers:</th>
  <td><input name=id value=\"" . value_quote($::FORM{'id'}) . "\"></td>
  <td><input type=checkbox name=doall" . ($::FORM{'doall'} ? " checked" : "") .
  ">Show <b>every</b> bug in the system with 
  dependencies</td>
  </tr>
  <tr><td colspan=3><input type=checkbox name=showsummary" .
  ($::FORM{'showsummary'} ? " checked" : "") . ">Show the summary of all bugs
  </tr>
  <tr><td colspan=3><select name=rankdir>
  <option value=\"TB\"" . ($::FORM{'rankdir'} eq 'TB' ? 'selected' : '') .
  ">Orient top-to-bottom
  <option value=\"LR\"" . ($::FORM{'rankdir'} eq 'LR' ? 'selected' : '') .
  ">Orient left-to-right
  </select></td></tr>
  </table>
  <input type=submit value=\"Submit\">
  </form>
   ";
  
  navigation_header();
  
  
  
  1.1                  apache-site/bugs/showdependencytree.cgi
  
  Index: showdependencytree.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  # Shut up misguided -w warnings about "used only once":
  
  use vars %::FORM;
  
  
  my $id = $::FORM{'id'};
  my $linkedid = qq{<a href="show_bug.cgi?id=$id">$id</a>};
  
  print "Content-type: text/html\n\n";
  PutHeader("Dependency tree", "Dependency tree", "Bug $linkedid");
  
  ConnectToDatabase();
  
  quietly_check_login();
  
  $::usergroupset = $::usergroupset; # More warning suppression silliness.
  
  my %seen;
  
  sub DumpKids {
      my ($i, $me, $target) = (@_);
      if (exists $seen{$i}) {
          return;
      }
      $seen{$i} = 1;
      SendSQL("select $target from dependencies where $me = $i order by $target");
      my @list;
      while (MoreSQLData()) {
          push(@list, FetchOneColumn());
      }
      if (@list) {
          print "<ul>\n";
          foreach my $kid (@list) {
              my ($bugid, $stat, $milestone) = ("", "", "");
              my ($userid, $short_desc) = ("", "");
              if (Param('usetargetmilestone')) {
                  SendSQL("select bug_id, bug_status, target_milestone, assigned_to, short_desc from bugs where bug_id = $kid and bugs.groupset & $::usergroupset = bugs.groupset");
                  ($bugid, $stat, $milestone, $userid, $short_desc) = (FetchSQLData());
              } else {
                  SendSQL("select bug_id, bug_status, assigned_to, short_desc from bugs where bug_id = $kid and bugs.groupset & $::usergroupset = bugs.groupset");
                  ($bugid, $stat, $userid, $short_desc) = (FetchSQLData());
  
              }
              if (!defined $bugid) {
                  next;
              }
              my $opened = ($stat eq "NEW" || $stat eq "ASSIGNED" ||
                            $stat eq "REOPENED");
              print "<li>";
              if (!$opened) {
                  print "<strike>";
              }
              $short_desc = html_quote($short_desc);
              SendSQL("select login_name from profiles where userid = $userid");
              my ($owner) = (FetchSQLData());
              if ( (Param('usetargetmilestone')) && ($milestone) ) {
                  print qq{<a href="show_bug.cgi?id=$kid">$kid [$milestone, $owner] - $short_desc</a>};
              } else {
                  print qq{<a href="show_bug.cgi?id=$kid">$kid [$owner] - $short_desc</a>};
              }
              if (!$opened) {
                  print "</strike>";
              }
              DumpKids($kid, $me, $target);
          }
          print "</ul>\n";
      }
  }
  
  print "<h1>Bugs that bug $linkedid depends on</h1>";
  DumpKids($id, "blocked", "dependson");
  print "<h1>Bugs that depend on bug $linkedid</h1>";
  undef %seen;
  DumpKids($id, "dependson", "blocked");
  
  navigation_header();
  
  
  
  1.1                  apache-site/bugs/showowners.cgi
  
  Index: showowners.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Bryce Nesbitt <br...@nextbus.com>
  #
  # This program lists all BugZilla users, and lists what modules they
  # either own or are default QA for.  It is very slow on large databases.
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  require "globals.pl";
  
  # Fetch, one row at a time, the product and module.
  # Build the contents of the table cell listing each unique
  # product just once, with all the modules.
  sub FetchAndFormat {
  	my $result = "";
  	my $temp = "";
  	my @row = "";
  
  	while (@row = FetchSQLData()) {
  		if( $temp ne $row[0] ) {
  			$result .= " " . $row[0] . ": ";
  		} else {
  			$result .= ", ";
  		}
  		$temp = $row[0];
  		$result .=  "<I>" . $row[1] .  "</I>";
  	}
  	return( $result );
  }
  
  
  # Start the resulting web page
  print "Content-type: text/html\n\n";
  print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">
  <html><head><title>BugZilla module owners list</title></head>\n";
  PutHeader("Owner list");
  
  ConnectToDatabase();
  GetVersionTable();
  
  # Collect all BugZilla user names
  SendSQL("select login_name,userid from profiles order by login_name");
  my @list;
  my @row;
  while (@row = FetchSQLData()) {
  	push @list, $row[0];
  }
  
  print "<P>The following is a list of BugZilla users who are the default owner
  for at least one module.  BugZilla will only assign or Cc: a bug to the exact
  name stored in the database.  Click on a name to see bugs assigned to that person:</P>\n";
  print "<table border=1>\n";
  print "<tr><td><B>Login name</B></td>\n";
  print "<td><B>Default owner for</B></td><td><B>Default QA for</B></td>\n";
  
  # If a user is a initialowner or initialqacontact, list their modules
  my $person;
  my $nospamperson;
  my $firstcell;
  my $secondcell;
  my @nocell;
  foreach $person (@list) {
  
  	my $qperson = SqlQuote($person);
  
  	SendSQL("select program,value from components\
  		 where initialowner = $qperson order by program,value");
  	$firstcell = FetchAndFormat();
  
  	SendSQL("select program,value from components\
  		 where initialqacontact = $qperson order by program,value");
  	$secondcell = FetchAndFormat();
  
  	$_ = $person;		# Anti-spam
  	s/@/ @/;		# Mangle
  	$nospamperson = $_;	# Email 
  
  	if( $firstcell || $secondcell ) {
  		print "<tr>";
  
  		print "<td>\n";
  		print "<a href=\"buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&email1=${person}&emailtype1=substring&emailassigned_to1=1&cmdtype=doit&newqueryname=&order=%22Importance%22&form_name=query\">${nospamperson}</a>\n";
  		print "</td>\n";
  
  		print "<td>";
  		print $firstcell;
  		print "</td>\n";
  
  		print "<td>";
  		print $secondcell;
  		print "</td>\n";
  
  		print "</tr>\n";
  	} else {
  		push @nocell, $person;
  	}
  }
  
  print "<tr>";
  print "<td colspan=3>";
  print "Other valid logins: ";
  foreach $person (@nocell) {
  	$_ = $person;		# Anti-spam
  	s/@/ @/;		# Mangle
  	$nospamperson = $_;	# Email 
  
  	print "<a href=\"buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&email1=${person}&emailtype1=substring&emailassigned_to1=1&cmdtype=doit&newqueryname=&order=%22Importance%22&form_name=query\">${nospamperson}</a>\n";
  	print ", ";
  }
  print "</td>";
  print "</tr>\n";
  
  print "</table>\n";
  
  # Enhancement ideas
  #	o Use just one table cell for each person.  The table gets unbalanced for installs
  #	  where just a few QA people handle lots of modules
  #	o Optimize for large systems.  Terry notes:
  # 	  The problem is that you go query the components table 10,000 times, 
  #	  twice for each of the 5,000 logins that we have.  Yow!
  #	 
  #	  It would be better to generate your initial list of logins by selecting 
  #	  for distinct initialqacontact and initialowner values from the 
  #	  components database.  Then when you generate the list of "other 
  #	  logins", you can query for the whole list of logins and subtract out 
  #	  things that were in the components database.
  
  
  
  1.1                  apache-site/bugs/showvotes.cgi
  
  Index: showvotes.cgi
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  use diagnostics;
  use strict;
  
  require "CGI.pl";
  
  if (defined $::FORM{'voteon'} || (!defined $::FORM{'bug_id'} &&
                                    !defined $::FORM{'user'})) {
      confirm_login();
      ConnectToDatabase();
      $::FORM{'user'} = DBNameToIdAndCheck($::COOKIE{'Bugzilla_login'});
  }
  
  print "Content-type: text/html\n\n";
  
  if (defined $::FORM{'bug_id'}) {
      my $id = $::FORM{'bug_id'};
      my $linkedid = qq{<a href="show_bug.cgi?id=$id">$id</a>};
      PutHeader("Show votes", "Show votes", "Bug $linkedid");
      ConnectToDatabase();
      SendSQL("select profiles.login_name, votes.who, votes.count from votes, profiles where votes.bug_id = " . SqlQuote($id) . " and profiles.userid = votes.who");
      print "<table>\n";
      print "<tr><th>Who</th><th>Number of votes</th></tr>\n";
      my $sum = 0;
      while (MoreSQLData()) {
          my ($name, $userid, $count) = (FetchSQLData());
          print qq{<tr><td><a href="showvotes.cgi?user=$userid">$name</a></td><td align=right>$count</td></tr>\n};
          $sum += $count;
      }
      print "</table>";
      print "<p>Total votes: $sum<p>\n";
  } elsif (defined $::FORM{'user'}) {
      ConnectToDatabase();
      quietly_check_login();
      GetVersionTable();
      my $who = $::FORM{'user'};
      my $name = DBID_to_name($who);
      PutHeader("Show votes", "Show votes", $name);
      print qq{<form action="doeditvotes.cgi">\n};
      print "<table><tr><td></td><th>Bug \#</th><th>Summary</th><th>Votes</th></tr>\n";
      SendSQL("lock tables bugs read, votes write");
      if (defined($::FORM{'voteon'})) {
          # Oh, boy, what a hack.  Make sure there is an entry for this bug
          # in the vote table, just so that things display right.
          # Yuck yuck yuck.###
          SendSQL("select votes.count from votes where votes.bug_id = $::FORM{'voteon'} and votes.who = $who");
          if (!MoreSQLData()) {
              SendSQL("insert into votes (who, bug_id, count) values ($who, $::FORM{'voteon'}, 0)");
          }
      }
      my $canedit = (defined $::COOKIE{'Bugzilla_login'} &&
                     $::COOKIE{'Bugzilla_login'} eq $name);
      foreach my $product (sort(keys(%::prodmaxvotes))) {
          if ($::prodmaxvotes{$product} <= 0) {
              next;
          }
          my $qprod = value_quote($product);
          SendSQL("select votes.bug_id, votes.count, bugs.short_desc, bugs.bug_status from votes, bugs where votes.who = $who and votes.bug_id = bugs.bug_id and bugs.product = " . SqlQuote($product) . "order by votes.bug_id");
          my $sum = 0;
          print "<tr><th>$product</th></tr>";
          while (MoreSQLData()) {
              my ($id, $count, $summary, $status) = (FetchSQLData());
              if (!defined $status) {
                  next;
              }
              my $opened = ($status eq "NEW" || $status eq "ASSIGNED" ||
                            $status eq "REOPENED");
              my $strike = $opened ? "" : "<strike>";
              my $endstrike = $opened ? "" : "</strike>";
              $summary = html_quote($summary);
              $sum += $count;
              if ($canedit) {
                  $count = "<input name=$id value=$count size=5>";
              }
              print qq{
  <tr>
  <td></td>
  <td><a href="showvotes.cgi?bug_id=$id">$id</a></td>
  <td><a href="show_bug.cgi?id=$id">$strike$summary$endstrike</a></td>
  <td align=right>$count</td>
  </tr>
  };
          }
          my $plural = (($sum == 1) ? "" : "s");
          print "<td colspan=3>$sum vote$plural used out of\n";
          print "$::prodmaxvotes{$product} allowed.</td>\n";
      }
      print "</table>\n";
      if ($canedit) {
          print qq{<input type=submit value="Submit">\n};
          print "<br>To change your votes, type in new numbers (using zero to\n";
          print "mean no votes), and then click <b>Submit</b>.\n";
      }
      print "<input type=hidden name=who value=$who>";
      print "</form>\n";
      SendSQL("delete from votes where count <= 0");
      SendSQL("unlock tables");
  }
      
  print qq{<a href="votehelp.html">Help!  I don't understand this voting stuff</a>};
  
  navigation_header();
      
  
  
  
  1.1                  apache-site/bugs/votehelp.html
  
  Index: votehelp.html
  ===================================================================
  <HTML>
  <!--
       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.
      
       The Original Code is the Bugzilla Bug Tracking System.
      
       The Initial Developer of the Original Code is Netscape Communications
       Corporation. Portions created by Netscape are
       Copyright (C) 1998 Netscape Communications Corporation. All
       Rights Reserved.
      
       Contributor(s): 
  
       Contributor(s): Terry Weissman <te...@mozilla.org>
  -->
  
  <TITLE>Bugzilla Voting</TITLE>
  <H1>Bugzilla Voting</H1>
  
  Bugzilla has a "voting" feature.  Each product allows users to have a
  certain number of votes.  (Some products may not allow any, which
  means you can't vote on things in that product at all.) With your
  vote, you indicate which bugs you think are the most important to be
  fixed.
  
  <p>
  
  You may vote for the same bug more than one time.  But remember, you
  only have so many votes to use in total!  So, you can either vote a
  little for many bugs, or vote a lot for a few bugs.
  
  <p>
  
  To look at votes:
  
  <ul>
    <li> Go to the query page.  Do a normal query, but enter 1 in the
         "At least ___ votes" field.  This will show you items that
         match your query that have at least one vote.
  </ul>
  
  <p>
  
  To vote for a bug:
  
  <ul>
    <li> Bring up the bug in question.
    <li> Click on the "Vote for this bug" link that appears just above
         the "Additional Comments" field.  (If no such link appears,
         then voting may not be allowed in this bug's product.)
    <li> Indicate how many votes you want to give this bug.  This page
         also displays how many votes you've given to other bugs, so you
         may rebalance your votes as necessary.
  </ul>
  
  You will automatically get email notifying you of any changes that
  occur on bugs you vote for.
  
  <p>
  
  You may review your votes at any time by clicking on the "Change your
  password or preferences" link at the bottom of the query page.
  
  
  
  1.1                  apache-site/bugs/whineatnews.pl
  
  Index: whineatnews.pl
  ===================================================================
  #!/usr/bonsaitools/bin/perl -w
  # -*- Mode: perl; indent-tabs-mode: nil -*-
  #
  # 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.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): Terry Weissman <te...@mozilla.org>
  
  
  # This is a script suitable for running once a day from a cron job.  It 
  # looks at all the bugs, and sends whiny mail to anyone who has a bug 
  # assigned to them that has status NEW that has not been touched for
  # more than 7 days.
  
  use diagnostics;
  use strict;
  
  require "globals.pl";
  
  ConnectToDatabase();
  
  SendSQL("select bug_id,login_name from bugs,profiles where " .
          "bug_status = 'NEW' and to_days(now()) - to_days(delta_ts) > " .
          Param('whinedays') . " and userid=assigned_to order by bug_id");
  
  my %bugs;
  my @row;
  
  while (@row = FetchSQLData()) {
      my ($id, $email) = (@row);
      if (!defined $bugs{$email}) {
          $bugs{$email} = [];
      }
      push @{$bugs{$email}}, $id;
  }
  
  
  my $template = Param('whinemail');
  my $urlbase = Param('urlbase');
  my $emailsuffix = Param('emailsuffix');
  
  foreach my $email (sort (keys %bugs)) {
      my %substs;
      $substs{'email'} = $email . $emailsuffix;
      $substs{'userid'} = $email;
      my $msg = PerformSubsts($template, \%substs);
  
      foreach my $i (@{$bugs{$email}}) {
          $msg .= "  ${urlbase}show_bug.cgi?id=$i\n"
      }
      open(SENDMAIL, "|/usr/lib/sendmail -t") || die "Can't open sendmail";
      print SENDMAIL $msg;
      close SENDMAIL;
      print "$email      " . join(" ", @{$bugs{$email}}) . "\n";
  }