1
#!/usr/bonsaitools/bin/perl -w
2
# -*- Mode: perl; indent-tabs-mode: nil -*-
4
# The contents of this file are subject to the Mozilla Public
5
# License Version 1.1 (the "License"); you may not use this file
6
# except in compliance with the License. You may obtain a copy of
7
# the License at http://www.mozilla.org/MPL/
9
# Software distributed under the License is distributed on an "AS
10
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11
# implied. See the License for the specific language governing
12
# rights and limitations under the License.
14
# The Original Code is mozilla.org code.
16
# The Initial Developer of the Original Code is Holger
17
# Schurig. Portions created by Holger Schurig are
18
# Copyright (C) 1999 Holger Schurig. All
21
# Contributor(s): Holger Schurig <holgerschurig@nikocity.de>
22
# Terry Weissman <terry@mozilla.org>
23
# Dan Mosedale <dmose@mozilla.org>
24
# Dave Miller <justdave@syndicomm.com>
25
# Zach Lipton <zach@zachlipton.com>
28
# Direct any questions on this source code to
30
# Holger Schurig <holgerschurig@nikocity.de>
36
# 'checksetup.pl' is a script that is supposed to run during installation
37
# time and also after every upgrade.
39
# The goal of this script is to make the installation even more easy.
40
# It does so by doing things for you as well as testing for problems
43
# And you can re-run it whenever you want. Especially after Bugzilla
44
# gets updated you SHOULD rerun it. Because then it may update your
45
# SQL table definitions so that they are again in sync with the code.
47
# So, currently this module does:
49
# - check for required perl modules
50
# - set defaults for local configuration variables
51
# - create and populate the data directory after installation
52
# - set the proper rights for the *.cgi, *.html ... etc files
53
# - check if the code can access MySQL
54
# - creates the database 'bugs' if the database does not exist
55
# - creates the tables inside the database if they don't exist
56
# - automatically changes the table definitions of older BugZilla
58
# - populates the groups
59
# - put the first user into all groups so that the system can
61
# - changes already existing SQL tables if you change your local
62
# settings, e.g. when you add a new platform
64
# People that install this module locally are not supposed to modify
65
# this script. This is done by shifting the user settable stuff into
66
# a local configuration file 'localconfig'. When this file get's
67
# changed and 'checkconfig.pl' will be re-run, then the user changes
68
# will be reflected back into the database.
70
# Developers however have to modify this file at various places. To
71
# make this easier, I have added some special comments that one can
76
# add/delete local configuration variables --LOCAL--
77
# check for more prerequired modules --MODULES--
78
# change the defaults for local configuration vars --LOCAL--
79
# update the assigned file permissions --CHMOD--
80
# add more MySQL-related checks --MYSQL--
81
# change table definitions --TABLE--
82
# add more groups --GROUPS--
83
# create initial administrator account --ADMIN--
85
# Note: sometimes those special comments occur more then once. For
86
# example, --LOCAL-- is at least 3 times in this code! --TABLE--
87
# also is used more than once. So search for every occurence!
91
###########################################################################
93
###########################################################################
98
# 12/17/00 justdave@syndicomm.com - removed declarations of the localconfig
99
# variables from this location. We don't want these declared here. They'll
100
# automatically get declared in the process of reading in localconfig, and
101
# this way we can look in the symbol table to see if they've been declared
105
###########################################################################
106
# Check required module
107
###########################################################################
110
# Here we check for --MODULES--
113
print "\nChecking perl modules ...\n";
114
unless (eval "require 5.005") {
115
die "Sorry, you need at least Perl 5.005\n";
118
# vers_cmp is adapted from Sort::Versions 1.3 1996/07/11 13:37:00 kjahds,
119
# which is not included with Perl by default, hence the need to copy it here.
120
# Seems silly to require it when this is the only place we need it...
122
if (@_ < 2) { die "not enough parameters for vers_cmp" }
123
if (@_ > 2) { die "too many parameters for vers_cmp" }
125
my (@A) = ($a =~ /(\.|\d+|[^\.\d]+)/g);
126
my (@B) = ($b =~ /(\.|\d+|[^\.\d]+)/g);
131
if ($A eq "." and $B eq ".") {
133
} elsif ( $A eq "." ) {
135
} elsif ( $B eq "." ) {
137
} elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) {
138
return $A <=> $B if $A <=> $B;
142
return $A cmp $B if $A cmp $B;
148
# This was originally clipped from the libnet Makefile.PL, adapted here to
149
# use the above vers_cmp routine for accurate version checking.
151
my ($pkg, $wanted) = @_;
152
my ($msg, $vnum, $vstr);
154
printf("Checking for %15s %-9s ", $pkg, !$wanted?'(any)':"(v$wanted)");
156
eval { my $p; ($p = $pkg . ".pm") =~ s!::!/!g; require $p; };
158
$vnum = ${"${pkg}::VERSION"} || ${"${pkg}::Version"} || 0;
161
if ($vnum eq "-1") { # string compare just in case it's non-numeric
164
elsif (vers_cmp($vnum,"0") > -1) {
165
$vstr = "found v$vnum";
168
$vstr = "found unknown version";
171
my $vok = (vers_cmp($vnum,$wanted) > -1);
172
print ((($vok) ? "ok: " : " "), "$vstr\n");
176
# Check versions of dependencies. 0 for version = any version acceptible
187
name => 'Data::Dumper',
191
name => 'Date::Parse',
199
name => 'DBD::mysql',
203
name => 'File::Spec',
207
name => 'File::Temp',
215
name => 'Text::Wrap',
216
version => '2001.0131'
221
foreach my $module (@{$modules}) {
222
unless (have_vers($module->{name}, $module->{version})) {
223
$missing{$module->{name}} = $module->{version};
227
# If CGI::Carp was loaded successfully for version checking, it changes the
228
# die and warn handlers, we don't want them changed, so we need to stash the
229
# original ones and set them back afterwards -- justdave@syndicomm.com
230
my $saved_die_handler = $::SIG{__DIE__};
231
my $saved_warn_handler = $::SIG{__WARN__};
232
unless (have_vers("CGI::Carp",0)) { $missing{'CGI::Carp'} = 0 }
233
$::SIG{__DIE__} = $saved_die_handler;
234
$::SIG{__WARN__} = $saved_warn_handler;
236
print "\nThe following Perl modules are optional:\n";
238
$charts++ if have_vers("GD","1.19");
239
$charts++ if have_vers("Chart::Base","0.99");
240
my $xmlparser = have_vers("XML::Parser",0);
244
print "If you you want to see graphical bug dependency charts, you may install\n",
245
"the optional libgd and the Perl modules GD-1.19 and Chart::Base-0.99b, e.g. by\n",
246
"running (as root)\n\n",
247
" perl -MCPAN -e'install \"LDS/GD-1.19.tar.gz\"'\n",
248
" perl -MCPAN -e'install \"N/NI/NINJAZ/Chart-0.99b.tar.gz\"'\n\n";
251
print "If you want to use the bug import/export feature to move bugs to or from\n",
252
"other bugzilla installations, you will need to install the XML::Parser module by\n",
253
"running (as root)\n\n",
254
" perl -MCPAN -e'install \"XML::Parser\"'\n\n";
258
print "Bugzilla requires some Perl modules which are either missing from your\n",
259
"system, or the version on your system is too old.\n",
260
"They can be installed by running (as root) the following:\n";
261
foreach my $module (keys %missing) {
262
print " perl -MCPAN -e 'install \"$module\"'\n";
263
if ($missing{$module} > 0) {
264
print " Minimum version required: $missing{$module}\n";
272
###########################################################################
273
# Check and update local configuration
274
###########################################################################
277
# This is quite tricky. But fun!
279
# First we read the file 'localconfig'. Then we check if the variables we
280
# need are defined. If not, localconfig will be amended by the new settings
281
# and the user informed to check this. The program then stops.
283
# Why do it this way around?
285
# Assume we will enhance Bugzilla and eventually more local configuration
286
# stuff arises on the horizon.
288
# But the file 'localconfig' is not in the Bugzilla CVS or tarfile. You
289
# know, we never want to overwrite your own version of 'localconfig', so
290
# we can't put it into the CVS/tarfile, can we?
292
# Now, we need a new variable. We simply add the necessary stuff to checksetup.
293
# The user get's the new version of Bugzilla from the CVS, runs checksetup
294
# and checksetup finds out "Oh, there is something new". Then it adds some
295
# default value to the user's local setup and informs the user to check that
296
# to see if that is what the user wants.
301
print "Checking user setup ...\n";
304
if ($@) { # capture errors in localconfig, bug 97290
306
An error has occurred while reading your
307
'localconfig' file. The text of the error message is:
311
Please fix the error in your 'localconfig' file.
312
Alternately rename your 'localconfig' file, rerun
313
checksetup.pl, and re-enter your answers.
315
\$ mv -f localconfig localconfig.old
320
die "Syntax error in localconfig";
325
my ($name, $definition) = @_;
326
return if ($main::{$name}); # if localconfig declared it, we're done.
327
$newstuff .= " " . $name;
328
open FILE, '>>localconfig';
329
print FILE $definition, "\n\n";
336
# Set up the defaults for the --LOCAL-- variables below:
339
LocalVar('index_html', <<'END');
341
# With the introduction of a configurable index page using the
342
# template toolkit, Bugzilla's main index page is now index.cgi.
343
# Most web servers will allow you to use index.cgi as a directory
344
# index and many come preconfigured that way, however if yours
345
# doesn't you'll need an index.html file that provides redirection
346
# to index.cgi. Setting $index_html to 1 below will allow
347
# checksetup.pl to create one for you if it doesn't exist.
348
# NOTE: checksetup.pl will not replace an existing file, so if you
349
# wish to have checksetup.pl create one for you, you must
350
# make sure that there isn't already an index.html
354
my $mysql_binaries = `which mysql`;
355
if ($mysql_binaries =~ /no mysql/) {
356
# If which didn't find it, just provide a reasonable default
357
$mysql_binaries = "/usr/bin";
359
$mysql_binaries =~ s:/mysql\n$::;
362
LocalVar('mysqlpath', <<"END");
364
# In order to do certain functions in Bugzilla (such as sync the shadow
365
# database), we require the MySQL Binaries (mysql, mysqldump, and mysqladmin).
366
# Because it's possible that these files aren't in your path, you can specify
367
# their location here.
368
# Please specify only the directory name, with no trailing slash.
369
\$mysqlpath = "$mysql_binaries";
373
LocalVar('create_htaccess', <<'END');
375
# If you are using Apache for your web server, Bugzilla can create .htaccess
376
# files for you that will instruct Apache not to serve files that shouldn't
377
# be accessed from the web (like your local configuration data and non-cgi
378
# executable files). For this to work, the directory your Bugzilla
379
# installation is in must be within the jurisdiction of a <Directory> block
380
# in the httpd.conf file that has 'AllowOverride Limit' in it. If it has
381
# 'AllowOverride All' or other options with Limit, that's fine.
382
# (Older Apache installations may use an access.conf file to store these
383
# <Directory> blocks.)
384
# If this is set to 1, Bugzilla will create these files if they don't exist.
385
# If this is set to 0, Bugzilla will not create these files.
386
$create_htaccess = 1;
390
LocalVar('webservergroup', '
392
# This is the group your web server runs on.
393
# If you have a windows box, ignore this setting.
394
# If you do not have access to the group your web server runs under,
395
# set this to "". If you do set this to "", then your Bugzilla installation
396
# will be _VERY_ insecure, because some files will be world readable/writable,
397
# and so anyone who can get local access to your machine can do whatever they
398
# want. You should only have this set to "" if this is a testing installation
399
# and you cannot set this up any other way. YOU HAVE BEEN WARNED.
400
# If you set this to anything besides "", you will need to run checksetup.pl
401
# as root, or as a user who is a member of the specified group.
402
$webservergroup = "nobody";
407
LocalVar('db_host', '
409
# How to access the SQL database:
411
$db_host = "localhost"; # where is the database?
412
$db_port = 3306; # which port to use
413
$db_name = "bugs"; # name of the MySQL database
414
$db_user = "bugs"; # user to attach to the MySQL database
416
LocalVar('db_pass', '
418
# Enter your database password here. It\'s normally advisable to specify
419
# a password for your bugzilla database user.
420
# If you use apostrophe (\') or a backslash (\\) in your password, you\'ll
421
# need to escape it by preceding it with a \\ character. (\\\') or (\\\\)
428
LocalVar('db_check', '
430
# Should checksetup.pl try to check if your MySQL setup is correct?
431
# (with some combinations of MySQL/Msql-mysql/Perl/moonphase this doesn\'t work)
437
LocalVar('severities', '
439
# Which bug and feature-request severities do you want?
454
LocalVar('priorities', '
456
# Which priorities do you want to assign to bugs and feature-request?
471
# What operatings systems may your products run on?
478
"Windows ME", # Millenium Edition (upgrade of 98)
511
LocalVar('platforms', '
513
# What hardware platforms may your products run on?
530
LocalVar('contenttypes', '
532
# The types of content that template files can generate, indexed by file extension.
535
"html" => "text/html" ,
536
"rdf" => "application/xml" ,
537
"xml" => "text/xml" ,
538
"js" => "application/x-javascript" ,
545
if ($newstuff ne "") {
546
print "\nThis version of Bugzilla contains some variables that you may want\n",
547
"to change and adapt to your local settings. Please edit the file\n",
548
"'localconfig' and rerun checksetup.pl\n\n",
549
"The following variables are new to localconfig since you last ran\n",
550
"checksetup.pl: $newstuff\n\n";
554
# 2000-Dec-18 - justdave@syndicomm.com - see Bug 52921
555
# This is a hack to read in the values defined in localconfig without getting
556
# them predeclared at compile time if they're missing from localconfig.
557
# Ideas swiped from pp. 281-282, O'Reilly's "Programming Perl 2nd Edition"
558
# Note that we won't need to do this in globals.pl because globals.pl couldn't
559
# care less whether they were defined ahead of time or not.
560
my $my_db_check = ${*{$main::{'db_check'}}{SCALAR}};
561
my $my_db_host = ${*{$main::{'db_host'}}{SCALAR}};
562
my $my_db_port = ${*{$main::{'db_port'}}{SCALAR}};
563
my $my_db_name = ${*{$main::{'db_name'}}{SCALAR}};
564
my $my_db_user = ${*{$main::{'db_user'}}{SCALAR}};
565
my $my_db_pass = ${*{$main::{'db_pass'}}{SCALAR}};
566
my $my_index_html = ${*{$main::{'index_html'}}{SCALAR}};
567
my $my_create_htaccess = ${*{$main::{'create_htaccess'}}{SCALAR}};
568
my $my_webservergroup = ${*{$main::{'webservergroup'}}{SCALAR}};
569
my @my_severities = @{*{$main::{'severities'}}{ARRAY}};
570
my @my_priorities = @{*{$main::{'priorities'}}{ARRAY}};
571
my @my_platforms = @{*{$main::{'platforms'}}{ARRAY}};
572
my @my_opsys = @{*{$main::{'opsys'}}{ARRAY}};
574
if ($my_webservergroup) {
575
if ($< != 0) { # zach: if not root, yell at them, bug 87398
578
Warning: you have entered a value for the "webservergroup" parameter
579
in localconfig, but you are not running this script as root.
580
This can cause permissions problems and decreased security. If you
581
experience problems running Bugzilla scripts, log in as root and re-run
582
this script, or remove the value of the "webservergroup" parameter.
583
Note that any warnings about "uninitialized values" that you may
584
see below are caused by this.
589
# Theres no webservergroup, this is very very very very bad.
590
# However, if we're being run on windows, then this option doesn't
591
# really make sense. Doesn't make it any more secure either, though,
592
# but don't print the message, since they can't do anything about it.
593
if ($^O !~ /MSWin32/i) {
596
********************************************************************************
597
WARNING! You have not entered a value for the "webservergroup" parameter
598
in localconfig. This means that certain files and directories which need
599
to be editable by both you and the webserver must be world writable, and
600
other files (including the localconfig file which stores your database
601
password) must be world readable. This means that _anyone_ who can obtain
602
local access to this machine can do whatever they want to your Bugzilla
603
installation, and is probably also able to run arbitrary Perl code as the
604
user that the webserver runs as.
606
You really, really, really need to change this setting.
607
********************************************************************************
613
###########################################################################
614
# Global Utility Library
615
###########################################################################
617
# globals.pl clears the PATH, but File::Find uses Cwd::cwd() instead of
618
# Cwd::getcwd(), which we need to do because `pwd` isn't in the path - see
619
# http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/2001-09/msg00115.html
620
# As a workaround, since we only use File::Find in checksetup, which doesn't
621
# run in taint mode anyway, preserve the path...
622
my $origPath = $::ENV{'PATH'};
624
# Use the Bugzilla utility library for various functions. We do this
625
# here rather than at the top of the file so globals.pl doesn't define
626
# localconfig variables for us before we get a chance to check for
627
# their existence and create them if they don't exist. Also, globals.pl
628
# removes $ENV{'path'}, which we need in order to run `which mysql` above.
629
require "globals.pl";
631
# ...and restore it. This doesn't change tainting, so this will still cause
632
# errors if this script ever does run with -T.
633
$::ENV{'PATH'} = $origPath;
635
###########################################################################
636
# Check data directory
637
###########################################################################
640
# Create initial --DATA-- directory and make the initial empty files there:
643
# The |require "globals.pl"| above ends up creating a template object with
644
# a COMPILE_DIR of 'data'. This means that TT creates the directory for us,
645
# so this code wouldn't run if we just checked for the existance of the
646
# directory. Instead, check for the existance of 'data/nomail', which is
647
# created in this block
648
unless (-d 'data' && -e 'data/nomail') {
649
print "Creating data directory ...\n";
650
# permissions for non-webservergroup are fixed later on
652
mkdir 'data/mimedump-tmp', 01777;
653
open FILE, '>>data/comments'; close FILE;
654
open FILE, '>>data/nomail'; close FILE;
655
open FILE, '>>data/mail'; close FILE;
658
# 2000-12-14 New graphing system requires a directory to put the graphs in
659
# This code copied from what happens for the 'data' dir above.
660
# If the graphs dir is not present, we assume that they have been using
661
# a Bugzilla with the old data format, and so upgrade their data files.
662
unless (-d 'graphs') {
663
print "Creating graphs directory...\n";
664
# permissions for non-webservergroup are fixed later on
665
mkdir 'graphs', 0770;
666
# Upgrade data format
667
foreach my $in_file (glob("data/mining/*"))
669
# Don't try and upgrade image or db files!
670
if (($in_file =~ /\.gif$/i) ||
671
($in_file =~ /\.png$/i) ||
672
($in_file =~ /\.db$/i) ||
673
($in_file =~ /\.orig$/i)) {
677
rename("$in_file", "$in_file.orig") or next;
678
open(IN, "$in_file.orig") or next;
679
open(OUT, ">$in_file") or next;
681
# Fields in the header
682
my @declared_fields = ();
684
# Fields we changed to half way through by mistake
685
# This list comes from an old version of collectstats.pl
686
# This part is only for people who ran later versions of 2.11 (devel)
687
my @intermediate_fields = qw(DATE UNCONFIRMED NEW ASSIGNED REOPENED
688
RESOLVED VERIFIED CLOSED);
690
# Fields we actually want (matches the current collectstats.pl)
691
my @out_fields = qw(DATE NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED
692
VERIFIED CLOSED FIXED INVALID WONTFIX LATER REMIND
693
DUPLICATE WORKSFORME MOVED);
696
if (/^# fields?: (.*)\s$/) {
697
@declared_fields = map uc, (split /\||\r/, $1);
698
print OUT "# fields: ", join('|', @out_fields), "\n";
700
elsif (/^(\d+\|.*)/) {
701
my @data = split /\||\r/, $1;
703
if (@data == @declared_fields) {
705
for my $i (0 .. $#declared_fields) {
706
$data{$declared_fields[$i]} = $data[$i];
709
elsif (@data == @intermediate_fields) {
710
# Must have changed over at this point
711
for my $i (0 .. $#intermediate_fields) {
712
$data{$intermediate_fields[$i]} = $data[$i];
715
elsif (@data == @out_fields) {
716
# This line's fine - it has the right number of entries
717
for my $i (0 .. $#out_fields) {
718
$data{$out_fields[$i]} = $data[$i];
722
print "Oh dear, input line $. of $in_file had " . scalar(@data) . " fields\n";
723
print "This was unexpected. You may want to check your data files.\n";
726
print OUT join('|', map {
727
defined ($data{$_}) ? ($data{$_}) : ""
728
} @out_fields), "\n";
740
unless (-d 'data/mining') {
741
mkdir 'data/mining', 0700;
744
unless (-d 'data/webdot') {
745
# perms/ownership are fixed up later
746
mkdir 'data/webdot', 0700;
749
if ($my_create_htaccess) {
752
if ($my_webservergroup) {
756
if (!-e ".htaccess") {
757
print "Creating .htaccess...\n";
758
open HTACCESS, ">.htaccess";
759
print HTACCESS <<'END';
760
# don't allow people to retrieve non-cgi executable files or our private data
761
<FilesMatch ^(.*\.pl|.*localconfig.*|processmail|syncshadowdb|runtests.sh)$>
764
<FilesMatch ^(localconfig.js|localconfig.rdf)$>
769
chmod $fileperm, ".htaccess";
771
# 2002-12-21 Bug 186383
772
open HTACCESS, ".htaccess";
778
if ($oldaccess =~ s/\|localconfig\|/\|.*localconfig.*\|/) {
779
print "Repairing .htaccess...\n";
780
open HTACCESS, ">.htaccess";
781
print HTACCESS $oldaccess;
782
print HTACCESS <<'END';
783
<FilesMatch ^(localconfig.js|localconfig.rdf)$>
791
if (!-e "data/.htaccess") {
792
print "Creating data/.htaccess...\n";
793
open HTACCESS, ">data/.htaccess";
794
print HTACCESS <<'END';
795
# nothing in this directory is retrievable unless overriden by an .htaccess
800
chmod $fileperm, "data/.htaccess";
802
if (!-e "template/.htaccess") {
803
print "Creating template/.htaccess...\n";
804
open HTACCESS, ">template/.htaccess";
805
print HTACCESS <<'END';
806
# nothing in this directory is retrievable unless overriden by an .htaccess
811
chmod $fileperm, "template/.htaccess";
813
if (!-e "data/webdot/.htaccess") {
814
print "Creating data/webdot/.htaccess...\n";
815
open HTACCESS, ">data/webdot/.htaccess";
816
print HTACCESS <<'END';
817
# Restrict access to .dot files to the public webdot server at research.att.com
818
# if research.att.com ever changed their IP, or if you use a different
819
# webdot server, you'll need to edit this
821
Allow from 192.20.225.10
825
# Allow access to .png files created by a local copy of 'dot'
830
# And no directory listings, either.
834
chmod $fileperm, "data/webdot/.htaccess";
839
if ($my_index_html) {
840
if (!-e "index.html") {
841
print "Creating index.html...\n";
842
open HTML, ">index.html";
844
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
847
<META HTTP-EQUIV="REFRESH" CONTENT="0; URL=index.cgi">
850
<H1>I think you are looking for <a href="index.cgi">index.cgi</a></H1>
857
open HTML, "index.html";
858
if (! grep /index\.cgi/, <HTML>) {
860
print "*** It appears that you still have an old index.html hanging\n";
861
print " around. The contents of this file should be moved into a\n";
862
print " template and placed in the 'template/en/custom' directory.\n\n";
869
eval("use Date::Parse");
870
# Templates will be recompiled if the source changes, but not if the
871
# settings in globals.pl change, so we need to be able to force a rebuild
874
# The last time the global template params were changed. Keep in UTC,
876
my $lastTemplateParamChange = str2time("2002-04-27", "UTC");
877
if (-e 'data/template') {
878
unless (-d 'data/template' && -e 'data/template/.lastRebuild' &&
879
(stat('data/template/.lastRebuild'))[9] >= $lastTemplateParamChange) {
880
print "Removing existing compiled templates ...\n";
882
# If File::Path::rmtree reported errors, then I'd use that
887
rmdir $_ || die "Couldn't rmdir $_: $!\n";
889
unlink $_ || die "Couldn't unlink $_: $!\n";
892
finddepth(\&remove, 'data/template');
896
# Precompile stuff. This speeds up initial access (so the template isn't
897
# compiled multiple times simulataneously by different servers), and helps
898
# to get the permissions right.
899
eval("use Template");
900
my $redir = ($^O =~ /MSWin32/i) ? "NUL" : "/dev/null";
901
my $template = Template->new(
903
# Output to /dev/null here
906
# Colon-separated list of directories containing templates.
907
INCLUDE_PATH => "template/en/custom:template/en/default",
912
COMPILE_DIR => 'data/', # becomes data/template/en/{custom,default}
914
# These don't actually need to do anything here, just exist
917
strike => sub { return $_; } ,
918
js => sub { return $_; },
919
html => sub { return $_; },
920
html_linebreak => sub { return $_; },
921
url_quote => sub { return $_; },
923
}) || die ("Could not create Template: " . Template->error() . "\n");
926
# no_chdir doesn't work on perl 5.005
928
my $origDir = $File::Find::dir;
929
my $name = $File::Find::name;
931
return if (-d $name);
932
return if ($name =~ /\/CVS\//);
933
return if ($name !~ /\.tmpl$/);
934
$name =~ s!template/en/default/!!; # trim the bit we don't pass to TT
938
$template->process($name, {})
939
|| die "Could not compile $name:" . $template->error() . "\n";
945
print "Precompiling templates ...\n";
951
$::baseDir = getcwd();
953
# Don't hang on templates which use the CGI library
954
eval("use CGI qw(-no_debug)");
956
# Disable warnings which come from running the compiled templates
957
# This way is OK, because they're all runtime warnings.
958
# The reason we get these warnings here is that none of the required
959
# vars will be present.
962
# Traverse the default hierachy. Custom templates will be picked up
963
# via the INCLUDE_PATH, but we know that bugzilla will only be
964
# calling stuff which exists in en/default
965
# FIXME - if we start doing dynamic INCLUDE_PATH we may have to
966
# recurse all of template/, changing the INCLUDE_PATH each time
968
find(\&compile, "template/en/default");
971
# update the time on the stamp file
972
open FILE, '>data/template/.lastRebuild'; close FILE;
973
utime $lastTemplateParamChange, $lastTemplateParamChange, ('data/template/.lastRebuild');
976
# Just to be sure ...
977
unlink "data/versioncache";
979
# Remove parameters from the data/params file that no longer exist in Bugzilla.
980
if (-e "data/params") {
981
require "data/params";
982
require "defparams.pl";
983
use vars @::param_list;
986
open(PARAMFILE, ">>old-params.txt")
987
|| die "$0: Can't open old-params.txt for writing: $!\n";
989
foreach my $item (keys %::param) {
990
if (!grep($_ eq $item, @::param_list) && $item ne "version") {
991
push (@oldparams, $item);
992
print PARAMFILE "\n\n$item:\n$::param{$item}\n";
994
delete $::param{$item};
999
print "The following parameters are no longer used in Bugzilla, " .
1000
"and so have been\nremoved from your parameters file and " .
1001
"appended to old-params.txt:\n";
1002
print join(", ", @oldparams) . "\n\n";
1010
###########################################################################
1012
###########################################################################
1015
# Here we use --CHMOD-- and friends to set the file permissions
1017
# The rationale is that the web server generally runs as nobody and so the cgi
1018
# scripts should not be writable for nobody, otherwise someone may be possible
1019
# to change the cgi's when exploiting some security flaw somewhere (not
1020
# necessarily in Bugzilla!)
1022
# Also, some *.pl files are executable, some are not.
1024
# +++ Can anybody tell me what a Windows Perl would do with this code?
1026
# Changes 03/14/00 by SML
1028
# This abstracts out what files are executable and what ones are not. It makes
1029
# for slightly neater code and lets us do things like determine exactly which
1030
# files are executable and which ones are not.
1032
# Not all directories have permissions changed on them. i.e., changing ./CVS
1033
# to be 0640 is bad.
1035
# Fixed bug in chmod invokation. chmod (at least on my linux box running perl
1036
# 5.005 needs a valid first argument, not 0.
1038
# (end changes, 03/14/00 by SML)
1040
# Changes 15/06/01 kiko@async.com.br
1042
# Fix file permissions for non-webservergroup installations (see
1043
# http://bugzilla.mozilla.org/show_bug.cgi?id=71555). I'm setting things
1044
# by default to world readable/executable for all files, and
1045
# world-writeable (with sticky on) to data and graphs.
1048
# These are the files which need to be marked executable
1049
my @executable_files = ('processmail', 'whineatnews.pl', 'collectstats.pl',
1050
'checksetup.pl', 'syncshadowdb', 'importxml.pl', 'runtests.sh');
1052
# tell me if a file is executable. All CGI files and those in @executable_files
1054
sub isExecutableFile {
1056
if ($file =~ /\.cgi/) {
1061
foreach $exec_file (@executable_files) {
1062
if ($file eq $exec_file) {
1069
# fix file (or files - wildcards ok) permissions
1071
my ($file_pattern, $owner, $group, $umask, $do_dirs) = @_;
1072
my @files = glob($file_pattern);
1073
my $execperm = 0777 & ~ $umask;
1074
my $normperm = 0666 & ~ $umask;
1075
foreach my $file (@files) {
1076
next if (!-e $file);
1077
# do not change permissions on directories here unless $do_dirs is set
1079
chown $owner, $group, $file;
1080
# check if the file is executable.
1081
if (isExecutableFile($file)) {
1082
#printf ("Changing $file to %o\n", $execperm);
1083
chmod $execperm, $file;
1085
#printf ("Changing $file to %o\n", $normperm);
1086
chmod $normperm, $file;
1090
chown $owner, $group, $file;
1091
if ($file =~ /CVS$/) {
1095
#printf ("Changing $file to %o\n", $execperm);
1096
chmod $execperm, $file;
1097
fixPerms("$file/.htaccess", $owner, $group, $umask, $do_dirs);
1098
fixPerms("$file/*", $owner, $group, $umask, $do_dirs); # do the contents of the directory
1104
if ($my_webservergroup) {
1105
# Funny! getgrname returns the GID if fed with NAME ...
1106
my $webservergid = getgrnam($my_webservergroup);
1107
# chown needs to be called with a valid uid, not 0. $< returns the
1108
# caller's uid. Maybe there should be a $bugzillauid, and call with that
1110
fixPerms('.htaccess', $<, $webservergid, 027); # glob('*') doesn't catch dotfiles
1111
fixPerms('data/.htaccess', $<, $webservergid, 027);
1112
fixPerms('data/duplicates', $<, $webservergid, 027, 1);
1113
fixPerms('data/mining', $<, $webservergid, 027, 1);
1114
fixPerms('data/template', $<, $webservergid, 007, 1); # webserver will write to these
1115
fixPerms('data/webdot', $<, $webservergid, 007, 1);
1116
fixPerms('data/webdot/.htaccess', $<, $webservergid, 027);
1117
fixPerms('data/params', $<, $webservergid, 017);
1118
fixPerms('data/comments', $<, $webservergid, 017);
1119
fixPerms('*', $<, $webservergid, 027);
1120
fixPerms('template', $<, $webservergid, 027, 1);
1121
fixPerms('css', $<, $webservergid, 027, 1);
1122
chmod 0644, 'globals.pl';
1123
chmod 0644, 'RelationSet.pm';
1125
# Don't use fixPerms here, because it won't change perms on the directory
1126
# unless its using recursion
1127
chown $<, $webservergid, 'data';
1129
chown $<, $webservergid, 'graphs';
1130
chmod 0770, 'graphs';
1132
# get current gid from $( list
1133
my $gid = (split " ", $()[0];
1134
fixPerms('.htaccess', $<, $gid, 022); # glob('*') doesn't catch dotfiles
1135
fixPerms('data/.htaccess', $<, $gid, 022);
1136
fixPerms('data/duplicates', $<, $gid, 022, 1);
1137
fixPerms('data/mining', $<, $gid, 022, 1);
1138
fixPerms('data/template', $<, $gid, 000, 1); # webserver will write to these
1139
fixPerms('data/webdot', $<, $gid, 000, 1);
1140
chmod 01777, 'data/webdot';
1141
fixPerms('data/webdot/.htaccess', $<, $gid, 022);
1142
fixPerms('data/params', $<, $gid, 011);
1143
fixPerms('*', $<, $gid, 022);
1144
fixPerms('template', $<, $gid, 022, 1);
1145
fixPerms('css', $<, $gid, 022, 1);
1147
# Don't use fixPerms here, because it won't change perms on the directory
1148
# unless its using recursion
1149
chown $<, $gid, 'data';
1151
chown $<, $gid, 'graphs';
1152
chmod 01777, 'graphs';
1156
###########################################################################
1158
###########################################################################
1161
# Check if we have access to --MYSQL--
1164
# This settings are not yet changeable, because other code depends on
1165
# the fact that we use MySQL and not, say, PostgreSQL.
1167
my $db_base = 'mysql';
1169
# No need to "use" this here. It should already be loaded from the
1170
# version-checking routines above, and this file won't even compile if
1171
# DBI isn't installed so the user gets nasty errors instead of our
1172
# pretty one saying they need to install it. -- justdave@syndicomm.com
1175
# get a handle to the low-level DBD driver
1176
my $drh = DBI->install_driver($db_base)
1177
or die "Can't connect to the $db_base. Is the database installed and up and running?\n";
1180
# Do we have the database itself?
1182
my $sql_want = "3.22.5"; # minimum version of MySQL
1184
# original DSN line was:
1185
# my $dsn = "DBI:$db_base:$my_db_name;$my_db_host;$my_db_port";
1186
# removed the $db_name because we don't know it exists yet, and this will fail
1187
# if we request it here and it doesn't. - justdave@syndicomm.com 2000/09/16
1188
my $dsn = "DBI:$db_base:;$my_db_host;$my_db_port";
1189
my $dbh = DBI->connect($dsn, $my_db_user, $my_db_pass)
1190
or die "Can't connect to the $db_base database. Is the database " .
1191
"installed and\nup and running? Do you have the correct username " .
1192
"and password selected in\nlocalconfig?\n\n";
1193
printf("Checking for %15s %-9s ", "MySQL Server", "(v$sql_want)");
1194
my $qh = $dbh->prepare("SELECT VERSION()");
1196
my ($sql_vers) = $qh->fetchrow_array;
1199
# Check what version of MySQL is installed and let the user know
1200
# if the version is too old to be used with Bugzilla.
1201
if ( vers_cmp($sql_vers,$sql_want) > -1 ) {
1202
print "ok: found v$sql_vers\n";
1204
die "Your MySQL server v$sql_vers is too old./n" .
1205
" Bugzilla requires version $sql_want or later of MySQL.\n" .
1206
" Please visit http://www.mysql.com/ and download a newer version.\n";
1209
my @databases = $dbh->func('_ListDBs');
1210
unless (grep /^$my_db_name$/, @databases) {
1211
print "Creating database $my_db_name ...\n";
1212
$drh->func('createdb', $my_db_name, "$my_db_host:$my_db_port", $my_db_user, $my_db_pass, 'admin')
1215
The '$my_db_name' database is not accessible. This might have several reasons:
1217
* MySQL is not running.
1218
* MySQL is running, but the rights are not set correct. Go and read the
1219
Bugzilla Guide in the doc directory and all parts of the MySQL
1221
* There is an subtle problem with Perl, DBI, DBD::mysql and MySQL. Make
1222
sure all settings in 'localconfig' are correct. If all else fails, set
1223
'\$db_check' to zero.\n
1226
$dbh->disconnect if $dbh;
1229
# now get a handle to the database:
1230
my $connectstring = "dbi:$db_base:$my_db_name:host=$my_db_host:port=$my_db_port";
1231
my $dbh = DBI->connect($connectstring, $my_db_user, $my_db_pass)
1232
or die "Can't connect to the table '$connectstring'.\n",
1233
"Have you read the Bugzilla Guide in the doc directory? Have you read the doc of '$db_base'?\n";
1235
END { $dbh->disconnect if $dbh }
1238
###########################################################################
1239
# Check GraphViz setup
1240
###########################################################################
1243
# If we are using a local 'dot' binary, verify the specified binary exists
1244
# and that the generated images are accessible.
1247
if(-e "data/params") {
1248
require "data/params";
1249
if( $::param{'webdotbase'} && $::param{'webdotbase'} !~ /^https?:/ ) {
1250
printf("Checking for %15s %-9s ", "GraphViz", "(any)");
1251
if(-x $::param{'webdotbase'}) {
1252
print "ok: found\n";
1254
print "not a valid executable: $::param{'webdotbase'}\n";
1257
# Check .htaccess allows access to generated images
1258
if(-e "data/webdot/.htaccess") {
1259
open HTACCESS, "data/webdot/.htaccess";
1260
if(! grep(/png/,<HTACCESS>)) {
1261
print "Dependency graph images are not accessible.\n";
1262
print "Delete data/webdot/.htaccess and re-run checksetup.pl to rectify.\n";
1272
###########################################################################
1274
###########################################################################
1277
# The following hash stores all --TABLE-- definitions. This will be used
1278
# to automatically create those tables that don't exist. The code is
1279
# safer than the make*.sh shell scripts used to be, because they won't
1280
# delete existing tables.
1282
# If you want intentionally do this, yon can always drop a table and re-run
1283
# checksetup, e.g. like this:
1286
# mysql> drop table votes;
1290
# If you change one of those field definitions, then also go below to the
1291
# next occurence of the string --TABLE-- (near the end of this file) to
1292
# add the code that updates older installations automatically.
1298
$table{bugs_activity} =
1299
'bug_id mediumint not null,
1300
attach_id mediumint null,
1301
who mediumint not null,
1302
bug_when datetime not null,
1303
fieldid mediumint not null,
1312
$table{attachments} =
1313
'attach_id mediumint not null auto_increment primary key,
1314
bug_id mediumint not null,
1315
creation_ts timestamp,
1316
description mediumtext not null,
1317
mimetype mediumtext not null,
1319
filename mediumtext not null,
1320
thedata longblob not null,
1321
submitter_id mediumint not null,
1322
isobsolete tinyint not null default 0,
1325
index(creation_ts)';
1327
# 2001-05-05 myk@mozilla.org: Tables to support attachment statuses.
1328
# "attachstatuses" stores one record for each status on each attachment.
1329
# "attachstatusdefs" defines the statuses that can be set on attachments.
1331
$table{attachstatuses} =
1333
attach_id MEDIUMINT NOT NULL ,
1334
statusid SMALLINT NOT NULL ,
1335
PRIMARY KEY(attach_id, statusid)
1338
$table{attachstatusdefs} =
1340
id SMALLINT NOT NULL PRIMARY KEY ,
1341
name VARCHAR(50) NOT NULL ,
1342
description MEDIUMTEXT NULL ,
1343
sortkey SMALLINT NOT NULL DEFAULT 0 ,
1344
product VARCHAR(64) NOT NULL
1348
# Apostrophe's are not supportied in the enum types.
1349
# See http://bugzilla.mozilla.org/show_bug.cgi?id=27309
1352
'bug_id mediumint not null auto_increment primary key,
1353
groupset bigint not null,
1354
assigned_to mediumint not null, # This is a comment.
1356
bug_severity enum($my_severities) not null,
1357
bug_status enum("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED", "RESOLVED", "VERIFIED", "CLOSED") not null,
1358
creation_ts datetime not null,
1360
short_desc mediumtext,
1361
op_sys enum($my_opsys) not null,
1362
priority enum($my_priorities) not null,
1363
product varchar(64) not null,
1364
rep_platform enum($my_platforms),
1365
reporter mediumint not null,
1366
version varchar(64) not null,
1367
component varchar(50) not null,
1368
resolution enum("", "FIXED", "INVALID", "WONTFIX", "LATER", "REMIND", "DUPLICATE", "WORKSFORME", "MOVED") not null,
1369
target_milestone varchar(20) not null default "---",
1370
qa_contact mediumint not null,
1371
status_whiteboard mediumtext not null,
1372
votes mediumint not null,
1373
keywords mediumtext not null, ' # Note: keywords field is only a cache;
1374
# the real data comes from the keywords table.
1376
lastdiffed datetime not null,
1377
everconfirmed tinyint not null,
1378
reporter_accessible tinyint not null default 1,
1379
cclist_accessible tinyint not null default 1,
1381
index (assigned_to),
1382
index (creation_ts),
1384
index (bug_severity),
1393
index (target_milestone),
1399
'bug_id mediumint not null,
1400
who mediumint not null,
1403
unique(bug_id,who)';
1406
'watcher mediumint not null,
1407
watched mediumint not null,
1410
unique(watcher,watched)';
1414
'bug_id mediumint not null,
1415
who mediumint not null,
1416
bug_when datetime not null,
1424
$table{components} =
1426
program varchar(64),
1427
initialowner mediumint not null,
1428
initialqacontact mediumint not null,
1429
description mediumtext not null';
1432
$table{dependencies} =
1433
'blocked mediumint not null,
1434
dependson mediumint not null,
1440
# Group bits must be a power of two. Groups are identified by a bit; sets of
1441
# groups are indicated by or-ing these values together.
1443
# isbuggroup is nonzero if this is a group that controls access to a set
1444
# of bugs. In otherword, the groupset field in the bugs table should only
1445
# have this group's bit set if isbuggroup is nonzero.
1447
# User regexp is which email addresses are initially put into this group.
1448
# This is only used when an email account is created; otherwise, profiles
1449
# may be individually tweaked to add them in and out of groups.
1451
# 2001-04-10 myk@mozilla.org:
1452
# isactive determines whether or not a group is active. An inactive group
1453
# cannot have bugs added to it. Deactivation is a much milder form of
1454
# deleting a group that allows users to continue to work on bugs in the group
1455
# without enabling them to extend the life of the group by adding bugs to it.
1456
# http://bugzilla.mozilla.org/show_bug.cgi?id=75482
1459
'bit bigint not null,
1460
name varchar(255) not null,
1461
description text not null,
1462
isbuggroup tinyint not null,
1463
userregexp tinytext not null,
1464
isactive tinyint not null default 1,
1469
$table{logincookies} =
1470
'cookie mediumint not null auto_increment primary key,
1471
userid mediumint not null,
1472
ipaddr varchar(40) NOT NULL,
1479
'product varchar(64),
1480
description mediumtext,
1481
milestoneurl tinytext not null,
1482
disallownew tinyint not null,
1483
votesperuser smallint not null,
1484
maxvotesperbug smallint not null default 10000,
1485
votestoconfirm smallint not null,
1486
defaultmilestone varchar(20) not null default "---"
1491
'userid mediumint not null auto_increment primary key,
1492
login_name varchar(255) not null,
1493
cryptpassword varchar(34),
1494
realname varchar(255),
1495
groupset bigint not null,
1496
disabledtext mediumtext not null,
1497
mybugslink tinyint not null default 1,
1498
blessgroupset bigint not null default 0,
1499
emailflags mediumtext,
1502
unique(login_name)';
1505
$table{profiles_activity} =
1506
'userid mediumint not null,
1507
who mediumint not null,
1508
profiles_when datetime not null,
1509
fieldid mediumint not null,
1514
index (profiles_when),
1518
$table{namedqueries} =
1519
'userid mediumint not null,
1520
name varchar(64) not null,
1521
watchfordiffs tinyint not null,
1522
linkinfooter tinyint not null,
1523
query mediumtext not null,
1525
unique(userid, name),
1526
index(watchfordiffs)';
1528
# This isn't quite cooked yet...
1530
# $table{diffprefs} =
1531
# 'userid mediumint not null,
1532
# fieldid mediumint not null,
1533
# mailhead tinyint not null,
1534
# maildiffs tinyint not null,
1539
'fieldid mediumint not null auto_increment primary key,
1540
name varchar(64) not null,
1541
description mediumtext not null,
1542
mailhead tinyint not null default 0,
1543
sortkey smallint not null,
1550
program varchar(64) not null';
1554
'who mediumint not null,
1555
bug_id mediumint not null,
1556
count smallint not null,
1562
'bug_id mediumint not null,
1563
keywordid smallint not null,
1566
unique(bug_id,keywordid)';
1568
$table{keyworddefs} =
1569
'id smallint not null primary key,
1570
name varchar(64) not null,
1571
description mediumtext,
1576
$table{milestones} =
1577
'value varchar(20) not null,
1578
product varchar(64) not null,
1579
sortkey smallint not null,
1580
unique (product, value)';
1583
'id int not null auto_increment primary key,
1585
reflected tinyint not null,
1586
command mediumtext not null,
1590
$table{duplicates} =
1591
'dupe_of mediumint(9) not null,
1592
dupe mediumint(9) not null primary key';
1594
# 2001-06-21, myk@mozilla.org, bug 77473:
1595
# Stores the tokens users receive when they want to change their password
1596
# or email address. Tokens provide an extra measure of security for these changes.
1598
'userid mediumint not null ,
1599
issuedate datetime not null ,
1600
token varchar(16) not null primary key ,
1601
tokentype varchar(8) not null ,
1602
eventdata tinytext null ,
1608
###########################################################################
1610
###########################################################################
1612
# Get a list of the existing tables (if any) in the database
1614
my $zz = $DBI::VERSION; # mention it to eliminate "used only once" warning on perl 5.00503
1615
if ($DBI::VERSION < 1.20) {
1616
@tables = map { $_ =~ s/.*\.//; $_ } $dbh->tables;
1619
my $sth = $dbh->table_info(undef, undef, undef, "TABLE");
1620
@tables = @{$dbh->selectcol_arrayref($sth, { Columns => [3] })};
1622
#print 'Tables: ', join " ", @tables, "\n";
1624
# add lines here if you add more --LOCAL-- config vars that end up in the enums:
1626
my $my_severities = '"' . join('", "', @my_severities) . '"';
1627
my $my_priorities = '"' . join('", "', @my_priorities) . '"';
1628
my $my_opsys = '"' . join('", "', @my_opsys) . '"';
1629
my $my_platforms = '"' . join('", "', @my_platforms) . '"';
1631
# go throught our %table hash and create missing tables
1632
while (my ($tabname, $fielddef) = each %table) {
1633
next if grep /^$tabname$/, @tables;
1634
print "Creating table $tabname ...\n";
1636
# add lines here if you add more --LOCAL-- config vars that end up in
1639
$fielddef =~ s/\$my_severities/$my_severities/;
1640
$fielddef =~ s/\$my_priorities/$my_priorities/;
1641
$fielddef =~ s/\$my_opsys/$my_opsys/;
1642
$fielddef =~ s/\$my_platforms/$my_platforms/;
1644
$dbh->do("CREATE TABLE $tabname (\n$fielddef\n)")
1645
or die "Could not create table '$tabname'. Please check your '$db_base' access.\n";
1652
###########################################################################
1653
# Populate groups table
1654
###########################################################################
1656
sub GroupDoesExist ($)
1659
my $sth = $dbh->prepare("SELECT name FROM groups WHERE name='$name'");
1669
# This subroutine checks if a group exist. If not, it will be automatically
1670
# created with the next available bit set
1674
my ($name, $desc, $userregexp) = @_;
1677
return if GroupDoesExist($name);
1679
# get highest bit number
1680
my $sth = $dbh->prepare("SELECT bit FROM groups ORDER BY bit DESC");
1682
my @row = $sth->fetchrow_array;
1686
if (defined $row[0]) {
1687
$bit = $row[0] << 1;
1693
print "Adding group $name ...\n";
1694
$sth = $dbh->prepare('INSERT INTO groups
1695
(bit, name, description, userregexp, isbuggroup)
1696
VALUES (?, ?, ?, ?, ?)');
1697
$sth->execute($bit, $name, $desc, $userregexp, 0);
1703
# BugZilla uses --GROUPS-- to assign various rights to its users.
1706
AddGroup 'tweakparams', 'Can tweak operating parameters';
1707
AddGroup 'editusers', 'Can edit or disable users';
1708
AddGroup 'creategroups', 'Can create and destroy groups.';
1709
AddGroup 'editcomponents', 'Can create, destroy, and edit components.';
1710
AddGroup 'editkeywords', 'Can create, destroy, and edit keywords.';
1712
# Add the groupset field here because this code is run before the
1713
# code that updates the database structure.
1714
&AddField('profiles', 'groupset', 'bigint not null');
1716
if (!GroupDoesExist("editbugs")) {
1717
my $id = AddGroup('editbugs', 'Can edit all aspects of any bug.', ".*");
1718
$dbh->do("UPDATE profiles SET groupset = groupset | $id");
1721
if (!GroupDoesExist("canconfirm")) {
1722
my $id = AddGroup('canconfirm', 'Can confirm a bug.', ".*");
1723
$dbh->do("UPDATE profiles SET groupset = groupset | $id");
1730
###########################################################################
1731
# Populate the list of fields.
1732
###########################################################################
1737
my ($name, $description, $mailhead) = (@_);
1739
$name = $dbh->quote($name);
1740
$description = $dbh->quote($description);
1742
my $sth = $dbh->prepare("SELECT fieldid FROM fielddefs " .
1743
"WHERE name = $name");
1745
my ($fieldid) = ($sth->fetchrow_array());
1750
$dbh->do("REPLACE INTO fielddefs " .
1751
"(fieldid, name, description, mailhead, sortkey) VALUES " .
1752
"($fieldid, $name, $description, $mailhead, $headernum)");
1757
AddFDef("bug_id", "Bug \#", 1);
1758
AddFDef("short_desc", "Summary", 1);
1759
AddFDef("product", "Product", 1);
1760
AddFDef("version", "Version", 1);
1761
AddFDef("rep_platform", "Platform", 1);
1762
AddFDef("bug_file_loc", "URL", 1);
1763
AddFDef("op_sys", "OS/Version", 1);
1764
AddFDef("bug_status", "Status", 1);
1765
AddFDef("status_whiteboard", "Status Whiteboard", 0);
1766
AddFDef("keywords", "Keywords", 0);
1767
AddFDef("resolution", "Resolution", 0);
1768
AddFDef("bug_severity", "Severity", 1);
1769
AddFDef("priority", "Priority", 1);
1770
AddFDef("component", "Component", 1);
1771
AddFDef("assigned_to", "AssignedTo", 1);
1772
AddFDef("reporter", "ReportedBy", 1);
1773
AddFDef("votes", "Votes", 0);
1774
AddFDef("qa_contact", "QAContact", 1);
1775
AddFDef("cc", "CC", 1);
1776
AddFDef("dependson", "BugsThisDependsOn", 0);
1777
AddFDef("blocked", "OtherBugsDependingOnThis", 0);
1778
AddFDef("attachments.description", "Attachment description", 0);
1779
AddFDef("attachments.thedata", "Attachment data", 0);
1780
AddFDef("attachments.mimetype", "Attachment mime type", 0);
1781
AddFDef("attachments.ispatch", "Attachment is patch", 0);
1782
AddFDef("attachments.isobsolete", "Attachment is obsolete", 0);
1783
AddFDef("attachstatusdefs.name", "Attachment Status", 0);
1784
AddFDef("target_milestone", "Target Milestone", 0);
1785
AddFDef("delta_ts", "Last changed date", 0);
1786
AddFDef("(to_days(now()) - to_days(bugs.delta_ts))", "Days since bug changed",
1788
AddFDef("longdesc", "Comment", 0);
1793
###########################################################################
1794
# Detect changed local settings
1795
###########################################################################
1797
sub GetFieldDef ($$)
1799
my ($table, $field) = @_;
1800
my $sth = $dbh->prepare("SHOW COLUMNS FROM $table");
1803
while (my $ref = $sth->fetchrow_arrayref) {
1804
next if $$ref[0] ne $field;
1809
sub GetIndexDef ($$)
1811
my ($table, $field) = @_;
1812
my $sth = $dbh->prepare("SHOW INDEX FROM $table");
1815
while (my $ref = $sth->fetchrow_arrayref) {
1816
next if $$ref[2] ne $field;
1821
sub CountIndexes ($)
1825
my $sth = $dbh->prepare("SHOW INDEX FROM $table");
1828
if ( $sth->rows == -1 ) {
1829
die ("Unexpected response while counting indexes in $table:" .
1830
" \$sth->rows == -1");
1833
return ($sth->rows);
1841
# get the list of indexes
1843
my $sth = $dbh->prepare("SHOW INDEX FROM $table");
1848
while ( my $ref = $sth->fetchrow_arrayref) {
1850
# note that some indexes are described by multiple rows in the
1851
# index table, so we may have already dropped the index described
1852
# in the current row.
1854
next if exists $SEEN{$$ref[2]};
1856
my $dropSth = $dbh->prepare("ALTER TABLE $table DROP INDEX $$ref[2]");
1859
$SEEN{$$ref[2]} = 1;
1865
# Check if the enums in the bugs table return the same values that are defined
1866
# in the various locally changeable variables. If this is true, then alter the
1870
sub CheckEnumField ($$@)
1872
my ($table, $field, @against) = @_;
1874
my $ref = GetFieldDef($table, $field);
1875
#print "0: $$ref[0] 1: $$ref[1] 2: $$ref[2] 3: $$ref[3] 4: $$ref[4]\n";
1877
$_ = "enum('" . join("','", @against) . "')";
1878
if ($$ref[1] ne $_) {
1879
print "Updating field $field in table $table ...\n";
1880
$_ .= " NOT NULL" if $$ref[3];
1881
$dbh->do("ALTER TABLE $table
1890
# This code changes the enum types of some SQL tables whenever you change
1891
# some --LOCAL-- variables. Once you have a running system, to add new
1892
# severities, priorities, operating systems and platforms, add them to
1893
# the localconfig file and then re-run checksetup.pl which will make the
1894
# necessary changes to your database. Additions to these fields in
1895
# checksetup.pl after the initial installation of bugzilla on a system
1899
CheckEnumField('bugs', 'bug_severity', @my_severities);
1900
CheckEnumField('bugs', 'priority', @my_priorities);
1901
CheckEnumField('bugs', 'op_sys', @my_opsys);
1902
CheckEnumField('bugs', 'rep_platform', @my_platforms);
1905
###########################################################################
1906
# Create Administrator --ADMIN--
1907
###########################################################################
1909
# Prompt the user for the email address and name of an administrator. Create
1910
# that login, if it doesn't exist already, and make it a member of all groups.
1912
sub bailout { # this is just in case we get interrupted while getting passwd
1913
system("stty","echo"); # re-enable input echoing
1917
my $sth = $dbh->prepare(<<_End_Of_SQL_);
1920
WHERE groupset=9223372036854775807
1923
# when we have no admin users, prompt for admin email address and password ...
1924
if ($sth->rows == 0) {
1930
my $admin_create = 1;
1931
my $mailcheckexp = "";
1934
# Here we look to see what the emailregexp is set to so we can
1935
# check the email addy they enter. Bug 96675. If they have no
1936
# params (likely but not always the case), we use the default.
1937
if (-e "data/params") {
1938
require "data/params"; # if they have a params file, use that
1940
if ($::param{emailregexp}) {
1941
$mailcheckexp = $::param{emailregexp};
1942
$mailcheck = $::param{emailregexpdesc};
1944
$mailcheckexp = '^[^@]+@[^@]+\\.[^@]+$';
1945
$mailcheck = 'A legal address must contain exactly one \'@\',
1946
and at least one \'.\' after the @.';
1949
print "\nLooks like we don't have an administrator set up yet. Either this is your\n";
1950
print "first time using Bugzilla, or your administrator's privs might have accidently\n";
1951
print "gotten deleted at some point.\n";
1952
while(! $admin_ok ) {
1953
while( $login eq "" ) {
1954
print "Enter the e-mail address of the administrator: ";
1958
print "\nYou DO want an administrator, don't you?\n";
1960
unless ($login =~ /$mailcheckexp/) {
1961
print "\nThe login address is invalid:\n";
1962
print "$mailcheck\n";
1963
print "You can change this test on the params page once checksetup has successfully\n";
1964
print "completed.\n\n";
1965
# Go round, and ask them again
1969
$login = $dbh->quote($login);
1970
$sth = $dbh->prepare(<<_End_Of_SQL_);
1973
WHERE login_name=$login
1976
if ($sth->rows > 0) {
1977
print "$login already has an account.\n";
1978
print "Make this user the administrator? [Y/n] ";
1985
print "OK, well, someone has to be the administrator. Try someone else.\n";
1989
print "You entered $login. Is this correct? [Y/n] ";
1995
print "That's okay, typos happen. Give it another shot.\n";
2001
if ($admin_create) {
2003
while( $realname eq "" ) {
2004
print "Enter the real name of the administrator: ";
2005
$realname = <STDIN>;
2008
print "\nReally. We need a full name.\n";
2012
# trap a few interrupts so we can fix the echo if we get aborted.
2013
$SIG{HUP} = \&bailout;
2014
$SIG{INT} = \&bailout;
2015
$SIG{QUIT} = \&bailout;
2016
$SIG{TERM} = \&bailout;
2018
system("stty","-echo"); # disable input echoing
2020
while( $pass1 ne $pass2 ) {
2021
while( $pass1 eq "" || $pass1 !~ /^[a-zA-Z0-9-_]{3,16}$/ ) {
2022
print "Enter a password for the administrator account: ";
2026
print "\n\nIt's just plain stupid to not have a password. Try again!\n";
2027
} elsif ( $pass1 !~ /^[a-zA-Z0-9-_]{3,16}$/ ) {
2028
print "\n\nThe password must be 3-16 characters in length, " .
2029
"and it may\ncontain only letters, digits, " .
2030
"underscores (_), and hyphens (-).\n";
2033
print "\nPlease retype the password to verify: ";
2036
if ($pass1 ne $pass2) {
2037
print "\n\nPasswords don't match. Try again!\n";
2043
# Crypt the administrator's password
2044
my $cryptedpassword = Crypt($pass1);
2046
system("stty","echo"); # re-enable input echoing
2047
$SIG{HUP} = 'DEFAULT'; # and remove our interrupt hooks
2048
$SIG{INT} = 'DEFAULT';
2049
$SIG{QUIT} = 'DEFAULT';
2050
$SIG{TERM} = 'DEFAULT';
2052
$realname = $dbh->quote($realname);
2053
$cryptedpassword = $dbh->quote($cryptedpassword);
2055
$dbh->do(<<_End_Of_SQL_);
2056
INSERT INTO profiles
2057
(login_name, realname, cryptpassword, groupset)
2058
VALUES ($login, $realname, $cryptedpassword, 0x7fffffffffffffff)
2061
$dbh->do(<<_End_Of_SQL_);
2063
SET groupset=0x7fffffffffffffff
2064
WHERE login_name=$login
2067
print "\n$login is now set up as the administrator account.\n";
2073
###########################################################################
2074
# Create initial test product if there are no products present.
2075
###########################################################################
2077
$sth = $dbh->prepare(<<_End_Of_SQL_);
2080
WHERE groupset=9223372036854775807
2083
my ($adminuid) = $sth->fetchrow_array;
2084
if (!$adminuid) { die "No administator!" } # should never get here
2085
$sth = $dbh->prepare("SELECT product FROM products");
2087
unless ($sth->rows) {
2088
print "Creating initial dummy product 'TestProduct' ...\n";
2089
$dbh->do('INSERT INTO products(product, description, milestoneurl, disallownew, votesperuser, votestoconfirm) VALUES ("TestProduct",
2090
"This is a test product. This ought to be blown away and ' .
2091
'replaced with real stuff in a finished installation of ' .
2092
'bugzilla.", "", 0, 0, 0)');
2093
$dbh->do('INSERT INTO versions (value, program) VALUES ("other", "TestProduct")');
2094
$dbh->do("INSERT INTO components (value, program, description, initialowner, initialqacontact)
2096
"'TestComponent', 'TestProduct', " .
2097
"'This is a test component in the test product database. " .
2098
"This ought to be blown away and replaced with real stuff in " .
2099
"a finished installation of bugzilla.', $adminuid, 0)");
2100
$dbh->do('INSERT INTO milestones (product, value) VALUES ("TestProduct","---")');
2107
###########################################################################
2108
# Update the tables to the current definition
2109
###########################################################################
2112
# As time passes, fields in tables get deleted, added, changed and so on.
2113
# So we need some helper subroutines to make this possible:
2116
sub ChangeFieldType ($$$)
2118
my ($table, $field, $newtype) = @_;
2120
my $ref = GetFieldDef($table, $field);
2121
#print "0: $$ref[0] 1: $$ref[1] 2: $$ref[2] 3: $$ref[3] 4: $$ref[4]\n";
2123
my $oldtype = $ref->[1];
2125
$oldtype .= qq{ not null};
2128
$oldtype .= qq{ default "$ref->[4]"};
2131
if ($oldtype ne $newtype) {
2132
print "Updating field type $field in table $table ...\n";
2133
print "old: $oldtype\n";
2134
print "new: $newtype\n";
2135
# 'not null' should be passed as part of the call to ChangeFieldType()
2136
# $newtype .= " NOT NULL" if $$ref[3];
2137
$dbh->do("ALTER TABLE $table
2143
sub RenameField ($$$)
2145
my ($table, $field, $newname) = @_;
2147
my $ref = GetFieldDef($table, $field);
2148
return unless $ref; # already fixed?
2149
#print "0: $$ref[0] 1: $$ref[1] 2: $$ref[2] 3: $$ref[3] 4: $$ref[4]\n";
2151
if ($$ref[1] ne $newname) {
2152
print "Updating field $field in table $table ...\n";
2153
my $type = $$ref[1];
2154
$type .= " NOT NULL" if $$ref[3];
2155
$dbh->do("ALTER TABLE $table
2163
my ($table, $field, $definition) = @_;
2165
my $ref = GetFieldDef($table, $field);
2166
return if $ref; # already added?
2168
print "Adding new field $field to table $table ...\n";
2169
$dbh->do("ALTER TABLE $table
2170
ADD COLUMN $field $definition");
2175
my ($table, $field) = @_;
2177
my $ref = GetFieldDef($table, $field);
2178
return unless $ref; # already dropped?
2180
print "Deleting unused field $field from table $table ...\n";
2181
$dbh->do("ALTER TABLE $table
2182
DROP COLUMN $field");
2185
# this uses a mysql specific command.
2192
my $sth = $dbh->prepare("SHOW TABLES");
2194
while ( ($dbtable) = $sth->fetchrow_array ) {
2195
if ($dbtable eq $table) {
2203
# really old fields that were added before checksetup.pl existed
2204
# but aren't in very old bugzilla's (like 2.1)
2205
# Steve Stock (sstock@iconnect-inc.com)
2206
AddField('bugs', 'target_milestone', 'varchar(20) not null default "---"');
2207
AddField('bugs', 'groupset', 'bigint not null');
2208
AddField('bugs', 'qa_contact', 'mediumint not null');
2209
AddField('bugs', 'status_whiteboard', 'mediumtext not null');
2210
AddField('products', 'disallownew', 'tinyint not null');
2211
AddField('products', 'milestoneurl', 'tinytext not null');
2212
AddField('components', 'initialqacontact', 'tinytext not null');
2213
AddField('components', 'description', 'mediumtext not null');
2214
ChangeFieldType('components', 'program', 'varchar(64)');
2217
# 1999-06-22 Added an entry to the attachments table to record who the
2218
# submitter was. Nothing uses this yet, but it still should be recorded.
2220
AddField('attachments', 'submitter_id', 'mediumint not null');
2223
# One could even populate this field automatically, e.g. with
2225
# unless (GetField('attachments', 'submitter_id') {
2230
# For now I was too lazy, so you should read the documentation :-)
2234
# 1999-9-15 Apparently, newer alphas of MySQL won't allow you to have "when"
2235
# as a column name. So, I have had to rename a column in the bugs_activity
2238
RenameField ('bugs_activity', 'when', 'bug_when');
2242
# 1999-10-11 Restructured voting database to add a cached value in each bug
2243
# recording how many total votes that bug has. While I'm at it, I removed
2244
# the unused "area" field from the bugs database. It is distressing to
2245
# realize that the bugs table has reached the maximum number of indices
2246
# allowed by MySQL (16), which may make future enhancements awkward.
2247
# (P.S. All is not lost; it appears that the latest betas of MySQL support
2248
# a new table format which will allow 32 indices.)
2250
DropField('bugs', 'area');
2251
AddField('bugs', 'votes', 'mediumint not null, add index (votes)');
2252
AddField('products', 'votesperuser', 'mediumint not null');
2256
# The product name used to be very different in various tables.
2258
# It was varchar(16) in bugs
2259
# tinytext in components
2260
# tinytext in products
2261
# tinytext in versions
2263
# tinytext is equivalent to varchar(255), which is quite huge, so I change
2264
# them all to varchar(64).
2266
ChangeFieldType ('bugs', 'product', 'varchar(64) not null');
2267
ChangeFieldType ('components', 'program', 'varchar(64)');
2268
ChangeFieldType ('products', 'product', 'varchar(64)');
2269
ChangeFieldType ('versions', 'program', 'varchar(64) not null');
2271
# 2000-01-16 Added a "keywords" field to the bugs table, which
2272
# contains a string copy of the entries of the keywords table for this
2273
# bug. This is so that I can easily sort and display a keywords
2274
# column in bug lists.
2276
if (!GetFieldDef('bugs', 'keywords')) {
2277
AddField('bugs', 'keywords', 'mediumtext not null');
2280
print "Making sure 'keywords' field of table 'bugs' is empty ...\n";
2281
$dbh->do("UPDATE bugs SET delta_ts = delta_ts, keywords = '' " .
2282
"WHERE keywords != ''");
2283
print "Repopulating 'keywords' field of table 'bugs' ...\n";
2284
my $sth = $dbh->prepare("SELECT keywords.bug_id, keyworddefs.name " .
2285
"FROM keywords, keyworddefs " .
2286
"WHERE keyworddefs.id = keywords.keywordid " .
2287
"ORDER BY keywords.bug_id, keyworddefs.name");
2293
my ($b, $k) = ($sth->fetchrow_array());
2294
if (!defined $b || $b ne $bugid) {
2296
$dbh->do("UPDATE bugs SET delta_ts = delta_ts, keywords = " .
2297
$dbh->quote(join(', ', @list)) .
2298
" WHERE bug_id = $bugid");
2311
# 2000-01-18 Added a "disabledtext" field to the profiles table. If not
2312
# empty, then this account has been disabled, and this field is to contain
2313
# text describing why.
2315
AddField('profiles', 'disabledtext', 'mediumtext not null');
2319
# 2000-01-20 Added a new "longdescs" table, which is supposed to have all the
2320
# long descriptions in it, replacing the old long_desc field in the bugs
2321
# table. The below hideous code populates this new table with things from
2322
# the old field, with ugly parsing and heuristics.
2325
my ($id, $who, $when, $buffer) = (@_);
2326
$buffer = trim($buffer);
2327
if ($buffer eq '') {
2330
$dbh->do("INSERT INTO longdescs (bug_id, who, bug_when, thetext) VALUES " .
2331
"($id, $who, " . time2str("'%Y/%m/%d %H:%M:%S'", $when) .
2332
", " . $dbh->quote($buffer) . ")");
2336
if (GetFieldDef('bugs', 'long_desc')) {
2337
eval("use Date::Parse");
2338
eval("use Date::Format");
2339
my $sth = $dbh->prepare("SELECT count(*) FROM bugs");
2341
my ($total) = ($sth->fetchrow_array);
2343
print "Populating new long_desc table. This is slow. There are $total\n";
2344
print "bugs to process; a line of dots will be printed for each 50.\n\n";
2347
$dbh->do("LOCK TABLES bugs write, longdescs write, profiles write");
2349
$dbh->do('DELETE FROM longdescs');
2351
$sth = $dbh->prepare("SELECT bug_id, creation_ts, reporter, long_desc " .
2352
"FROM bugs ORDER BY bug_id");
2356
my ($id, $createtime, $reporterid, $desc) = ($sth->fetchrow_array());
2362
if ($count % 10 == 0) {
2364
if ($count % 50 == 0) {
2365
print "$count/$total (" . int($count * 100 / $total) . "%)\n";
2369
my $who = $reporterid;
2370
my $when = str2time($createtime);
2372
foreach my $line (split(/\n/, $desc)) {
2373
$line =~ s/\s+$//g; # Trim trailing whitespace.
2374
if ($line =~ /^------- Additional Comments From ([^\s]+)\s+(\d.+\d)\s+-------$/) {
2376
my $date = str2time($2);
2377
$date += 59; # Oy, what a hack. The creation time is
2378
# accurate to the second. But we the long
2379
# text only contains things accurate to the
2380
# minute. And so, if someone makes a comment
2381
# within a minute of the original bug creation,
2382
# then the comment can come *before* the
2383
# bug creation. So, we add 59 seconds to
2384
# the time of all comments, so that they
2385
# are always considered to have happened at
2386
# the *end* of the given minute, not the
2388
if ($date >= $when) {
2389
WriteOneDesc($id, $who, $when, $buffer);
2392
my $s2 = $dbh->prepare("SELECT userid FROM profiles " .
2393
"WHERE login_name = " .
2394
$dbh->quote($name));
2396
($who) = ($s2->fetchrow_array());
2398
# This username doesn't exist. Try a special
2399
# netscape-only hack (sorry about that, but I don't
2400
# think it will hurt any other installations). We
2401
# have many entries in the bugsystem from an ancient
2402
# world where the "@netscape.com" part of the loginname
2403
# was omitted. So, look up the user again with that
2404
# appended, and use it if it's there.
2405
if ($name !~ /\@/) {
2406
my $nsname = $name . "\@netscape.com";
2408
$dbh->prepare("SELECT userid FROM profiles " .
2409
"WHERE login_name = " .
2410
$dbh->quote($nsname));
2412
($who) = ($s2->fetchrow_array());
2417
# This username doesn't exist. Maybe someone renamed
2418
# him or something. Invent a new profile entry,
2419
# disabled, just to represent him.
2420
$dbh->do("INSERT INTO profiles " .
2421
"(login_name, cryptpassword," .
2422
" disabledtext) VALUES (" .
2423
$dbh->quote($name) .
2424
", " . $dbh->quote(Crypt('okthen')) . ", " .
2425
"'Account created only to maintain database integrity')");
2426
$s2 = $dbh->prepare("SELECT LAST_INSERT_ID()");
2428
($who) = ($s2->fetchrow_array());
2432
# print "\nDecided this line of bug $id has a date of " .
2433
# time2str("'%Y/%m/%d %H:%M:%S'", $date) .
2434
# "\nwhich is less than previous line:\n$line\n\n";
2438
$buffer .= $line . "\n";
2440
WriteOneDesc($id, $who, $when, $buffer);
2446
DropField('bugs', 'long_desc');
2448
$dbh->do("UNLOCK TABLES");
2452
# 2000-01-18 Added a new table fielddefs that records information about the
2453
# different fields we keep an activity log on. The bugs_activity table
2454
# now has a pointer into that table instead of recording the name directly.
2456
if (GetFieldDef('bugs_activity', 'field')) {
2457
AddField('bugs_activity', 'fieldid',
2458
'mediumint not null, ADD INDEX (fieldid)');
2459
print "Populating new fieldid field ...\n";
2461
$dbh->do("LOCK TABLES bugs_activity WRITE, fielddefs WRITE");
2463
my $sth = $dbh->prepare('SELECT DISTINCT field FROM bugs_activity');
2466
while (my ($f) = ($sth->fetchrow_array())) {
2467
my $q = $dbh->quote($f);
2469
$dbh->prepare("SELECT fieldid FROM fielddefs WHERE name = $q");
2471
my ($id) = ($s2->fetchrow_array());
2473
$dbh->do("INSERT INTO fielddefs (name, description) VALUES " .
2475
$s2 = $dbh->prepare("SELECT LAST_INSERT_ID()");
2477
($id) = ($s2->fetchrow_array());
2479
$dbh->do("UPDATE bugs_activity SET fieldid = $id WHERE field = $q");
2481
$dbh->do("UNLOCK TABLES");
2483
DropField('bugs_activity', 'field');
2487
# 2000-01-18 New email-notification scheme uses a new field in the bug to
2488
# record when email notifications were last sent about this bug. Also,
2489
# added a user pref whether a user wants to use the brand new experimental
2491
# 2001-04-29 jake@acutex.net - The newemailtech field is no longer needed
2492
# http://bugzilla.mozilla.org/show_bugs.cgi?id=71552
2494
if (!GetFieldDef('bugs', 'lastdiffed')) {
2495
AddField('bugs', 'lastdiffed', 'datetime not null');
2496
$dbh->do('UPDATE bugs SET lastdiffed = now(), delta_ts = delta_ts');
2500
# 2000-01-22 The "login_name" field in the "profiles" table was not
2501
# declared to be unique. Sure enough, somehow, I got 22 duplicated entries
2502
# in my database. This code detects that, cleans up the duplicates, and
2503
# then tweaks the table to declare the field to be unique. What a pain.
2505
if (GetIndexDef('profiles', 'login_name')->[1]) {
2506
print "Searching for duplicate entries in the profiles table ...\n";
2508
# This code is weird in that it loops around and keeps doing this
2509
# select again. That's because I'm paranoid about deleting entries
2510
# out from under us in the profiles table. Things get weird if
2511
# there are *three* or more entries for the same user...
2512
$sth = $dbh->prepare("SELECT p1.userid, p2.userid, p1.login_name " .
2513
"FROM profiles AS p1, profiles AS p2 " .
2514
"WHERE p1.userid < p2.userid " .
2515
"AND p1.login_name = p2.login_name " .
2516
"ORDER BY p1.login_name");
2518
my ($u1, $u2, $n) = ($sth->fetchrow_array);
2522
print "Both $u1 & $u2 are ids for $n! Merging $u2 into $u1 ...\n";
2523
foreach my $i (["bugs", "reporter"],
2524
["bugs", "assigned_to"],
2525
["bugs", "qa_contact"],
2526
["attachments", "submitter_id"],
2527
["bugs_activity", "who"],
2530
["longdescs", "who"]) {
2531
my ($table, $field) = (@$i);
2532
print " Updating $table.$field ...\n";
2534
if ($table eq "bugs") {
2535
$extra = ", delta_ts = delta_ts";
2537
$dbh->do("UPDATE $table SET $field = $u1 $extra " .
2538
"WHERE $field = $u2");
2540
$dbh->do("DELETE FROM profiles WHERE userid = $u2");
2542
print "OK, changing index type to prevent duplicates in the future ...\n";
2544
$dbh->do("ALTER TABLE profiles DROP INDEX login_name");
2545
$dbh->do("ALTER TABLE profiles ADD UNIQUE (login_name)");
2550
# 2000-01-24 Added a new field to let people control whether the "My
2551
# bugs" link appears at the bottom of each page. Also can control
2552
# whether each named query should show up there.
2554
AddField('profiles', 'mybugslink', 'tinyint not null default 1');
2555
AddField('namedqueries', 'linkinfooter', 'tinyint not null');
2558
# 2000-02-12 Added a new state to bugs, UNCONFIRMED. Added ability to confirm
2559
# a vote via bugs. Added user bits to control which users can confirm bugs
2560
# by themselves, and which users can edit bugs without their names on them.
2561
# Added a user field which controls which groups a user can put other users
2564
my @resolutions = ("", "FIXED", "INVALID", "WONTFIX", "LATER", "REMIND",
2565
"DUPLICATE", "WORKSFORME", "MOVED");
2566
CheckEnumField('bugs', 'resolution', @resolutions);
2568
if (($_ = GetFieldDef('components', 'initialowner')) and ($_->[1] eq 'tinytext')) {
2569
$sth = $dbh->prepare("SELECT program, value, initialowner, initialqacontact FROM components");
2571
while (my ($program, $value, $initialowner) = $sth->fetchrow_array()) {
2572
$initialowner =~ s/([\\\'])/\\$1/g; $initialowner =~ s/\0/\\0/g;
2573
$program =~ s/([\\\'])/\\$1/g; $program =~ s/\0/\\0/g;
2574
$value =~ s/([\\\'])/\\$1/g; $value =~ s/\0/\\0/g;
2576
my $s2 = $dbh->prepare("SELECT userid FROM profiles WHERE login_name = '$initialowner'");
2579
my $initialownerid = $s2->fetchrow_array();
2581
unless (defined $initialownerid) {
2582
print "Warning: You have an invalid initial owner '$initialowner' in program '$program', component '$value'!\n";
2583
$initialownerid = 0;
2586
my $update = "UPDATE components SET initialowner = $initialownerid ".
2587
"WHERE program = '$program' AND value = '$value'";
2588
my $s3 = $dbh->prepare("UPDATE components SET initialowner = $initialownerid ".
2589
"WHERE program = '$program' AND value = '$value';");
2593
ChangeFieldType('components','initialowner','mediumint');
2596
if (($_ = GetFieldDef('components', 'initialqacontact')) and ($_->[1] eq 'tinytext')) {
2597
$sth = $dbh->prepare("SELECT program, value, initialqacontact, initialqacontact FROM components");
2599
while (my ($program, $value, $initialqacontact) = $sth->fetchrow_array()) {
2600
$initialqacontact =~ s/([\\\'])/\\$1/g; $initialqacontact =~ s/\0/\\0/g;
2601
$program =~ s/([\\\'])/\\$1/g; $program =~ s/\0/\\0/g;
2602
$value =~ s/([\\\'])/\\$1/g; $value =~ s/\0/\\0/g;
2604
my $s2 = $dbh->prepare("SELECT userid FROM profiles WHERE login_name = '$initialqacontact'");
2607
my $initialqacontactid = $s2->fetchrow_array();
2609
unless (defined $initialqacontactid) {
2610
if ($initialqacontact ne '') {
2611
print "Warning: You have an invalid initial QA contact '$initialqacontact' in program '$program', component '$value'!\n";
2613
$initialqacontactid = 0;
2616
my $update = "UPDATE components SET initialqacontact = $initialqacontactid ".
2617
"WHERE program = '$program' AND value = '$value'";
2618
my $s3 = $dbh->prepare("UPDATE components SET initialqacontact = $initialqacontactid ".
2619
"WHERE program = '$program' AND value = '$value';");
2623
ChangeFieldType('components','initialqacontact','mediumint');
2628
my @states = ("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED", "RESOLVED",
2629
"VERIFIED", "CLOSED");
2630
CheckEnumField('bugs', 'bug_status', @states);
2632
if (!GetFieldDef('bugs', 'everconfirmed')) {
2633
AddField('bugs', 'everconfirmed', 'tinyint not null');
2634
$dbh->do("UPDATE bugs SET everconfirmed = 1, delta_ts = delta_ts");
2636
AddField('products', 'maxvotesperbug', 'smallint not null default 10000');
2637
AddField('products', 'votestoconfirm', 'smallint not null');
2638
AddField('profiles', 'blessgroupset', 'bigint not null');
2640
# 2000-03-21 Adding a table for target milestones to
2641
# database - matthew@zeroknowledge.com
2643
$sth = $dbh->prepare("SELECT count(*) from milestones");
2645
if (!($sth->fetchrow_arrayref()->[0])) {
2646
print "Replacing blank milestones...\n";
2647
$dbh->do("UPDATE bugs SET target_milestone = '---', delta_ts=delta_ts WHERE target_milestone = ' '");
2649
# Populate milestone table with all exisiting values in database
2650
$sth = $dbh->prepare("SELECT DISTINCT target_milestone, product FROM bugs");
2653
print "Populating milestones table...\n";
2657
while(($value, $product) = $sth->fetchrow_array())
2659
# check if the value already exists
2660
my $sortkey = substr($value, 1);
2661
if ($sortkey !~ /^\d+$/) {
2666
$value = $dbh->quote($value);
2667
$product = $dbh->quote($product);
2668
my $s2 = $dbh->prepare("SELECT value FROM milestones WHERE value = $value AND product = $product");
2671
if(!$s2->fetchrow_array())
2673
$dbh->do("INSERT INTO milestones(value, product, sortkey) VALUES($value, $product, $sortkey)");
2678
# 2000-03-22 Changed the default value for target_milestone to be "---"
2679
# (which is still not quite correct, but much better than what it was
2680
# doing), and made the size of the value field in the milestones table match
2681
# the size of the target_milestone field in the bugs table.
2683
ChangeFieldType('bugs', 'target_milestone',
2684
'varchar(20) not null default "---"');
2685
ChangeFieldType('milestones', 'value', 'varchar(20) not null');
2688
# 2000-03-23 Added a defaultmilestone field to the products table, so that
2689
# we know which milestone to initially assign bugs to.
2691
if (!GetFieldDef('products', 'defaultmilestone')) {
2692
AddField('products', 'defaultmilestone',
2693
'varchar(20) not null default "---"');
2694
$sth = $dbh->prepare("SELECT product, defaultmilestone FROM products");
2696
while (my ($product, $defaultmilestone) = $sth->fetchrow_array()) {
2697
$product = $dbh->quote($product);
2698
$defaultmilestone = $dbh->quote($defaultmilestone);
2699
my $s2 = $dbh->prepare("SELECT value FROM milestones " .
2700
"WHERE value = $defaultmilestone " .
2701
"AND product = $product");
2703
if (!$s2->fetchrow_array()) {
2704
$dbh->do("INSERT INTO milestones(value, product) " .
2705
"VALUES ($defaultmilestone, $product)");
2710
# 2000-03-24 Added unique indexes into the cc and keyword tables. This
2711
# prevents certain database inconsistencies, and, moreover, is required for
2712
# new generalized list code to work.
2714
if ( CountIndexes('cc') != 3 ) {
2716
# XXX should eliminate duplicate entries before altering
2718
print "Recreating indexes on cc table.\n";
2720
$dbh->do("ALTER TABLE cc ADD UNIQUE (bug_id,who)");
2721
$dbh->do("ALTER TABLE cc ADD INDEX (who)");
2724
if ( CountIndexes('keywords') != 3 ) {
2726
# XXX should eliminate duplicate entries before altering
2728
print "Recreating indexes on keywords table.\n";
2729
DropIndexes('keywords');
2730
$dbh->do("ALTER TABLE keywords ADD INDEX (keywordid)");
2731
$dbh->do("ALTER TABLE keywords ADD UNIQUE (bug_id,keywordid)");
2735
# 2000-07-15 Added duplicates table so Bugzilla tracks duplicates in a better
2736
# way than it used to. This code searches the comments to populate the table
2737
# initially. It's executed if the table is empty; if it's empty because there
2738
# are no dupes (as opposed to having just created the table) it won't have
2739
# any effect anyway, so it doesn't matter.
2740
$sth = $dbh->prepare("SELECT count(*) from duplicates");
2742
if (!($sth->fetchrow_arrayref()->[0])) {
2744
print("Populating duplicates table...\n");
2746
$sth = $dbh->prepare("SELECT longdescs.bug_id, thetext FROM longdescs left JOIN bugs using(bug_id) WHERE (thetext " .
2747
"regexp '[.*.]{3,3} This bug has been marked as a duplicate of [[:digit:]]{1,5} [.*.]{3,3}') AND (resolution = 'DUPLICATE') ORDER" .
2748
" BY longdescs.bug_when");
2754
# Because of the way hashes work, this loop removes all but the last dupe
2755
# resolution found for a given bug.
2756
while (my ($dupe, $dupe_of) = $sth->fetchrow_array()) {
2757
$dupes{$dupe} = $dupe_of;
2760
foreach $key (keys(%dupes))
2762
$dupes{$key} =~ /^.*\*\*\* This bug has been marked as a duplicate of (\d+) \*\*\*$/ms;
2764
$dbh->do("INSERT INTO duplicates VALUES('$dupes{$key}', '$key')");
2765
# BugItsADupeOf Dupe
2769
# 2000-12-18. Added an 'emailflags' field for storing preferences about
2770
# when email gets sent on a per-user basis.
2771
if (!GetFieldDef('profiles', 'emailflags')) {
2772
AddField('profiles', 'emailflags', 'mediumtext');
2775
# 2000-11-27 For Bugzilla 2.5 and later. Change table 'comments' to
2776
# 'longdescs' - the new name of the comments table.
2777
if (&TableExists('comments')) {
2778
RenameField ('comments', 'when', 'bug_when');
2779
ChangeFieldType('comments', 'bug_id', 'mediumint not null');
2780
ChangeFieldType('comments', 'who', 'mediumint not null');
2781
ChangeFieldType('comments', 'bug_when', 'datetime not null');
2782
RenameField('comments','comment','thetext');
2783
# Here we rename comments to longdescs
2784
$dbh->do("DROP TABLE longdescs");
2785
$dbh->do("ALTER TABLE comments RENAME longdescs");
2788
# 2001-04-08 Added a special directory for the duplicates stats.
2789
unless (-d 'data/duplicates') {
2790
print "Creating duplicates directory...\n";
2791
mkdir 'data/duplicates', 0770;
2792
if ($my_webservergroup eq "") {
2793
chmod 01777, 'data/duplicates';
2798
# 2001-04-10 myk@mozilla.org:
2799
# isactive determines whether or not a group is active. An inactive group
2800
# cannot have bugs added to it. Deactivation is a much milder form of
2801
# deleting a group that allows users to continue to work on bugs in the group
2802
# without enabling them to extend the life of the group by adding bugs to it.
2803
# http://bugzilla.mozilla.org/show_bug.cgi?id=75482
2805
AddField('groups', 'isactive', 'tinyint not null default 1');
2808
# 2001-06-15 myk@mozilla.org:
2809
# isobsolete determines whether or not an attachment is pertinent/relevant/valid.
2811
AddField('attachments', 'isobsolete', 'tinyint not null default 0');
2813
# 2001-04-29 jake@acutex.net - Remove oldemailtech
2814
# http://bugzilla.mozilla.org/show_bugs.cgi?id=71552
2816
print "Removing shadow directory...\n";
2817
unlink glob("shadow/*");
2818
unlink glob("shadow/.*");
2821
DropField("profiles", "emailnotification");
2822
DropField("profiles", "newemailtech");
2824
# 2001-06-12; myk@mozilla.org; bugs 74032, 77473:
2825
# Recrypt passwords using Perl &crypt instead of the mysql equivalent
2826
# and delete plaintext passwords from the database.
2827
if ( GetFieldDef('profiles', 'password') ) {
2830
Your current installation of Bugzilla stores passwords in plaintext
2831
in the database and uses mysql's encrypt function instead of Perl's
2832
crypt function to crypt passwords. Passwords are now going to be
2833
re-crypted with the Perl function, and plaintext passwords will be
2834
deleted from the database. This could take a while if your
2835
installation has many users.
2838
# Re-crypt everyone's password.
2839
my $sth = $dbh->prepare("SELECT userid, password FROM profiles");
2844
print "Fixing password #1... ";
2845
while (my ($userid, $password) = $sth->fetchrow_array()) {
2846
my $cryptpassword = $dbh->quote(Crypt($password));
2847
$dbh->do("UPDATE profiles SET cryptpassword = $cryptpassword WHERE userid = $userid");
2849
# Let the user know where we are at every 500 records.
2850
print "$i... " if !($i%500);
2852
print "$i... Done.\n";
2854
# Drop the plaintext password field and resize the cryptpassword field.
2855
DropField('profiles', 'password');
2856
ChangeFieldType('profiles', 'cryptpassword', 'varchar(34)');
2861
# 2001-06-06 justdave@syndicomm.com:
2862
# There was no index on the 'who' column in the long descriptions table.
2863
# This caused queries by who posted comments to take a LONG time.
2864
# http://bugzilla.mozilla.org/show_bug.cgi?id=57350
2865
if (!defined GetIndexDef('longdescs','who')) {
2866
print "Adding index for who column in longdescs table...\n";
2867
$dbh->do('ALTER TABLE longdescs ADD INDEX (who)');
2870
# 2001-06-15 kiko@async.com.br - Change bug:version size to avoid
2871
# truncates re http://bugzilla.mozilla.org/show_bug.cgi?id=9352
2872
ChangeFieldType('bugs', 'version','varchar(64) not null');
2874
# 2001-07-20 jake@acutex.net - Change bugs_activity to only record changes
2875
# http://bugzilla.mozilla.org/show_bug.cgi?id=55161
2876
if (GetFieldDef('bugs_activity', 'oldvalue')) {
2877
AddField("bugs_activity", "removed", "tinytext");
2878
AddField("bugs_activity", "added", "tinytext");
2880
# Need to get fieldid's for the fields that have multipule values
2882
foreach my $f ("cc", "dependson", "blocked", "keywords") {
2883
my $sth = $dbh->prepare("SELECT fieldid FROM fielddefs WHERE name = '$f'");
2885
my ($fid) = $sth->fetchrow_array();
2886
push (@multi, $fid);
2889
# Now we need to process the bugs_activity table and reformat the data
2891
print "Fixing activity log ";
2892
my $sth = $dbh->prepare("SELECT bug_id, who, bug_when, fieldid,
2893
oldvalue, newvalue FROM bugs_activity");
2895
while (my ($bug_id, $who, $bug_when, $fieldid, $oldvalue, $newvalue) = $sth->fetchrow_array()) {
2896
# print the iteration count every 500 records so the user knows we didn't die
2897
print "$i..." if !($i++ % 500);
2898
# Make sure (old|new)value isn't null (to suppress warnings)
2901
my ($added, $removed) = "";
2902
if (grep ($_ eq $fieldid, @multi)) {
2903
$oldvalue =~ s/[\s,]+/ /g;
2904
$newvalue =~ s/[\s,]+/ /g;
2905
my @old = split(" ", $oldvalue);
2906
my @new = split(" ", $newvalue);
2907
my (@add, @remove) = ();
2908
# Find values that were "added"
2909
foreach my $value(@new) {
2910
if (! grep ($_ eq $value, @old)) {
2911
push (@add, $value);
2914
# Find values that were removed
2915
foreach my $value(@old) {
2916
if (! grep ($_ eq $value, @new)) {
2917
push (@remove, $value);
2920
$added = join (", ", @add);
2921
$removed = join (", ", @remove);
2922
# If we can't determine what changed, put a ? in both fields
2923
unless ($added || $removed) {
2927
# If the origianl field (old|new)value was full, then this
2928
# could be incomplete data.
2929
if (length($oldvalue) == 255 || length($newvalue) == 255) {
2930
$added = "? $added";
2931
$removed = "? $removed";
2934
$removed = $oldvalue;
2937
$added = $dbh->quote($added);
2938
$removed = $dbh->quote($removed);
2939
$dbh->do("UPDATE bugs_activity SET removed = $removed, added = $added
2940
WHERE bug_id = $bug_id AND who = $who
2941
AND bug_when = '$bug_when' AND fieldid = $fieldid");
2944
DropField("bugs_activity", "oldvalue");
2945
DropField("bugs_activity", "newvalue");
2948
# 2001-07-24 jake@acutex.net - disabledtext was being handled inconsitantly
2949
# http://bugzilla.mozilla.org/show_bug.cgi?id=90933
2950
ChangeFieldType("profiles", "disabledtext", "mediumtext not null");
2952
# 2001-07-26 myk@mozilla.org bug39816:
2953
# Add fields to the bugs table that record whether or not the reporter,
2954
# assignee, QA contact, and users on the cc: list can see bugs even when
2955
# they are not members of groups to which the bugs are restricted.
2956
# 2002-02-06 bbaetz@student.usyd.edu.au - assignee/qa can always see the bug
2957
AddField("bugs", "reporter_accessible", "tinyint not null default 1");
2958
#AddField("bugs", "assignee_accessible", "tinyint not null default 1");
2959
#AddField("bugs", "qacontact_accessible", "tinyint not null default 1");
2960
AddField("bugs", "cclist_accessible", "tinyint not null default 1");
2962
# 2001-08-21 myk@mozilla.org bug84338:
2963
# Add a field for the attachment ID to the bugs_activity table, so installations
2964
# using the attachment manager can record changes to attachments.
2965
AddField("bugs_activity", "attach_id", "mediumint null");
2967
# 2002-02-04 bbaetz@student.usyd.edu.au bug 95732
2968
# Remove logincookies.cryptpassword, and delete entries which become
2970
if (GetFieldDef("logincookies", "cryptpassword")) {
2971
# We need to delete any cookies which are invalid, before dropping the
2974
print "Removing invalid login cookies...\n";
2976
# mysql doesn't support DELETE with multi-table queries, so we have
2978
my $sth = $dbh->prepare("SELECT cookie FROM logincookies, profiles " .
2979
"WHERE logincookies.cryptpassword != " .
2980
"profiles.cryptpassword AND " .
2981
"logincookies.userid = profiles.userid");
2983
while (my ($cookie) = $sth->fetchrow_array()) {
2984
$dbh->do("DELETE FROM logincookies WHERE cookie = $cookie");
2987
DropField("logincookies", "cryptpassword");
2990
# 2002-02-13 bbaetz@student.usyd.edu.au - bug 97471
2991
# qacontact/assignee should always be able to see bugs,
2992
# so remove their restriction column
2993
if (GetFieldDef("bugs","qacontact_accessible")) {
2994
print "Removing restrictions on bugs for assignee and qacontact...\n";
2996
DropField("bugs", "qacontact_accessible");
2997
DropField("bugs", "assignee_accessible");
3000
# 2002-03-15 bbaetz@student.usyd.edu.au - bug 129466
3001
# 2002-05-13 preed@sigkill.com - bug 129446 patch backported to the
3002
# BUGZILLA-2_14_1-BRANCH as a security blocker for the 2.14.2 release
3003
# Use the ip, not the hostname, in the logincookies table
3004
if (GetFieldDef("logincookies", "hostname")) {
3005
# We've changed what we match against, so all entries are now invalid
3006
$dbh->do("DELETE FROM logincookies");
3008
# Now update the logincookies schema
3009
DropField("logincookies", "hostname");
3010
AddField("logincookies", "ipaddr", "varchar(40) NOT NULL");
3013
# If you had to change the --TABLE-- definition in any way, then add your
3014
# differential change code *** A B O V E *** this comment.
3016
# That is: if you add a new field, you first search for the first occurence
3017
# of --TABLE-- and add your field to into the table hash. This new setting
3018
# would be honored for every new installation. Then add your
3019
# AddField/DropField/ChangeFieldType/RenameField code above. This would then
3020
# be honored by everyone who updates his Bugzilla installation.
3025
unlink "data/versioncache";
3027
print "Reminder: Bugzilla now requires version 8.7 or later of sendmail.\n";