Compare commits
8 Commits
tags/BUGZI
...
test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f633a6fd70 | ||
|
|
4a4328283c | ||
|
|
ef0e3748f6 | ||
|
|
1f89995e4b | ||
|
|
06ef9b2f92 | ||
|
|
9f04a8a614 | ||
|
|
1d45b3c9b8 | ||
|
|
f30717a14f |
2
mozilla/tools/tinderbox-configs/firefox/linux/CLOBBER
Normal file
2
mozilla/tools/tinderbox-configs/firefox/linux/CLOBBER
Normal file
@@ -0,0 +1,2 @@
|
||||
Clobbering to pick up changes from bug 409803.
|
||||
|
||||
27
mozilla/tools/tinderbox-configs/firefox/linux/mozconfig
Normal file
27
mozilla/tools/tinderbox-configs/firefox/linux/mozconfig
Normal file
@@ -0,0 +1,27 @@
|
||||
#
|
||||
## hostname: fx-linux-tbox
|
||||
## uname: Linux fx-linux-tbox.build.mozilla.org 2.6.18-8.el5 #1 SMP Thu Mar 15 19:57:35 EDT 2007 i686 i686 i386 GNU/Linux
|
||||
#
|
||||
|
||||
export CFLAGS="-gstabs+"
|
||||
export CXXFLAGS="-gstabs+"
|
||||
|
||||
mk_add_options MOZ_CO_PROJECT=browser
|
||||
mk_add_options PROFILE_GEN_SCRIPT=@TOPSRCDIR@/build/profile_pageloader.pl
|
||||
mk_add_options MOZ_CO_MODULE="mozilla/tools/update-packaging mozilla/tools/codesighs"
|
||||
ac_add_options --enable-application=browser
|
||||
|
||||
ac_add_options --enable-update-channel=nightly
|
||||
ac_add_options --enable-update-packaging
|
||||
|
||||
# Don't add explicit optimize flags here, set them in configure.in, see bug 407794.
|
||||
ac_add_options --enable-optimize
|
||||
ac_add_options --disable-debug
|
||||
ac_add_options --disable-tests
|
||||
#not yet
|
||||
#ac_add_options --enable-glitz
|
||||
|
||||
ac_add_options --enable-codesighs
|
||||
|
||||
CC=/tools/gcc/bin/gcc
|
||||
CXX=/tools/gcc/bin/g++
|
||||
269
mozilla/tools/tinderbox-configs/firefox/linux/tinder-config.pl
Normal file
269
mozilla/tools/tinderbox-configs/firefox/linux/tinder-config.pl
Normal file
@@ -0,0 +1,269 @@
|
||||
#
|
||||
## hostname: fx-linux-tbox
|
||||
## uname: Linux fx-linux-tbox.build.mozilla.org 2.6.18-8.el5 #1 SMP Thu Mar 15 19:57:35 EDT 2007 i686 i686 i386 GNU/Linux
|
||||
#
|
||||
|
||||
#- tinder-config.pl - Tinderbox configuration file.
|
||||
#- Uncomment the variables you need to set.
|
||||
#- The default values are the same as the commented variables.
|
||||
|
||||
$ENV{CVS_RSH} = "ssh";
|
||||
$ENV{MOZ_CRASHREPORTER_NO_REPORT} = '1';
|
||||
|
||||
# To ensure Talkback client builds properly on some Linux boxen where LANG
|
||||
# is set to "en_US.UTF-8" by default, override that setting here by setting
|
||||
# it to "en_US.iso885915" (the setting on ocean). Proper fix is to update
|
||||
# where xrestool is called in the build system so that 'LANG=C' in its
|
||||
# environment, according to bryner.
|
||||
$ENV{LANG} = "en_US.iso885915";
|
||||
|
||||
# $ENV{MOZ_PACKAGE_MSI}
|
||||
#-----------------------------------------------------------------------------
|
||||
# Default: 0
|
||||
# Values: 0 | 1
|
||||
# Purpose: Controls whether a MSI package is made.
|
||||
# Requires: Windows and a local MakeMSI installation.
|
||||
#$ENV{MOZ_PACKAGE_MSI} = 0;
|
||||
|
||||
# $ENV{MOZ_SYMBOLS_TRANSFER_TYPE}
|
||||
#-----------------------------------------------------------------------------
|
||||
# Default: scp
|
||||
# Values: scp | rsync
|
||||
# Purpose: Use scp or rsync to transfer symbols to the Talkback server.
|
||||
# Requires: The selected type requires the command be available both locally
|
||||
# and on the Talkback server.
|
||||
#$ENV{MOZ_SYMBOLS_TRANSFER_TYPE} = "scp";
|
||||
|
||||
#- PLEASE FILL THIS IN WITH YOUR PROPER EMAIL ADDRESS
|
||||
$BuildAdministrator = 'build@mozilla.org';
|
||||
#$BuildAdministrator = "$ENV{USER}\@$ENV{HOST}";
|
||||
#$BuildAdministrator = ($ENV{USER} || "cltbld") . "\@" . ($ENV{HOST} || "dhcp");
|
||||
|
||||
#- You'll need to change these to suit your machine's needs
|
||||
$DisplayServer = ':0.0';
|
||||
|
||||
#- Default values of command-line opts
|
||||
#-
|
||||
#$BuildDepend = 1; # Depend or Clobber
|
||||
#$BuildDebug = 0; # Debug or Opt (Darwin)
|
||||
#$ReportStatus = 1; # Send results to server, or not
|
||||
#$ReportFinalStatus = 1; # Finer control over $ReportStatus.
|
||||
#$UseTimeStamp = 1; # Use the CVS 'pull-by-timestamp' option, or not
|
||||
#$BuildOnce = 0; # Build once, don't send results to server
|
||||
#$TestOnly = 0; # Only run tests, don't pull/build
|
||||
#$BuildEmbed = 0; # After building seamonkey, go build embed app.
|
||||
#$SkipMozilla = 0; # Use to debug post-mozilla.pl scripts.
|
||||
#$BuildLocales = 0; # Do l10n packaging?
|
||||
|
||||
# Tests
|
||||
$CleanProfile = 1;
|
||||
#$ResetHomeDirForTests = 1;
|
||||
$ProductName = "Firefox";
|
||||
$VendorName = 'Mozilla';
|
||||
|
||||
# CONFIG: $RunMozillaTests = %runMozillaTests%;
|
||||
$RunMozillaTests = 1;
|
||||
$RegxpcomTest = 1;
|
||||
$AliveTest = 1;
|
||||
#$JavaTest = 0;
|
||||
#$ViewerTest = 0;
|
||||
#$BloatTest = 0; # warren memory bloat test
|
||||
#$BloatTest2 = 0; # dbaron memory bloat test, require tracemalloc
|
||||
#$DomToTextConversionTest = 0;
|
||||
#$XpcomGlueTest = 0;
|
||||
$CodesizeTest = 1; # Z, require mozilla/tools/codesighs
|
||||
$EmbedCodesizeTest = 1; # mZ, require mozilla/tools/codesigns
|
||||
#$MailBloatTest = 0;
|
||||
#$EmbedTest = 0; # Assumes you wanted $BuildEmbed=1
|
||||
$LayoutPerformanceTest = 0; # Tp
|
||||
$DHTMLPerformanceTest = 0; # Tdhtml
|
||||
#$QATest = 0;
|
||||
#$XULWindowOpenTest = 0; # Txul
|
||||
$StartupPerformanceTest = 0; # Ts
|
||||
|
||||
$TestsPhoneHome = 0; # Should test report back to server?
|
||||
$GraphNameOverride = 'fx-linux-tbox';
|
||||
|
||||
# $results_server
|
||||
#----------------------------------------------------------------------------
|
||||
# Server on which test results will be accessible. This was originally tegu,
|
||||
# then became axolotl. Once we moved services from axolotl, it was time
|
||||
# to give this service its own hostname to make future transitions easier.
|
||||
# - cmp@mozilla.org
|
||||
#$results_server = "build-graphs.mozilla.org";
|
||||
|
||||
#$pageload_server = "spider"; # localhost
|
||||
$pageload_server = "pageload.build.mozilla.org";
|
||||
|
||||
|
||||
#
|
||||
# Timeouts, values are in seconds.
|
||||
#
|
||||
#$CVSCheckoutTimeout = 3600;
|
||||
#$CreateProfileTimeout = 45;
|
||||
#$RegxpcomTestTimeout = 120;
|
||||
|
||||
#$AliveTestTimeout = 45;
|
||||
#$ViewerTestTimeout = 45;
|
||||
#$EmbedTestTimeout = 45;
|
||||
#$BloatTestTimeout = 120; # seconds
|
||||
#$MailBloatTestTimeout = 120; # seconds
|
||||
#$JavaTestTimeout = 45;
|
||||
#$DomTestTimeout = 45; # seconds
|
||||
#$XpcomGlueTestTimeout = 15;
|
||||
#$CodesizeTestTimeout = 900; # seconds
|
||||
#$CodesizeTestType = "auto"; # {"auto"|"base"}
|
||||
#$LayoutPerformanceTestTimeout = 1200; # entire test, seconds
|
||||
#$DHTMLPerformanceTestTimeout = 1200; # entire test, seconds
|
||||
#$QATestTimeout = 1200; # entire test, seconds
|
||||
#$LayoutPerformanceTestPageTimeout = 30000; # each page, ms
|
||||
#$StartupPerformanceTestTimeout = 15; # seconds
|
||||
#$XULWindowOpenTestTimeout = 150; # seconds
|
||||
|
||||
|
||||
#$MozConfigFileName = 'mozconfig';
|
||||
|
||||
#$UseMozillaProfile = 1;
|
||||
#$MozProfileName = 'default';
|
||||
|
||||
#- Set these to what makes sense for your system
|
||||
#$Make = 'gmake'; # Must be GNU make
|
||||
#$MakeOverrides = '';
|
||||
#$mail = '/bin/mail';
|
||||
#$CVS = 'cvs -q';
|
||||
#$CVSCO = 'checkout -P';
|
||||
|
||||
# win32 usually doesn't have /bin/mail
|
||||
#$blat = 'c:/nstools/bin/blat';
|
||||
#$use_blat = 0;
|
||||
|
||||
# Set moz_cvsroot to something like:
|
||||
# :pserver:$ENV{USER}%netscape.com\@cvs.mozilla.org:/cvsroot
|
||||
# :pserver:anonymous\@cvs-mirror.mozilla.org:/cvsroot
|
||||
#
|
||||
# Note that win32 may not need \@, depends on ' or ".
|
||||
# :pserver:$ENV{USER}%netscape.com@cvs.mozilla.org:/cvsroot
|
||||
|
||||
#$moz_cvsroot = $ENV{CVSROOT};
|
||||
# CONFIG: $moz_cvsroot = '%mozillaCvsroot%';
|
||||
$moz_cvsroot = ':ext:cltbld@cvs.mozilla.org:/cvsroot';
|
||||
|
||||
#- Set these proper values for your tinderbox server
|
||||
#$Tinderbox_server = 'tinderbox-daemon@tinderbox.mozilla.org';
|
||||
|
||||
# Allow for non-client builds, e.g. camino.
|
||||
#$moz_client_mk = 'client.mk';
|
||||
|
||||
#- Set if you want to build in a separate object tree
|
||||
$ObjDir = 'obj-fx-trunk';
|
||||
|
||||
# Extra build name, if needed.
|
||||
$BuildNameExtra = 'Nightly';
|
||||
|
||||
# User comment, eg. ip address for dhcp builds.
|
||||
# ex: $UserComment = "ip = 208.12.36.108";
|
||||
#$UserComment = 0;
|
||||
|
||||
#-
|
||||
#- The rest should not need to be changed
|
||||
#-
|
||||
|
||||
#- Minimum wait period from start of build to start of next build in minutes.
|
||||
#$BuildSleep = 10;
|
||||
|
||||
#- Until you get the script working. When it works,
|
||||
#- change to the tree you're actually building
|
||||
# CONFIG: $BuildTree = '%buildTree%';
|
||||
$BuildTree = 'MozillaTest';
|
||||
|
||||
#$BuildName = '';
|
||||
#$BuildTag = '';
|
||||
#$BuildConfigDir = 'mozilla/config';
|
||||
#$Topsrcdir = 'mozilla';
|
||||
|
||||
$BinaryName = 'firefox-bin';
|
||||
|
||||
#
|
||||
# For embedding app, use:
|
||||
#$EmbedBinaryName = 'TestGtkEmbed';
|
||||
#$EmbedDistDir = 'dist/bin'
|
||||
|
||||
|
||||
#$ShellOverride = ''; # Only used if the default shell is too stupid
|
||||
#$ConfigureArgs = '';
|
||||
#$ConfigureEnvArgs = '';
|
||||
#$Compiler = 'gcc';
|
||||
#$NSPRArgs = '';
|
||||
#$ShellOverride = '';
|
||||
|
||||
# Release build options
|
||||
$ReleaseBuild = 1;
|
||||
$shiptalkback = 0;
|
||||
$ReleaseToLatest = 1; # Push the release to latest-<milestone>?
|
||||
$ReleaseToDated = 1; # Push the release to YYYY-MM-DD-HH-<milestone>?
|
||||
$build_hour = 14;
|
||||
$package_creation_path = "/browser/installer";
|
||||
# needs setting for mac + talkback: $mac_bundle_path = "/browser/app";
|
||||
$ssh_version = "2";
|
||||
# CONFIG: $ssh_user = "%sshUser%";
|
||||
$ssh_user = "ffxbld";
|
||||
$ssh_key = "'$ENV{HOME}/.ssh/ffxbld_dsa'";
|
||||
# CONFIG: $ssh_server = "%sshServer%";
|
||||
$ssh_server = "stage-old.mozilla.org";
|
||||
$ReleaseGroup = "firefox";
|
||||
$ftp_path = "/home/ftp/pub/firefox/nightly/experimental";
|
||||
$url_path = "http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/experimental";
|
||||
$tbox_ftp_path = "/home/ftp/pub/firefox/tinderbox-builds";
|
||||
$tbox_url_path = "http://ftp.mozilla.org/pub/mozilla.org/firefox/tinderbox-builds";
|
||||
$milestone = "trunk";
|
||||
$notify_list = 'build-announce@mozilla.org';
|
||||
$stub_installer = 0;
|
||||
$sea_installer = 0;
|
||||
$archive = 1;
|
||||
$push_raw_xpis = 0;
|
||||
# CONFIG: $update_aus_host = '%ausServer%';
|
||||
$update_aus_host = 'aus2-staging.mozilla.org';
|
||||
$update_pushinfo = 0;
|
||||
$update_package = 1;
|
||||
$update_product = "Firefox";
|
||||
$update_version = "trunk";
|
||||
$update_platform = "Linux_x86-gcc3";
|
||||
$update_hash = "sha1";
|
||||
# CONFIG: $update_filehost = '%ftpServer%';
|
||||
$update_filehost = 'ftp.mozilla.org';
|
||||
$update_ver_file = 'browser/config/version.txt';
|
||||
$crashreporter_buildsymbols = 1;
|
||||
$crashreporter_pushsymbols = 1;
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_HOST'} = '%symbolServer%';
|
||||
$ENV{'SYMBOL_SERVER_HOST'} = 'dm-symbolpush01.mozilla.org';
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_USER'} = '%symbolServerUser%';
|
||||
$ENV{'SYMBOL_SERVER_USER'} = 'ffxbld';
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_PATH'} = '%symbolServerPath%';
|
||||
$ENV{'SYMBOL_SERVER_PATH'} = '/mnt/netapp/breakpad/symbols_ffx';
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_SSH_KEY'} = '%symbolServerKey%';
|
||||
$ENV{'SYMBOL_SERVER_SSH_KEY'} = '/home/cltbld/.ssh/ffxbld_dsa';
|
||||
|
||||
# Reboot the OS at the end of build-and-test cycle. This is primarily
|
||||
# intended for Win9x, which can't last more than a few cycles before
|
||||
# locking up (and testing would be suspect even after a couple of cycles).
|
||||
# Right now, there is only code to force the reboot for Win9x, so even
|
||||
# setting this to 1, will not have an effect on other platforms. Setting
|
||||
# up win9x to automatically logon and begin running tinderbox is left
|
||||
# as an exercise to the reader.
|
||||
#$RebootSystem = 0;
|
||||
|
||||
# LogCompression specifies the type of compression used on the log file.
|
||||
# Valid options are 'gzip', and 'bzip2'. Please make sure the binaries
|
||||
# for 'gzip' or 'bzip2' are in the user's path before setting this
|
||||
# option.
|
||||
#$LogCompression = '';
|
||||
|
||||
# LogEncoding specifies the encoding format used for the logs. Valid
|
||||
# options are 'base64', and 'uuencode'. If $LogCompression is set above,
|
||||
# this needs to be set to 'base64' or 'uuencode' to ensure that the
|
||||
# binary data is transferred properly.
|
||||
#$LogEncoding = '';
|
||||
|
||||
# Prevent Extension Manager from spawning child processes during tests
|
||||
# - processes that tbox scripts cannot kill.
|
||||
#$ENV{NO_EM_RESTART} = '1';
|
||||
1
mozilla/tools/tinderbox-configs/firefox/macosx/CLOBBER
Normal file
1
mozilla/tools/tinderbox-configs/firefox/macosx/CLOBBER
Normal file
@@ -0,0 +1 @@
|
||||
trigger a nightly to push the fix to bug 421841 to users
|
||||
28
mozilla/tools/tinderbox-configs/firefox/macosx/mozconfig
Normal file
28
mozilla/tools/tinderbox-configs/firefox/macosx/mozconfig
Normal file
@@ -0,0 +1,28 @@
|
||||
#
|
||||
## hostname: bm-xserve08.build.mozilla.org
|
||||
## uname: Darwin bm-xserve08.build.mozilla.org 8.8.4 Darwin Kernel Version 8.8.4: Sun Oct 29 15:26:54 PST 2006; root:xnu-792.16.4.obj~1/RELEASE_I386 i386 i386
|
||||
#
|
||||
|
||||
# symbols for breakpad
|
||||
export CFLAGS="-g -gfull"
|
||||
export CXXFLAGS="-g -gfull"
|
||||
|
||||
. $topsrcdir/build/macosx/universal/mozconfig
|
||||
|
||||
mk_add_options MOZ_MAKE_FLAGS="-j4"
|
||||
mk_add_options MOZ_CO_MODULE="mozilla/tools/update-packaging mozilla/tools/codesighs"
|
||||
mk_add_options MOZ_CO_PROJECT="browser"
|
||||
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../build/universal
|
||||
|
||||
ac_add_options --enable-application=browser
|
||||
ac_add_options --enable-update-channel=nightly
|
||||
# Don't add explicit optimize flags here, set them in configure.in, see bug 407794.
|
||||
ac_add_options --enable-optimize
|
||||
ac_add_options --disable-debug
|
||||
ac_add_options --disable-tests
|
||||
ac_add_options --enable-update-packaging
|
||||
|
||||
# ac_add_options --enable-official-branding
|
||||
ac_add_app_options ppc --enable-prebinding
|
||||
|
||||
ac_add_options --enable-codesighs
|
||||
269
mozilla/tools/tinderbox-configs/firefox/macosx/tinder-config.pl
Normal file
269
mozilla/tools/tinderbox-configs/firefox/macosx/tinder-config.pl
Normal file
@@ -0,0 +1,269 @@
|
||||
#
|
||||
## hostname: bm-xserve08.build.mozilla.org
|
||||
## uname: Darwin bm-xserve08.build.mozilla.org 8.8.4 Darwin Kernel Version 8.8.4: Sun Oct 29 15:26:54 PST 2006; root:xnu-792.16.4.obj~1/RELEASE_I386 i386 i386
|
||||
#
|
||||
|
||||
#- tinder-config.pl - Tinderbox configuration file.
|
||||
#- Uncomment the variables you need to set.
|
||||
#- The default values are the same as the commented variables.
|
||||
|
||||
$ENV{NO_EM_RESTART} = "1";
|
||||
$ENV{DYLD_NO_FIX_PREBINDING} = "1";
|
||||
$ENV{LD_PREBIND_ALLOW_OVERLAP} = "1";
|
||||
$ENV{CVS_RSH} = "ssh";
|
||||
$ENV{MOZ_CRASHREPORTER_NO_REPORT} = '1';
|
||||
|
||||
$MacUniversalBinary = 1;
|
||||
|
||||
# $ENV{MOZ_PACKAGE_MSI}
|
||||
#-----------------------------------------------------------------------------
|
||||
# Default: 0
|
||||
# Values: 0 | 1
|
||||
# Purpose: Controls whether a MSI package is made.
|
||||
# Requires: Windows and a local MakeMSI installation.
|
||||
#$ENV{MOZ_PACKAGE_MSI} = 0;
|
||||
|
||||
# $ENV{MOZ_SYMBOLS_TRANSFER_TYPE}
|
||||
#-----------------------------------------------------------------------------
|
||||
# Default: scp
|
||||
# Values: scp | rsync
|
||||
# Purpose: Use scp or rsync to transfer symbols to the Talkback server.
|
||||
# Requires: The selected type requires the command be available both locally
|
||||
# and on the Talkback server.
|
||||
#$ENV{MOZ_SYMBOLS_TRANSFER_TYPE} = "scp";
|
||||
|
||||
#- PLEASE FILL THIS IN WITH YOUR PROPER EMAIL ADDRESS
|
||||
$BuildAdministrator = 'build@mozilla.org';
|
||||
#$BuildAdministrator = "$ENV{USER}\@$ENV{HOST}";
|
||||
#$BuildAdministrator = ($ENV{USER} || "cltbld") . "\@" . ($ENV{HOST} || "dhcp");
|
||||
|
||||
#- You'll need to change these to suit your machine's needs
|
||||
#$DisplayServer = ':0.0';
|
||||
|
||||
#- Default values of command-line opts
|
||||
#-
|
||||
#$BuildDepend = 1; # Depend or Clobber
|
||||
#$BuildDebug = 0; # Debug or Opt (Darwin)
|
||||
#$ReportStatus = 1; # Send results to server, or not
|
||||
#$ReportFinalStatus = 1; # Finer control over $ReportStatus.
|
||||
#$UseTimeStamp = 1; # Use the CVS 'pull-by-timestamp' option, or not
|
||||
#$BuildOnce = 0; # Build once, don't send results to server
|
||||
#$TestOnly = 0; # Only run tests, don't pull/build
|
||||
#$BuildEmbed = 0; # After building seamonkey, go build embed app.
|
||||
#$SkipMozilla = 0; # Use to debug post-mozilla.pl scripts.
|
||||
#$BuildLocales = 0; # Do l10n packaging?
|
||||
|
||||
# Tests
|
||||
$CleanProfile = 1;
|
||||
#$ResetHomeDirForTests = 1;
|
||||
$ProductName = 'Minefield';
|
||||
$VendorName = "";
|
||||
|
||||
# CONFIG: $RunMozillaTests = %runMozillaTests%;
|
||||
$RunMozillaTests = 1;
|
||||
$RegxpcomTest = 1;
|
||||
$AliveTest = 1;
|
||||
#$JavaTest = 0;
|
||||
#$ViewerTest = 0;
|
||||
#$BloatTest = 0; # warren memory bloat test
|
||||
#$BloatTest2 = 0; # dbaron memory bloat test, require tracemalloc
|
||||
#$DomToTextConversionTest = 0;
|
||||
#$XpcomGlueTest = 0;
|
||||
$CodesizeTest = 1; # Z, require mozilla/tools/codesighs
|
||||
$EmbedCodesizeTest = 0; # mZ, require mozilla/tools/codesigns
|
||||
#$MailBloatTest = 0;
|
||||
#$EmbedTest = 0; # Assumes you wanted $BuildEmbed=1
|
||||
$LayoutPerformanceTest = 0; # Tp
|
||||
$LayoutPerformanceLocalTest = 0; # Tp2
|
||||
$DHTMLPerformanceTest = 0; # Tdhtml
|
||||
#$QATest = 0;
|
||||
$XULWindowOpenTest = 0; # Txul
|
||||
$StartupPerformanceTest = 0; # Ts
|
||||
|
||||
$TestsPhoneHome = 0; # Should test report back to server?
|
||||
|
||||
$GraphNameOverride = 'xserve08.build.mozilla.org_Fx-Trunk';
|
||||
|
||||
# $results_server
|
||||
#----------------------------------------------------------------------------
|
||||
# Server on which test results will be accessible. This was originally tegu,
|
||||
# then became axolotl. Once we moved services from axolotl, it was time
|
||||
# to give this service its own hostname to make future transitions easier.
|
||||
# - cmp@mozilla.org
|
||||
#$results_server = "build-graphs.mozilla.org";
|
||||
|
||||
#$pageload_server = "spider"; # localhost
|
||||
$pageload_server = "pageload.build.mozilla.org"; # localhost
|
||||
|
||||
#
|
||||
# Timeouts, values are in seconds.
|
||||
#
|
||||
#$CVSCheckoutTimeout = 3600;
|
||||
#$CreateProfileTimeout = 45;
|
||||
#$RegxpcomTestTimeout = 120;
|
||||
|
||||
$AliveTestTimeout = 10;
|
||||
#$ViewerTestTimeout = 45;
|
||||
#$EmbedTestTimeout = 45;
|
||||
#$BloatTestTimeout = 120; # seconds
|
||||
#$MailBloatTestTimeout = 120; # seconds
|
||||
#$JavaTestTimeout = 45;
|
||||
#$DomTestTimeout = 45; # seconds
|
||||
#$XpcomGlueTestTimeout = 15;
|
||||
#$CodesizeTestTimeout = 900; # seconds
|
||||
#$CodesizeTestType = "auto"; # {"auto"|"base"}
|
||||
$LayoutPerformanceTestTimeout = 300; # entire test, seconds
|
||||
$LayoutPerformanceLocalTestTimeout = 180; # entire test, seconds
|
||||
$DHTMLPerformanceTestTimeout = 180; # entire test, seconds
|
||||
#$QATestTimeout = 1200; # entire test, seconds
|
||||
#$LayoutPerformanceTestPageTimeout = 30000; # each page, ms
|
||||
#$StartupPerformanceTestTimeout = 15; # seconds
|
||||
#$XULWindowOpenTestTimeout = 150; # seconds
|
||||
|
||||
|
||||
#$MozConfigFileName = 'mozconfig';
|
||||
|
||||
#$UseMozillaProfile = 1;
|
||||
#$MozProfileName = 'default';
|
||||
|
||||
#- Set these to what makes sense for your system
|
||||
#$Make = 'gmake'; # Must be GNU make
|
||||
#$MakeOverrides = '';
|
||||
#$mail = '/bin/mail';
|
||||
#$CVS = 'cvs -q';
|
||||
#$CVSCO = 'checkout -P';
|
||||
|
||||
# win32 usually doesn't have /bin/mail
|
||||
#$blat = 'c:/nstools/bin/blat';
|
||||
#$use_blat = 0;
|
||||
|
||||
# Set moz_cvsroot to something like:
|
||||
# :pserver:$ENV{USER}%netscape.com\@cvs.mozilla.org:/cvsroot
|
||||
# :pserver:anonymous\@cvs-mirror.mozilla.org:/cvsroot
|
||||
#
|
||||
# Note that win32 may not need \@, depends on ' or ".
|
||||
# :pserver:$ENV{USER}%netscape.com@cvs.mozilla.org:/cvsroot
|
||||
|
||||
# CONFIG: $moz_cvsroot = '%mozillaCvsroot%';
|
||||
$moz_cvsroot = ':ext:cltbld@cvs.mozilla.org:/cvsroot';
|
||||
|
||||
#- Set these proper values for your tinderbox server
|
||||
#$Tinderbox_server = 'tinderbox-daemon@tinderbox.mozilla.org';
|
||||
|
||||
# Allow for non-client builds, e.g. camino.
|
||||
#$moz_client_mk = 'client.mk';
|
||||
|
||||
#- Set if you want to build in a separate object tree
|
||||
$ObjDir = '../build/universal';
|
||||
|
||||
# Extra build name, if needed.
|
||||
$BuildNameExtra = 'Universal Nightly';
|
||||
|
||||
# User comment, eg. ip address for dhcp builds.
|
||||
# ex: $UserComment = "ip = 208.12.36.108";
|
||||
#$UserComment = 0;
|
||||
|
||||
#-
|
||||
#- The rest should not need to be changed
|
||||
#-
|
||||
|
||||
#- Minimum wait period from start of build to start of next build in minutes.
|
||||
#$BuildSleep = 10;
|
||||
|
||||
#- Until you get the script working. When it works,
|
||||
#- change to the tree you're actually building
|
||||
# CONFIG: $BuildTree = '%buildTree%';
|
||||
$BuildTree = 'MozillaTest';
|
||||
|
||||
#$BuildName = '';
|
||||
#$BuildTag = '';
|
||||
#$BuildConfigDir = 'mozilla/config';
|
||||
#$Topsrcdir = 'mozilla';
|
||||
|
||||
$BinaryName = 'firefox-bin';
|
||||
|
||||
#
|
||||
# For embedding app, use:
|
||||
#$EmbedBinaryName = 'TestGtkEmbed';
|
||||
#$EmbedDistDir = 'dist/bin'
|
||||
|
||||
|
||||
#$ShellOverride = ''; # Only used if the default shell is too stupid
|
||||
#$ConfigureArgs = '';
|
||||
#$ConfigureEnvArgs = '';
|
||||
#$Compiler = 'gcc';
|
||||
#$NSPRArgs = '';
|
||||
#$ShellOverride = '';
|
||||
|
||||
# Release build options
|
||||
$ReleaseBuild = 1;
|
||||
$shiptalkback = 0;
|
||||
$ReleaseToLatest = 1; # Push the release to latest-<milestone>?
|
||||
$ReleaseToDated = 1; # Push the release to YYYY-MM-DD-HH-<milestone>?
|
||||
$build_hour = "14";
|
||||
$package_creation_path = "/browser/installer";
|
||||
# needs setting for mac + talkback: $mac_bundle_path = "/browser/app";
|
||||
$mac_bundle_path = "/browser/app";
|
||||
$ssh_version = "2";
|
||||
# CONFIG: $ssh_user = "%sshUser%";
|
||||
$ssh_user = "ffxbld";
|
||||
$ssh_key = "'$ENV{HOME}/.ssh/ffxbld_dsa'";
|
||||
# CONFIG: $ssh_server = "%sshServer%";
|
||||
$ssh_server = "stage-old.mozilla.org";
|
||||
$ReleaseGroup = "firefox";
|
||||
$ftp_path = "/home/ftp/pub/firefox/nightly/experimental";
|
||||
$url_path = "http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/experimental";
|
||||
$tbox_ftp_path = "/home/ftp/pub/firefox/tinderbox-builds";
|
||||
$tbox_url_path = "http://ftp.mozilla.org/pub/mozilla.org/firefox/tinderbox-builds";
|
||||
$milestone = "trunk";
|
||||
$notify_list = "build-announce\@mozilla.org";
|
||||
$stub_installer = 0;
|
||||
$sea_installer = 0;
|
||||
$archive = 1;
|
||||
$push_raw_xpis = 0;
|
||||
# CONFIG: $update_aus_host = '%ausServer%';
|
||||
$update_aus_host = 'aus2-staging.mozilla.org';
|
||||
$update_package = 1;
|
||||
$update_product = "Firefox";
|
||||
$update_version = "trunk";
|
||||
$update_platform = "Darwin_Universal-gcc3";
|
||||
$update_hash = "sha1";
|
||||
# CONFIG: $update_filehost = '%ftpServer%';
|
||||
$update_filehost = 'ftp.mozilla.org';
|
||||
$update_ver_file = 'browser/config/version.txt';
|
||||
$update_pushinfo = 0;
|
||||
$crashreporter_buildsymbols = 1;
|
||||
$crashreporter_pushsymbols = 1;
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_HOST'} = '%symbolServer%';
|
||||
$ENV{'SYMBOL_SERVER_HOST'} = 'dm-symbolpush01.mozilla.org';
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_USER'} = '%symbolServerUser%';
|
||||
$ENV{'SYMBOL_SERVER_USER'} = 'ffxbld';
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_PATH'} = '%symbolServerPath%';
|
||||
$ENV{'SYMBOL_SERVER_PATH'} = '/mnt/netapp/breakpad/symbols_ffx';
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_SSH_KEY'} = '%symbolServerKey%';
|
||||
$ENV{'SYMBOL_SERVER_SSH_KEY'} = '/Users/cltbld/.ssh/ffxbld_dsa';
|
||||
|
||||
# Reboot the OS at the end of build-and-test cycle. This is primarily
|
||||
# intended for Win9x, which can't last more than a few cycles before
|
||||
# locking up (and testing would be suspect even after a couple of cycles).
|
||||
# Right now, there is only code to force the reboot for Win9x, so even
|
||||
# setting this to 1, will not have an effect on other platforms. Setting
|
||||
# up win9x to automatically logon and begin running tinderbox is left
|
||||
# as an exercise to the reader.
|
||||
#$RebootSystem = 0;
|
||||
|
||||
# LogCompression specifies the type of compression used on the log file.
|
||||
# Valid options are 'gzip', and 'bzip2'. Please make sure the binaries
|
||||
# for 'gzip' or 'bzip2' are in the user's path before setting this
|
||||
# option.
|
||||
#$LogCompression = '';
|
||||
|
||||
# LogEncoding specifies the encoding format used for the logs. Valid
|
||||
# options are 'base64', and 'uuencode'. If $LogCompression is set above,
|
||||
# this needs to be set to 'base64' or 'uuencode' to ensure that the
|
||||
# binary data is transferred properly.
|
||||
#$LogEncoding = '';
|
||||
|
||||
# Prevent Extension Manager from spawning child processes during tests
|
||||
# - processes that tbox scripts cannot kill.
|
||||
#$ENV{NO_EM_RESTART} = '1';
|
||||
1
mozilla/tools/tinderbox-configs/firefox/win32/CLOBBER
Normal file
1
mozilla/tools/tinderbox-configs/firefox/win32/CLOBBER
Normal file
@@ -0,0 +1 @@
|
||||
Clobbering to pick up fixes from bug 419319.
|
||||
20
mozilla/tools/tinderbox-configs/firefox/win32/mozconfig
Normal file
20
mozilla/tools/tinderbox-configs/firefox/win32/mozconfig
Normal file
@@ -0,0 +1,20 @@
|
||||
#
|
||||
## hostname: fx-win32-tbox
|
||||
## uname: MINGW32_NT-5.2 FX-WIN32-TBOX 1.0.11(0.46/3/2) 2007-01-12 12:05 i686 Msys
|
||||
#
|
||||
export CFLAGS="-GL -wd4624 -wd4952"
|
||||
export CXXFLAGS="-GL -wd4624 -wd4952"
|
||||
export LDFLAGS="-LTCG"
|
||||
|
||||
mk_add_options MOZ_CO_PROJECT=browser
|
||||
mk_add_options MOZ_MAKE_FLAGS="-j5"
|
||||
mk_add_options MOZ_CO_MODULE="mozilla/tools/update-packaging"
|
||||
mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) $(MOZ_OBJDIR)/_profile/pgo/profileserver.py'
|
||||
|
||||
ac_add_options --enable-application=browser
|
||||
ac_add_options --enable-update-channel=nightly
|
||||
ac_add_options --enable-optimize
|
||||
ac_add_options --disable-debug
|
||||
ac_add_options --disable-tests
|
||||
ac_add_options --enable-update-packaging
|
||||
ac_add_options --enable-jemalloc
|
||||
264
mozilla/tools/tinderbox-configs/firefox/win32/tinder-config.pl
Normal file
264
mozilla/tools/tinderbox-configs/firefox/win32/tinder-config.pl
Normal file
@@ -0,0 +1,264 @@
|
||||
#
|
||||
## hostname: fx-win32-tbox
|
||||
## uname: MINGW32_NT-5.2 FX-WIN32-TBOX 1.0.11(0.46/3/2) 2007-01-12 12:05 i686 Msys
|
||||
#
|
||||
|
||||
#- tinder-config.pl - Tinderbox configuration file.
|
||||
#- Uncomment the variables you need to set.
|
||||
#- The default values are the same as the commented variables.
|
||||
|
||||
$ENV{NO_EM_RESTART} = '1';
|
||||
$ENV{CVS_RSH} = "ssh";
|
||||
$ENV{MOZ_CRASHREPORTER_NO_REPORT} = '1';
|
||||
|
||||
# $ENV{MOZ_PACKAGE_MSI}
|
||||
#-----------------------------------------------------------------------------
|
||||
# Default: 0
|
||||
# Values: 0 | 1
|
||||
# Purpose: Controls whether a MSI package is made.
|
||||
# Requires: Windows and a local MakeMSI installation.
|
||||
#$ENV{MOZ_PACKAGE_MSI} = 0;
|
||||
|
||||
# $ENV{MOZ_SYMBOLS_TRANSFER_TYPE}
|
||||
#-----------------------------------------------------------------------------
|
||||
# Default: scp
|
||||
# Values: scp | rsync
|
||||
# Purpose: Use scp or rsync to transfer symbols to the Talkback server.
|
||||
# Requires: The selected type requires the command be available both locally
|
||||
# and on the Talkback server.
|
||||
#$ENV{MOZ_SYMBOLS_TRANSFER_TYPE} = "scp";
|
||||
|
||||
#- PLEASE FILL THIS IN WITH YOUR PROPER EMAIL ADDRESS
|
||||
$BuildAdministrator = 'build@mozilla.org';
|
||||
#$BuildAdministrator = "$ENV{USER}\@$ENV{HOST}";
|
||||
#$BuildAdministrator = ($ENV{USER} || "cltbld") . "\@" . ($ENV{HOST} || "dhcp");
|
||||
|
||||
#- You'll need to change these to suit your machine's needs
|
||||
#$DisplayServer = ':0.0';
|
||||
|
||||
#- Default values of command-line opts
|
||||
#-
|
||||
#$BuildDepend = 1; # Depend or Clobber
|
||||
#$BuildDebug = 0; # Debug or Opt (Darwin)
|
||||
#$ReportStatus = 1; # Send results to server, or not
|
||||
#$ReportFinalStatus = 1; # Finer control over $ReportStatus.
|
||||
#$UseTimeStamp = 1; # Use the CVS 'pull-by-timestamp' option, or not
|
||||
#$BuildOnce = 0; # Build once, don't send results to server
|
||||
#$TestOnly = 0; # Only run tests, don't pull/build
|
||||
#$BuildEmbed = 0; # After building seamonkey, go build embed app.
|
||||
#$SkipMozilla = 0; # Use to debug post-mozilla.pl scripts.
|
||||
#$BuildLocales = 0; # Do l10n packaging?
|
||||
|
||||
# Tests
|
||||
$CleanProfile = 1;
|
||||
#$ResetHomeDirForTests = 1;
|
||||
$ProductName = "Firefox";
|
||||
$VendorName = "Mozilla";
|
||||
|
||||
# CONFIG: $RunMozillaTests = %runMozillaTests%;
|
||||
$RunMozillaTests = 1;
|
||||
$RegxpcomTest = 1;
|
||||
$AliveTest = 1;
|
||||
$JavaTest = 0;
|
||||
$ViewerTest = 0;
|
||||
$BloatTest = 0; # warren memory bloat test
|
||||
$BloatTest2 = 0; # dbaron memory bloat test, require tracemalloc
|
||||
$DomToTextConversionTest = 0;
|
||||
$XpcomGlueTest = 0;
|
||||
$CodesizeTest = 0; # Z, require mozilla/tools/codesighs
|
||||
$EmbedCodesizeTest = 0; # mZ, require mozilla/tools/codesigns
|
||||
$MailBloatTest = 0;
|
||||
$EmbedTest = 0; # Assumes you wanted $BuildEmbed=1
|
||||
$LayoutPerformanceTest = 0; # Tp
|
||||
$DHTMLPerformanceTest = 0; # Tdhtml
|
||||
$QATest = 0;
|
||||
$XULWindowOpenTest = 0; # Txul
|
||||
$StartupPerformanceTest = 0; # Ts
|
||||
$NeckoUnitTest = 0;
|
||||
$RenderPerformanceTest = 0; # Tgfx
|
||||
|
||||
$TestsPhoneHome = 0; # Should test report back to server?
|
||||
$GraphNameOverride = 'fx-win32-tbox';
|
||||
|
||||
# $results_server
|
||||
#----------------------------------------------------------------------------
|
||||
# Server on which test results will be accessible. This was originally tegu,
|
||||
# then became axolotl. Once we moved services from axolotl, it was time
|
||||
# to give this service its own hostname to make future transitions easier.
|
||||
# - cmp@mozilla.org
|
||||
#$results_server = "build-graphs.mozilla.org";
|
||||
|
||||
$pageload_server = "pageload.build.mozilla.org"; # localhost
|
||||
|
||||
#
|
||||
# Timeouts, values are in seconds.
|
||||
#
|
||||
#$CVSCheckoutTimeout = 3600;
|
||||
#$CreateProfileTimeout = 45;
|
||||
#$RegxpcomTestTimeout = 120;
|
||||
|
||||
#$AliveTestTimeout = 30;
|
||||
#$ViewerTestTimeout = 45;
|
||||
#$EmbedTestTimeout = 45;
|
||||
#$BloatTestTimeout = 120; # seconds
|
||||
#$MailBloatTestTimeout = 120; # seconds
|
||||
#$JavaTestTimeout = 45;
|
||||
#$DomTestTimeout = 45; # seconds
|
||||
#$XpcomGlueTestTimeout = 15;
|
||||
#$CodesizeTestTimeout = 900; # seconds
|
||||
#$CodesizeTestType = "auto"; # {"auto"|"base"}
|
||||
$LayoutPerformanceTestTimeout = 800; # entire test, seconds
|
||||
#$DHTMLPerformanceTestTimeout = 1200; # entire test, seconds
|
||||
#$QATestTimeout = 1200; # entire test, seconds
|
||||
#$LayoutPerformanceTestPageTimeout = 30000; # each page, ms
|
||||
#$StartupPerformanceTestTimeout = 20; # seconds
|
||||
#$XULWindowOpenTestTimeout = 90; # seconds
|
||||
#$NeckoUnitTestTimeout = 30; # seconds
|
||||
$RenderPerformanceTestTimeout = 1800; # seconds
|
||||
|
||||
#$MozConfigFileName = 'mozconfig';
|
||||
|
||||
#$UseMozillaProfile = 1;
|
||||
#$MozProfileName = 'default';
|
||||
|
||||
#- Set these to what makes sense for your system
|
||||
$Make = 'make'; # Must be GNU make
|
||||
#$MakeOverrides = '';
|
||||
#$mail = '/bin/mail';
|
||||
#$CVS = 'cvs -q';
|
||||
#$CVSCO = 'checkout -P';
|
||||
|
||||
# win32 usually doesn't have /bin/mail
|
||||
$blat = '/d/mozilla-build/blat261/full/blat';
|
||||
#$use_blat = 1;
|
||||
|
||||
# Set moz_cvsroot to something like:
|
||||
# :pserver:$ENV{USER}%netscape.com\@cvs.mozilla.org:/cvsroot
|
||||
# :pserver:anonymous\@cvs-mirror.mozilla.org:/cvsroot
|
||||
#
|
||||
# Note that win32 may not need \@, depends on ' or ".
|
||||
# :pserver:$ENV{USER}%netscape.com@cvs.mozilla.org:/cvsroot
|
||||
|
||||
# CONFIG: $moz_cvsroot = '%mozillaCvsroot%';
|
||||
$moz_cvsroot = ':ext:cltbld@cvs.mozilla.org:/cvsroot';
|
||||
|
||||
#- Set these proper values for your tinderbox server
|
||||
#$Tinderbox_server = 'tinderbox-daemon@tinderbox.mozilla.org';
|
||||
|
||||
# Allow for non-client builds, e.g. camino.
|
||||
#$moz_client_mk = 'client.mk';
|
||||
|
||||
#- Set if you want to build in a separate object tree
|
||||
$ObjDir = 'obj-fx-trunk';
|
||||
|
||||
# Extra build name, if needed.
|
||||
$BuildNameExtra = 'Nightly';
|
||||
|
||||
# User comment, eg. ip address for dhcp builds.
|
||||
# ex: $UserComment = "ip = 208.12.36.108";
|
||||
#$UserComment = 0;
|
||||
|
||||
#-
|
||||
#- The rest should not need to be changed
|
||||
#-
|
||||
|
||||
#- Minimum wait period from start of build to start of next build in minutes.
|
||||
#$BuildSleep = 10;
|
||||
|
||||
#- Until you get the script working. When it works,
|
||||
#- change to the tree you're actually building
|
||||
# CONFIG: $BuildTree = '%buildTree%';
|
||||
$BuildTree = 'MozillaTest';
|
||||
|
||||
#$BuildName = '';
|
||||
#$BuildTag = '';
|
||||
#$BuildConfigDir = 'mozilla/config';
|
||||
#$Topsrcdir = 'mozilla';
|
||||
|
||||
$BinaryName = 'firefox.exe';
|
||||
|
||||
#
|
||||
# For embedding app, use:
|
||||
#$EmbedBinaryName = 'TestGtkEmbed';
|
||||
#$EmbedDistDir = 'dist/bin'
|
||||
|
||||
|
||||
#$ShellOverride = ''; # Only used if the default shell is too stupid
|
||||
#$ConfigureArgs = '';
|
||||
#$ConfigureEnvArgs = '';
|
||||
#$Compiler = 'gcc';
|
||||
#$NSPRArgs = '';
|
||||
#$ShellOverride = '';
|
||||
|
||||
$ProfiledBuild = 1;
|
||||
# Release build options
|
||||
$ReleaseBuild = 1;
|
||||
$shiptalkback = 0;
|
||||
$ReleaseToLatest = 1; # Push the release to latest-<milestone>?
|
||||
$ReleaseToDated = 1; # Push the release to YYYY-MM-DD-HH-<milestone>?
|
||||
$build_hour = "14";
|
||||
$package_creation_path = "/browser/installer";
|
||||
# needs setting for mac + talkback: $mac_bundle_path = "/browser/app";
|
||||
$ssh_version = "2";
|
||||
# CONFIG: $ssh_user = "%sshUser%";
|
||||
$ssh_user = "ffxbld";
|
||||
$ssh_key = "'$ENV{HOME}/.ssh/ffxbld_dsa'";
|
||||
# CONFIG: $ssh_server = "%sshServer%";
|
||||
$ssh_server = "stage-old.mozilla.org";
|
||||
$ReleaseGroup = "firefox";
|
||||
$ftp_path = "/home/ftp/pub/firefox/nightly/experimental";
|
||||
$url_path = "http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/experimental";
|
||||
$tbox_ftp_path = "/home/ftp/pub/firefox/tinderbox-builds";
|
||||
$tbox_url_path = "http://ftp.mozilla.org/pub/mozilla.org/firefox/tinderbox-builds";
|
||||
$milestone = "trunk";
|
||||
$notify_list = 'build-announce@mozilla.org';
|
||||
$stub_installer = 0;
|
||||
$sea_installer = 1;
|
||||
$archive = 1;
|
||||
$push_raw_xpis = 0;
|
||||
# CONFIG: $update_aus_host = '%ausServer%';
|
||||
$update_aus_host = 'aus2-staging.mozilla.org';
|
||||
$update_package = 1;
|
||||
$update_product = "Firefox";
|
||||
$update_version = "trunk";
|
||||
$update_platform = "WINNT_x86-msvc";
|
||||
$update_hash = "sha1";
|
||||
# CONFIG: $update_filehost = '%ftpServer%';
|
||||
$update_filehost = 'ftp.mozilla.org';
|
||||
$update_ver_file = 'browser/config/version.txt';
|
||||
$update_pushinfo = 0;
|
||||
$crashreporter_buildsymbols = 1;
|
||||
$crashreporter_pushsymbols = 1;
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_HOST'} = '%symbolServer%';
|
||||
$ENV{'SYMBOL_SERVER_HOST'} = 'dm-symbolpush01.mozilla.org';
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_USER'} = '%symbolServerUser%';
|
||||
$ENV{'SYMBOL_SERVER_USER'} = 'ffxbld';
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_PATH'} = '%symbolServerPath%';
|
||||
$ENV{'SYMBOL_SERVER_PATH'} = '/mnt/netapp/breakpad/symbols_ffx';
|
||||
# CONFIG: $ENV{'SYMBOL_SERVER_SSH_KEY'} = '%symbolServerKey%';
|
||||
$ENV{'SYMBOL_SERVER_SSH_KEY'} = '/c/Documents and Settings/cltbld/.ssh/ffxbld_dsa';
|
||||
|
||||
# Reboot the OS at the end of build-and-test cycle. This is primarily
|
||||
# intended for Win9x, which can't last more than a few cycles before
|
||||
# locking up (and testing would be suspect even after a couple of cycles).
|
||||
# Right now, there is only code to force the reboot for Win9x, so even
|
||||
# setting this to 1, will not have an effect on other platforms. Setting
|
||||
# up win9x to automatically logon and begin running tinderbox is left
|
||||
# as an exercise to the reader.
|
||||
#$RebootSystem = 0;
|
||||
|
||||
# LogCompression specifies the type of compression used on the log file.
|
||||
# Valid options are 'gzip', and 'bzip2'. Please make sure the binaries
|
||||
# for 'gzip' or 'bzip2' are in the user's path before setting this
|
||||
# option.
|
||||
#$LogCompression = '';
|
||||
|
||||
# LogEncoding specifies the encoding format used for the logs. Valid
|
||||
# options are 'base64', and 'uuencode'. If $LogCompression is set above,
|
||||
# this needs to be set to 'base64' or 'uuencode' to ensure that the
|
||||
# binary data is transferred properly.
|
||||
#$LogEncoding = '';
|
||||
|
||||
# Prevent Extension Manager from spawning child processes during tests
|
||||
# - processes that tbox scripts cannot kill.
|
||||
#$ENV{NO_EM_RESTART} = '1';
|
||||
@@ -1,697 +0,0 @@
|
||||
# -*- 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): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# A. Karl Kornel <karl@kornel.name>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla;
|
||||
|
||||
use strict;
|
||||
|
||||
# We want any compile errors to get to the browser, if possible.
|
||||
BEGIN {
|
||||
# This makes sure we're in a CGI.
|
||||
if ($ENV{SERVER_SOFTWARE} && !$ENV{MOD_PERL}) {
|
||||
require CGI::Carp;
|
||||
CGI::Carp->import('fatalsToBrowser');
|
||||
}
|
||||
}
|
||||
|
||||
use Bugzilla::Config;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Auth;
|
||||
use Bugzilla::Auth::Persist::Cookie;
|
||||
use Bugzilla::CGI;
|
||||
use Bugzilla::DB;
|
||||
use Bugzilla::Install::Localconfig qw(read_localconfig);
|
||||
use Bugzilla::Template;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::Flag;
|
||||
|
||||
use File::Basename;
|
||||
use File::Spec::Functions;
|
||||
use Safe;
|
||||
|
||||
# This creates the request cache for non-mod_perl installations.
|
||||
our $_request_cache = {};
|
||||
|
||||
#####################################################################
|
||||
# Constants
|
||||
#####################################################################
|
||||
|
||||
# Scripts that are not stopped by shutdownhtml being in effect.
|
||||
use constant SHUTDOWNHTML_EXEMPT => [
|
||||
'editparams.cgi',
|
||||
'checksetup.pl',
|
||||
'recode.pl',
|
||||
];
|
||||
|
||||
# Non-cgi scripts that should silently exit.
|
||||
use constant SHUTDOWNHTML_EXIT_SILENTLY => [
|
||||
'whine.pl'
|
||||
];
|
||||
|
||||
#####################################################################
|
||||
# Global Code
|
||||
#####################################################################
|
||||
|
||||
# $::SIG{__DIE__} = i_am_cgi() ? \&CGI::Carp::confess : \&Carp::confess;
|
||||
|
||||
# Note that this is a raw subroutine, not a method, so $class isn't available.
|
||||
sub init_page {
|
||||
(binmode STDOUT, ':utf8') if Bugzilla->params->{'utf8'};
|
||||
|
||||
# Some environment variables are not taint safe
|
||||
delete @::ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
|
||||
# Some modules throw undefined errors (notably File::Spec::Win32) if
|
||||
# PATH is undefined.
|
||||
$ENV{'PATH'} = '';
|
||||
|
||||
# IIS prints out warnings to the webpage, so ignore them, or log them
|
||||
# to a file if the file exists.
|
||||
if ($ENV{SERVER_SOFTWARE} && $ENV{SERVER_SOFTWARE} =~ /microsoft-iis/i) {
|
||||
$SIG{__WARN__} = sub {
|
||||
my ($msg) = @_;
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
if (-w "$datadir/errorlog") {
|
||||
my $warning_log = new IO::File(">>$datadir/errorlog");
|
||||
print $warning_log $msg;
|
||||
$warning_log->close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
# If Bugzilla is shut down, do not allow anything to run, just display a
|
||||
# message to the user about the downtime and log out. Scripts listed in
|
||||
# SHUTDOWNHTML_EXEMPT are exempt from this message.
|
||||
#
|
||||
# Because this is code which is run live from perl "use" commands of other
|
||||
# scripts, we're skipping this part if we get here during a perl syntax
|
||||
# check -- runtests.pl compiles scripts without running them, so we
|
||||
# need to make sure that this check doesn't apply to 'perl -c' calls.
|
||||
#
|
||||
# This code must go here. It cannot go anywhere in Bugzilla::CGI, because
|
||||
# it uses Template, and that causes various dependency loops.
|
||||
if (!$^C && Bugzilla->params->{"shutdownhtml"}
|
||||
&& lsearch(SHUTDOWNHTML_EXEMPT, basename($0)) == -1)
|
||||
{
|
||||
# Allow non-cgi scripts to exit silently (without displaying any
|
||||
# message), if desired. At this point, no DBI call has been made
|
||||
# yet, and no error will be returned if the DB is inaccessible.
|
||||
if (lsearch(SHUTDOWNHTML_EXIT_SILENTLY, basename($0)) > -1
|
||||
&& !i_am_cgi())
|
||||
{
|
||||
exit;
|
||||
}
|
||||
|
||||
# For security reasons, log out users when Bugzilla is down.
|
||||
# Bugzilla->login() is required to catch the logincookie, if any.
|
||||
my $user;
|
||||
eval { $user = Bugzilla->login(LOGIN_OPTIONAL); };
|
||||
if ($@) {
|
||||
# The DB is not accessible. Use the default user object.
|
||||
$user = Bugzilla->user;
|
||||
$user->{settings} = {};
|
||||
}
|
||||
my $userid = $user->id;
|
||||
Bugzilla->logout();
|
||||
|
||||
my $template = Bugzilla->template;
|
||||
my $vars = {};
|
||||
$vars->{'message'} = 'shutdown';
|
||||
$vars->{'userid'} = $userid;
|
||||
# Generate and return a message about the downtime, appropriately
|
||||
# for if we're a command-line script or a CGI script.
|
||||
my $extension;
|
||||
if (i_am_cgi() && (!Bugzilla->cgi->param('ctype')
|
||||
|| Bugzilla->cgi->param('ctype') eq 'html')) {
|
||||
$extension = 'html';
|
||||
}
|
||||
else {
|
||||
$extension = 'txt';
|
||||
}
|
||||
print Bugzilla->cgi->header() if i_am_cgi();
|
||||
my $t_output;
|
||||
$template->process("global/message.$extension.tmpl", $vars, \$t_output)
|
||||
|| ThrowTemplateError($template->error);
|
||||
print $t_output . "\n";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
init_page() if !$ENV{MOD_PERL};
|
||||
|
||||
#####################################################################
|
||||
# Subroutines and Methods
|
||||
#####################################################################
|
||||
|
||||
sub template {
|
||||
my $class = shift;
|
||||
$class->request_cache->{language} = "";
|
||||
$class->request_cache->{template} ||= Bugzilla::Template->create();
|
||||
return $class->request_cache->{template};
|
||||
}
|
||||
|
||||
sub template_inner {
|
||||
my ($class, $lang) = @_;
|
||||
$lang = defined($lang) ? $lang : ($class->request_cache->{language} || "");
|
||||
$class->request_cache->{language} = $lang;
|
||||
$class->request_cache->{"template_inner_$lang"}
|
||||
||= Bugzilla::Template->create();
|
||||
return $class->request_cache->{"template_inner_$lang"};
|
||||
}
|
||||
|
||||
sub cgi {
|
||||
my $class = shift;
|
||||
$class->request_cache->{cgi} ||= new Bugzilla::CGI();
|
||||
return $class->request_cache->{cgi};
|
||||
}
|
||||
|
||||
sub localconfig {
|
||||
my $class = shift;
|
||||
$class->request_cache->{localconfig} ||= read_localconfig();
|
||||
return $class->request_cache->{localconfig};
|
||||
}
|
||||
|
||||
sub params {
|
||||
my $class = shift;
|
||||
$class->request_cache->{params} ||= Bugzilla::Config::read_param_file();
|
||||
return $class->request_cache->{params};
|
||||
}
|
||||
|
||||
sub user {
|
||||
my $class = shift;
|
||||
$class->request_cache->{user} ||= new Bugzilla::User;
|
||||
return $class->request_cache->{user};
|
||||
}
|
||||
|
||||
sub set_user {
|
||||
my ($class, $user) = @_;
|
||||
$class->request_cache->{user} = $user;
|
||||
}
|
||||
|
||||
sub sudoer {
|
||||
my $class = shift;
|
||||
return $class->request_cache->{sudoer};
|
||||
}
|
||||
|
||||
sub sudo_request {
|
||||
my ($class, $new_user, $new_sudoer) = @_;
|
||||
$class->request_cache->{user} = $new_user;
|
||||
$class->request_cache->{sudoer} = $new_sudoer;
|
||||
# NOTE: If you want to log the start of an sudo session, do it here.
|
||||
}
|
||||
|
||||
sub login {
|
||||
my ($class, $type) = @_;
|
||||
|
||||
return $class->user if $class->user->id;
|
||||
|
||||
my $authorizer = new Bugzilla::Auth();
|
||||
$type = LOGIN_REQUIRED if $class->cgi->param('GoAheadAndLogIn');
|
||||
if (!defined $type || $type == LOGIN_NORMAL) {
|
||||
$type = $class->params->{'requirelogin'} ? LOGIN_REQUIRED : LOGIN_NORMAL;
|
||||
}
|
||||
my $authenticated_user = $authorizer->login($type);
|
||||
|
||||
# At this point, we now know if a real person is logged in.
|
||||
# We must now check to see if an sudo session is in progress.
|
||||
# For a session to be in progress, the following must be true:
|
||||
# 1: There must be a logged in user
|
||||
# 2: That user must be in the 'bz_sudoer' group
|
||||
# 3: There must be a valid value in the 'sudo' cookie
|
||||
# 4: A Bugzilla::User object must exist for the given cookie value
|
||||
# 5: That user must NOT be in the 'bz_sudo_protect' group
|
||||
my $sudo_cookie = $class->cgi->cookie('sudo');
|
||||
detaint_natural($sudo_cookie) if defined($sudo_cookie);
|
||||
my $sudo_target;
|
||||
$sudo_target = new Bugzilla::User($sudo_cookie) if defined($sudo_cookie);
|
||||
if (defined($authenticated_user) &&
|
||||
$authenticated_user->in_group('bz_sudoers') &&
|
||||
defined($sudo_cookie) &&
|
||||
defined($sudo_target) &&
|
||||
!($sudo_target->in_group('bz_sudo_protect'))
|
||||
)
|
||||
{
|
||||
$class->set_user($sudo_target);
|
||||
$class->request_cache->{sudoer} = $authenticated_user;
|
||||
# And make sure that both users have the same Auth object,
|
||||
# since we never call Auth::login for the sudo target.
|
||||
$sudo_target->set_authorizer($authenticated_user->authorizer);
|
||||
|
||||
# NOTE: If you want to do any special logging, do it here.
|
||||
}
|
||||
else {
|
||||
$class->set_user($authenticated_user);
|
||||
}
|
||||
|
||||
# We run after the login has completed since
|
||||
# some of the checks in ssl_require_redirect
|
||||
# look for Bugzilla->user->id to determine
|
||||
# if redirection is required.
|
||||
if (i_am_cgi() && ssl_require_redirect()) {
|
||||
$class->cgi->require_https($class->params->{'sslbase'});
|
||||
}
|
||||
|
||||
return $class->user;
|
||||
}
|
||||
|
||||
sub logout {
|
||||
my ($class, $option) = @_;
|
||||
|
||||
# If we're not logged in, go away
|
||||
return unless $class->user->id;
|
||||
|
||||
$option = LOGOUT_CURRENT unless defined $option;
|
||||
Bugzilla::Auth::Persist::Cookie->logout({type => $option});
|
||||
$class->logout_request() unless $option eq LOGOUT_KEEP_CURRENT;
|
||||
}
|
||||
|
||||
sub logout_user {
|
||||
my ($class, $user) = @_;
|
||||
# When we're logging out another user we leave cookies alone, and
|
||||
# therefore avoid calling Bugzilla->logout() directly.
|
||||
Bugzilla::Auth::Persist::Cookie->logout({user => $user});
|
||||
}
|
||||
|
||||
# just a compatibility front-end to logout_user that gets a user by id
|
||||
sub logout_user_by_id {
|
||||
my ($class, $id) = @_;
|
||||
my $user = new Bugzilla::User($id);
|
||||
$class->logout_user($user);
|
||||
}
|
||||
|
||||
# hack that invalidates credentials for a single request
|
||||
sub logout_request {
|
||||
my $class = shift;
|
||||
delete $class->request_cache->{user};
|
||||
delete $class->request_cache->{sudoer};
|
||||
# We can't delete from $cgi->cookie, so logincookie data will remain
|
||||
# there. Don't rely on it: use Bugzilla->user->login instead!
|
||||
}
|
||||
|
||||
sub dbh {
|
||||
my $class = shift;
|
||||
# If we're not connected, then we must want the main db
|
||||
$class->request_cache->{dbh} ||= $class->request_cache->{dbh_main}
|
||||
= Bugzilla::DB::connect_main();
|
||||
|
||||
return $class->request_cache->{dbh};
|
||||
}
|
||||
|
||||
sub languages {
|
||||
my $class = shift;
|
||||
return $class->request_cache->{languages}
|
||||
if $class->request_cache->{languages};
|
||||
|
||||
my @files = glob(catdir(bz_locations->{'templatedir'}, '*'));
|
||||
my @languages;
|
||||
foreach my $dir_entry (@files) {
|
||||
# It's a language directory only if it contains "default" or
|
||||
# "custom". This auto-excludes CVS directories as well.
|
||||
next unless (-d catdir($dir_entry, 'default')
|
||||
|| -d catdir($dir_entry, 'custom'));
|
||||
$dir_entry = basename($dir_entry);
|
||||
# Check for language tag format conforming to RFC 1766.
|
||||
next unless $dir_entry =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/;
|
||||
push(@languages, $dir_entry);
|
||||
}
|
||||
return $class->request_cache->{languages} = \@languages;
|
||||
}
|
||||
|
||||
sub error_mode {
|
||||
my ($class, $newval) = @_;
|
||||
if (defined $newval) {
|
||||
$class->request_cache->{error_mode} = $newval;
|
||||
}
|
||||
return $class->request_cache->{error_mode}
|
||||
|| Bugzilla::Constants::ERROR_MODE_WEBPAGE;
|
||||
}
|
||||
|
||||
sub usage_mode {
|
||||
my ($class, $newval) = @_;
|
||||
if (defined $newval) {
|
||||
if ($newval == USAGE_MODE_BROWSER) {
|
||||
$class->error_mode(ERROR_MODE_WEBPAGE);
|
||||
}
|
||||
elsif ($newval == USAGE_MODE_CMDLINE) {
|
||||
$class->error_mode(ERROR_MODE_DIE);
|
||||
}
|
||||
elsif ($newval == USAGE_MODE_WEBSERVICE) {
|
||||
$class->error_mode(ERROR_MODE_DIE_SOAP_FAULT);
|
||||
}
|
||||
elsif ($newval == USAGE_MODE_EMAIL) {
|
||||
$class->error_mode(ERROR_MODE_DIE);
|
||||
}
|
||||
else {
|
||||
ThrowCodeError('usage_mode_invalid',
|
||||
{'invalid_usage_mode', $newval});
|
||||
}
|
||||
$class->request_cache->{usage_mode} = $newval;
|
||||
}
|
||||
return $class->request_cache->{usage_mode}
|
||||
|| Bugzilla::Constants::USAGE_MODE_BROWSER;
|
||||
}
|
||||
|
||||
sub installation_mode {
|
||||
my ($class, $newval) = @_;
|
||||
($class->request_cache->{installation_mode} = $newval) if defined $newval;
|
||||
return $class->request_cache->{installation_mode}
|
||||
|| INSTALLATION_MODE_INTERACTIVE;
|
||||
}
|
||||
|
||||
sub installation_answers {
|
||||
my ($class, $filename) = @_;
|
||||
if ($filename) {
|
||||
my $s = new Safe;
|
||||
$s->rdo($filename);
|
||||
|
||||
die "Error reading $filename: $!" if $!;
|
||||
die "Error evaluating $filename: $@" if $@;
|
||||
|
||||
# Now read the param back out from the sandbox
|
||||
$class->request_cache->{installation_answers} = $s->varglob('answer');
|
||||
}
|
||||
return $class->request_cache->{installation_answers} || {};
|
||||
}
|
||||
|
||||
sub switch_to_shadow_db {
|
||||
my $class = shift;
|
||||
|
||||
if (!$class->request_cache->{dbh_shadow}) {
|
||||
if ($class->params->{'shadowdb'}) {
|
||||
$class->request_cache->{dbh_shadow} = Bugzilla::DB::connect_shadow();
|
||||
} else {
|
||||
$class->request_cache->{dbh_shadow} = request_cache()->{dbh_main};
|
||||
}
|
||||
}
|
||||
|
||||
$class->request_cache->{dbh} = $class->request_cache->{dbh_shadow};
|
||||
# we have to return $class->dbh instead of {dbh} as
|
||||
# {dbh_shadow} may be undefined if no shadow DB is used
|
||||
# and no connection to the main DB has been established yet.
|
||||
return $class->dbh;
|
||||
}
|
||||
|
||||
sub switch_to_main_db {
|
||||
my $class = shift;
|
||||
|
||||
$class->request_cache->{dbh} = $class->request_cache->{dbh_main};
|
||||
# We have to return $class->dbh instead of {dbh} as
|
||||
# {dbh_main} may be undefined if no connection to the main DB
|
||||
# has been established yet.
|
||||
return $class->dbh;
|
||||
}
|
||||
|
||||
sub get_fields {
|
||||
my $class = shift;
|
||||
my $criteria = shift;
|
||||
# This function may be called during installation, and Field::match
|
||||
# may fail at that time. so we want to return an empty list in that
|
||||
# case.
|
||||
my $fields = eval { Bugzilla::Field->match($criteria) } || [];
|
||||
return @$fields;
|
||||
}
|
||||
|
||||
sub active_custom_fields {
|
||||
my $class = shift;
|
||||
if (!exists $class->request_cache->{active_custom_fields}) {
|
||||
$class->request_cache->{active_custom_fields} =
|
||||
Bugzilla::Field->match({ custom => 1, obsolete => 0 });
|
||||
}
|
||||
return @{$class->request_cache->{active_custom_fields}};
|
||||
}
|
||||
|
||||
sub has_flags {
|
||||
my $class = shift;
|
||||
|
||||
if (!defined $class->request_cache->{has_flags}) {
|
||||
$class->request_cache->{has_flags} = Bugzilla::Flag::has_flags();
|
||||
}
|
||||
return $class->request_cache->{has_flags};
|
||||
}
|
||||
|
||||
sub hook_args {
|
||||
my ($class, $args) = @_;
|
||||
$class->request_cache->{hook_args} = $args if $args;
|
||||
return $class->request_cache->{hook_args};
|
||||
}
|
||||
|
||||
sub request_cache {
|
||||
if ($ENV{MOD_PERL}) {
|
||||
require Apache2::RequestUtil;
|
||||
return Apache2::RequestUtil->request->pnotes();
|
||||
}
|
||||
return $_request_cache;
|
||||
}
|
||||
|
||||
# Private methods
|
||||
|
||||
# Per-process cleanup. Note that this is a plain subroutine, not a method,
|
||||
# so we don't have $class available.
|
||||
sub _cleanup {
|
||||
my $main = Bugzilla->request_cache->{dbh_main};
|
||||
my $shadow = Bugzilla->request_cache->{dbh_shadow};
|
||||
foreach my $dbh ($main, $shadow) {
|
||||
next if !$dbh;
|
||||
$dbh->bz_rollback_transaction() if $dbh->bz_in_transaction;
|
||||
$dbh->disconnect;
|
||||
}
|
||||
undef $_request_cache;
|
||||
}
|
||||
|
||||
sub END {
|
||||
# Bugzilla.pm cannot compile in mod_perl.pl if this runs.
|
||||
_cleanup() unless $ENV{MOD_PERL};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla - Semi-persistent collection of various objects used by scripts
|
||||
and modules
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla;
|
||||
|
||||
sub someModulesSub {
|
||||
Bugzilla->dbh->prepare(...);
|
||||
Bugzilla->template->process(...);
|
||||
}
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Several Bugzilla 'things' are used by a variety of modules and scripts. This
|
||||
includes database handles, template objects, and so on.
|
||||
|
||||
This module is a singleton intended as a central place to store these objects.
|
||||
This approach has several advantages:
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
They're not global variables, so we don't have issues with them staying around
|
||||
with mod_perl
|
||||
|
||||
=item *
|
||||
|
||||
Everything is in one central place, so it's easy to access, modify, and maintain
|
||||
|
||||
=item *
|
||||
|
||||
Code in modules can get access to these objects without having to have them
|
||||
all passed from the caller, and the caller's caller, and....
|
||||
|
||||
=item *
|
||||
|
||||
We can reuse objects across requests using mod_perl where appropriate (eg
|
||||
templates), whilst destroying those which are only valid for a single request
|
||||
(such as the current user)
|
||||
|
||||
=back
|
||||
|
||||
Note that items accessible via this object are demand-loaded when requested.
|
||||
|
||||
For something to be added to this object, it should either be able to benefit
|
||||
from persistence when run under mod_perl (such as the a C<template> object),
|
||||
or should be something which is globally required by a large ammount of code
|
||||
(such as the current C<user> object).
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
Note that all C<Bugzilla> functionality is method based; use C<Bugzilla-E<gt>dbh>
|
||||
rather than C<Bugzilla::dbh>. Nothing cares about this now, but don't rely on
|
||||
that.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<template>
|
||||
|
||||
The current C<Template> object, to be used for output
|
||||
|
||||
=item C<template_inner>
|
||||
|
||||
If you ever need a L<Bugzilla::Template> object while you're already
|
||||
processing a template, use this. Also use it if you want to specify
|
||||
the language to use. If no argument is passed, it uses the last
|
||||
language set. If the argument is "" (empty string), the language is
|
||||
reset to the current one (the one used by Bugzilla->template).
|
||||
|
||||
=item C<cgi>
|
||||
|
||||
The current C<cgi> object. Note that modules should B<not> be using this in
|
||||
general. Not all Bugzilla actions are cgi requests. Its useful as a convenience
|
||||
method for those scripts/templates which are only use via CGI, though.
|
||||
|
||||
=item C<user>
|
||||
|
||||
C<undef> if there is no currently logged in user or if the login code has not
|
||||
yet been run. If an sudo session is in progress, the C<Bugzilla::User>
|
||||
corresponding to the person who is being impersonated. If no session is in
|
||||
progress, the current C<Bugzilla::User>.
|
||||
|
||||
=item C<set_user>
|
||||
|
||||
Allows you to directly set what L</user> will return. You can use this
|
||||
if you want to bypass L</login> for some reason and directly "log in"
|
||||
a specific L<Bugzilla::User>. Be careful with it, though!
|
||||
|
||||
=item C<sudoer>
|
||||
|
||||
C<undef> if there is no currently logged in user, the currently logged in user
|
||||
is not in the I<sudoer> group, or there is no session in progress. If an sudo
|
||||
session is in progress, returns the C<Bugzilla::User> object corresponding to
|
||||
the person who logged in and initiated the session. If no session is in
|
||||
progress, returns the C<Bugzilla::User> object corresponding to the currently
|
||||
logged in user.
|
||||
|
||||
=item C<sudo_request>
|
||||
This begins an sudo session for the current request. It is meant to be
|
||||
used when a session has just started. For normal use, sudo access should
|
||||
normally be set at login time.
|
||||
|
||||
=item C<login>
|
||||
|
||||
Logs in a user, returning a C<Bugzilla::User> object, or C<undef> if there is
|
||||
no logged in user. See L<Bugzilla::Auth|Bugzilla::Auth>, and
|
||||
L<Bugzilla::User|Bugzilla::User>.
|
||||
|
||||
=item C<logout($option)>
|
||||
|
||||
Logs out the current user, which involves invalidating user sessions and
|
||||
cookies. Three options are available from
|
||||
L<Bugzilla::Constants|Bugzilla::Constants>: LOGOUT_CURRENT (the
|
||||
default), LOGOUT_ALL or LOGOUT_KEEP_CURRENT.
|
||||
|
||||
=item C<logout_user($user)>
|
||||
|
||||
Logs out the specified user (invalidating all his sessions), taking a
|
||||
Bugzilla::User instance.
|
||||
|
||||
=item C<logout_by_id($id)>
|
||||
|
||||
Logs out the user with the id specified. This is a compatibility
|
||||
function to be used in callsites where there is only a userid and no
|
||||
Bugzilla::User instance.
|
||||
|
||||
=item C<logout_request>
|
||||
|
||||
Essentially, causes calls to C<Bugzilla-E<gt>user> to return C<undef>. This has the
|
||||
effect of logging out a user for the current request only; cookies and
|
||||
database sessions are left intact.
|
||||
|
||||
=item C<error_mode>
|
||||
|
||||
Call either C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE)>
|
||||
or C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE_SOAP_FAULT)> to
|
||||
change this flag's default of C<Bugzilla::Constants::ERROR_MODE_WEBPAGE> and to
|
||||
indicate that errors should be passed to error mode specific error handlers
|
||||
rather than being sent to a browser and finished with an exit().
|
||||
|
||||
This is useful, for example, to keep C<eval> blocks from producing wild HTML
|
||||
on errors, making it easier for you to catch them.
|
||||
(Remember to reset the error mode to its previous value afterwards, though.)
|
||||
|
||||
C<Bugzilla->error_mode> will return the current state of this flag.
|
||||
|
||||
Note that C<Bugzilla->error_mode> is being called by C<Bugzilla->usage_mode> on
|
||||
usage mode changes.
|
||||
|
||||
=item C<usage_mode>
|
||||
|
||||
Call either C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_CMDLINE)>
|
||||
or C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_WEBSERVICE)> near the
|
||||
beginning of your script to change this flag's default of
|
||||
C<Bugzilla::Constants::USAGE_MODE_BROWSER> and to indicate that Bugzilla is
|
||||
being called in a non-interactive manner.
|
||||
This influences error handling because on usage mode changes, C<usage_mode>
|
||||
calls C<Bugzilla->error_mode> to set an error mode which makes sense for the
|
||||
usage mode.
|
||||
|
||||
C<Bugzilla->usage_mode> will return the current state of this flag.
|
||||
|
||||
=item C<installation_mode>
|
||||
|
||||
Determines whether or not installation should be silent. See
|
||||
L<Bugzilla::Constants> for the C<INSTALLATION_MODE> constants.
|
||||
|
||||
=item C<installation_answers>
|
||||
|
||||
Returns a hashref representing any "answers" file passed to F<checksetup.pl>,
|
||||
used to automatically answer or skip prompts.
|
||||
|
||||
=item C<dbh>
|
||||
|
||||
The current database handle. See L<DBI>.
|
||||
|
||||
=item C<languages>
|
||||
|
||||
Currently installed languages.
|
||||
Returns a reference to a list of RFC 1766 language tags of installed languages.
|
||||
|
||||
=item C<switch_to_shadow_db>
|
||||
|
||||
Switch from using the main database to using the shadow database.
|
||||
|
||||
=item C<switch_to_main_db>
|
||||
|
||||
Change the database object to refer to the main database.
|
||||
|
||||
=item C<params>
|
||||
|
||||
The current Parameters of Bugzilla, as a hashref. If C<data/params>
|
||||
does not exist, then we return an empty hashref. If C<data/params>
|
||||
is unreadable or is not valid perl, we C<die>.
|
||||
|
||||
=item C<hook_args>
|
||||
|
||||
If you are running inside a code hook (see L<Bugzilla::Hook>) this
|
||||
is how you get the arguments passed to the hook.
|
||||
|
||||
=back
|
||||
@@ -1,959 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Myk Melez <myk@mozilla.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Attachment;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Attachment - a file related to a bug that a user has uploaded
|
||||
to the Bugzilla server
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Attachment;
|
||||
|
||||
# Get the attachment with the given ID.
|
||||
my $attachment = Bugzilla::Attachment->get($attach_id);
|
||||
|
||||
# Get the attachments with the given IDs.
|
||||
my $attachments = Bugzilla::Attachment->get_list($attach_ids);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module defines attachment objects, which represent files related to bugs
|
||||
that users upload to the Bugzilla server.
|
||||
|
||||
=cut
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Flag;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Field;
|
||||
|
||||
sub get {
|
||||
my $invocant = shift;
|
||||
my $id = shift;
|
||||
|
||||
my $attachments = _retrieve([$id]);
|
||||
my $self = $attachments->[0];
|
||||
bless($self, ref($invocant) || $invocant) if $self;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_list {
|
||||
my $invocant = shift;
|
||||
my $ids = shift;
|
||||
|
||||
my $attachments = _retrieve($ids);
|
||||
foreach my $attachment (@$attachments) {
|
||||
bless($attachment, ref($invocant) || $invocant);
|
||||
}
|
||||
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
sub _retrieve {
|
||||
my ($ids) = @_;
|
||||
|
||||
return [] if scalar(@$ids) == 0;
|
||||
|
||||
my @columns = (
|
||||
'attachments.attach_id AS id',
|
||||
'attachments.bug_id AS bug_id',
|
||||
'attachments.description AS description',
|
||||
'attachments.mimetype AS contenttype',
|
||||
'attachments.submitter_id AS attacher_id',
|
||||
Bugzilla->dbh->sql_date_format('attachments.creation_ts',
|
||||
'%Y.%m.%d %H:%i') . " AS attached",
|
||||
'attachments.modification_time',
|
||||
'attachments.filename AS filename',
|
||||
'attachments.ispatch AS ispatch',
|
||||
'attachments.isurl AS isurl',
|
||||
'attachments.isobsolete AS isobsolete',
|
||||
'attachments.isprivate AS isprivate'
|
||||
);
|
||||
my $columns = join(", ", @columns);
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $records = $dbh->selectall_arrayref(
|
||||
"SELECT $columns
|
||||
FROM attachments
|
||||
WHERE "
|
||||
. Bugzilla->dbh->sql_in('attach_id', $ids)
|
||||
. " ORDER BY attach_id",
|
||||
{ Slice => {} });
|
||||
return $records;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Instance Properties
|
||||
|
||||
=over
|
||||
|
||||
=item C<id>
|
||||
|
||||
the unique identifier for the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub id {
|
||||
my $self = shift;
|
||||
return $self->{id};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<bug_id>
|
||||
|
||||
the ID of the bug to which the attachment is attached
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
# XXX Once Bug.pm slims down sufficiently this should become a reference
|
||||
# to a bug object.
|
||||
sub bug_id {
|
||||
my $self = shift;
|
||||
return $self->{bug_id};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<description>
|
||||
|
||||
user-provided text describing the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub description {
|
||||
my $self = shift;
|
||||
return $self->{description};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<contenttype>
|
||||
|
||||
the attachment's MIME media type
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub contenttype {
|
||||
my $self = shift;
|
||||
return $self->{contenttype};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<attacher>
|
||||
|
||||
the user who attached the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub attacher {
|
||||
my $self = shift;
|
||||
return $self->{attacher} if exists $self->{attacher};
|
||||
$self->{attacher} = new Bugzilla::User($self->{attacher_id});
|
||||
return $self->{attacher};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<attached>
|
||||
|
||||
the date and time on which the attacher attached the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub attached {
|
||||
my $self = shift;
|
||||
return $self->{attached};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<modification_time>
|
||||
|
||||
the date and time on which the attachment was last modified.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub modification_time {
|
||||
my $self = shift;
|
||||
return $self->{modification_time};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<filename>
|
||||
|
||||
the name of the file the attacher attached
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub filename {
|
||||
my $self = shift;
|
||||
return $self->{filename};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<ispatch>
|
||||
|
||||
whether or not the attachment is a patch
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub ispatch {
|
||||
my $self = shift;
|
||||
return $self->{ispatch};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<isurl>
|
||||
|
||||
whether or not the attachment is a URL
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub isurl {
|
||||
my $self = shift;
|
||||
return $self->{isurl};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<isobsolete>
|
||||
|
||||
whether or not the attachment is obsolete
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub isobsolete {
|
||||
my $self = shift;
|
||||
return $self->{isobsolete};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<isprivate>
|
||||
|
||||
whether or not the attachment is private
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub isprivate {
|
||||
my $self = shift;
|
||||
return $self->{isprivate};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<is_viewable>
|
||||
|
||||
Returns 1 if the attachment has a content-type viewable in this browser.
|
||||
Note that we don't use $cgi->Accept()'s ability to check if a content-type
|
||||
matches, because this will return a value even if it's matched by the generic
|
||||
*/* which most browsers add to the end of their Accept: headers.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub is_viewable {
|
||||
my $self = shift;
|
||||
my $contenttype = $self->contenttype;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
# We assume we can view all text and image types.
|
||||
return 1 if ($contenttype =~ /^(text|image)\//);
|
||||
|
||||
# Mozilla can view XUL. Note the trailing slash on the Gecko detection to
|
||||
# avoid sending XUL to Safari.
|
||||
return 1 if (($contenttype =~ /^application\/vnd\.mozilla\./)
|
||||
&& ($cgi->user_agent() =~ /Gecko\//));
|
||||
|
||||
# If it's not one of the above types, we check the Accept: header for any
|
||||
# types mentioned explicitly.
|
||||
my $accept = join(",", $cgi->Accept());
|
||||
return 1 if ($accept =~ /^(.*,)?\Q$contenttype\E(,.*)?$/);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<data>
|
||||
|
||||
the content of the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub data {
|
||||
my $self = shift;
|
||||
return $self->{data} if exists $self->{data};
|
||||
|
||||
# First try to get the attachment data from the database.
|
||||
($self->{data}) = Bugzilla->dbh->selectrow_array("SELECT thedata
|
||||
FROM attach_data
|
||||
WHERE id = ?",
|
||||
undef,
|
||||
$self->{id});
|
||||
|
||||
# If there's no attachment data in the database, the attachment is stored
|
||||
# in a local file, so retrieve it from there.
|
||||
if (length($self->{data}) == 0) {
|
||||
if (open(AH, $self->_get_local_filename())) {
|
||||
local $/;
|
||||
binmode AH;
|
||||
$self->{data} = <AH>;
|
||||
close(AH);
|
||||
}
|
||||
}
|
||||
|
||||
return $self->{data};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<datasize>
|
||||
|
||||
the length (in characters) of the attachment content
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
# datasize is a property of the data itself, and it's unclear whether we should
|
||||
# expose it at all, since you can easily derive it from the data itself: in TT,
|
||||
# attachment.data.size; in Perl, length($attachment->{data}). But perhaps
|
||||
# it makes sense for performance reasons, since accessing the data forces it
|
||||
# to get retrieved from the database/filesystem and loaded into memory,
|
||||
# while datasize avoids loading the attachment into memory, calling SQL's
|
||||
# LENGTH() function or stat()ing the file instead. I've left it in for now.
|
||||
|
||||
sub datasize {
|
||||
my $self = shift;
|
||||
return $self->{datasize} if exists $self->{datasize};
|
||||
|
||||
# If we have already retrieved the data, return its size.
|
||||
return length($self->{data}) if exists $self->{data};
|
||||
|
||||
$self->{datasize} =
|
||||
Bugzilla->dbh->selectrow_array("SELECT LENGTH(thedata)
|
||||
FROM attach_data
|
||||
WHERE id = ?",
|
||||
undef, $self->{id}) || 0;
|
||||
|
||||
# If there's no attachment data in the database, either the attachment
|
||||
# is stored in a local file, and so retrieve its size from the file,
|
||||
# or the attachment has been deleted.
|
||||
unless ($self->{datasize}) {
|
||||
if (open(AH, $self->_get_local_filename())) {
|
||||
binmode AH;
|
||||
$self->{datasize} = (stat(AH))[7];
|
||||
close(AH);
|
||||
}
|
||||
}
|
||||
|
||||
return $self->{datasize};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<flags>
|
||||
|
||||
flags that have been set on the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub flags {
|
||||
my $self = shift;
|
||||
return $self->{flags} if exists $self->{flags};
|
||||
|
||||
$self->{flags} = Bugzilla::Flag->match({ 'attach_id' => $self->id });
|
||||
return $self->{flags};
|
||||
}
|
||||
|
||||
# Instance methods; no POD documentation here yet because the only ones so far
|
||||
# are private.
|
||||
|
||||
sub _get_local_filename {
|
||||
my $self = shift;
|
||||
my $hash = ($self->id % 100) + 100;
|
||||
$hash =~ s/.*(\d\d)$/group.$1/;
|
||||
return bz_locations()->{'attachdir'} . "/$hash/attachment." . $self->id;
|
||||
}
|
||||
|
||||
sub _validate_filename {
|
||||
my ($throw_error) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
defined $cgi->upload('data')
|
||||
|| ($throw_error ? ThrowUserError("file_not_specified") : return 0);
|
||||
|
||||
my $filename = $cgi->upload('data');
|
||||
|
||||
# Remove path info (if any) from the file name. The browser should do this
|
||||
# for us, but some are buggy. This may not work on Mac file names and could
|
||||
# mess up file names with slashes in them, but them's the breaks. We only
|
||||
# use this as a hint to users downloading attachments anyway, so it's not
|
||||
# a big deal if it munges incorrectly occasionally.
|
||||
$filename =~ s/^.*[\/\\]//;
|
||||
|
||||
# Truncate the filename to 100 characters, counting from the end of the
|
||||
# string to make sure we keep the filename extension.
|
||||
$filename = substr($filename, -100, 100);
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
sub _validate_data {
|
||||
my ($throw_error, $hr_vars) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $maxsize = $cgi->param('ispatch') ? Bugzilla->params->{'maxpatchsize'}
|
||||
: Bugzilla->params->{'maxattachmentsize'};
|
||||
$maxsize *= 1024; # Convert from K
|
||||
my $fh;
|
||||
# Skip uploading into a local variable if the user wants to upload huge
|
||||
# attachments into local files.
|
||||
if (!$cgi->param('bigfile')) {
|
||||
$fh = $cgi->upload('data');
|
||||
}
|
||||
my $data;
|
||||
|
||||
# We could get away with reading only as much as required, except that then
|
||||
# we wouldn't have a size to print to the error handler below.
|
||||
if (!$cgi->param('bigfile')) {
|
||||
# enable 'slurp' mode
|
||||
local $/;
|
||||
$data = <$fh>;
|
||||
}
|
||||
|
||||
$data
|
||||
|| ($cgi->param('bigfile'))
|
||||
|| ($throw_error ? ThrowUserError("zero_length_file") : return 0);
|
||||
|
||||
# Windows screenshots are usually uncompressed BMP files which
|
||||
# makes for a quick way to eat up disk space. Let's compress them.
|
||||
# We do this before we check the size since the uncompressed version
|
||||
# could easily be greater than maxattachmentsize.
|
||||
if (Bugzilla->params->{'convert_uncompressed_images'}
|
||||
&& $cgi->param('contenttype') eq 'image/bmp') {
|
||||
require Image::Magick;
|
||||
my $img = Image::Magick->new(magick=>'bmp');
|
||||
$img->BlobToImage($data);
|
||||
$img->set(magick=>'png');
|
||||
my $imgdata = $img->ImageToBlob();
|
||||
$data = $imgdata;
|
||||
$cgi->param('contenttype', 'image/png');
|
||||
$hr_vars->{'convertedbmp'} = 1;
|
||||
}
|
||||
|
||||
# Make sure the attachment does not exceed the maximum permitted size
|
||||
my $len = $data ? length($data) : 0;
|
||||
if ($maxsize && $len > $maxsize) {
|
||||
my $vars = { filesize => sprintf("%.0f", $len/1024) };
|
||||
if ($cgi->param('ispatch')) {
|
||||
$throw_error ? ThrowUserError("patch_too_large", $vars) : return 0;
|
||||
}
|
||||
else {
|
||||
$throw_error ? ThrowUserError("file_too_large", $vars) : return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $data || '';
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Class Methods
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_attachments_by_bug($bug_id)>
|
||||
|
||||
Description: retrieves and returns the attachments the currently logged in
|
||||
user can view for the given bug.
|
||||
|
||||
Params: C<$bug_id> - integer - the ID of the bug for which
|
||||
to retrieve and return attachments.
|
||||
|
||||
Returns: a reference to an array of attachment objects.
|
||||
|
||||
=cut
|
||||
|
||||
sub get_attachments_by_bug {
|
||||
my ($class, $bug_id) = @_;
|
||||
my $user = Bugzilla->user;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# By default, private attachments are not accessible, unless the user
|
||||
# is in the insider group or submitted the attachment.
|
||||
my $and_restriction = '';
|
||||
my @values = ($bug_id);
|
||||
|
||||
unless ($user->is_insider) {
|
||||
$and_restriction = 'AND (isprivate = 0 OR submitter_id = ?)';
|
||||
push(@values, $user->id);
|
||||
}
|
||||
|
||||
my $attach_ids = $dbh->selectcol_arrayref("SELECT attach_id FROM attachments
|
||||
WHERE bug_id = ? $and_restriction",
|
||||
undef, @values);
|
||||
my $attachments = Bugzilla::Attachment->get_list($attach_ids);
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_is_patch()>
|
||||
|
||||
Description: validates the "patch" flag passed in by CGI.
|
||||
|
||||
Returns: 1 on success.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_is_patch {
|
||||
my ($class, $throw_error) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
# Set the ispatch flag to zero if it is undefined, since the UI uses
|
||||
# an HTML checkbox to represent this flag, and unchecked HTML checkboxes
|
||||
# do not get sent in HTML requests.
|
||||
$cgi->param('ispatch', $cgi->param('ispatch') ? 1 : 0);
|
||||
|
||||
# Set the content type to text/plain if the attachment is a patch.
|
||||
$cgi->param('contenttype', 'text/plain') if $cgi->param('ispatch');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_description()>
|
||||
|
||||
Description: validates the description passed in by CGI.
|
||||
|
||||
Returns: 1 on success.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_description {
|
||||
my ($class, $throw_error) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
$cgi->param('description')
|
||||
|| ($throw_error ? ThrowUserError("missing_attachment_description") : return 0);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_content_type()>
|
||||
|
||||
Description: validates the content type passed in by CGI.
|
||||
|
||||
Returns: 1 on success.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_content_type {
|
||||
my ($class, $throw_error) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
if (!defined $cgi->param('contenttypemethod')) {
|
||||
$throw_error ? ThrowUserError("missing_content_type_method") : return 0;
|
||||
}
|
||||
elsif ($cgi->param('contenttypemethod') eq 'autodetect') {
|
||||
my $contenttype =
|
||||
$cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
|
||||
# The user asked us to auto-detect the content type, so use the type
|
||||
# specified in the HTTP request headers.
|
||||
if ( !$contenttype ) {
|
||||
$throw_error ? ThrowUserError("missing_content_type") : return 0;
|
||||
}
|
||||
$cgi->param('contenttype', $contenttype);
|
||||
}
|
||||
elsif ($cgi->param('contenttypemethod') eq 'list') {
|
||||
# The user selected a content type from the list, so use their
|
||||
# selection.
|
||||
$cgi->param('contenttype', $cgi->param('contenttypeselection'));
|
||||
}
|
||||
elsif ($cgi->param('contenttypemethod') eq 'manual') {
|
||||
# The user entered a content type manually, so use their entry.
|
||||
$cgi->param('contenttype', $cgi->param('contenttypeentry'));
|
||||
}
|
||||
else {
|
||||
$throw_error ?
|
||||
ThrowCodeError("illegal_content_type_method",
|
||||
{ contenttypemethod => $cgi->param('contenttypemethod') }) :
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( $cgi->param('contenttype') !~
|
||||
/^(application|audio|image|message|model|multipart|text|video)\/.+$/ ) {
|
||||
$throw_error ?
|
||||
ThrowUserError("invalid_content_type",
|
||||
{ contenttype => $cgi->param('contenttype') }) :
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_can_edit($attachment, $product_id)>
|
||||
|
||||
Description: validates if the user is allowed to view and edit the attachment.
|
||||
Only the submitter or someone with editbugs privs can edit it.
|
||||
Only the submitter and users in the insider group can view
|
||||
private attachments.
|
||||
|
||||
Params: $attachment - the attachment object being edited.
|
||||
$product_id - the product ID the attachment belongs to.
|
||||
|
||||
Returns: 1 on success. Else an error is thrown.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_can_edit {
|
||||
my ($attachment, $product_id) = @_;
|
||||
my $user = Bugzilla->user;
|
||||
|
||||
# The submitter can edit their attachments.
|
||||
return 1 if ($attachment->attacher->id == $user->id
|
||||
|| ((!$attachment->isprivate || $user->is_insider)
|
||||
&& $user->in_group('editbugs', $product_id)));
|
||||
|
||||
# If we come here, then this attachment cannot be seen by the user.
|
||||
ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
|
||||
}
|
||||
|
||||
=item C<validate_obsolete($bug)>
|
||||
|
||||
Description: validates if attachments the user wants to mark as obsolete
|
||||
really belong to the given bug and are not already obsolete.
|
||||
Moreover, a user cannot mark an attachment as obsolete if
|
||||
he cannot view it (due to restrictions on it).
|
||||
|
||||
Params: $bug - The bug object obsolete attachments should belong to.
|
||||
|
||||
Returns: 1 on success. Else an error is thrown.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_obsolete {
|
||||
my ($class, $bug) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
# Make sure the attachment id is valid and the user has permissions to view
|
||||
# the bug to which it is attached. Make sure also that the user can view
|
||||
# the attachment itself.
|
||||
my @obsolete_attachments;
|
||||
foreach my $attachid ($cgi->param('obsolete')) {
|
||||
my $vars = {};
|
||||
$vars->{'attach_id'} = $attachid;
|
||||
|
||||
detaint_natural($attachid)
|
||||
|| ThrowCodeError('invalid_attach_id_to_obsolete', $vars);
|
||||
|
||||
my $attachment = Bugzilla::Attachment->get($attachid);
|
||||
|
||||
# Make sure the attachment exists in the database.
|
||||
ThrowUserError('invalid_attach_id', $vars) unless $attachment;
|
||||
|
||||
# Check that the user can view and edit this attachment.
|
||||
$attachment->validate_can_edit($bug->product_id);
|
||||
|
||||
$vars->{'description'} = $attachment->description;
|
||||
|
||||
if ($attachment->bug_id != $bug->bug_id) {
|
||||
$vars->{'my_bug_id'} = $bug->bug_id;
|
||||
$vars->{'attach_bug_id'} = $attachment->bug_id;
|
||||
ThrowCodeError('mismatched_bug_ids_on_obsolete', $vars);
|
||||
}
|
||||
|
||||
if ($attachment->isobsolete) {
|
||||
ThrowCodeError('attachment_already_obsolete', $vars);
|
||||
}
|
||||
|
||||
push(@obsolete_attachments, $attachment);
|
||||
}
|
||||
return @obsolete_attachments;
|
||||
}
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=item C<insert_attachment_for_bug($throw_error, $bug, $user, $timestamp, $hr_vars)>
|
||||
|
||||
Description: inserts an attachment from CGI input for the given bug.
|
||||
|
||||
Params: C<$bug> - Bugzilla::Bug object - the bug for which to insert
|
||||
the attachment.
|
||||
C<$user> - Bugzilla::User object - the user we're inserting an
|
||||
attachment for.
|
||||
C<$timestamp> - scalar - timestamp of the insert as returned
|
||||
by SELECT NOW().
|
||||
C<$hr_vars> - hash reference - reference to a hash of template
|
||||
variables.
|
||||
|
||||
Returns: the ID of the new attachment.
|
||||
|
||||
=cut
|
||||
|
||||
sub insert_attachment_for_bug {
|
||||
my ($class, $throw_error, $bug, $user, $timestamp, $hr_vars) = @_;
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $attachurl = $cgi->param('attachurl') || '';
|
||||
my $data;
|
||||
my $filename;
|
||||
my $contenttype;
|
||||
my $isurl;
|
||||
$class->validate_is_patch($throw_error) || return;
|
||||
$class->validate_description($throw_error) || return;
|
||||
|
||||
if (Bugzilla->params->{'allow_attach_url'}
|
||||
&& ($attachurl =~ /^(http|https|ftp):\/\/\S+/)
|
||||
&& !defined $cgi->upload('data'))
|
||||
{
|
||||
$filename = '';
|
||||
$data = $attachurl;
|
||||
$isurl = 1;
|
||||
$contenttype = 'text/plain';
|
||||
$cgi->param('ispatch', 0);
|
||||
$cgi->delete('bigfile');
|
||||
}
|
||||
else {
|
||||
$filename = _validate_filename($throw_error) || return;
|
||||
# need to validate content type before data as
|
||||
# we now check the content type for image/bmp in _validate_data()
|
||||
unless ($cgi->param('ispatch')) {
|
||||
$class->validate_content_type($throw_error) || return;
|
||||
|
||||
# Set the ispatch flag to 1 if we're set to autodetect
|
||||
# and the content type is text/x-diff or text/x-patch
|
||||
if ($cgi->param('contenttypemethod') eq 'autodetect'
|
||||
&& $cgi->param('contenttype') =~ m{text/x-(?:diff|patch)})
|
||||
{
|
||||
$cgi->param('ispatch', 1);
|
||||
$cgi->param('contenttype', 'text/plain');
|
||||
}
|
||||
}
|
||||
$data = _validate_data($throw_error, $hr_vars);
|
||||
# If the attachment is stored locally, $data eq ''.
|
||||
# If an error is thrown, $data eq '0'.
|
||||
($data ne '0') || return;
|
||||
$contenttype = $cgi->param('contenttype');
|
||||
|
||||
# These are inserted using placeholders so no need to panic
|
||||
trick_taint($filename);
|
||||
trick_taint($contenttype);
|
||||
$isurl = 0;
|
||||
}
|
||||
|
||||
# Check attachments the user tries to mark as obsolete.
|
||||
my @obsolete_attachments;
|
||||
if ($cgi->param('obsolete')) {
|
||||
@obsolete_attachments = $class->validate_obsolete($bug);
|
||||
}
|
||||
|
||||
# The order of these function calls is important, as Flag::validate
|
||||
# assumes User::match_field has ensured that the
|
||||
# values in the requestee fields are legitimate user email addresses.
|
||||
my $match_status = Bugzilla::User::match_field($cgi, {
|
||||
'^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
|
||||
}, MATCH_SKIP_CONFIRM);
|
||||
|
||||
$hr_vars->{'match_field'} = 'requestee';
|
||||
if ($match_status == USER_MATCH_FAILED) {
|
||||
$hr_vars->{'message'} = 'user_match_failed';
|
||||
}
|
||||
elsif ($match_status == USER_MATCH_MULTIPLE) {
|
||||
$hr_vars->{'message'} = 'user_match_multiple';
|
||||
}
|
||||
|
||||
# Escape characters in strings that will be used in SQL statements.
|
||||
my $description = $cgi->param('description');
|
||||
trick_taint($description);
|
||||
my $isprivate = $cgi->param('isprivate') ? 1 : 0;
|
||||
|
||||
# Insert the attachment into the database.
|
||||
my $sth = $dbh->do(
|
||||
"INSERT INTO attachments
|
||||
(bug_id, creation_ts, modification_time, filename, description,
|
||||
mimetype, ispatch, isurl, isprivate, submitter_id)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)", undef, ($bug->bug_id, $timestamp, $timestamp,
|
||||
$filename, $description, $contenttype, $cgi->param('ispatch'),
|
||||
$isurl, $isprivate, $user->id));
|
||||
# Retrieve the ID of the newly created attachment record.
|
||||
my $attachid = $dbh->bz_last_key('attachments', 'attach_id');
|
||||
|
||||
# We only use $data here in this INSERT with a placeholder,
|
||||
# so it's safe.
|
||||
$sth = $dbh->prepare("INSERT INTO attach_data
|
||||
(id, thedata) VALUES ($attachid, ?)");
|
||||
trick_taint($data);
|
||||
$sth->bind_param(1, $data, $dbh->BLOB_TYPE);
|
||||
$sth->execute();
|
||||
|
||||
# If the file is to be stored locally, stream the file from the web server
|
||||
# to the local file without reading it into a local variable.
|
||||
if ($cgi->param('bigfile')) {
|
||||
my $attachdir = bz_locations()->{'attachdir'};
|
||||
my $fh = $cgi->upload('data');
|
||||
my $hash = ($attachid % 100) + 100;
|
||||
$hash =~ s/.*(\d\d)$/group.$1/;
|
||||
mkdir "$attachdir/$hash", 0770;
|
||||
chmod 0770, "$attachdir/$hash";
|
||||
open(AH, ">$attachdir/$hash/attachment.$attachid");
|
||||
binmode AH;
|
||||
my $sizecount = 0;
|
||||
my $limit = (Bugzilla->params->{"maxlocalattachment"} * 1048576);
|
||||
while (<$fh>) {
|
||||
print AH $_;
|
||||
$sizecount += length($_);
|
||||
if ($sizecount > $limit) {
|
||||
close AH;
|
||||
close $fh;
|
||||
unlink "$attachdir/$hash/attachment.$attachid";
|
||||
$throw_error ? ThrowUserError("local_file_too_large") : return;
|
||||
}
|
||||
}
|
||||
close AH;
|
||||
close $fh;
|
||||
}
|
||||
|
||||
# Make existing attachments obsolete.
|
||||
my $fieldid = get_field_id('attachments.isobsolete');
|
||||
|
||||
foreach my $obsolete_attachment (@obsolete_attachments) {
|
||||
# If the obsolete attachment has request flags, cancel them.
|
||||
# This call must be done before updating the 'attachments' table.
|
||||
Bugzilla::Flag->CancelRequests($bug, $obsolete_attachment, $timestamp);
|
||||
|
||||
$dbh->do('UPDATE attachments SET isobsolete = 1, modification_time = ?
|
||||
WHERE attach_id = ?',
|
||||
undef, ($timestamp, $obsolete_attachment->id));
|
||||
|
||||
$dbh->do('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
|
||||
fieldid, removed, added)
|
||||
VALUES (?,?,?,?,?,?,?)',
|
||||
undef, ($bug->bug_id, $obsolete_attachment->id, $user->id,
|
||||
$timestamp, $fieldid, 0, 1));
|
||||
}
|
||||
|
||||
my $attachment = Bugzilla::Attachment->get($attachid);
|
||||
|
||||
# 1. Add flags, if any. To avoid dying if something goes wrong
|
||||
# while processing flags, we will eval() flag validation.
|
||||
# This requires errors to die().
|
||||
# XXX: this can go away as soon as flag validation is able to
|
||||
# fail without dying.
|
||||
#
|
||||
# 2. Flag::validate() should not detect any reference to existing flags
|
||||
# when creating a new attachment. Setting the third param to -1 will
|
||||
# force this function to check this point.
|
||||
my $error_mode_cache = Bugzilla->error_mode;
|
||||
Bugzilla->error_mode(ERROR_MODE_DIE);
|
||||
eval {
|
||||
Bugzilla::Flag::validate($bug->bug_id, -1, SKIP_REQUESTEE_ON_ERROR);
|
||||
Bugzilla::Flag->process($bug, $attachment, $timestamp, $hr_vars);
|
||||
};
|
||||
Bugzilla->error_mode($error_mode_cache);
|
||||
if ($@) {
|
||||
$hr_vars->{'message'} = 'flag_creation_failed';
|
||||
$hr_vars->{'flag_creation_error'} = $@;
|
||||
}
|
||||
|
||||
# Return the new attachment object.
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: removes an attachment from the DB.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
$dbh->do('DELETE FROM flags WHERE attach_id = ?', undef, $self->id);
|
||||
$dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $self->id);
|
||||
$dbh->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isurl = ?, isobsolete = ?
|
||||
WHERE attach_id = ?', undef, ('text/plain', 0, 0, 1, $self->id));
|
||||
$dbh->bz_commit_transaction();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,295 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): John Keiser <john@johnkeiser.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Attachment::PatchReader;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Attachment;
|
||||
use Bugzilla::Util;
|
||||
|
||||
sub process_diff {
|
||||
my ($attachment, $format, $context) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $lc = Bugzilla->localconfig;
|
||||
my $vars = {};
|
||||
|
||||
my ($reader, $last_reader) = setup_patch_readers(undef, $context);
|
||||
|
||||
if ($format eq 'raw') {
|
||||
require PatchReader::DiffPrinter::raw;
|
||||
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
|
||||
# Actually print out the patch.
|
||||
print $cgi->header(-type => 'text/plain',
|
||||
-expires => '+3M');
|
||||
disable_utf8();
|
||||
$reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
|
||||
}
|
||||
else {
|
||||
my @other_patches = ();
|
||||
if ($lc->{interdiffbin} && $lc->{diffpath}) {
|
||||
# Get the list of attachments that the user can view in this bug.
|
||||
my @attachments =
|
||||
@{Bugzilla::Attachment->get_attachments_by_bug($attachment->bug_id)};
|
||||
# Extract patches only.
|
||||
@attachments = grep {$_->ispatch == 1} @attachments;
|
||||
# We want them sorted from newer to older.
|
||||
@attachments = sort { $b->id <=> $a->id } @attachments;
|
||||
|
||||
# Ignore the current patch, but select the one right before it
|
||||
# chronologically.
|
||||
my $select_next_patch = 0;
|
||||
foreach my $attach (@attachments) {
|
||||
if ($attach->id == $attachment->id) {
|
||||
$select_next_patch = 1;
|
||||
}
|
||||
else {
|
||||
push(@other_patches, { 'id' => $attach->id,
|
||||
'desc' => $attach->description,
|
||||
'selected' => $select_next_patch });
|
||||
$select_next_patch = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$vars->{'bugid'} = $attachment->bug_id;
|
||||
$vars->{'attachid'} = $attachment->id;
|
||||
$vars->{'description'} = $attachment->description;
|
||||
$vars->{'other_patches'} = \@other_patches;
|
||||
|
||||
setup_template_patch_reader($last_reader, $format, $context, $vars);
|
||||
# The patch is going to be displayed in a HTML page and if the utf8
|
||||
# param is enabled, we have to encode attachment data as utf8.
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
$attachment->data; # Populate ->{data}
|
||||
utf8::decode($attachment->{data});
|
||||
}
|
||||
$reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
|
||||
}
|
||||
}
|
||||
|
||||
sub process_interdiff {
|
||||
my ($old_attachment, $new_attachment, $format, $context) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $lc = Bugzilla->localconfig;
|
||||
my $vars = {};
|
||||
|
||||
# Encode attachment data as utf8 if it's going to be displayed in a HTML
|
||||
# page using the UTF-8 encoding.
|
||||
if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
|
||||
$old_attachment->data; # Populate ->{data}
|
||||
utf8::decode($old_attachment->{data});
|
||||
$new_attachment->data; # Populate ->{data}
|
||||
utf8::decode($new_attachment->{data});
|
||||
}
|
||||
|
||||
# Get old patch data.
|
||||
my ($old_filename, $old_file_list) = get_unified_diff($old_attachment, $format);
|
||||
# Get new patch data.
|
||||
my ($new_filename, $new_file_list) = get_unified_diff($new_attachment, $format);
|
||||
|
||||
my $warning = warn_if_interdiff_might_fail($old_file_list, $new_file_list);
|
||||
|
||||
# Send through interdiff, send output directly to template.
|
||||
# Must hack path so that interdiff will work.
|
||||
$ENV{'PATH'} = $lc->{diffpath};
|
||||
open my $interdiff_fh, "$lc->{interdiffbin} $old_filename $new_filename|";
|
||||
binmode $interdiff_fh;
|
||||
my ($reader, $last_reader) = setup_patch_readers("", $context);
|
||||
|
||||
if ($format eq 'raw') {
|
||||
require PatchReader::DiffPrinter::raw;
|
||||
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
|
||||
# Actually print out the patch.
|
||||
print $cgi->header(-type => 'text/plain',
|
||||
-expires => '+3M');
|
||||
disable_utf8();
|
||||
}
|
||||
else {
|
||||
# In case the HTML page is displayed with the UTF-8 encoding.
|
||||
binmode $interdiff_fh, ':utf8' if Bugzilla->params->{'utf8'};
|
||||
|
||||
$vars->{'warning'} = $warning if $warning;
|
||||
$vars->{'bugid'} = $new_attachment->bug_id;
|
||||
$vars->{'oldid'} = $old_attachment->id;
|
||||
$vars->{'old_desc'} = $old_attachment->description;
|
||||
$vars->{'newid'} = $new_attachment->id;
|
||||
$vars->{'new_desc'} = $new_attachment->description;
|
||||
|
||||
setup_template_patch_reader($last_reader, $format, $context, $vars);
|
||||
}
|
||||
$reader->iterate_fh($interdiff_fh, 'interdiff #' . $old_attachment->id .
|
||||
' #' . $new_attachment->id);
|
||||
close $interdiff_fh;
|
||||
$ENV{'PATH'} = '';
|
||||
|
||||
# Delete temporary files.
|
||||
unlink($old_filename) or warn "Could not unlink $old_filename: $!";
|
||||
unlink($new_filename) or warn "Could not unlink $new_filename: $!";
|
||||
}
|
||||
|
||||
######################
|
||||
# Internal routines
|
||||
######################
|
||||
|
||||
sub get_unified_diff {
|
||||
my ($attachment, $format) = @_;
|
||||
|
||||
# Bring in the modules we need.
|
||||
require PatchReader::Raw;
|
||||
require PatchReader::FixPatchRoot;
|
||||
require PatchReader::DiffPrinter::raw;
|
||||
require PatchReader::PatchInfoGrabber;
|
||||
require File::Temp;
|
||||
|
||||
$attachment->ispatch
|
||||
|| ThrowCodeError('must_be_patch', { 'attach_id' => $attachment->id });
|
||||
|
||||
# Reads in the patch, converting to unified diff in a temp file.
|
||||
my $reader = new PatchReader::Raw;
|
||||
my $last_reader = $reader;
|
||||
|
||||
# Fixes patch root (makes canonical if possible).
|
||||
if (Bugzilla->params->{'cvsroot'}) {
|
||||
my $fix_patch_root =
|
||||
new PatchReader::FixPatchRoot(Bugzilla->params->{'cvsroot'});
|
||||
$last_reader->sends_data_to($fix_patch_root);
|
||||
$last_reader = $fix_patch_root;
|
||||
}
|
||||
|
||||
# Grabs the patch file info.
|
||||
my $patch_info_grabber = new PatchReader::PatchInfoGrabber();
|
||||
$last_reader->sends_data_to($patch_info_grabber);
|
||||
$last_reader = $patch_info_grabber;
|
||||
|
||||
# Prints out to temporary file.
|
||||
my ($fh, $filename) = File::Temp::tempfile();
|
||||
if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
|
||||
# The HTML page will be displayed with the UTF-8 encoding.
|
||||
binmode $fh, ':utf8';
|
||||
}
|
||||
my $raw_printer = new PatchReader::DiffPrinter::raw($fh);
|
||||
$last_reader->sends_data_to($raw_printer);
|
||||
$last_reader = $raw_printer;
|
||||
|
||||
# Iterate!
|
||||
$reader->iterate_string($attachment->id, $attachment->data);
|
||||
|
||||
return ($filename, $patch_info_grabber->patch_info()->{files});
|
||||
}
|
||||
|
||||
sub warn_if_interdiff_might_fail {
|
||||
my ($old_file_list, $new_file_list) = @_;
|
||||
|
||||
# Verify that the list of files diffed is the same.
|
||||
my @old_files = sort keys %{$old_file_list};
|
||||
my @new_files = sort keys %{$new_file_list};
|
||||
if (@old_files != @new_files
|
||||
|| join(' ', @old_files) ne join(' ', @new_files))
|
||||
{
|
||||
return 'interdiff1';
|
||||
}
|
||||
|
||||
# Verify that the revisions in the files are the same.
|
||||
foreach my $file (keys %{$old_file_list}) {
|
||||
if ($old_file_list->{$file}{old_revision} ne
|
||||
$new_file_list->{$file}{old_revision})
|
||||
{
|
||||
return 'interdiff2';
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub setup_patch_readers {
|
||||
my ($diff_root, $context) = @_;
|
||||
|
||||
# Parameters:
|
||||
# format=raw|html
|
||||
# context=patch|file|0-n
|
||||
# collapsed=0|1
|
||||
# headers=0|1
|
||||
|
||||
# Define the patch readers.
|
||||
# The reader that reads the patch in (whatever its format).
|
||||
require PatchReader::Raw;
|
||||
my $reader = new PatchReader::Raw;
|
||||
my $last_reader = $reader;
|
||||
# Fix the patch root if we have a cvs root.
|
||||
if (Bugzilla->params->{'cvsroot'}) {
|
||||
require PatchReader::FixPatchRoot;
|
||||
$last_reader->sends_data_to(new PatchReader::FixPatchRoot(Bugzilla->params->{'cvsroot'}));
|
||||
$last_reader->sends_data_to->diff_root($diff_root) if defined($diff_root);
|
||||
$last_reader = $last_reader->sends_data_to;
|
||||
}
|
||||
|
||||
# Add in cvs context if we have the necessary info to do it
|
||||
if ($context ne 'patch' && Bugzilla->localconfig->{cvsbin}
|
||||
&& Bugzilla->params->{'cvsroot_get'})
|
||||
{
|
||||
require PatchReader::AddCVSContext;
|
||||
# We need to set $cvsbin as global, because PatchReader::CVSClient
|
||||
# needs it in order to find 'cvs'.
|
||||
$main::cvsbin = Bugzilla->localconfig->{cvsbin};
|
||||
$last_reader->sends_data_to(
|
||||
new PatchReader::AddCVSContext($context, Bugzilla->params->{'cvsroot_get'}));
|
||||
$last_reader = $last_reader->sends_data_to;
|
||||
}
|
||||
|
||||
return ($reader, $last_reader);
|
||||
}
|
||||
|
||||
sub setup_template_patch_reader {
|
||||
my ($last_reader, $format, $context, $vars) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
require PatchReader::DiffPrinter::template;
|
||||
|
||||
# Define the vars for templates.
|
||||
if (defined $cgi->param('headers')) {
|
||||
$vars->{'headers'} = $cgi->param('headers');
|
||||
}
|
||||
else {
|
||||
$vars->{'headers'} = 1;
|
||||
}
|
||||
|
||||
$vars->{'collapsed'} = $cgi->param('collapsed');
|
||||
$vars->{'context'} = $context;
|
||||
$vars->{'do_context'} = Bugzilla->localconfig->{cvsbin}
|
||||
&& Bugzilla->params->{'cvsroot_get'} && !$vars->{'newid'};
|
||||
|
||||
# Print everything out.
|
||||
print $cgi->header(-type => 'text/html',
|
||||
-expires => '+3M');
|
||||
|
||||
$last_reader->sends_data_to(new PatchReader::DiffPrinter::template($template,
|
||||
"attachment/diff-header.$format.tmpl",
|
||||
"attachment/diff-file.$format.tmpl",
|
||||
"attachment/diff-footer.$format.tmpl",
|
||||
{ %{$vars},
|
||||
bonsai_url => Bugzilla->params->{'bonsai_url'},
|
||||
lxr_url => Bugzilla->params->{'lxr_url'},
|
||||
lxr_root => Bugzilla->params->{'lxr_root'},
|
||||
}));
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
@@ -1,466 +0,0 @@
|
||||
# -*- 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): Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth;
|
||||
|
||||
use strict;
|
||||
use fields qw(
|
||||
_info_getter
|
||||
_verifier
|
||||
_persister
|
||||
);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Auth::Login::Stack;
|
||||
use Bugzilla::Auth::Verify::Stack;
|
||||
use Bugzilla::Auth::Persist::Cookie;
|
||||
|
||||
sub new {
|
||||
my ($class, $params) = @_;
|
||||
my $self = fields::new($class);
|
||||
|
||||
$params ||= {};
|
||||
$params->{Login} ||= Bugzilla->params->{'user_info_class'} . ',Cookie';
|
||||
$params->{Verify} ||= Bugzilla->params->{'user_verify_class'};
|
||||
|
||||
$self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login});
|
||||
$self->{_verifier} = new Bugzilla::Auth::Verify::Stack($params->{Verify});
|
||||
# If we ever have any other login persistence methods besides cookies,
|
||||
# this could become more configurable.
|
||||
$self->{_persister} = new Bugzilla::Auth::Persist::Cookie();
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub login {
|
||||
my ($self, $type) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Get login info from the cookie, form, environment variables, etc.
|
||||
my $login_info = $self->{_info_getter}->get_login_info();
|
||||
|
||||
if ($login_info->{failure}) {
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
|
||||
# Now verify his username and password against the DB, LDAP, etc.
|
||||
if ($self->{_info_getter}->{successful}->requires_verification) {
|
||||
$login_info = $self->{_verifier}->check_credentials($login_info);
|
||||
if ($login_info->{failure}) {
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
$login_info =
|
||||
$self->{_verifier}->{successful}->create_or_update_user($login_info);
|
||||
}
|
||||
else {
|
||||
$login_info = $self->{_verifier}->create_or_update_user($login_info);
|
||||
}
|
||||
|
||||
if ($login_info->{failure}) {
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
|
||||
# Make sure the user isn't disabled.
|
||||
my $user = $login_info->{user};
|
||||
if ($user->disabledtext) {
|
||||
return $self->_handle_login_result({ failure => AUTH_DISABLED,
|
||||
user => $user }, $type);
|
||||
}
|
||||
$user->set_authorizer($self);
|
||||
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
|
||||
sub can_change_password {
|
||||
my ($self) = @_;
|
||||
my $verifier = $self->{_verifier}->{successful};
|
||||
$verifier ||= $self->{_verifier};
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
$getter = $self->{_info_getter}
|
||||
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
|
||||
return $verifier->can_change_password &&
|
||||
$getter->user_can_create_account;
|
||||
}
|
||||
|
||||
sub can_login {
|
||||
my ($self) = @_;
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
$getter = $self->{_info_getter}
|
||||
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
|
||||
return $getter->can_login;
|
||||
}
|
||||
|
||||
sub can_logout {
|
||||
my ($self) = @_;
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
# If there's no successful getter, we're not logged in, so of
|
||||
# course we can't log out!
|
||||
return 0 unless $getter;
|
||||
return $getter->can_logout;
|
||||
}
|
||||
|
||||
sub user_can_create_account {
|
||||
my ($self) = @_;
|
||||
my $verifier = $self->{_verifier}->{successful};
|
||||
$verifier ||= $self->{_verifier};
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
$getter = $self->{_info_getter}
|
||||
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
|
||||
return $verifier->user_can_create_account
|
||||
&& $getter->user_can_create_account;
|
||||
}
|
||||
|
||||
sub can_change_email {
|
||||
return $_[0]->user_can_create_account;
|
||||
}
|
||||
|
||||
sub _handle_login_result {
|
||||
my ($self, $result, $login_type) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $user = $result->{user};
|
||||
my $fail_code = $result->{failure};
|
||||
|
||||
if (!$fail_code) {
|
||||
if ($self->{_info_getter}->{successful}->requires_persistence) {
|
||||
$self->{_persister}->persist_login($user);
|
||||
}
|
||||
}
|
||||
elsif ($fail_code == AUTH_ERROR) {
|
||||
ThrowCodeError($result->{error}, $result->{details});
|
||||
}
|
||||
elsif ($fail_code == AUTH_NODATA) {
|
||||
if ($login_type == LOGIN_REQUIRED) {
|
||||
# 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.
|
||||
$dbh->do("DELETE FROM logincookies WHERE " .
|
||||
$dbh->sql_to_days('NOW()') . " - " .
|
||||
$dbh->sql_to_days('lastused') . " > 30");
|
||||
$self->{_info_getter}->fail_nodata($self);
|
||||
}
|
||||
# Otherwise, we just return the "default" user.
|
||||
$user = Bugzilla->user;
|
||||
}
|
||||
# The username/password may be wrong
|
||||
# Don't let the user know whether the username exists or whether
|
||||
# the password was just wrong. (This makes it harder for a cracker
|
||||
# to find account names by brute force)
|
||||
elsif (($fail_code == AUTH_LOGINFAILED) || ($fail_code == AUTH_NO_SUCH_USER)) {
|
||||
ThrowUserError("invalid_username_or_password");
|
||||
}
|
||||
# The account may be disabled
|
||||
elsif ($fail_code == AUTH_DISABLED) {
|
||||
$self->{_persister}->logout();
|
||||
# XXX This is NOT a good way to do this, architecturally.
|
||||
$self->{_persister}->clear_browser_cookies();
|
||||
# and throw a user error
|
||||
ThrowUserError("account_disabled",
|
||||
{'disabled_reason' => $result->{user}->disabledtext});
|
||||
}
|
||||
# If we get here, then we've run out of options, which shouldn't happen.
|
||||
else {
|
||||
ThrowCodeError("authres_unhandled", { value => $fail_code });
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth - An object that authenticates the login credentials for
|
||||
a user.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Handles authentication for Bugzilla users.
|
||||
|
||||
Authentication from Bugzilla involves two sets of modules. One set is
|
||||
used to obtain the username/password (from CGI, email, etc), and the
|
||||
other set uses this data to authenticate against the datasource
|
||||
(the Bugzilla DB, LDAP, PAM, etc.).
|
||||
|
||||
Modules for obtaining the username/password are subclasses of
|
||||
L<Bugzilla::Auth::Login>, and modules for authenticating are subclasses
|
||||
of L<Bugzilla::Auth::Verify>.
|
||||
|
||||
=head1 AUTHENTICATION ERROR CODES
|
||||
|
||||
Whenever a method in the C<Bugzilla::Auth> family fails in some way,
|
||||
it will return a hashref containing at least a single key called C<failure>.
|
||||
C<failure> will point to an integer error code, and depending on the error
|
||||
code the hashref may contain more data.
|
||||
|
||||
The error codes are explained here below.
|
||||
|
||||
=head2 C<AUTH_NODATA>
|
||||
|
||||
Insufficient login data was provided by the user. This may happen in several
|
||||
cases, such as cookie authentication when the cookie is not present.
|
||||
|
||||
=head2 C<AUTH_ERROR>
|
||||
|
||||
An error occurred when trying to use the login mechanism.
|
||||
|
||||
The hashref will also contain an C<error> element, which is the name
|
||||
of an error from C<template/en/default/global/code-error.html> --
|
||||
the same type of error that would be thrown by
|
||||
L<Bugzilla::Error::ThrowCodeError>.
|
||||
|
||||
The hashref *may* contain an element called C<details>, which is a hashref
|
||||
that should be passed to L<Bugzilla::Error::ThrowCodeError> as the
|
||||
various fields to be used in the error message.
|
||||
|
||||
=head2 C<AUTH_LOGINFAILED>
|
||||
|
||||
An incorrect username or password was given.
|
||||
|
||||
=head2 C<AUTH_NO_SUCH_USER>
|
||||
|
||||
This is an optional more-specific version of C<AUTH_LOGINFAILED>.
|
||||
Modules should throw this error when they discover that the
|
||||
requested user account actually does not exist, according to them.
|
||||
|
||||
That is, for example, L<Bugzilla::Auth::Verify::LDAP> would throw
|
||||
this if the user didn't exist in LDAP.
|
||||
|
||||
The difference between C<AUTH_NO_SUCH_USER> and C<AUTH_LOGINFAILED>
|
||||
should never be communicated to the user, for security reasons.
|
||||
|
||||
=head2 C<AUTH_DISABLED>
|
||||
|
||||
The user successfully logged in, but their account has been disabled.
|
||||
Usually this is throw only by C<Bugzilla::Auth::login>.
|
||||
|
||||
=head1 LOGIN TYPES
|
||||
|
||||
The C<login> function (below) can do different types of login, depending
|
||||
on what constant you pass into it:
|
||||
|
||||
=head2 C<LOGIN_OPTIONAL>
|
||||
|
||||
A login is never required to access this data. Attempting to login is
|
||||
still useful, because this allows the page to be personalised. Note that
|
||||
an incorrect login will still trigger an error, even though the lack of
|
||||
a login will be OK.
|
||||
|
||||
=head2 C<LOGIN_NORMAL>
|
||||
|
||||
A login may or may not be required, depending on the setting of the
|
||||
I<requirelogin> parameter. This is the default if you don't specify a
|
||||
type.
|
||||
|
||||
=head2 C<LOGIN_REQUIRED>
|
||||
|
||||
A login is always required to access this data.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
These are methods that can be called on a C<Bugzilla::Auth> object
|
||||
itself.
|
||||
|
||||
=head2 Login
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<login($type)>
|
||||
|
||||
Description: Logs a user in. For more details on how this works
|
||||
internally, see the section entitled "STRUCTURE."
|
||||
Params: $type - One of the Login Types from above.
|
||||
Returns: An authenticated C<Bugzilla::User>. Or, if the type was
|
||||
not C<LOGIN_REQUIRED>, then we return an
|
||||
empty C<Bugzilla::User> if no login data was passed in.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Info Methods
|
||||
|
||||
These are methods that give information about the Bugzilla::Auth object.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<can_change_password>
|
||||
|
||||
Description: Tells you whether or not the current login system allows
|
||||
changing passwords.
|
||||
Params: None
|
||||
Returns: C<true> if users and administrators should be allowed to
|
||||
change passwords, C<false> otherwise.
|
||||
|
||||
=item C<can_login>
|
||||
|
||||
Description: Tells you whether or not the current login system allows
|
||||
users to log in through the web interface.
|
||||
Params: None
|
||||
Returns: C<true> if users can log in through the web interface,
|
||||
C<false> otherwise.
|
||||
|
||||
=item C<can_logout>
|
||||
|
||||
Description: Tells you whether or not the current login system allows
|
||||
users to log themselves out.
|
||||
Params: None
|
||||
Returns: C<true> if users can log themselves out, C<false> otherwise.
|
||||
If a user isn't logged in, we always return C<false>.
|
||||
|
||||
=item C<user_can_create_account>
|
||||
|
||||
Description: Tells you whether or not users are allowed to manually create
|
||||
their own accounts, based on the current login system in use.
|
||||
Note that this doesn't check the C<createemailregexp>
|
||||
parameter--you have to do that by yourself in your code.
|
||||
Params: None
|
||||
Returns: C<true> if users are allowed to create new Bugzilla accounts,
|
||||
C<false> otherwise.
|
||||
|
||||
=item C<can_change_email>
|
||||
|
||||
Description: Whether or not the current login system allows users to
|
||||
change their own email address.
|
||||
Params: None
|
||||
Returns: C<true> if users can change their own email address,
|
||||
C<false> otherwise.
|
||||
|
||||
=back
|
||||
|
||||
=head1 STRUCTURE
|
||||
|
||||
This section is mostly interesting to developers who want to implement
|
||||
a new authentication type. It describes the general structure of the
|
||||
Bugzilla::Auth family, and how the C<login> function works.
|
||||
|
||||
A C<Bugzilla::Auth> object is essentially a collection of a few other
|
||||
objects: the "Info Getter," the "Verifier," and the "Persistence
|
||||
Mechanism."
|
||||
|
||||
They are used inside the C<login> function in the following order:
|
||||
|
||||
=head2 The Info Getter
|
||||
|
||||
This is a C<Bugzilla::Auth::Login> object. Basically, it gets the
|
||||
username and password from the user, somehow. Or, it just gets enough
|
||||
information to uniquely identify a user, and passes that on down the line.
|
||||
(For example, a C<user_id> is enough to uniquely identify a user,
|
||||
even without a username and password.)
|
||||
|
||||
Some Info Getters don't require any verification. For example, if we got
|
||||
the C<user_id> from a Cookie, we don't need to check the username and
|
||||
password.
|
||||
|
||||
If an Info Getter returns only a C<user_id> and no username/password,
|
||||
then it MUST NOT require verification. If an Info Getter requires
|
||||
verfication, then it MUST return at least a C<username>.
|
||||
|
||||
=head2 The Verifier
|
||||
|
||||
This verifies that the username and password are valid.
|
||||
|
||||
It's possible that some methods of verification don't require a password.
|
||||
|
||||
=head2 The Persistence Mechanism
|
||||
|
||||
This makes it so that the user doesn't have to log in on every page.
|
||||
Normally this object just sends a cookie to the user's web browser,
|
||||
as that's the most common method of "login persistence."
|
||||
|
||||
=head2 Other Things We Do
|
||||
|
||||
After we verify the username and password, sometimes we automatically
|
||||
create an account in the Bugzilla database, for certain authentication
|
||||
types. We use the "Account Source" to get data about the user, and
|
||||
create them in the database. (Or, if their data has changed since the
|
||||
last time they logged in, their data gets updated.)
|
||||
|
||||
=head2 The C<$login_data> Hash
|
||||
|
||||
All of the C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify>
|
||||
methods take an argument called C<$login_data>. This is basically
|
||||
a hash that becomes more and more populated as we go through the
|
||||
C<login> function.
|
||||
|
||||
All C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods
|
||||
also *return* the C<$login_data> structure, when they succeed. They
|
||||
may have added new data to it.
|
||||
|
||||
For all C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods,
|
||||
the rule is "you must return the same hashref you were passed in." You can
|
||||
modify the hashref all you want, but you can't create a new one. The only
|
||||
time you can return a new one is if you're returning some error code
|
||||
instead of the C<$login_data> structure.
|
||||
|
||||
Each C<Bugzilla::Auth::Login> or C<Bugzilla::Auth::Verify> method
|
||||
explains in its documentation which C<$login_data> elements are
|
||||
required by it, and which are set by it.
|
||||
|
||||
Here are all of the elements that *may* be in C<$login_data>:
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<user_id>
|
||||
|
||||
A Bugzilla C<user_id> that uniquely identifies a user.
|
||||
|
||||
=item C<username>
|
||||
|
||||
The username that was provided by the user.
|
||||
|
||||
=item C<bz_username>
|
||||
|
||||
The username of this user inside of Bugzilla. Sometimes this differs from
|
||||
C<username>.
|
||||
|
||||
=item C<password>
|
||||
|
||||
The password provided by the user.
|
||||
|
||||
=item C<realname>
|
||||
|
||||
The real name of the user.
|
||||
|
||||
=item C<extern_id>
|
||||
|
||||
Some string that uniquely identifies the user in an external account
|
||||
source. If this C<extern_id> already exists in the database with
|
||||
a different username, the username will be *changed* to be the
|
||||
username specified in this C<$login_data>.
|
||||
|
||||
That is, let's my extern_id is C<mkanat>. I already have an account
|
||||
in Bugzilla with the username of C<mkanat@foo.com>. But this time,
|
||||
when I log in, I have an extern_id of C<mkanat> and a C<username>
|
||||
of C<mkanat@bar.org>. So now, Bugzilla will automatically change my
|
||||
username to C<mkanat@bar.org> instead of C<mkanat@foo.com>.
|
||||
|
||||
=item C<user>
|
||||
|
||||
A L<Bugzilla::User> object representing the authenticated user.
|
||||
Note that C<Bugzilla::Auth::login> may modify this object at various points.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login;
|
||||
|
||||
use strict;
|
||||
use fields qw();
|
||||
|
||||
# Determines whether or not a user can logout. It's really a subroutine,
|
||||
# but we implement it here as a constant. Override it in subclasses if
|
||||
# that particular type of login method cannot log out.
|
||||
use constant can_logout => 1;
|
||||
use constant can_login => 1;
|
||||
use constant requires_persistence => 1;
|
||||
use constant requires_verification => 1;
|
||||
use constant user_can_create_account => 0;
|
||||
|
||||
sub new {
|
||||
my ($class) = @_;
|
||||
my $self = fields::new($class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth::Login - Gets username/password data from the user.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Auth::Login is used to get information that uniquely identifies
|
||||
a user and allows us to authorize their Bugzilla access.
|
||||
|
||||
It is mostly an abstract class, requiring subclasses to implement
|
||||
most methods.
|
||||
|
||||
Note that callers outside of the C<Bugzilla::Auth> package should never
|
||||
create this object directly. Just create a C<Bugzilla::Auth> object
|
||||
and call C<login> on it.
|
||||
|
||||
=head1 LOGIN METHODS
|
||||
|
||||
These are methods that have to do with getting the actual login data
|
||||
from the user or handling a login somehow.
|
||||
|
||||
These methods are abstract -- they MUST be implemented by a subclass.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<get_login_info()>
|
||||
|
||||
Description: Gets a username/password from the user, or some other
|
||||
information that uniquely identifies them.
|
||||
Params: None
|
||||
Returns: A C<$login_data> hashref. (See L<Bugzilla::Auth> for details.)
|
||||
The hashref MUST contain: C<user_id> *or* C<username>
|
||||
If this is a login method that requires verification,
|
||||
the hashref MUST contain C<password>.
|
||||
The hashref MAY contain C<realname> and C<extern_id>.
|
||||
|
||||
=item C<fail_nodata()>
|
||||
|
||||
Description: This function is called when Bugzilla doesn't get
|
||||
a username/password and the login type is C<LOGIN_REQUIRED>
|
||||
(See L<Bugzilla::Auth> for a description of C<LOGIN_REQUIRED>).
|
||||
That is, this handles C<AUTH_NODATA> in that situation.
|
||||
|
||||
This function MUST stop CGI execution when it is complete.
|
||||
That is, it must call C<exit> or C<ThrowUserError> or some
|
||||
such thing.
|
||||
Params: None
|
||||
Returns: Never Returns.
|
||||
|
||||
=back
|
||||
|
||||
=head1 INFO METHODS
|
||||
|
||||
These are methods that describe the capabilities of this
|
||||
C<Bugzilla::Auth::Login> object. These are all no-parameter
|
||||
methods that return either C<true> or C<false>.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<can_logout>
|
||||
|
||||
Whether or not users can log out if they logged in using this
|
||||
object. Defaults to C<true>.
|
||||
|
||||
=item C<can_login>
|
||||
|
||||
Whether or not users can log in through the web interface using
|
||||
this object. Defaults to C<true>.
|
||||
|
||||
=item C<requires_persistence>
|
||||
|
||||
Whether or not we should send the user a cookie if they logged in with
|
||||
this method. Defaults to C<true>.
|
||||
|
||||
=item C<requires_verification>
|
||||
|
||||
Whether or not we should check the username/password that we
|
||||
got from this login method. Defaults to C<true>.
|
||||
|
||||
=item C<user_can_create_account>
|
||||
|
||||
Whether or not users can create accounts, if this login method is
|
||||
currently being used by the system. Defaults to C<false>.
|
||||
|
||||
=back
|
||||
@@ -1,86 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login::CGI;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Login);
|
||||
use constant user_can_create_account => 1;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::WebService::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
sub get_login_info {
|
||||
my ($self) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
my $username = trim($cgi->param("Bugzilla_login"));
|
||||
my $password = $cgi->param("Bugzilla_password");
|
||||
|
||||
$cgi->delete('Bugzilla_login', 'Bugzilla_password');
|
||||
|
||||
if (!defined $username || !defined $password) {
|
||||
return { failure => AUTH_NODATA };
|
||||
}
|
||||
|
||||
return { username => $username, password => $password };
|
||||
}
|
||||
|
||||
sub fail_nodata {
|
||||
my ($self) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
if (Bugzilla->error_mode == Bugzilla::Constants::ERROR_MODE_DIE_SOAP_FAULT) {
|
||||
die SOAP::Fault
|
||||
->faultcode(ERROR_AUTH_NODATA)
|
||||
->faultstring('Login Required');
|
||||
}
|
||||
|
||||
# If system is not configured to never require SSL connections
|
||||
# we want to always redirect to SSL since passing usernames and
|
||||
# passwords over an unprotected connection is a bad idea. If we
|
||||
# get here then a login form will be provided to the user so we
|
||||
# want this to be protected if possible.
|
||||
if ($cgi->protocol ne 'https' && Bugzilla->params->{'sslbase'} ne ''
|
||||
&& Bugzilla->params->{'ssl'} ne 'never')
|
||||
{
|
||||
$cgi->require_https(Bugzilla->params->{'sslbase'});
|
||||
}
|
||||
|
||||
print $cgi->header();
|
||||
$template->process("account/auth/login.html.tmpl",
|
||||
{ 'target' => $cgi->url(-relative=>1) })
|
||||
|| ThrowTemplateError($template->error());
|
||||
exit;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,96 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login::Cookie;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Login);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use List::Util qw(first);
|
||||
|
||||
use constant requires_persistence => 0;
|
||||
use constant requires_verification => 0;
|
||||
use constant can_login => 0;
|
||||
|
||||
# Note that Cookie never consults the Verifier, it always assumes
|
||||
# it has a valid DB account or it fails.
|
||||
sub get_login_info {
|
||||
my ($self) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $ip_addr = $cgi->remote_addr();
|
||||
my $net_addr = get_netaddr($ip_addr);
|
||||
my $login_cookie = $cgi->cookie("Bugzilla_logincookie");
|
||||
my $user_id = $cgi->cookie("Bugzilla_login");
|
||||
|
||||
# If cookies cannot be found, this could mean that they haven't
|
||||
# been made available yet. In this case, look at Bugzilla_cookie_list.
|
||||
unless ($login_cookie) {
|
||||
my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
|
||||
@{$cgi->{'Bugzilla_cookie_list'}};
|
||||
$login_cookie = $cookie->value if $cookie;
|
||||
}
|
||||
unless ($user_id) {
|
||||
my $cookie = first {$_->name eq 'Bugzilla_login'}
|
||||
@{$cgi->{'Bugzilla_cookie_list'}};
|
||||
$user_id = $cookie->value if $cookie;
|
||||
}
|
||||
|
||||
if ($login_cookie && $user_id) {
|
||||
# Anything goes for these params - they're just strings which
|
||||
# we're going to verify against the db
|
||||
trick_taint($ip_addr);
|
||||
trick_taint($login_cookie);
|
||||
detaint_natural($user_id);
|
||||
|
||||
my $query = "SELECT userid
|
||||
FROM logincookies
|
||||
WHERE logincookies.cookie = ?
|
||||
AND logincookies.userid = ?
|
||||
AND (logincookies.ipaddr = ?";
|
||||
|
||||
# If we have a network block that's allowed to use this cookie,
|
||||
# as opposed to just a single IP.
|
||||
my @params = ($login_cookie, $user_id, $ip_addr);
|
||||
if (defined $net_addr) {
|
||||
trick_taint($net_addr);
|
||||
$query .= " OR logincookies.ipaddr = ?";
|
||||
push(@params, $net_addr);
|
||||
}
|
||||
$query .= ")";
|
||||
|
||||
# If the cookie is valid, return a valid username.
|
||||
if ($dbh->selectrow_array($query, undef, @params)) {
|
||||
# If we logged in successfully, then update the lastused
|
||||
# time on the login cookie
|
||||
$dbh->do("UPDATE logincookies SET lastused = NOW()
|
||||
WHERE cookie = ?", undef, $login_cookie);
|
||||
return { user_id => $user_id };
|
||||
}
|
||||
}
|
||||
|
||||
# Either the he cookie is invalid, or we got no cookie. We don't want
|
||||
# to ever return AUTH_LOGINFAILED, because we don't want Bugzilla to
|
||||
# actually throw an error when it gets a bad cookie. It should just
|
||||
# look like there was no cookie to begin with.
|
||||
return { failure => AUTH_NODATA };
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,52 +0,0 @@
|
||||
# -*- 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): Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login::Env;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Login);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use constant can_logout => 0;
|
||||
use constant can_login => 0;
|
||||
use constant requires_verification => 0;
|
||||
|
||||
sub get_login_info {
|
||||
my ($self) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $env_id = $ENV{Bugzilla->params->{"auth_env_id"}} || '';
|
||||
my $env_email = $ENV{Bugzilla->params->{"auth_env_email"}} || '';
|
||||
my $env_realname = $ENV{Bugzilla->params->{"auth_env_realname"}} || '';
|
||||
|
||||
return { failure => AUTH_NODATA } if !$env_email;
|
||||
|
||||
return { username => $env_email, extern_id => $env_id,
|
||||
realname => $env_realname };
|
||||
}
|
||||
|
||||
sub fail_nodata {
|
||||
ThrowCodeError('env_no_email');
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,87 +0,0 @@
|
||||
# -*- 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): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login::Stack;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Login);
|
||||
use fields qw(
|
||||
_stack
|
||||
successful
|
||||
);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
my $list = shift;
|
||||
$self->{_stack} = [];
|
||||
foreach my $login_method (split(',', $list)) {
|
||||
require "Bugzilla/Auth/Login/${login_method}.pm";
|
||||
push(@{$self->{_stack}},
|
||||
"Bugzilla::Auth::Login::$login_method"->new(@_));
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_login_info {
|
||||
my $self = shift;
|
||||
my $result;
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
$result = $object->get_login_info(@_);
|
||||
$self->{successful} = $object;
|
||||
last if !$result->{failure};
|
||||
# So that if none of them succeed, it's undef.
|
||||
$self->{successful} = undef;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub fail_nodata {
|
||||
my $self = shift;
|
||||
# We fail from the bottom of the stack.
|
||||
my @reverse_stack = reverse @{$self->{_stack}};
|
||||
foreach my $object (@reverse_stack) {
|
||||
# We pick the first object that actually has the method
|
||||
# implemented.
|
||||
if ($object->can('fail_nodata')) {
|
||||
$object->fail_nodata(@_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub can_login {
|
||||
my ($self) = @_;
|
||||
# We return true if any method can log in.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->can_login;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub user_can_create_account {
|
||||
my ($self) = @_;
|
||||
# We return true if any method allows users to create accounts.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->user_can_create_account;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,157 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Persist::Cookie;
|
||||
use strict;
|
||||
use fields qw();
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Token;
|
||||
|
||||
use List::Util qw(first);
|
||||
|
||||
sub new {
|
||||
my ($class) = @_;
|
||||
my $self = fields::new($class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub persist_login {
|
||||
my ($self, $user) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
my $ip_addr = $cgi->remote_addr;
|
||||
unless ($cgi->param('Bugzilla_restrictlogin') ||
|
||||
Bugzilla->params->{'loginnetmask'} == 32)
|
||||
{
|
||||
$ip_addr = get_netaddr($ip_addr);
|
||||
}
|
||||
|
||||
# The IP address is valid, at least for comparing with itself in a
|
||||
# subsequent login
|
||||
trick_taint($ip_addr);
|
||||
|
||||
my $login_cookie =
|
||||
Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
|
||||
|
||||
$dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused)
|
||||
VALUES (?, ?, ?, NOW())",
|
||||
undef, $login_cookie, $user->id, $ip_addr);
|
||||
|
||||
# Prevent JavaScript from accessing login cookies.
|
||||
my %cookieargs = ('-httponly' => 1);
|
||||
|
||||
# Remember cookie only if admin has told so
|
||||
# or admin didn't forbid it and user told to remember.
|
||||
if ( Bugzilla->params->{'rememberlogin'} eq 'on' ||
|
||||
(Bugzilla->params->{'rememberlogin'} ne 'off' &&
|
||||
$cgi->param('Bugzilla_remember') &&
|
||||
$cgi->param('Bugzilla_remember') eq 'on') )
|
||||
{
|
||||
# Not a session cookie, so set an infinite expiry
|
||||
$cookieargs{'-expires'} = 'Fri, 01-Jan-2038 00:00:00 GMT';
|
||||
}
|
||||
if (Bugzilla->params->{'ssl'} ne 'never'
|
||||
&& Bugzilla->params->{'sslbase'} ne '')
|
||||
{
|
||||
# Bugzilla->login will automatically redirect to https://,
|
||||
# so it's safe to turn on the 'secure' bit.
|
||||
$cookieargs{'-secure'} = 1;
|
||||
}
|
||||
|
||||
$cgi->send_cookie(-name => 'Bugzilla_login',
|
||||
-value => $user->id,
|
||||
%cookieargs);
|
||||
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
|
||||
-value => $login_cookie,
|
||||
%cookieargs);
|
||||
}
|
||||
|
||||
sub logout {
|
||||
my ($self, $param) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$param = {} unless $param;
|
||||
my $user = $param->{user} || Bugzilla->user;
|
||||
my $type = $param->{type} || LOGOUT_ALL;
|
||||
|
||||
if ($type == LOGOUT_ALL) {
|
||||
$dbh->do("DELETE FROM logincookies WHERE userid = ?",
|
||||
undef, $user->id);
|
||||
return;
|
||||
}
|
||||
|
||||
# The LOGOUT_*_CURRENT options require the current login cookie.
|
||||
# If a new cookie has been issued during this run, that's the current one.
|
||||
# If not, it's the one we've received.
|
||||
my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
|
||||
@{$cgi->{'Bugzilla_cookie_list'}};
|
||||
my $login_cookie;
|
||||
if ($cookie) {
|
||||
$login_cookie = $cookie->value;
|
||||
}
|
||||
else {
|
||||
$login_cookie = $cgi->cookie("Bugzilla_logincookie");
|
||||
}
|
||||
trick_taint($login_cookie);
|
||||
|
||||
# These queries use both the cookie ID and the user ID as keys. Even
|
||||
# though we know the userid must match, we still check it in the SQL
|
||||
# as a sanity check, since there is no locking here, and if the user
|
||||
# logged out from two machines simultaneously, while someone else
|
||||
# logged in and got the same cookie, we could be logging the other
|
||||
# user out here. Yes, this is very very very unlikely, but why take
|
||||
# chances? - bbaetz
|
||||
if ($type == LOGOUT_KEEP_CURRENT) {
|
||||
$dbh->do("DELETE FROM logincookies WHERE cookie != ? AND userid = ?",
|
||||
undef, $login_cookie, $user->id);
|
||||
} elsif ($type == LOGOUT_CURRENT) {
|
||||
$dbh->do("DELETE FROM logincookies WHERE cookie = ? AND userid = ?",
|
||||
undef, $login_cookie, $user->id);
|
||||
} else {
|
||||
die("Invalid type $type supplied to logout()");
|
||||
}
|
||||
|
||||
if ($type != LOGOUT_KEEP_CURRENT) {
|
||||
clear_browser_cookies();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub clear_browser_cookies {
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$cgi->remove_cookie('Bugzilla_login');
|
||||
$cgi->remove_cookie('Bugzilla_logincookie');
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,235 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Verify;
|
||||
|
||||
use strict;
|
||||
use fields qw();
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use constant user_can_create_account => 1;
|
||||
|
||||
sub new {
|
||||
my ($class, $login_type) = @_;
|
||||
my $self = fields::new($class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub can_change_password {
|
||||
return $_[0]->can('change_password');
|
||||
}
|
||||
|
||||
sub create_or_update_user {
|
||||
my ($self, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $extern_id = $params->{extern_id};
|
||||
my $username = $params->{bz_username} || $params->{username};
|
||||
my $password = $params->{password} || '*';
|
||||
my $real_name = $params->{realname} || '';
|
||||
my $user_id = $params->{user_id};
|
||||
|
||||
# A passed-in user_id always overrides anything else, for determining
|
||||
# what account we should return.
|
||||
if (!$user_id) {
|
||||
my $username_user_id = login_to_id($username || '');
|
||||
my $extern_user_id;
|
||||
if ($extern_id) {
|
||||
trick_taint($extern_id);
|
||||
$extern_user_id = $dbh->selectrow_array('SELECT userid
|
||||
FROM profiles WHERE extern_id = ?', undef, $extern_id);
|
||||
}
|
||||
|
||||
# If we have both a valid extern_id and a valid username, and they are
|
||||
# not the same id, then we have a conflict.
|
||||
if ($username_user_id && $extern_user_id
|
||||
&& $username_user_id ne $extern_user_id)
|
||||
{
|
||||
my $extern_name = Bugzilla::User->new($extern_user_id)->login;
|
||||
return { failure => AUTH_ERROR, error => "extern_id_conflict",
|
||||
details => {extern_id => $extern_id,
|
||||
extern_user => $extern_name,
|
||||
username => $username} };
|
||||
}
|
||||
|
||||
# If we have a valid username, but no valid id,
|
||||
# then we have to create the user. This happens when we're
|
||||
# passed only a username, and that username doesn't exist already.
|
||||
if ($username && !$username_user_id && !$extern_user_id) {
|
||||
validate_email_syntax($username)
|
||||
|| return { failure => AUTH_ERROR,
|
||||
error => 'auth_invalid_email',
|
||||
details => {addr => $username} };
|
||||
# Usually we'd call validate_password, but external authentication
|
||||
# systems might follow different standards than ours. So in this
|
||||
# place here, we call trick_taint without checks.
|
||||
trick_taint($password);
|
||||
|
||||
# XXX Theoretically this could fail with an error, but the fix for
|
||||
# that is too involved to be done right now.
|
||||
my $user = Bugzilla::User->create({
|
||||
login_name => $username,
|
||||
cryptpassword => $password,
|
||||
realname => $real_name});
|
||||
$username_user_id = $user->id;
|
||||
}
|
||||
|
||||
# If we have a valid username id and an extern_id, but no valid
|
||||
# extern_user_id, then we have to set the user's extern_id.
|
||||
if ($extern_id && $username_user_id && !$extern_user_id) {
|
||||
$dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
|
||||
undef, $extern_id, $username_user_id);
|
||||
}
|
||||
|
||||
# Finally, at this point, one of these will give us a valid user id.
|
||||
$user_id = $extern_user_id || $username_user_id;
|
||||
}
|
||||
|
||||
# If we still don't have a valid user_id, then we weren't passed
|
||||
# enough information in $params, and we should die right here.
|
||||
ThrowCodeError('bad_arg', {argument => 'params', function =>
|
||||
'Bugzilla::Auth::Verify::create_or_update_user'})
|
||||
unless $user_id;
|
||||
|
||||
my $user = new Bugzilla::User($user_id);
|
||||
|
||||
# Now that we have a valid User, we need to see if any data has to be
|
||||
# updated.
|
||||
if ($username && lc($user->login) ne lc($username)) {
|
||||
validate_email_syntax($username)
|
||||
|| return { failure => AUTH_ERROR, error => 'auth_invalid_email',
|
||||
details => {addr => $username} };
|
||||
$user->set_login($username);
|
||||
}
|
||||
if ($real_name && $user->name ne $real_name) {
|
||||
# $real_name is more than likely tainted, but we only use it
|
||||
# in a placeholder and we never use it after this.
|
||||
trick_taint($real_name);
|
||||
$user->set_name($real_name);
|
||||
}
|
||||
$user->update();
|
||||
|
||||
return { user => $user };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth::Verify - An object that verifies usernames and passwords.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Auth::Verify provides the "Verifier" part of the Bugzilla
|
||||
login process. (For details, see the "STRUCTURE" section of
|
||||
L<Bugzilla::Auth>.)
|
||||
|
||||
It is mostly an abstract class, requiring subclasses to implement
|
||||
most methods.
|
||||
|
||||
Note that callers outside of the C<Bugzilla::Auth> package should never
|
||||
create this object directly. Just create a C<Bugzilla::Auth> object
|
||||
and call C<login> on it.
|
||||
|
||||
=head1 VERIFICATION METHODS
|
||||
|
||||
These are the methods that have to do with the actual verification.
|
||||
|
||||
Subclasses MUST implement these methods.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<check_credentials($login_data)>
|
||||
|
||||
Description: Checks whether or not a username is valid.
|
||||
Params: $login_data - A C<$login_data> hashref, as described in
|
||||
L<Bugzilla::Auth>.
|
||||
This C<$login_data> hashref MUST contain
|
||||
C<username>, and SHOULD also contain
|
||||
C<password>.
|
||||
Returns: A C<$login_data> hashref with C<bz_username> set. This
|
||||
method may also set C<realname>. It must avoid changing
|
||||
anything that is already set.
|
||||
|
||||
=back
|
||||
|
||||
=head1 MODIFICATION METHODS
|
||||
|
||||
These are methods that change data in the actual authentication backend.
|
||||
|
||||
These methods are optional, they do not have to be implemented by
|
||||
subclasses.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<create_or_update_user($login_data)>
|
||||
|
||||
Description: Automatically creates a user account in the database
|
||||
if it doesn't already exist, or updates the account
|
||||
data if C<$login_data> contains newer information.
|
||||
|
||||
Params: $login_data - A C<$login_data> hashref, as described in
|
||||
L<Bugzilla::Auth>.
|
||||
This C<$login_data> hashref MUST contain
|
||||
either C<user_id>, C<bz_username>, or
|
||||
C<username>. If both C<username> and C<bz_username>
|
||||
are specified, C<bz_username> is used as the
|
||||
login name of the user to create in the database.
|
||||
It MAY also contain C<extern_id>, in which
|
||||
case it still MUST contain C<bz_username> or
|
||||
C<username>.
|
||||
It MAY contain C<password> and C<realname>.
|
||||
|
||||
Returns: A hashref with one element, C<user>, which is a
|
||||
L<Bugzilla::User> object. May also return a login error
|
||||
as described in L<Bugzilla::Auth>.
|
||||
|
||||
Note: This method is not abstract, it is actually implemented
|
||||
and creates accounts in the Bugzilla database. Subclasses
|
||||
should probably all call the C<Bugzilla::Auth::Verify>
|
||||
version of this function at the end of their own
|
||||
C<create_or_update_user>.
|
||||
|
||||
=item C<change_password($user, $password)>
|
||||
|
||||
Description: Modifies the user's password in the authentication backend.
|
||||
Params: $user - A L<Bugzilla::User> object representing the user
|
||||
whose password we want to change.
|
||||
$password - The user's new password.
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=head1 INFO METHODS
|
||||
|
||||
These are methods that describe the capabilities of this object.
|
||||
These are all no-parameter methods that return either C<true> or
|
||||
C<false>.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<user_can_create_account>
|
||||
|
||||
Whether or not users can manually create accounts in this type of
|
||||
account source. Defaults to C<true>.
|
||||
|
||||
=back
|
||||
@@ -1,83 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
|
||||
package Bugzilla::Auth::Verify::DB;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Verify);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Token;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
|
||||
sub check_credentials {
|
||||
my ($self, $login_data) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $username = $login_data->{username};
|
||||
my $user_id = login_to_id($username);
|
||||
|
||||
return { failure => AUTH_NO_SUCH_USER } unless $user_id;
|
||||
|
||||
$login_data->{bz_username} = $username;
|
||||
my $password = $login_data->{password};
|
||||
|
||||
trick_taint($username);
|
||||
my ($real_password_crypted) = $dbh->selectrow_array(
|
||||
"SELECT cryptpassword FROM profiles WHERE userid = ?",
|
||||
undef, $user_id);
|
||||
|
||||
# Wide characters cause crypt to die
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
utf8::encode($password) if utf8::is_utf8($password);
|
||||
}
|
||||
|
||||
# Using the internal crypted password as the salt,
|
||||
# crypt the password the user entered.
|
||||
my $entered_password_crypted = crypt($password, $real_password_crypted);
|
||||
|
||||
return { failure => AUTH_LOGINFAILED }
|
||||
if $entered_password_crypted ne $real_password_crypted;
|
||||
|
||||
# The user's credentials are okay, so delete any outstanding
|
||||
# password tokens they may have generated.
|
||||
Bugzilla::Token::DeletePasswordTokens($user_id, "user_logged_in");
|
||||
|
||||
return $login_data;
|
||||
}
|
||||
|
||||
sub change_password {
|
||||
my ($self, $user, $password) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cryptpassword = bz_crypt($password);
|
||||
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
|
||||
undef, $cryptpassword, $user->id);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,176 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Verify::LDAP;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Verify);
|
||||
use fields qw(
|
||||
ldap
|
||||
);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use Net::LDAP;
|
||||
|
||||
use constant admin_can_create_account => 0;
|
||||
use constant user_can_create_account => 0;
|
||||
|
||||
sub check_credentials {
|
||||
my ($self, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# We need to bind anonymously to the LDAP server. This is
|
||||
# because we need to get the Distinguished Name of the user trying
|
||||
# to log in. Some servers (such as iPlanet) allow you to have unique
|
||||
# uids spread out over a subtree of an area (such as "People"), so
|
||||
# just appending the Base DN to the uid isn't sufficient to get the
|
||||
# user's DN. For servers which don't work this way, there will still
|
||||
# be no harm done.
|
||||
$self->_bind_ldap_anonymously();
|
||||
|
||||
# Now, we verify that the user exists, and get a LDAP Distinguished
|
||||
# Name for the user.
|
||||
my $username = $params->{username};
|
||||
my $dn_result = $self->ldap->search(_bz_search_params($username),
|
||||
attrs => ['dn']);
|
||||
return { failure => AUTH_ERROR, error => "ldap_search_error",
|
||||
details => {errstr => $dn_result->error, username => $username}
|
||||
} if $dn_result->code;
|
||||
|
||||
return { failure => AUTH_NO_SUCH_USER } if !$dn_result->count;
|
||||
|
||||
my $dn = $dn_result->shift_entry->dn;
|
||||
|
||||
# Check the password.
|
||||
my $pw_result = $self->ldap->bind($dn, password => $params->{password});
|
||||
return { failure => AUTH_LOGINFAILED } if $pw_result->code;
|
||||
|
||||
# And now we fill in the user's details.
|
||||
my $detail_result = $self->ldap->search(_bz_search_params($username));
|
||||
return { failure => AUTH_ERROR, error => "ldap_search_error",
|
||||
details => {errstr => $detail_result->error, username => $username}
|
||||
} if $detail_result->code;
|
||||
|
||||
my $user_entry = $detail_result->shift_entry;
|
||||
|
||||
my $mail_attr = Bugzilla->params->{"LDAPmailattribute"};
|
||||
if ($mail_attr) {
|
||||
if (!$user_entry->exists($mail_attr)) {
|
||||
return { failure => AUTH_ERROR,
|
||||
error => "ldap_cannot_retreive_attr",
|
||||
details => {attr => $mail_attr} };
|
||||
}
|
||||
|
||||
my @emails = $user_entry->get_value($mail_attr);
|
||||
|
||||
# Default to the first email address returned.
|
||||
$params->{bz_username} = $emails[0];
|
||||
|
||||
if (@emails > 1) {
|
||||
# Cycle through the adresses and check if they're Bugzilla logins.
|
||||
# Use the first one that returns a valid id.
|
||||
foreach my $email (@emails) {
|
||||
if ( login_to_id($email) ) {
|
||||
$params->{bz_username} = $email;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
$params->{bz_username} = $username;
|
||||
}
|
||||
|
||||
$params->{realname} ||= $user_entry->get_value("displayName");
|
||||
$params->{realname} ||= $user_entry->get_value("cn");
|
||||
|
||||
$params->{extern_id} = $username;
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
sub _bz_search_params {
|
||||
my ($username) = @_;
|
||||
return (base => Bugzilla->params->{"LDAPBaseDN"},
|
||||
scope => "sub",
|
||||
filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"}
|
||||
. "=$username)"
|
||||
. Bugzilla->params->{"LDAPfilter"} . ')');
|
||||
}
|
||||
|
||||
sub _bind_ldap_anonymously {
|
||||
my ($self) = @_;
|
||||
my $bind_result;
|
||||
if (Bugzilla->params->{"LDAPbinddn"}) {
|
||||
my ($LDAPbinddn,$LDAPbindpass) =
|
||||
split(":",Bugzilla->params->{"LDAPbinddn"});
|
||||
$bind_result =
|
||||
$self->ldap->bind($LDAPbinddn, password => $LDAPbindpass);
|
||||
}
|
||||
else {
|
||||
$bind_result = $self->ldap->bind();
|
||||
}
|
||||
ThrowCodeError("ldap_bind_failed", {errstr => $bind_result->error})
|
||||
if $bind_result->code;
|
||||
}
|
||||
|
||||
# We can't just do this in new(), because we're not allowed to throw any
|
||||
# error from anywhere under Bugzilla::Auth::new -- otherwise we
|
||||
# could create a situation where the admin couldn't get to editparams
|
||||
# to fix his mistake. (Because Bugzilla->login always calls
|
||||
# Bugzilla::Auth->new, and almost every page calls Bugzilla->login.)
|
||||
sub ldap {
|
||||
my ($self) = @_;
|
||||
return $self->{ldap} if $self->{ldap};
|
||||
|
||||
my @servers = split(/[\s,]+/, Bugzilla->params->{"LDAPserver"});
|
||||
ThrowCodeError("ldap_server_not_defined") unless @servers;
|
||||
|
||||
foreach (@servers) {
|
||||
$self->{ldap} = new Net::LDAP(trim($_));
|
||||
last if $self->{ldap};
|
||||
}
|
||||
ThrowCodeError("ldap_connect_failed", { server => join(", ", @servers) })
|
||||
unless $self->{ldap};
|
||||
|
||||
# try to start TLS if needed
|
||||
if (Bugzilla->params->{"LDAPstarttls"}) {
|
||||
my $mesg = $self->{ldap}->start_tls();
|
||||
ThrowCodeError("ldap_start_tls_failed", { error => $mesg->error() })
|
||||
if $mesg->code();
|
||||
}
|
||||
|
||||
return $self->{ldap};
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,64 +0,0 @@
|
||||
# -*- 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 Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Auth::Verify::RADIUS;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Verify);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use Authen::Radius;
|
||||
|
||||
use constant admin_can_create_account => 0;
|
||||
use constant user_can_create_account => 0;
|
||||
|
||||
sub check_credentials {
|
||||
my ($self, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $address_suffix = Bugzilla->params->{'RADIUS_email_suffix'};
|
||||
my $username = $params->{username};
|
||||
|
||||
# If we're using RADIUS_email_suffix, we may need to cut it off from
|
||||
# the login name.
|
||||
if ($address_suffix) {
|
||||
$username =~ s/\Q$address_suffix\E$//i;
|
||||
}
|
||||
|
||||
# Create RADIUS object.
|
||||
my $radius =
|
||||
new Authen::Radius(Host => Bugzilla->params->{'RADIUS_server'},
|
||||
Secret => Bugzilla->params->{'RADIUS_secret'})
|
||||
|| return { failure => AUTH_ERROR, error => 'radius_preparation_error',
|
||||
details => {errstr => Authen::Radius::strerror() } };
|
||||
|
||||
# Check the password.
|
||||
$radius->check_pwd($username, $params->{password},
|
||||
Bugzilla->params->{'RADIUS_NAS_IP'} || undef)
|
||||
|| return { failure => AUTH_LOGINFAILED };
|
||||
|
||||
# Build the user account's e-mail address.
|
||||
$params->{bz_username} = $username . $address_suffix;
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,81 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Verify::Stack;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Verify);
|
||||
use fields qw(
|
||||
_stack
|
||||
successful
|
||||
);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $list = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
$self->{_stack} = [];
|
||||
foreach my $verify_method (split(',', $list)) {
|
||||
require "Bugzilla/Auth/Verify/${verify_method}.pm";
|
||||
push(@{$self->{_stack}},
|
||||
"Bugzilla::Auth::Verify::$verify_method"->new(@_));
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub can_change_password {
|
||||
my ($self) = @_;
|
||||
# We return true if any method can change passwords.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->can_change_password;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub check_credentials {
|
||||
my $self = shift;
|
||||
my $result;
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
$result = $object->check_credentials(@_);
|
||||
$self->{successful} = $object;
|
||||
last if !$result->{failure};
|
||||
# So that if none of them succeed, it's undef.
|
||||
$self->{successful} = undef;
|
||||
}
|
||||
# Returns the result at the bottom of the stack if they all fail.
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub create_or_update_user {
|
||||
my $self = shift;
|
||||
my $result;
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
$result = $object->create_or_update_user(@_);
|
||||
last if !$result->{failure};
|
||||
}
|
||||
# Returns the result at the bottom of the stack if they all fail.
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub user_can_create_account {
|
||||
my ($self) = @_;
|
||||
# We return true if any method allows the user to create an account.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->user_can_create_account;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,733 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>,
|
||||
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Alan Raetz <al_raetz@yahoo.com>
|
||||
# Jacob Steenhagen <jake@actex.net>
|
||||
# Matthew Tuck <matty@chariot.net.au>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Byron Jones <bugzilla@glob.com.au>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::BugMail;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Bug;
|
||||
use Bugzilla::Classification;
|
||||
use Bugzilla::Product;
|
||||
use Bugzilla::Component;
|
||||
use Bugzilla::Status;
|
||||
use Bugzilla::Mailer;
|
||||
|
||||
use Date::Parse;
|
||||
use Date::Format;
|
||||
|
||||
use constant FORMAT_TRIPLE => "%19s|%-28s|%-28s";
|
||||
use constant FORMAT_3_SIZE => [19,28,28];
|
||||
use constant FORMAT_DOUBLE => "%19s %-55s";
|
||||
use constant FORMAT_2_SIZE => [19,55];
|
||||
|
||||
use constant BIT_DIRECT => 1;
|
||||
use constant BIT_WATCHING => 2;
|
||||
|
||||
# We need these strings for the X-Bugzilla-Reasons header
|
||||
# Note: this hash uses "," rather than "=>" to avoid auto-quoting of the LHS.
|
||||
use constant REL_NAMES => {
|
||||
REL_ASSIGNEE , "AssignedTo",
|
||||
REL_REPORTER , "Reporter",
|
||||
REL_QA , "QAcontact",
|
||||
REL_CC , "CC",
|
||||
REL_VOTER , "Voter",
|
||||
REL_GLOBAL_WATCHER, "GlobalWatcher"
|
||||
};
|
||||
|
||||
# We use this instead of format because format doesn't deal well with
|
||||
# multi-byte languages.
|
||||
sub multiline_sprintf {
|
||||
my ($format, $args, $sizes) = @_;
|
||||
my @parts;
|
||||
my @my_sizes = @$sizes; # Copy this so we don't modify the input array.
|
||||
foreach my $string (@$args) {
|
||||
my $size = shift @my_sizes;
|
||||
my @pieces = split("\n", wrap_hard($string, $size));
|
||||
push(@parts, \@pieces);
|
||||
}
|
||||
|
||||
my $formatted;
|
||||
while (1) {
|
||||
# Get the first item of each part.
|
||||
my @line = map { shift @$_ } @parts;
|
||||
# If they're all undef, we're done.
|
||||
last if !grep { defined $_ } @line;
|
||||
# Make any single undef item into ''
|
||||
@line = map { defined $_ ? $_ : '' } @line;
|
||||
# And append a formatted line
|
||||
$formatted .= sprintf($format, @line);
|
||||
# Remove trailing spaces, or they become lots of =20's in
|
||||
# quoted-printable emails.
|
||||
$formatted =~ s/\s+$//;
|
||||
$formatted .= "\n";
|
||||
}
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
sub three_columns {
|
||||
return multiline_sprintf(FORMAT_TRIPLE, \@_, FORMAT_3_SIZE);
|
||||
}
|
||||
|
||||
# This is a bit of a hack, basically keeping the old system()
|
||||
# cmd line interface. Should clean this up at some point.
|
||||
#
|
||||
# args: bug_id, and an optional hash ref which may have keys for:
|
||||
# changer, owner, qa, reporter, cc
|
||||
# Optional hash contains values of people which will be forced to those
|
||||
# roles when the email is sent.
|
||||
# All the names are email addresses, not userids
|
||||
# values are scalars, except for cc, which is a list
|
||||
# This hash usually comes from the "mailrecipients" var in a template call.
|
||||
sub Send {
|
||||
my ($id, $forced) = (@_);
|
||||
|
||||
my @headerlist;
|
||||
my %defmailhead;
|
||||
my %fielddescription;
|
||||
|
||||
my $msg = "";
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# XXX - These variables below are useless. We could use field object
|
||||
# methods directly. But we first have to implement a cache in
|
||||
# Bugzilla->get_fields to avoid querying the DB all the time.
|
||||
foreach my $field (Bugzilla->get_fields({obsolete => 0})) {
|
||||
push(@headerlist, $field->name);
|
||||
$defmailhead{$field->name} = $field->in_new_bugmail;
|
||||
$fielddescription{$field->name} = $field->description;
|
||||
}
|
||||
|
||||
my %values = %{$dbh->selectrow_hashref(
|
||||
'SELECT ' . join(',', editable_bug_fields()) . ', reporter,
|
||||
lastdiffed AS start_time, LOCALTIMESTAMP(0) AS end_time
|
||||
FROM bugs WHERE bug_id = ?',
|
||||
undef, $id)};
|
||||
|
||||
my $product = new Bugzilla::Product($values{product_id});
|
||||
$values{product} = $product->name;
|
||||
if (Bugzilla->params->{'useclassification'}) {
|
||||
$values{classification} = Bugzilla::Classification->new($product->classification_id)->name;
|
||||
}
|
||||
my $component = new Bugzilla::Component($values{component_id});
|
||||
$values{component} = $component->name;
|
||||
|
||||
my ($start, $end) = ($values{start_time}, $values{end_time});
|
||||
|
||||
# User IDs of people in various roles. More than one person can 'have' a
|
||||
# role, if the person in that role has changed, or people are watching.
|
||||
my $reporter = $values{'reporter'};
|
||||
my @assignees = ($values{'assigned_to'});
|
||||
my @qa_contacts = ($values{'qa_contact'});
|
||||
|
||||
my $cc_users = $dbh->selectall_arrayref(
|
||||
"SELECT cc.who, profiles.login_name
|
||||
FROM cc
|
||||
INNER JOIN profiles
|
||||
ON cc.who = profiles.userid
|
||||
WHERE bug_id = ?",
|
||||
undef, $id);
|
||||
|
||||
my (@ccs, @cc_login_names);
|
||||
foreach my $cc_user (@$cc_users) {
|
||||
my ($user_id, $user_login) = @$cc_user;
|
||||
push (@ccs, $user_id);
|
||||
push (@cc_login_names, $user_login);
|
||||
}
|
||||
|
||||
# Include the people passed in as being in particular roles.
|
||||
# This can include people who used to hold those roles.
|
||||
# At this point, we don't care if there are duplicates in these arrays.
|
||||
my $changer = $forced->{'changer'};
|
||||
if ($forced->{'owner'}) {
|
||||
push (@assignees, login_to_id($forced->{'owner'}, THROW_ERROR));
|
||||
}
|
||||
|
||||
if ($forced->{'qacontact'}) {
|
||||
push (@qa_contacts, login_to_id($forced->{'qacontact'}, THROW_ERROR));
|
||||
}
|
||||
|
||||
if ($forced->{'cc'}) {
|
||||
foreach my $cc (@{$forced->{'cc'}}) {
|
||||
push(@ccs, login_to_id($cc, THROW_ERROR));
|
||||
}
|
||||
}
|
||||
|
||||
# Convert to names, for later display
|
||||
$values{'changer'} = $changer;
|
||||
# If no changer is specified, then it has no name.
|
||||
if ($changer) {
|
||||
$values{'changername'} = Bugzilla::User->new({name => $changer})->name;
|
||||
}
|
||||
$values{'assigned_to'} = user_id_to_login($values{'assigned_to'});
|
||||
$values{'reporter'} = user_id_to_login($values{'reporter'});
|
||||
if ($values{'qa_contact'}) {
|
||||
$values{'qa_contact'} = user_id_to_login($values{'qa_contact'});
|
||||
}
|
||||
$values{'cc'} = join(', ', @cc_login_names);
|
||||
$values{'estimated_time'} = format_time_decimal($values{'estimated_time'});
|
||||
|
||||
if ($values{'deadline'}) {
|
||||
$values{'deadline'} = time2str("%Y-%m-%d", str2time($values{'deadline'}));
|
||||
}
|
||||
|
||||
my $dependslist = $dbh->selectcol_arrayref(
|
||||
'SELECT dependson FROM dependencies
|
||||
WHERE blocked = ? ORDER BY dependson',
|
||||
undef, ($id));
|
||||
|
||||
$values{'dependson'} = join(",", @$dependslist);
|
||||
|
||||
my $blockedlist = $dbh->selectcol_arrayref(
|
||||
'SELECT blocked FROM dependencies
|
||||
WHERE dependson = ? ORDER BY blocked',
|
||||
undef, ($id));
|
||||
|
||||
$values{'blocked'} = join(",", @$blockedlist);
|
||||
|
||||
my @args = ($id);
|
||||
|
||||
# If lastdiffed is NULL, then we don't limit the search on time.
|
||||
my $when_restriction = '';
|
||||
if ($start) {
|
||||
$when_restriction = ' AND bug_when > ? AND bug_when <= ?';
|
||||
push @args, ($start, $end);
|
||||
}
|
||||
|
||||
my $diffs = $dbh->selectall_arrayref(
|
||||
"SELECT profiles.login_name, profiles.realname, fielddefs.description,
|
||||
bugs_activity.bug_when, bugs_activity.removed,
|
||||
bugs_activity.added, bugs_activity.attach_id, fielddefs.name
|
||||
FROM bugs_activity
|
||||
INNER JOIN fielddefs
|
||||
ON fielddefs.id = bugs_activity.fieldid
|
||||
INNER JOIN profiles
|
||||
ON profiles.userid = bugs_activity.who
|
||||
WHERE bugs_activity.bug_id = ?
|
||||
$when_restriction
|
||||
ORDER BY bugs_activity.bug_when", undef, @args);
|
||||
|
||||
my @new_depbugs;
|
||||
my $difftext = "";
|
||||
my $diffheader = "";
|
||||
my @diffparts;
|
||||
my $lastwho = "";
|
||||
my $fullwho;
|
||||
my @changedfields;
|
||||
foreach my $ref (@$diffs) {
|
||||
my ($who, $whoname, $what, $when, $old, $new, $attachid, $fieldname) = (@$ref);
|
||||
my $diffpart = {};
|
||||
if ($who ne $lastwho) {
|
||||
$lastwho = $who;
|
||||
$fullwho = $whoname ? "$whoname <$who>" : $who;
|
||||
$diffheader = "\n$fullwho changed:\n\n";
|
||||
$diffheader .= three_columns("What ", "Removed", "Added");
|
||||
$diffheader .= ('-' x 76) . "\n";
|
||||
}
|
||||
$what =~ s/^(Attachment )?/Attachment #$attachid / if $attachid;
|
||||
if( $fieldname eq 'estimated_time' ||
|
||||
$fieldname eq 'remaining_time' ) {
|
||||
$old = format_time_decimal($old);
|
||||
$new = format_time_decimal($new);
|
||||
}
|
||||
if ($fieldname eq 'dependson') {
|
||||
push(@new_depbugs, grep {$_ =~ /^\d+$/} split(/[\s,]+/, $new));
|
||||
}
|
||||
if ($attachid) {
|
||||
($diffpart->{'isprivate'}) = $dbh->selectrow_array(
|
||||
'SELECT isprivate FROM attachments WHERE attach_id = ?',
|
||||
undef, ($attachid));
|
||||
}
|
||||
$difftext = three_columns($what, $old, $new);
|
||||
$diffpart->{'header'} = $diffheader;
|
||||
$diffpart->{'fieldname'} = $fieldname;
|
||||
$diffpart->{'text'} = $difftext;
|
||||
push(@diffparts, $diffpart);
|
||||
push(@changedfields, $what);
|
||||
}
|
||||
$values{'changed_fields'} = join(' ', @changedfields);
|
||||
|
||||
my @depbugs;
|
||||
my $deptext = "";
|
||||
# Do not include data about dependent bugs when they have just been added.
|
||||
# Completely skip checking for dependent bugs on bug creation as all
|
||||
# dependencies bugs will just have been added.
|
||||
if ($start) {
|
||||
my $dep_restriction = "";
|
||||
if (scalar @new_depbugs) {
|
||||
$dep_restriction = "AND bugs_activity.bug_id NOT IN (" .
|
||||
join(", ", @new_depbugs) . ")";
|
||||
}
|
||||
|
||||
my $dependency_diffs = $dbh->selectall_arrayref(
|
||||
"SELECT bugs_activity.bug_id, bugs.short_desc, fielddefs.name,
|
||||
bugs_activity.removed, bugs_activity.added
|
||||
FROM bugs_activity
|
||||
INNER JOIN bugs
|
||||
ON bugs.bug_id = bugs_activity.bug_id
|
||||
INNER JOIN dependencies
|
||||
ON bugs_activity.bug_id = dependencies.dependson
|
||||
INNER JOIN fielddefs
|
||||
ON fielddefs.id = bugs_activity.fieldid
|
||||
WHERE dependencies.blocked = ?
|
||||
AND (fielddefs.name = 'bug_status'
|
||||
OR fielddefs.name = 'resolution')
|
||||
$when_restriction
|
||||
$dep_restriction
|
||||
ORDER BY bugs_activity.bug_when, bugs.bug_id", undef, @args);
|
||||
|
||||
my $thisdiff = "";
|
||||
my $lastbug = "";
|
||||
my $interestingchange = 0;
|
||||
foreach my $dependency_diff (@$dependency_diffs) {
|
||||
my ($depbug, $summary, $what, $old, $new) = @$dependency_diff;
|
||||
|
||||
if ($depbug ne $lastbug) {
|
||||
if ($interestingchange) {
|
||||
$deptext .= $thisdiff;
|
||||
}
|
||||
$lastbug = $depbug;
|
||||
my $urlbase = Bugzilla->params->{"urlbase"};
|
||||
$thisdiff =
|
||||
"\nBug $id depends on bug $depbug, which changed state.\n\n" .
|
||||
"Bug $depbug Summary: $summary\n" .
|
||||
"${urlbase}show_bug.cgi?id=$depbug\n\n";
|
||||
$thisdiff .= three_columns("What ", "Old Value", "New Value");
|
||||
$thisdiff .= ('-' x 76) . "\n";
|
||||
$interestingchange = 0;
|
||||
}
|
||||
$thisdiff .= three_columns($fielddescription{$what}, $old, $new);
|
||||
if ($what eq 'bug_status'
|
||||
&& is_open_state($old) ne is_open_state($new))
|
||||
{
|
||||
$interestingchange = 1;
|
||||
}
|
||||
push(@depbugs, $depbug);
|
||||
}
|
||||
|
||||
if ($interestingchange) {
|
||||
$deptext .= $thisdiff;
|
||||
}
|
||||
$deptext = trim($deptext);
|
||||
|
||||
if ($deptext) {
|
||||
my $diffpart = {};
|
||||
$diffpart->{'text'} = "\n" . trim("\n\n" . $deptext);
|
||||
push(@diffparts, $diffpart);
|
||||
}
|
||||
}
|
||||
|
||||
my ($raw_comments, $anyprivate, $count) = get_comments_by_bug($id, $start, $end);
|
||||
|
||||
###########################################################################
|
||||
# Start of email filtering code
|
||||
###########################################################################
|
||||
|
||||
# A user_id => roles hash to keep track of people.
|
||||
my %recipients;
|
||||
my %watching;
|
||||
|
||||
# Now we work out all the people involved with this bug, and note all of
|
||||
# the relationships in a hash. The keys are userids, the values are an
|
||||
# array of role constants.
|
||||
|
||||
# Voters
|
||||
my $voters = $dbh->selectcol_arrayref(
|
||||
"SELECT who FROM votes WHERE bug_id = ?", undef, ($id));
|
||||
|
||||
$recipients{$_}->{+REL_VOTER} = BIT_DIRECT foreach (@$voters);
|
||||
|
||||
# CCs
|
||||
$recipients{$_}->{+REL_CC} = BIT_DIRECT foreach (@ccs);
|
||||
|
||||
# Reporter (there's only ever one)
|
||||
$recipients{$reporter}->{+REL_REPORTER} = BIT_DIRECT;
|
||||
|
||||
# QA Contact
|
||||
if (Bugzilla->params->{'useqacontact'}) {
|
||||
foreach (@qa_contacts) {
|
||||
# QA Contact can be blank; ignore it if so.
|
||||
$recipients{$_}->{+REL_QA} = BIT_DIRECT if $_;
|
||||
}
|
||||
}
|
||||
|
||||
# Assignee
|
||||
$recipients{$_}->{+REL_ASSIGNEE} = BIT_DIRECT foreach (@assignees);
|
||||
|
||||
# The last relevant set of people are those who are being removed from
|
||||
# their roles in this change. We get their names out of the diffs.
|
||||
foreach my $ref (@$diffs) {
|
||||
my ($who, $whoname, $what, $when, $old, $new) = (@$ref);
|
||||
if ($old) {
|
||||
# You can't stop being the reporter, and mail isn't sent if you
|
||||
# remove your vote.
|
||||
# Ignore people whose user account has been deleted or renamed.
|
||||
if ($what eq "CC") {
|
||||
foreach my $cc_user (split(/[\s,]+/, $old)) {
|
||||
my $uid = login_to_id($cc_user);
|
||||
$recipients{$uid}->{+REL_CC} = BIT_DIRECT if $uid;
|
||||
}
|
||||
}
|
||||
elsif ($what eq "QAContact") {
|
||||
my $uid = login_to_id($old);
|
||||
$recipients{$uid}->{+REL_QA} = BIT_DIRECT if $uid;
|
||||
}
|
||||
elsif ($what eq "AssignedTo") {
|
||||
my $uid = login_to_id($old);
|
||||
$recipients{$uid}->{+REL_ASSIGNEE} = BIT_DIRECT if $uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Bugzilla->params->{"supportwatchers"}) {
|
||||
# Find all those user-watching anyone on the current list, who is not
|
||||
# on it already themselves.
|
||||
my $involved = join(",", keys %recipients);
|
||||
|
||||
my $userwatchers =
|
||||
$dbh->selectall_arrayref("SELECT watcher, watched FROM watch
|
||||
WHERE watched IN ($involved)");
|
||||
|
||||
# Mark these people as having the role of the person they are watching
|
||||
foreach my $watch (@$userwatchers) {
|
||||
while (my ($role, $bits) = each %{$recipients{$watch->[1]}}) {
|
||||
$recipients{$watch->[0]}->{$role} |= BIT_WATCHING
|
||||
if $bits & BIT_DIRECT;
|
||||
}
|
||||
push (@{$watching{$watch->[0]}}, $watch->[1]);
|
||||
}
|
||||
}
|
||||
|
||||
# Global watcher
|
||||
my @watchers = split(/[,\s]+/, Bugzilla->params->{'globalwatchers'});
|
||||
foreach (@watchers) {
|
||||
my $watcher_id = login_to_id($_);
|
||||
next unless $watcher_id;
|
||||
$recipients{$watcher_id}->{+REL_GLOBAL_WATCHER} = BIT_DIRECT;
|
||||
}
|
||||
|
||||
# We now have a complete set of all the users, and their relationships to
|
||||
# the bug in question. However, we are not necessarily going to mail them
|
||||
# all - there are preferences, permissions checks and all sorts to do yet.
|
||||
my @sent;
|
||||
my @excluded;
|
||||
|
||||
# Some comments are language specific. We cache them here.
|
||||
my %comments;
|
||||
|
||||
foreach my $user_id (keys %recipients) {
|
||||
my %rels_which_want;
|
||||
my $sent_mail = 0;
|
||||
|
||||
my $user = new Bugzilla::User($user_id);
|
||||
# Deleted users must be excluded.
|
||||
next unless $user;
|
||||
|
||||
# What's the language chosen by this user for email?
|
||||
my $lang = $user->settings->{'lang'}->{'value'};
|
||||
|
||||
if ($user->can_see_bug($id)) {
|
||||
# It's time to format language specific comments.
|
||||
unless (exists $comments{$lang}) {
|
||||
Bugzilla->template_inner($lang);
|
||||
$comments{$lang} = prepare_comments($raw_comments, $count);
|
||||
Bugzilla->template_inner("");
|
||||
}
|
||||
|
||||
# Go through each role the user has and see if they want mail in
|
||||
# that role.
|
||||
foreach my $relationship (keys %{$recipients{$user_id}}) {
|
||||
if ($user->wants_bug_mail($id,
|
||||
$relationship,
|
||||
$diffs,
|
||||
$comments{$lang},
|
||||
$deptext,
|
||||
$changer,
|
||||
!$start))
|
||||
{
|
||||
$rels_which_want{$relationship} =
|
||||
$recipients{$user_id}->{$relationship};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar(%rels_which_want)) {
|
||||
# So the user exists, can see the bug, and wants mail in at least
|
||||
# one role. But do we want to send it to them?
|
||||
|
||||
# If we are using insiders, and the comment is private, only send
|
||||
# to insiders
|
||||
my $insider_ok = 1;
|
||||
$insider_ok = 0 if (Bugzilla->params->{"insidergroup"} &&
|
||||
($anyprivate != 0) &&
|
||||
(!$user->groups->{Bugzilla->params->{"insidergroup"}}));
|
||||
|
||||
# We shouldn't send mail if this is a dependency mail (i.e. there
|
||||
# is something in @depbugs), and any of the depending bugs are not
|
||||
# visible to the user. This is to avoid leaking the summaries of
|
||||
# confidential bugs.
|
||||
my $dep_ok = 1;
|
||||
foreach my $dep_id (@depbugs) {
|
||||
if (!$user->can_see_bug($dep_id)) {
|
||||
$dep_ok = 0;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure the user isn't in the nomail list, and the insider and
|
||||
# dep checks passed.
|
||||
if ($user->email_enabled &&
|
||||
$insider_ok &&
|
||||
$dep_ok)
|
||||
{
|
||||
# OK, OK, if we must. Email the user.
|
||||
$sent_mail = sendMail($user,
|
||||
\@headerlist,
|
||||
\%rels_which_want,
|
||||
\%values,
|
||||
\%defmailhead,
|
||||
\%fielddescription,
|
||||
\@diffparts,
|
||||
$comments{$lang},
|
||||
$anyprivate,
|
||||
! $start,
|
||||
$id,
|
||||
exists $watching{$user_id} ?
|
||||
$watching{$user_id} : undef);
|
||||
}
|
||||
}
|
||||
|
||||
if ($sent_mail) {
|
||||
push(@sent, $user->login);
|
||||
}
|
||||
else {
|
||||
push(@excluded, $user->login);
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->do('UPDATE bugs SET lastdiffed = ? WHERE bug_id = ?',
|
||||
undef, ($end, $id));
|
||||
|
||||
return {'sent' => \@sent, 'excluded' => \@excluded};
|
||||
}
|
||||
|
||||
sub sendMail {
|
||||
my ($user, $hlRef, $relRef, $valueRef, $dmhRef, $fdRef,
|
||||
$diffRef, $newcomments, $anyprivate, $isnew,
|
||||
$id, $watchingRef) = @_;
|
||||
|
||||
my %values = %$valueRef;
|
||||
my @headerlist = @$hlRef;
|
||||
my %mailhead = %$dmhRef;
|
||||
my %fielddescription = %$fdRef;
|
||||
my @diffparts = @$diffRef;
|
||||
my $head = "";
|
||||
|
||||
foreach my $f (@headerlist) {
|
||||
if ($mailhead{$f}) {
|
||||
my $value = $values{$f};
|
||||
# If there isn't anything to show, don't include this header
|
||||
if (! $value) {
|
||||
next;
|
||||
}
|
||||
# Only send estimated_time if it is enabled and the user is in the group
|
||||
if (($f ne 'estimated_time' && $f ne 'deadline') ||
|
||||
$user->groups->{Bugzilla->params->{'timetrackinggroup'}}) {
|
||||
|
||||
my $desc = $fielddescription{$f};
|
||||
$head .= multiline_sprintf(FORMAT_DOUBLE, ["$desc:", $value],
|
||||
FORMAT_2_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Build difftext (the actions) by verifying the user should see them
|
||||
my $difftext = "";
|
||||
my $diffheader = "";
|
||||
my $add_diff;
|
||||
|
||||
foreach my $diff (@diffparts) {
|
||||
$add_diff = 0;
|
||||
|
||||
if (exists($diff->{'fieldname'}) &&
|
||||
($diff->{'fieldname'} eq 'estimated_time' ||
|
||||
$diff->{'fieldname'} eq 'remaining_time' ||
|
||||
$diff->{'fieldname'} eq 'work_time' ||
|
||||
$diff->{'fieldname'} eq 'deadline')){
|
||||
if ($user->groups->{Bugzilla->params->{"timetrackinggroup"}}) {
|
||||
$add_diff = 1;
|
||||
}
|
||||
} elsif (($diff->{'isprivate'})
|
||||
&& Bugzilla->params->{'insidergroup'}
|
||||
&& !($user->groups->{Bugzilla->params->{'insidergroup'}})
|
||||
) {
|
||||
$add_diff = 0;
|
||||
} else {
|
||||
$add_diff = 1;
|
||||
}
|
||||
|
||||
if ($add_diff) {
|
||||
if (exists($diff->{'header'}) &&
|
||||
($diffheader ne $diff->{'header'})) {
|
||||
$diffheader = $diff->{'header'};
|
||||
$difftext .= $diffheader;
|
||||
}
|
||||
$difftext .= $diff->{'text'};
|
||||
}
|
||||
}
|
||||
|
||||
if ($difftext eq "" && $newcomments eq "" && !$isnew) {
|
||||
# Whoops, no differences!
|
||||
return 0;
|
||||
}
|
||||
|
||||
# If an attachment was created, then add an URL. (Note: the 'g'lobal
|
||||
# replace should work with comments with multiple attachments.)
|
||||
|
||||
if ( $newcomments =~ /Created an attachment \(/ ) {
|
||||
|
||||
my $showattachurlbase =
|
||||
Bugzilla->params->{'urlbase'} . "attachment.cgi?id=";
|
||||
|
||||
$newcomments =~ s/(Created an attachment \(id=([0-9]+)\))/$1\n --> \(${showattachurlbase}$2\)/g;
|
||||
}
|
||||
|
||||
my $diffs = $difftext . "\n\n" . $newcomments;
|
||||
if ($isnew) {
|
||||
$diffs = $head . ($difftext ? "\n\n" : "") . $diffs;
|
||||
}
|
||||
|
||||
my (@reasons, @reasons_watch);
|
||||
while (my ($relationship, $bits) = each %{$relRef}) {
|
||||
push(@reasons, $relationship) if ($bits & BIT_DIRECT);
|
||||
push(@reasons_watch, $relationship) if ($bits & BIT_WATCHING);
|
||||
}
|
||||
|
||||
my @headerrel = map { REL_NAMES->{$_} } @reasons;
|
||||
my @watchingrel = map { REL_NAMES->{$_} } @reasons_watch;
|
||||
push(@headerrel, 'None') unless @headerrel;
|
||||
push(@watchingrel, 'None') unless @watchingrel;
|
||||
push @watchingrel, map { user_id_to_login($_) } @$watchingRef;
|
||||
|
||||
my $threadingmarker = build_thread_marker($id, $user->id, $isnew);
|
||||
|
||||
my $vars = {
|
||||
isnew => $isnew,
|
||||
to => $user->email,
|
||||
bugid => $id,
|
||||
alias => Bugzilla->params->{'usebugaliases'} ? $values{'alias'} : "",
|
||||
classification => $values{'classification'},
|
||||
product => $values{'product'},
|
||||
comp => $values{'component'},
|
||||
keywords => $values{'keywords'},
|
||||
severity => $values{'bug_severity'},
|
||||
status => $values{'bug_status'},
|
||||
priority => $values{'priority'},
|
||||
assignedto => $values{'assigned_to'},
|
||||
assignedtoname => Bugzilla::User->new({name => $values{'assigned_to'}})->name,
|
||||
targetmilestone => $values{'target_milestone'},
|
||||
changedfields => $values{'changed_fields'},
|
||||
summary => $values{'short_desc'},
|
||||
reasons => \@reasons,
|
||||
reasons_watch => \@reasons_watch,
|
||||
reasonsheader => join(" ", @headerrel),
|
||||
reasonswatchheader => join(" ", @watchingrel),
|
||||
changer => $values{'changer'},
|
||||
changername => $values{'changername'},
|
||||
reporter => $values{'reporter'},
|
||||
reportername => Bugzilla::User->new({name => $values{'reporter'}})->name,
|
||||
diffs => $diffs,
|
||||
threadingmarker => $threadingmarker
|
||||
};
|
||||
|
||||
my $msg;
|
||||
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
|
||||
$template->process("email/newchangedmail.txt.tmpl", $vars, \$msg)
|
||||
|| ThrowTemplateError($template->error());
|
||||
Bugzilla->template_inner("");
|
||||
|
||||
MessageToMTA($msg);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Get bug comments for the given period.
|
||||
sub get_comments_by_bug {
|
||||
my ($id, $start, $end) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $result = "";
|
||||
my $count = 0;
|
||||
my $anyprivate = 0;
|
||||
|
||||
# $start will be undef for new bugs, and defined for pre-existing bugs.
|
||||
if ($start) {
|
||||
# If $start is not NULL, obtain the count-index
|
||||
# of this comment for the leading "Comment #xxx" line.
|
||||
$count = $dbh->selectrow_array('SELECT COUNT(*) FROM longdescs
|
||||
WHERE bug_id = ? AND bug_when <= ?',
|
||||
undef, ($id, $start));
|
||||
}
|
||||
|
||||
my $raw = 1; # Do not format comments which are not of type CMT_NORMAL.
|
||||
my $comments = Bugzilla::Bug::GetComments($id, "oldest_to_newest", $start, $end, $raw);
|
||||
|
||||
if (Bugzilla->params->{'insidergroup'}) {
|
||||
$anyprivate = 1 if scalar(grep {$_->{'isprivate'} > 0} @$comments);
|
||||
}
|
||||
|
||||
return ($comments, $anyprivate, $count);
|
||||
}
|
||||
|
||||
# Prepare comments for the given language.
|
||||
sub prepare_comments {
|
||||
my ($raw_comments, $count) = @_;
|
||||
|
||||
my $result = "";
|
||||
foreach my $comment (@$raw_comments) {
|
||||
if ($count) {
|
||||
$result .= "\n\n--- Comment #$count from " . $comment->{'author'}->identity .
|
||||
" " . format_time($comment->{'time'}) . " ---\n";
|
||||
}
|
||||
# Format language specific comments. We don't update $comment->{'body'}
|
||||
# directly, otherwise it would grow everytime you call format_comment()
|
||||
# with a different language as some text may be appended to the existing one.
|
||||
my $body = Bugzilla::Bug::format_comment($comment);
|
||||
$result .= ($comment->{'already_wrapped'} ? $body : wrap_comment($body));
|
||||
$count++;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,385 +0,0 @@
|
||||
# -*- 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): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Byron Jones <bugzilla@glob.com.au>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::CGI;
|
||||
|
||||
BEGIN {
|
||||
if ($^O =~ /MSWin32/i) {
|
||||
# Help CGI find the correct temp directory as the default list
|
||||
# isn't Windows friendly (Bug 248988)
|
||||
$ENV{'TMPDIR'} = $ENV{'TEMP'} || $ENV{'TMP'} || "$ENV{'WINDIR'}\\TEMP";
|
||||
}
|
||||
}
|
||||
|
||||
use CGI qw(-no_xhtml -oldstyle_urls :private_tempfiles :unique_headers SERVER_PUSH);
|
||||
|
||||
use base qw(CGI);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
# We need to disable output buffering - see bug 179174
|
||||
$| = 1;
|
||||
|
||||
# Ignore SIGTERM and SIGPIPE - this prevents DB corruption. If the user closes
|
||||
# their browser window while a script is running, the web server sends these
|
||||
# signals, and we don't want to die half way through a write.
|
||||
$::SIG{TERM} = 'IGNORE';
|
||||
$::SIG{PIPE} = 'IGNORE';
|
||||
|
||||
# CGI.pm uses AUTOLOAD, but explicitly defines a DESTROY sub.
|
||||
# We need to do so, too, otherwise perl dies when the object is destroyed
|
||||
# and we don't have a DESTROY method (because CGI.pm's AUTOLOAD will |die|
|
||||
# on getting an unknown sub to try to call)
|
||||
sub DESTROY {
|
||||
my $self = shift;
|
||||
$self->SUPER::DESTROY(@_);
|
||||
};
|
||||
|
||||
sub new {
|
||||
my ($invocant, @args) = @_;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
|
||||
my $self = $class->SUPER::new(@args);
|
||||
|
||||
# Make sure our outgoing cookie list is empty on each invocation
|
||||
$self->{Bugzilla_cookie_list} = [];
|
||||
|
||||
# Send appropriate charset
|
||||
$self->charset(Bugzilla->params->{'utf8'} ? 'UTF-8' : '');
|
||||
|
||||
# Check for errors
|
||||
# All of the Bugzilla code wants to do this, so do it here instead of
|
||||
# in each script
|
||||
|
||||
my $err = $self->cgi_error;
|
||||
|
||||
if ($err) {
|
||||
# Note that this error block is only triggered by CGI.pm for malformed
|
||||
# multipart requests, and so should never happen unless there is a
|
||||
# browser bug.
|
||||
|
||||
print $self->header(-status => $err);
|
||||
|
||||
# ThrowCodeError wants to print the header, so it grabs Bugzilla->cgi
|
||||
# which creates a new Bugzilla::CGI object, which fails again, which
|
||||
# ends up here, and calls ThrowCodeError, and then recurses forever.
|
||||
# So don't use it.
|
||||
# In fact, we can't use templates at all, because we need a CGI object
|
||||
# to determine the template lang as well as the current url (from the
|
||||
# template)
|
||||
# Since this is an internal error which indicates a severe browser bug,
|
||||
# just die.
|
||||
die "CGI parsing error: $err";
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# We want this sorted plus the ability to exclude certain params
|
||||
sub canonicalise_query {
|
||||
my ($self, @exclude) = @_;
|
||||
|
||||
# Reconstruct the URL by concatenating the sorted param=value pairs
|
||||
my @parameters;
|
||||
foreach my $key (sort($self->param())) {
|
||||
# Leave this key out if it's in the exclude list
|
||||
next if lsearch(\@exclude, $key) != -1;
|
||||
|
||||
my $esc_key = url_quote($key);
|
||||
|
||||
foreach my $value ($self->param($key)) {
|
||||
if (defined($value)) {
|
||||
my $esc_value = url_quote($value);
|
||||
|
||||
push(@parameters, "$esc_key=$esc_value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return join("&", @parameters);
|
||||
}
|
||||
|
||||
sub clean_search_url {
|
||||
my $self = shift;
|
||||
# Delete any empty URL parameter
|
||||
my @cgi_params = $self->param;
|
||||
|
||||
foreach my $param (@cgi_params) {
|
||||
if (defined $self->param($param) && $self->param($param) eq '') {
|
||||
$self->delete($param);
|
||||
$self->delete("${param}_type");
|
||||
}
|
||||
|
||||
# Boolean Chart stuff is empty if it's "noop"
|
||||
if ($param =~ /\d-\d-\d/ && defined $self->param($param)
|
||||
&& $self->param($param) eq 'noop')
|
||||
{
|
||||
$self->delete($param);
|
||||
}
|
||||
}
|
||||
|
||||
# Delete certain parameters if the associated parameter is empty.
|
||||
$self->delete('bugidtype') if !$self->param('bug_id');
|
||||
$self->delete('emailtype1') if !$self->param('email1');
|
||||
$self->delete('emailtype2') if !$self->param('email2');
|
||||
}
|
||||
|
||||
# Overwrite to ensure nph doesn't get set, and unset HEADERS_ONCE
|
||||
sub multipart_init {
|
||||
my $self = shift;
|
||||
|
||||
# Keys are case-insensitive, map to lowercase
|
||||
my %args = @_;
|
||||
my %param;
|
||||
foreach my $key (keys %args) {
|
||||
$param{lc $key} = $args{$key};
|
||||
}
|
||||
|
||||
# Set the MIME boundary and content-type
|
||||
my $boundary = $param{'-boundary'} || '------- =_aaaaaaaaaa0';
|
||||
delete $param{'-boundary'};
|
||||
$self->{'separator'} = "\r\n--$boundary\r\n";
|
||||
$self->{'final_separator'} = "\r\n--$boundary--\r\n";
|
||||
$param{'-type'} = SERVER_PUSH($boundary);
|
||||
|
||||
# Note: CGI.pm::multipart_init up to v3.04 explicitly set nph to 0
|
||||
# CGI.pm::multipart_init v3.05 explicitly sets nph to 1
|
||||
# CGI.pm's header() sets nph according to a param or $CGI::NPH, which
|
||||
# is the desired behaviour.
|
||||
|
||||
return $self->header(
|
||||
%param,
|
||||
) . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $self->multipart_end;
|
||||
}
|
||||
|
||||
# Have to add the cookies in.
|
||||
sub multipart_start {
|
||||
my $self = shift;
|
||||
|
||||
my %args = @_;
|
||||
|
||||
# CGI.pm::multipart_start doesn't honour its own charset information, so
|
||||
# we do it ourselves here
|
||||
if (defined $self->charset() && defined $args{-type}) {
|
||||
# Remove any existing charset specifier
|
||||
$args{-type} =~ s/;.*$//;
|
||||
# and add the specified one
|
||||
$args{-type} .= '; charset=' . $self->charset();
|
||||
}
|
||||
|
||||
my $headers = $self->SUPER::multipart_start(%args);
|
||||
# Eliminate the one extra CRLF at the end.
|
||||
$headers =~ s/$CGI::CRLF$//;
|
||||
# Add the cookies. We have to do it this way instead of
|
||||
# passing them to multpart_start, because CGI.pm's multipart_start
|
||||
# doesn't understand a '-cookie' argument pointing to an arrayref.
|
||||
foreach my $cookie (@{$self->{Bugzilla_cookie_list}}) {
|
||||
$headers .= "Set-Cookie: ${cookie}${CGI::CRLF}";
|
||||
}
|
||||
$headers .= $CGI::CRLF;
|
||||
return $headers;
|
||||
}
|
||||
|
||||
# Override header so we can add the cookies in
|
||||
sub header {
|
||||
my $self = shift;
|
||||
|
||||
# Add the cookies in if we have any
|
||||
if (scalar(@{$self->{Bugzilla_cookie_list}})) {
|
||||
if (scalar(@_) == 1) {
|
||||
# if there's only one parameter, then it's a Content-Type.
|
||||
# Since we're adding parameters we have to name it.
|
||||
unshift(@_, '-type' => shift(@_));
|
||||
}
|
||||
unshift(@_, '-cookie' => $self->{Bugzilla_cookie_list});
|
||||
}
|
||||
|
||||
return $self->SUPER::header(@_) || "";
|
||||
}
|
||||
|
||||
# CGI.pm is not utf8-aware and passes data as bytes instead of UTF-8 strings.
|
||||
sub param {
|
||||
my $self = shift;
|
||||
if (Bugzilla->params->{'utf8'} && scalar(@_) == 1) {
|
||||
if (wantarray) {
|
||||
return map { _fix_utf8($_) } $self->SUPER::param(@_);
|
||||
}
|
||||
else {
|
||||
return _fix_utf8(scalar $self->SUPER::param(@_));
|
||||
}
|
||||
}
|
||||
return $self->SUPER::param(@_);
|
||||
}
|
||||
|
||||
sub _fix_utf8 {
|
||||
my $input = shift;
|
||||
# The is_utf8 is here in case CGI gets smart about utf8 someday.
|
||||
utf8::decode($input) if defined $input && !utf8::is_utf8($input);
|
||||
return $input;
|
||||
}
|
||||
|
||||
# The various parts of Bugzilla which create cookies don't want to have to
|
||||
# pass them around to all of the callers. Instead, store them locally here,
|
||||
# and then output as required from |header|.
|
||||
sub send_cookie {
|
||||
my $self = shift;
|
||||
|
||||
# Move the param list into a hash for easier handling.
|
||||
my %paramhash;
|
||||
my @paramlist;
|
||||
my ($key, $value);
|
||||
while ($key = shift) {
|
||||
$value = shift;
|
||||
$paramhash{$key} = $value;
|
||||
}
|
||||
|
||||
# Complain if -value is not given or empty (bug 268146).
|
||||
if (!exists($paramhash{'-value'}) || !$paramhash{'-value'}) {
|
||||
ThrowCodeError('cookies_need_value');
|
||||
}
|
||||
|
||||
# Add the default path and the domain in.
|
||||
$paramhash{'-path'} = Bugzilla->params->{'cookiepath'};
|
||||
$paramhash{'-domain'} = Bugzilla->params->{'cookiedomain'}
|
||||
if Bugzilla->params->{'cookiedomain'};
|
||||
|
||||
# Move the param list back into an array for the call to cookie().
|
||||
foreach (keys(%paramhash)) {
|
||||
unshift(@paramlist, $_ => $paramhash{$_});
|
||||
}
|
||||
|
||||
push(@{$self->{'Bugzilla_cookie_list'}}, $self->cookie(@paramlist));
|
||||
}
|
||||
|
||||
# Cookies are removed by setting an expiry date in the past.
|
||||
# This method is a send_cookie wrapper doing exactly this.
|
||||
sub remove_cookie {
|
||||
my $self = shift;
|
||||
my ($cookiename) = (@_);
|
||||
|
||||
# Expire the cookie, giving a non-empty dummy value (bug 268146).
|
||||
$self->send_cookie('-name' => $cookiename,
|
||||
'-expires' => 'Tue, 15-Sep-1998 21:49:00 GMT',
|
||||
'-value' => 'X');
|
||||
}
|
||||
|
||||
# Redirect to https if required
|
||||
sub require_https {
|
||||
my ($self, $url) = @_;
|
||||
# Do not create query string if data submitted via XMLRPC
|
||||
# since we want the data to be resubmitted over POST method.
|
||||
my $query = Bugzilla->usage_mode == USAGE_MODE_WEBSERVICE ? 0 : 1;
|
||||
# XMLRPC clients (SOAP::Lite at least) requires 301 to redirect properly
|
||||
# and do not work with 302.
|
||||
my $status = Bugzilla->usage_mode == USAGE_MODE_WEBSERVICE ? 301 : 302;
|
||||
if (defined $url) {
|
||||
$url .= $self->url('-path_info' => 1, '-query' => $query, '-relative' => 1);
|
||||
} else {
|
||||
$url = $self->self_url;
|
||||
$url =~ s/^http:/https:/i;
|
||||
}
|
||||
print $self->redirect(-location => $url, -status => $status);
|
||||
# When using XML-RPC with mod_perl, we need the headers sent immediately.
|
||||
$self->r->rflush if $ENV{MOD_PERL};
|
||||
exit;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::CGI - CGI handling for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::CGI;
|
||||
|
||||
my $cgi = new Bugzilla::CGI();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This package inherits from the standard CGI module, to provide additional
|
||||
Bugzilla-specific functionality. In general, see L<the CGI.pm docs|CGI> for
|
||||
documention.
|
||||
|
||||
=head1 CHANGES FROM L<CGI.PM|CGI>
|
||||
|
||||
Bugzilla::CGI has some differences from L<CGI.pm|CGI>.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<cgi_error> is automatically checked
|
||||
|
||||
After creating the CGI object, C<Bugzilla::CGI> automatically checks
|
||||
I<cgi_error>, and throws a CodeError if a problem is detected.
|
||||
|
||||
=back
|
||||
|
||||
=head1 ADDITIONAL FUNCTIONS
|
||||
|
||||
I<Bugzilla::CGI> also includes additional functions.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<canonicalise_query(@exclude)>
|
||||
|
||||
This returns a sorted string of the parameters, suitable for use in a url.
|
||||
Values in C<@exclude> are not included in the result.
|
||||
|
||||
=item C<send_cookie>
|
||||
|
||||
This routine is identical to the cookie generation part of CGI.pm's C<cookie>
|
||||
routine, except that it knows about Bugzilla's cookie_path and cookie_domain
|
||||
parameters and takes them into account if necessary.
|
||||
This should be used by all Bugzilla code (instead of C<cookie> or the C<-cookie>
|
||||
argument to C<header>), so that under mod_perl the headers can be sent
|
||||
correctly, using C<print> or the mod_perl APIs as appropriate.
|
||||
|
||||
To remove (expire) a cookie, use C<remove_cookie>.
|
||||
|
||||
=item C<remove_cookie>
|
||||
|
||||
This is a wrapper around send_cookie, setting an expiry date in the past,
|
||||
effectively removing the cookie.
|
||||
|
||||
As its only argument, it takes the name of the cookie to expire.
|
||||
|
||||
=item C<require_https($baseurl)>
|
||||
|
||||
This routine redirects the client to a different location using the https protocol.
|
||||
If the client is using XMLRPC, it will not retain the QUERY_STRING since XMLRPC uses POST.
|
||||
|
||||
It takes an optional argument which will be used as the base URL. If $baseurl
|
||||
is not provided, the current URL is used.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<CGI|CGI>, L<CGI::Cookie|CGI::Cookie>
|
||||
@@ -1,446 +0,0 @@
|
||||
# -*- 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): Gervase Markham <gerv@gerv.net>
|
||||
# Albert Ting <altlst@sonic.net>
|
||||
# A. Karl Kornel <karl@kornel.name>
|
||||
|
||||
use strict;
|
||||
|
||||
# This module represents a chart.
|
||||
#
|
||||
# Note that it is perfectly legal for the 'lines' member variable of this
|
||||
# class (which is an array of Bugzilla::Series objects) to have empty members
|
||||
# in it. If this is true, the 'labels' array will also have empty members at
|
||||
# the same points.
|
||||
package Bugzilla::Chart;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Series;
|
||||
|
||||
use Date::Format;
|
||||
use Date::Parse;
|
||||
use List::Util qw(max);
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
|
||||
# Create a ref to an empty hash and bless it
|
||||
my $self = {};
|
||||
bless($self, $class);
|
||||
|
||||
if ($#_ == 0) {
|
||||
# Construct from a CGI object.
|
||||
$self->init($_[0]);
|
||||
}
|
||||
else {
|
||||
die("CGI object not passed in - invalid number of args \($#_\)($_)");
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub init {
|
||||
my $self = shift;
|
||||
my $cgi = shift;
|
||||
|
||||
# The data structure is a list of lists (lines) of Series objects.
|
||||
# There is a separate list for the labels.
|
||||
#
|
||||
# The URL encoding is:
|
||||
# line0=67&line0=73&line1=81&line2=67...
|
||||
# &label0=B+/+R+/+NEW&label1=...
|
||||
# &select0=1&select3=1...
|
||||
# &cumulate=1&datefrom=2002-02-03&dateto=2002-04-04&ctype=html...
|
||||
# >=1&labelgt=Grand+Total
|
||||
foreach my $param ($cgi->param()) {
|
||||
# Store all the lines
|
||||
if ($param =~ /^line(\d+)$/) {
|
||||
foreach my $series_id ($cgi->param($param)) {
|
||||
detaint_natural($series_id)
|
||||
|| ThrowCodeError("invalid_series_id");
|
||||
my $series = new Bugzilla::Series($series_id);
|
||||
push(@{$self->{'lines'}[$1]}, $series) if $series;
|
||||
}
|
||||
}
|
||||
|
||||
# Store all the labels
|
||||
if ($param =~ /^label(\d+)$/) {
|
||||
$self->{'labels'}[$1] = $cgi->param($param);
|
||||
}
|
||||
}
|
||||
|
||||
# Store the miscellaneous metadata
|
||||
$self->{'cumulate'} = $cgi->param('cumulate') ? 1 : 0;
|
||||
$self->{'gt'} = $cgi->param('gt') ? 1 : 0;
|
||||
$self->{'labelgt'} = $cgi->param('labelgt');
|
||||
$self->{'datefrom'} = $cgi->param('datefrom');
|
||||
$self->{'dateto'} = $cgi->param('dateto');
|
||||
|
||||
# If we are cumulating, a grand total makes no sense
|
||||
$self->{'gt'} = 0 if $self->{'cumulate'};
|
||||
|
||||
# Make sure the dates are ones we are able to interpret
|
||||
foreach my $date ('datefrom', 'dateto') {
|
||||
if ($self->{$date}) {
|
||||
$self->{$date} = str2time($self->{$date})
|
||||
|| ThrowUserError("illegal_date", { date => $self->{$date}});
|
||||
}
|
||||
}
|
||||
|
||||
# datefrom can't be after dateto
|
||||
if ($self->{'datefrom'} && $self->{'dateto'} &&
|
||||
$self->{'datefrom'} > $self->{'dateto'})
|
||||
{
|
||||
ThrowUserError("misarranged_dates",
|
||||
{'datefrom' => $cgi->param('datefrom'),
|
||||
'dateto' => $cgi->param('dateto')});
|
||||
}
|
||||
}
|
||||
|
||||
# Alter Chart so that the selected series are added to it.
|
||||
sub add {
|
||||
my $self = shift;
|
||||
my @series_ids = @_;
|
||||
|
||||
# Get the current size of the series; required for adding Grand Total later
|
||||
my $current_size = scalar($self->getSeriesIDs());
|
||||
|
||||
# Count the number of added series
|
||||
my $added = 0;
|
||||
# Create new Series and push them on to the list of lines.
|
||||
# Note that new lines have no label; the display template is responsible
|
||||
# for inventing something sensible.
|
||||
foreach my $series_id (@series_ids) {
|
||||
my $series = new Bugzilla::Series($series_id);
|
||||
if ($series) {
|
||||
push(@{$self->{'lines'}}, [$series]);
|
||||
push(@{$self->{'labels'}}, "");
|
||||
$added++;
|
||||
}
|
||||
}
|
||||
|
||||
# If we are going from < 2 to >= 2 series, add the Grand Total line.
|
||||
if (!$self->{'gt'}) {
|
||||
if ($current_size < 2 &&
|
||||
$current_size + $added >= 2)
|
||||
{
|
||||
$self->{'gt'} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Alter Chart so that the selections are removed from it.
|
||||
sub remove {
|
||||
my $self = shift;
|
||||
my @line_ids = @_;
|
||||
|
||||
foreach my $line_id (@line_ids) {
|
||||
if ($line_id == 65536) {
|
||||
# Magic value - delete Grand Total.
|
||||
$self->{'gt'} = 0;
|
||||
}
|
||||
else {
|
||||
delete($self->{'lines'}->[$line_id]);
|
||||
delete($self->{'labels'}->[$line_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Alter Chart so that the selections are summed.
|
||||
sub sum {
|
||||
my $self = shift;
|
||||
my @line_ids = @_;
|
||||
|
||||
# We can't add the Grand Total to things.
|
||||
@line_ids = grep(!/^65536$/, @line_ids);
|
||||
|
||||
# We can't add less than two things.
|
||||
return if scalar(@line_ids) < 2;
|
||||
|
||||
my @series;
|
||||
my $label = "";
|
||||
my $biggestlength = 0;
|
||||
|
||||
# We rescue the Series objects of all the series involved in the sum.
|
||||
foreach my $line_id (@line_ids) {
|
||||
my @line = @{$self->{'lines'}->[$line_id]};
|
||||
|
||||
foreach my $series (@line) {
|
||||
push(@series, $series);
|
||||
}
|
||||
|
||||
# We keep the label that labels the line with the most series.
|
||||
if (scalar(@line) > $biggestlength) {
|
||||
$biggestlength = scalar(@line);
|
||||
$label = $self->{'labels'}->[$line_id];
|
||||
}
|
||||
}
|
||||
|
||||
$self->remove(@line_ids);
|
||||
|
||||
push(@{$self->{'lines'}}, \@series);
|
||||
push(@{$self->{'labels'}}, $label);
|
||||
}
|
||||
|
||||
sub data {
|
||||
my $self = shift;
|
||||
$self->{'_data'} ||= $self->readData();
|
||||
return $self->{'_data'};
|
||||
}
|
||||
|
||||
# Convert the Chart's data into a plottable form in $self->{'_data'}.
|
||||
sub readData {
|
||||
my $self = shift;
|
||||
my @data;
|
||||
my @maxvals;
|
||||
|
||||
# Note: you get a bad image if getSeriesIDs returns nothing
|
||||
# We need to handle errors better.
|
||||
my $series_ids = join(",", $self->getSeriesIDs());
|
||||
|
||||
return [] unless $series_ids;
|
||||
|
||||
# Work out the date boundaries for our data.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# The date used is the one given if it's in a sensible range; otherwise,
|
||||
# it's the earliest or latest date in the database as appropriate.
|
||||
my $datefrom = $dbh->selectrow_array("SELECT MIN(series_date) " .
|
||||
"FROM series_data " .
|
||||
"WHERE series_id IN ($series_ids)");
|
||||
$datefrom = str2time($datefrom);
|
||||
|
||||
if ($self->{'datefrom'} && $self->{'datefrom'} > $datefrom) {
|
||||
$datefrom = $self->{'datefrom'};
|
||||
}
|
||||
|
||||
my $dateto = $dbh->selectrow_array("SELECT MAX(series_date) " .
|
||||
"FROM series_data " .
|
||||
"WHERE series_id IN ($series_ids)");
|
||||
$dateto = str2time($dateto);
|
||||
|
||||
if ($self->{'dateto'} && $self->{'dateto'} < $dateto) {
|
||||
$dateto = $self->{'dateto'};
|
||||
}
|
||||
|
||||
# Convert UNIX times back to a date format usable for SQL queries.
|
||||
my $sql_from = time2str('%Y-%m-%d', $datefrom);
|
||||
my $sql_to = time2str('%Y-%m-%d', $dateto);
|
||||
|
||||
# Prepare the query which retrieves the data for each series
|
||||
my $query = "SELECT " . $dbh->sql_to_days('series_date') . " - " .
|
||||
$dbh->sql_to_days('?') . ", series_value " .
|
||||
"FROM series_data " .
|
||||
"WHERE series_id = ? " .
|
||||
"AND series_date >= ?";
|
||||
if ($dateto) {
|
||||
$query .= " AND series_date <= ?";
|
||||
}
|
||||
|
||||
my $sth = $dbh->prepare($query);
|
||||
|
||||
my $gt_index = $self->{'gt'} ? scalar(@{$self->{'lines'}}) : undef;
|
||||
my $line_index = 0;
|
||||
|
||||
$maxvals[$gt_index] = 0 if $gt_index;
|
||||
|
||||
my @datediff_total;
|
||||
|
||||
foreach my $line (@{$self->{'lines'}}) {
|
||||
# Even if we end up with no data, we need an empty arrayref to prevent
|
||||
# errors in the PNG-generating code
|
||||
$data[$line_index] = [];
|
||||
$maxvals[$line_index] = 0;
|
||||
|
||||
foreach my $series (@$line) {
|
||||
|
||||
# Get the data for this series and add it on
|
||||
if ($dateto) {
|
||||
$sth->execute($sql_from, $series->{'series_id'}, $sql_from, $sql_to);
|
||||
}
|
||||
else {
|
||||
$sth->execute($sql_from, $series->{'series_id'}, $sql_from);
|
||||
}
|
||||
my $points = $sth->fetchall_arrayref();
|
||||
|
||||
foreach my $point (@$points) {
|
||||
my ($datediff, $value) = @$point;
|
||||
$data[$line_index][$datediff] ||= 0;
|
||||
$data[$line_index][$datediff] += $value;
|
||||
if ($data[$line_index][$datediff] > $maxvals[$line_index]) {
|
||||
$maxvals[$line_index] = $data[$line_index][$datediff];
|
||||
}
|
||||
|
||||
$datediff_total[$datediff] += $value;
|
||||
|
||||
# Add to the grand total, if we are doing that
|
||||
if ($gt_index) {
|
||||
$data[$gt_index][$datediff] ||= 0;
|
||||
$data[$gt_index][$datediff] += $value;
|
||||
if ($data[$gt_index][$datediff] > $maxvals[$gt_index]) {
|
||||
$maxvals[$gt_index] = $data[$gt_index][$datediff];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# We are done with the series making up this line, go to the next one
|
||||
$line_index++;
|
||||
}
|
||||
|
||||
# calculate maximum y value
|
||||
if ($self->{'cumulate'}) {
|
||||
# Make sure we do not try to take the max of an array with undef values
|
||||
my @processed_datediff;
|
||||
while (@datediff_total) {
|
||||
my $datediff = shift @datediff_total;
|
||||
push @processed_datediff, $datediff if defined($datediff);
|
||||
}
|
||||
$self->{'y_max_value'} = max(@processed_datediff);
|
||||
}
|
||||
else {
|
||||
$self->{'y_max_value'} = max(@maxvals);
|
||||
}
|
||||
$self->{'y_max_value'} |= 1; # For log()
|
||||
|
||||
# Align the max y value:
|
||||
# For one- or two-digit numbers, increase y_max_value until divisible by 8
|
||||
# For larger numbers, see the comments below to figure out what's going on
|
||||
if ($self->{'y_max_value'} < 100) {
|
||||
do {
|
||||
++$self->{'y_max_value'};
|
||||
} while ($self->{'y_max_value'} % 8 != 0);
|
||||
}
|
||||
else {
|
||||
# First, get the # of digits in the y_max_value
|
||||
my $num_digits = 1+int(log($self->{'y_max_value'})/log(10));
|
||||
|
||||
# We want to zero out all but the top 2 digits
|
||||
my $mask_length = $num_digits - 2;
|
||||
$self->{'y_max_value'} /= 10**$mask_length;
|
||||
$self->{'y_max_value'} = int($self->{'y_max_value'});
|
||||
$self->{'y_max_value'} *= 10**$mask_length;
|
||||
|
||||
# Add 10^$mask_length to the max value
|
||||
# Continue to increase until it's divisible by 8 * 10^($mask_length-1)
|
||||
# (Throwing in the -1 keeps at least the smallest digit at zero)
|
||||
do {
|
||||
$self->{'y_max_value'} += 10**$mask_length;
|
||||
} while ($self->{'y_max_value'} % (8*(10**($mask_length-1))) != 0);
|
||||
}
|
||||
|
||||
|
||||
# Add the x-axis labels into the data structure
|
||||
my $date_progression = generateDateProgression($datefrom, $dateto);
|
||||
unshift(@data, $date_progression);
|
||||
|
||||
if ($self->{'gt'}) {
|
||||
# Add Grand Total to label list
|
||||
push(@{$self->{'labels'}}, $self->{'labelgt'});
|
||||
|
||||
$data[$gt_index] ||= [];
|
||||
}
|
||||
|
||||
return \@data;
|
||||
}
|
||||
|
||||
# Flatten the data structure into a list of series_ids
|
||||
sub getSeriesIDs {
|
||||
my $self = shift;
|
||||
my @series_ids;
|
||||
|
||||
foreach my $line (@{$self->{'lines'}}) {
|
||||
foreach my $series (@$line) {
|
||||
push(@series_ids, $series->{'series_id'});
|
||||
}
|
||||
}
|
||||
|
||||
return @series_ids;
|
||||
}
|
||||
|
||||
# Class method to get the data necessary to populate the "select series"
|
||||
# widgets on various pages.
|
||||
sub getVisibleSeries {
|
||||
my %cats;
|
||||
|
||||
# List of groups the user is in; use -1 to make sure it's not empty.
|
||||
my $grouplist = join(", ", (-1, values(%{Bugzilla->user->groups})));
|
||||
|
||||
# Get all visible series
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $serieses = $dbh->selectall_arrayref("SELECT cc1.name, cc2.name, " .
|
||||
"series.name, series.series_id " .
|
||||
"FROM series " .
|
||||
"INNER JOIN series_categories AS cc1 " .
|
||||
" ON series.category = cc1.id " .
|
||||
"INNER JOIN series_categories AS cc2 " .
|
||||
" ON series.subcategory = cc2.id " .
|
||||
"LEFT JOIN category_group_map AS cgm " .
|
||||
" ON series.category = cgm.category_id " .
|
||||
" AND cgm.group_id NOT IN($grouplist) " .
|
||||
"WHERE creator = " . Bugzilla->user->id . " OR " .
|
||||
" cgm.category_id IS NULL " .
|
||||
$dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
|
||||
'series.name'));
|
||||
foreach my $series (@$serieses) {
|
||||
my ($cat, $subcat, $name, $series_id) = @$series;
|
||||
$cats{$cat}{$subcat}{$name} = $series_id;
|
||||
}
|
||||
|
||||
return \%cats;
|
||||
}
|
||||
|
||||
sub generateDateProgression {
|
||||
my ($datefrom, $dateto) = @_;
|
||||
my @progression;
|
||||
|
||||
$dateto = $dateto || time();
|
||||
my $oneday = 60 * 60 * 24;
|
||||
|
||||
# When the from and to dates are converted by str2time(), you end up with
|
||||
# a time figure representing midnight at the beginning of that day. We
|
||||
# adjust the times by 1/3 and 2/3 of a day respectively to prevent
|
||||
# edge conditions in time2str().
|
||||
$datefrom += $oneday / 3;
|
||||
$dateto += (2 * $oneday) / 3;
|
||||
|
||||
while ($datefrom < $dateto) {
|
||||
push (@progression, time2str("%Y-%m-%d", $datefrom));
|
||||
$datefrom += $oneday;
|
||||
}
|
||||
|
||||
return \@progression;
|
||||
}
|
||||
|
||||
sub dump {
|
||||
my $self = shift;
|
||||
|
||||
# Make sure we've read in our data
|
||||
my $data = $self->data;
|
||||
|
||||
require Data::Dumper;
|
||||
print "<pre>Bugzilla::Chart object:\n";
|
||||
print Data::Dumper::Dumper($self);
|
||||
print "</pre>";
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,252 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
#
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Classification;
|
||||
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Product;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
classifications.id
|
||||
classifications.name
|
||||
classifications.description
|
||||
classifications.sortkey
|
||||
);
|
||||
|
||||
our $columns = join(", ", DB_COLUMNS);
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my $self = {};
|
||||
bless($self, $class);
|
||||
return $self->_init(@_);
|
||||
}
|
||||
|
||||
sub _init {
|
||||
my $self = shift;
|
||||
my ($param) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $id = $param unless (ref $param eq 'HASH');
|
||||
my $classification;
|
||||
|
||||
if (defined $id) {
|
||||
detaint_natural($id)
|
||||
|| ThrowCodeError('param_must_be_numeric',
|
||||
{function => 'Bugzilla::Classification::_init'});
|
||||
|
||||
$classification = $dbh->selectrow_hashref(qq{
|
||||
SELECT $columns FROM classifications
|
||||
WHERE id = ?}, undef, $id);
|
||||
|
||||
} elsif (defined $param->{'name'}) {
|
||||
|
||||
trick_taint($param->{'name'});
|
||||
$classification = $dbh->selectrow_hashref(qq{
|
||||
SELECT $columns FROM classifications
|
||||
WHERE name = ?}, undef, $param->{'name'});
|
||||
} else {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'param',
|
||||
function => 'Bugzilla::Classification::_init'});
|
||||
}
|
||||
|
||||
return undef unless (defined $classification);
|
||||
|
||||
foreach my $field (keys %$classification) {
|
||||
$self->{$field} = $classification->{$field};
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub product_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'product_count'}) {
|
||||
$self->{'product_count'} = $dbh->selectrow_array(q{
|
||||
SELECT COUNT(*) FROM products
|
||||
WHERE classification_id = ?}, undef, $self->id) || 0;
|
||||
}
|
||||
return $self->{'product_count'};
|
||||
}
|
||||
|
||||
sub products {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!$self->{'products'}) {
|
||||
my $product_ids = $dbh->selectcol_arrayref(q{
|
||||
SELECT id FROM products
|
||||
WHERE classification_id = ?
|
||||
ORDER BY name}, undef, $self->id);
|
||||
|
||||
$self->{'products'} = Bugzilla::Product->new_from_list($product_ids);
|
||||
}
|
||||
return $self->{'products'};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Accessors ####
|
||||
###############################
|
||||
|
||||
sub id { return $_[0]->{'id'}; }
|
||||
sub name { return $_[0]->{'name'}; }
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
|
||||
###############################
|
||||
#### Subroutines ####
|
||||
###############################
|
||||
|
||||
sub get_all_classifications {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $ids = $dbh->selectcol_arrayref(q{
|
||||
SELECT id FROM classifications ORDER BY sortkey, name});
|
||||
|
||||
my @classifications;
|
||||
foreach my $id (@$ids) {
|
||||
push @classifications, new Bugzilla::Classification($id);
|
||||
}
|
||||
return @classifications;
|
||||
}
|
||||
|
||||
sub check_classification {
|
||||
my ($class_name) = @_;
|
||||
|
||||
unless ($class_name) {
|
||||
ThrowUserError("classification_not_specified");
|
||||
}
|
||||
|
||||
my $classification =
|
||||
new Bugzilla::Classification({name => $class_name});
|
||||
|
||||
unless ($classification) {
|
||||
ThrowUserError("classification_doesnt_exist",
|
||||
{ name => $class_name });
|
||||
}
|
||||
|
||||
return $classification;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Classification - Bugzilla classification class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Classification;
|
||||
|
||||
my $classification = new Bugzilla::Classification(1);
|
||||
my $classification = new Bugzilla::Classification({name => 'Acme'});
|
||||
|
||||
my $id = $classification->id;
|
||||
my $name = $classification->name;
|
||||
my $description = $classification->description;
|
||||
my $product_count = $classification->product_count;
|
||||
my $products = $classification->products;
|
||||
|
||||
my $hash_ref = Bugzilla::Classification::get_all_classifications();
|
||||
my $classification = $hash_ref->{1};
|
||||
|
||||
my $classification =
|
||||
Bugzilla::Classification::check_classification('AcmeClass');
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Classification.pm represents a Classification object.
|
||||
|
||||
A Classification is a higher-level grouping of Products.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<new($param)>
|
||||
|
||||
Description: The constructor is used to load an existing
|
||||
classification by passing a classification
|
||||
id or classification name using a hash.
|
||||
|
||||
Params: $param - If you pass an integer, the integer is the
|
||||
classification_id from the database that we
|
||||
want to read in. If you pass in a hash with
|
||||
'name' key, then the value of the name key
|
||||
is the name of a classification from the DB.
|
||||
|
||||
Returns: A Bugzilla::Classification object.
|
||||
|
||||
=item C<product_count()>
|
||||
|
||||
Description: Returns the total number of products that belong to
|
||||
the classification.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer - The total of products inside the classification.
|
||||
|
||||
=item C<products>
|
||||
|
||||
Description: Returns all products of the classification.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A reference to an array of Bugzilla::Product objects.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_all_classifications()>
|
||||
|
||||
Description: Returns all classifications.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Bugzilla::Classification object list.
|
||||
|
||||
=item C<check_classification($classification_name)>
|
||||
|
||||
Description: Checks if the classification name passed in is a
|
||||
valid classification.
|
||||
|
||||
Params: $classification_name - String with a classification name.
|
||||
|
||||
Returns: Bugzilla::Classification object.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,647 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Akamai Technologies <bugzilla-dev@akamai.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Component;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::FlagType;
|
||||
use Bugzilla::Series;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_TABLE => 'components';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
name
|
||||
product_id
|
||||
initialowner
|
||||
initialqacontact
|
||||
description
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(
|
||||
name
|
||||
product
|
||||
initialowner
|
||||
description
|
||||
);
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
name
|
||||
initialowner
|
||||
initialqacontact
|
||||
description
|
||||
);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
product => \&_check_product,
|
||||
initialowner => \&_check_initialowner,
|
||||
initialqacontact => \&_check_initialqacontact,
|
||||
description => \&_check_description,
|
||||
initial_cc => \&_check_cc_list,
|
||||
};
|
||||
|
||||
use constant UPDATE_VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
};
|
||||
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $param = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $product;
|
||||
if (ref $param) {
|
||||
$product = $param->{product};
|
||||
my $name = $param->{name};
|
||||
if (!defined $product) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'product',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
if (!defined $name) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'name',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
|
||||
my $condition = 'product_id = ? AND name = ?';
|
||||
my @values = ($product->id, $name);
|
||||
$param = { condition => $condition, values => \@values };
|
||||
}
|
||||
|
||||
unshift @_, $param;
|
||||
my $component = $class->SUPER::new(@_);
|
||||
# Add the product object as attribute only if the component exists.
|
||||
$component->{product} = $product if ($component && $product);
|
||||
return $component;
|
||||
}
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
$class->check_required_create_fields(@_);
|
||||
my $params = $class->run_create_validators(@_);
|
||||
my $cc_list = delete $params->{initial_cc};
|
||||
|
||||
my $component = $class->insert_create_data($params);
|
||||
|
||||
# We still have to fill the component_cc table.
|
||||
$component->_update_cc_list($cc_list);
|
||||
|
||||
# Create series for the new component.
|
||||
$component->_create_series();
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
return $component;
|
||||
}
|
||||
|
||||
sub run_create_validators {
|
||||
my $class = shift;
|
||||
my $params = $class->SUPER::run_create_validators(@_);
|
||||
|
||||
my $product = delete $params->{product};
|
||||
$params->{product_id} = $product->id;
|
||||
$params->{name} = $class->_check_name($params->{name}, $product);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my $changes = $self->SUPER::update(@_);
|
||||
|
||||
# Update the component_cc table if necessary.
|
||||
if (defined $self->{cc_ids}) {
|
||||
my $diff = $self->_update_cc_list($self->{cc_ids});
|
||||
$changes->{cc_list} = $diff if defined $diff;
|
||||
}
|
||||
return $changes;
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
if ($self->bug_count) {
|
||||
if (Bugzilla->params->{'allowbugdeletion'}) {
|
||||
require Bugzilla::Bug;
|
||||
foreach my $bug_id (@{$self->bug_ids}) {
|
||||
# Note: We allow admins to delete bugs even if they can't
|
||||
# see them, as long as they can see the product.
|
||||
my $bug = new Bugzilla::Bug($bug_id);
|
||||
$bug->remove_from_db();
|
||||
}
|
||||
} else {
|
||||
ThrowUserError('component_has_bugs', {nb => $self->bug_count});
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->do('DELETE FROM flaginclusions WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
$dbh->do('DELETE FROM flagexclusions WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
$dbh->do('DELETE FROM component_cc WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
$dbh->do('DELETE FROM components WHERE id = ?', undef, $self->id);
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
}
|
||||
|
||||
################################
|
||||
# Validators
|
||||
################################
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name, $product) = @_;
|
||||
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError('component_blank_name');
|
||||
|
||||
if (length($name) > MAX_COMPONENT_SIZE) {
|
||||
ThrowUserError('component_name_too_long', {'name' => $name});
|
||||
}
|
||||
|
||||
$product = $invocant->product if (ref $invocant);
|
||||
my $component = new Bugzilla::Component({product => $product, name => $name});
|
||||
if ($component && (!ref $invocant || $component->id != $invocant->id)) {
|
||||
ThrowUserError('component_already_exists', { name => $component->name,
|
||||
product => $product });
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_description {
|
||||
my ($invocant, $description) = @_;
|
||||
|
||||
$description = trim($description);
|
||||
$description || ThrowUserError('component_blank_description');
|
||||
return $description;
|
||||
}
|
||||
|
||||
sub _check_initialowner {
|
||||
my ($invocant, $owner) = @_;
|
||||
|
||||
$owner || ThrowUserError('component_need_initialowner');
|
||||
my $owner_id = Bugzilla::User->check($owner)->id;
|
||||
return $owner_id;
|
||||
}
|
||||
|
||||
sub _check_initialqacontact {
|
||||
my ($invocant, $qa_contact) = @_;
|
||||
|
||||
my $qa_contact_id;
|
||||
if (Bugzilla->params->{'useqacontact'}) {
|
||||
$qa_contact_id = Bugzilla::User->check($qa_contact)->id if $qa_contact;
|
||||
}
|
||||
elsif (ref $invocant) {
|
||||
$qa_contact_id = $invocant->{initialqacontact};
|
||||
}
|
||||
return $qa_contact_id;
|
||||
}
|
||||
|
||||
sub _check_product {
|
||||
my ($invocant, $product) = @_;
|
||||
return Bugzilla->user->check_can_admin_product($product->name);
|
||||
}
|
||||
|
||||
sub _check_cc_list {
|
||||
my ($invocant, $cc_list) = @_;
|
||||
|
||||
my %cc_ids;
|
||||
foreach my $cc (@$cc_list) {
|
||||
my $id = login_to_id($cc, THROW_ERROR);
|
||||
$cc_ids{$id} = 1;
|
||||
}
|
||||
return [keys %cc_ids];
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub _update_cc_list {
|
||||
my ($self, $cc_list) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $old_cc_list =
|
||||
$dbh->selectcol_arrayref('SELECT user_id FROM component_cc
|
||||
WHERE component_id = ?', undef, $self->id);
|
||||
|
||||
my ($removed, $added) = diff_arrays($old_cc_list, $cc_list);
|
||||
my $diff;
|
||||
if (scalar @$removed || scalar @$added) {
|
||||
$diff = [join(', ', @$removed), join(', ', @$added)];
|
||||
}
|
||||
|
||||
$dbh->do('DELETE FROM component_cc WHERE component_id = ?', undef, $self->id);
|
||||
|
||||
my $sth = $dbh->prepare('INSERT INTO component_cc
|
||||
(user_id, component_id) VALUES (?, ?)');
|
||||
$sth->execute($_, $self->id) foreach (@$cc_list);
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
sub _create_series {
|
||||
my $self = shift;
|
||||
|
||||
# Insert default charting queries for this product.
|
||||
# If they aren't using charting, this won't do any harm.
|
||||
my $prodcomp = "&product=" . url_quote($self->product->name) .
|
||||
"&component=" . url_quote($self->name);
|
||||
|
||||
my $open_query = 'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.' .
|
||||
$prodcomp;
|
||||
my $nonopen_query = 'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.' .
|
||||
$prodcomp;
|
||||
|
||||
my @series = ([get_text('series_all_open'), $open_query],
|
||||
[get_text('series_all_closed'), $nonopen_query]);
|
||||
|
||||
foreach my $sdata (@series) {
|
||||
my $series = new Bugzilla::Series(undef, $self->product->name,
|
||||
$self->name, $sdata->[0],
|
||||
Bugzilla->user->id, 1, $sdata->[1], 1);
|
||||
$series->writeToDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
sub set_default_assignee {
|
||||
my ($self, $owner) = @_;
|
||||
|
||||
$self->set('initialowner', $owner);
|
||||
# Reset the default owner object.
|
||||
delete $self->{default_assignee};
|
||||
}
|
||||
sub set_default_qa_contact {
|
||||
my ($self, $qa_contact) = @_;
|
||||
|
||||
$self->set('initialqacontact', $qa_contact);
|
||||
# Reset the default QA contact object.
|
||||
delete $self->{default_qa_contact};
|
||||
}
|
||||
sub set_cc_list {
|
||||
my ($self, $cc_list) = @_;
|
||||
|
||||
$self->{cc_ids} = $self->_check_cc_list($cc_list);
|
||||
# Reset the list of CC user objects.
|
||||
delete $self->{initial_cc};
|
||||
}
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_count'}) {
|
||||
$self->{'bug_count'} = $dbh->selectrow_array(q{
|
||||
SELECT COUNT(*) FROM bugs
|
||||
WHERE component_id = ?}, undef, $self->id) || 0;
|
||||
}
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
sub bug_ids {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bugs_ids'}) {
|
||||
$self->{'bugs_ids'} = $dbh->selectcol_arrayref(q{
|
||||
SELECT bug_id FROM bugs
|
||||
WHERE component_id = ?}, undef, $self->id);
|
||||
}
|
||||
return $self->{'bugs_ids'};
|
||||
}
|
||||
|
||||
sub default_assignee {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'default_assignee'}) {
|
||||
$self->{'default_assignee'} =
|
||||
new Bugzilla::User($self->{'initialowner'});
|
||||
}
|
||||
return $self->{'default_assignee'};
|
||||
}
|
||||
|
||||
sub default_qa_contact {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'default_qa_contact'}) {
|
||||
$self->{'default_qa_contact'} =
|
||||
new Bugzilla::User($self->{'initialqacontact'});
|
||||
}
|
||||
return $self->{'default_qa_contact'};
|
||||
}
|
||||
|
||||
sub flag_types {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'flag_types'}) {
|
||||
$self->{'flag_types'} = {};
|
||||
$self->{'flag_types'}->{'bug'} =
|
||||
Bugzilla::FlagType::match({ 'target_type' => 'bug',
|
||||
'product_id' => $self->product_id,
|
||||
'component_id' => $self->id });
|
||||
|
||||
$self->{'flag_types'}->{'attachment'} =
|
||||
Bugzilla::FlagType::match({ 'target_type' => 'attachment',
|
||||
'product_id' => $self->product_id,
|
||||
'component_id' => $self->id });
|
||||
}
|
||||
return $self->{'flag_types'};
|
||||
}
|
||||
|
||||
sub initial_cc {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'initial_cc'}) {
|
||||
# If set_cc_list() has been called but data are not yet written
|
||||
# into the DB, we want the new values defined by it.
|
||||
my $cc_ids = $self->{cc_ids}
|
||||
|| $dbh->selectcol_arrayref('SELECT user_id FROM component_cc
|
||||
WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
|
||||
$self->{'initial_cc'} = Bugzilla::User->new_from_list($cc_ids);
|
||||
}
|
||||
return $self->{'initial_cc'};
|
||||
}
|
||||
|
||||
sub product {
|
||||
my $self = shift;
|
||||
if (!defined $self->{'product'}) {
|
||||
require Bugzilla::Product; # We cannot |use| it.
|
||||
$self->{'product'} = new Bugzilla::Product($self->product_id);
|
||||
}
|
||||
return $self->{'product'};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Accessors ####
|
||||
###############################
|
||||
|
||||
sub id { return $_[0]->{'id'}; }
|
||||
sub name { return $_[0]->{'name'}; }
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub product_id { return $_[0]->{'product_id'}; }
|
||||
|
||||
###############################
|
||||
#### Subroutines ####
|
||||
###############################
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Component - Bugzilla product component class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Component;
|
||||
|
||||
my $component = new Bugzilla::Component($comp_id);
|
||||
my $component = new Bugzilla::Component({ product => $product, name => $name });
|
||||
|
||||
my $bug_count = $component->bug_count();
|
||||
my $bug_ids = $component->bug_ids();
|
||||
my $id = $component->id;
|
||||
my $name = $component->name;
|
||||
my $description = $component->description;
|
||||
my $product_id = $component->product_id;
|
||||
my $default_assignee = $component->default_assignee;
|
||||
my $default_qa_contact = $component->default_qa_contact;
|
||||
my $initial_cc = $component->initial_cc;
|
||||
my $product = $component->product;
|
||||
my $bug_flag_types = $component->flag_types->{'bug'};
|
||||
my $attach_flag_types = $component->flag_types->{'attachment'};
|
||||
|
||||
my $component = Bugzilla::Component->check({ product => $product, name => $name });
|
||||
|
||||
my $component =
|
||||
Bugzilla::Component->create({ name => $name,
|
||||
product => $product,
|
||||
initialowner => $user_login1,
|
||||
initialqacontact => $user_login2,
|
||||
description => $description});
|
||||
|
||||
$component->set_name($new_name);
|
||||
$component->set_description($new_description);
|
||||
$component->set_default_assignee($new_login_name);
|
||||
$component->set_default_qa_contact($new_login_name);
|
||||
$component->set_cc_list(\@new_login_names);
|
||||
$component->update();
|
||||
|
||||
$component->remove_from_db;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Component.pm represents a Product Component object.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<new($param)>
|
||||
|
||||
Description: The constructor is used to load an existing component
|
||||
by passing a component ID or a hash with the product
|
||||
object the component belongs to and the component name.
|
||||
|
||||
Params: $param - If you pass an integer, the integer is the
|
||||
component ID from the database that we want to
|
||||
read in. If you pass in a hash with the 'name'
|
||||
and 'product' keys, then the value of the name
|
||||
key is the name of a component being in the given
|
||||
product.
|
||||
|
||||
Returns: A Bugzilla::Component object.
|
||||
|
||||
=item C<bug_count()>
|
||||
|
||||
Description: Returns the total of bugs that belong to the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer with the number of bugs.
|
||||
|
||||
=item C<bugs_ids()>
|
||||
|
||||
Description: Returns all bug IDs that belong to the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A reference to an array of bug IDs.
|
||||
|
||||
=item C<default_assignee()>
|
||||
|
||||
Description: Returns a user object that represents the default assignee for
|
||||
the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::User object.
|
||||
|
||||
=item C<default_qa_contact()>
|
||||
|
||||
Description: Returns a user object that represents the default QA contact for
|
||||
the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::User object.
|
||||
|
||||
=item C<initial_cc>
|
||||
|
||||
Description: Returns a list of user objects representing users being
|
||||
in the initial CC list.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: An arrayref of L<Bugzilla::User> objects.
|
||||
|
||||
=item C<flag_types()>
|
||||
|
||||
Description: Returns all bug and attachment flagtypes available for
|
||||
the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Two references to an array of flagtype objects.
|
||||
|
||||
=item C<product()>
|
||||
|
||||
Description: Returns the product the component belongs to.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::Product object.
|
||||
|
||||
=item C<set_name($new_name)>
|
||||
|
||||
Description: Changes the name of the component.
|
||||
|
||||
Params: $new_name - new name of the component (string). This name
|
||||
must be unique within the product.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_description($new_desc)>
|
||||
|
||||
Description: Changes the description of the component.
|
||||
|
||||
Params: $new_desc - new description of the component (string).
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_default_assignee($new_assignee)>
|
||||
|
||||
Description: Changes the default assignee of the component.
|
||||
|
||||
Params: $new_owner - login name of the new default assignee of
|
||||
the component (string). This user account
|
||||
must already exist.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_default_qa_contact($new_qa_contact)>
|
||||
|
||||
Description: Changes the default QA contact of the component.
|
||||
|
||||
Params: $new_qa_contact - login name of the new QA contact of
|
||||
the component (string). This user
|
||||
account must already exist.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_cc_list(\@cc_list)>
|
||||
|
||||
Description: Changes the list of users being in the CC list by default.
|
||||
|
||||
Params: \@cc_list - list of login names (string). All the user
|
||||
accounts must already exist.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<update()>
|
||||
|
||||
Description: Write changes made to the component into the DB.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A hashref with changes made to the component object.
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: Deletes the current component from the DB. The object itself
|
||||
is not destroyed.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<create(\%params)>
|
||||
|
||||
Description: Create a new component for the given product.
|
||||
|
||||
Params: The hashref must have the following keys:
|
||||
name - name of the new component (string). This name
|
||||
must be unique within the product.
|
||||
product - a Bugzilla::Product object to which
|
||||
the Component is being added.
|
||||
description - description of the new component (string).
|
||||
initialowner - login name of the default assignee (string).
|
||||
The following keys are optional:
|
||||
initiaqacontact - login name of the default QA contact (string),
|
||||
or an empty string to clear it.
|
||||
initial_cc - an arrayref of login names to add to the
|
||||
CC list by default.
|
||||
|
||||
Returns: A Bugzilla::Component object.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,404 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jake <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
package Bugzilla::Config;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter);
|
||||
use Bugzilla::Constants;
|
||||
use Data::Dumper;
|
||||
use File::Temp;
|
||||
|
||||
# Don't export localvars by default - people should have to explicitly
|
||||
# ask for it, as a (probably futile) attempt to stop code using it
|
||||
# when it shouldn't
|
||||
%Bugzilla::Config::EXPORT_TAGS =
|
||||
(
|
||||
admin => [qw(update_params SetParam write_params)],
|
||||
);
|
||||
Exporter::export_ok_tags('admin');
|
||||
|
||||
use vars qw(@param_list);
|
||||
|
||||
# INITIALISATION CODE
|
||||
# Perl throws a warning if we use bz_locations() directly after do.
|
||||
our %params;
|
||||
# Load in the param definitions
|
||||
sub _load_params {
|
||||
my $panels = param_panels();
|
||||
foreach my $panel (keys %$panels) {
|
||||
my $module = $panels->{$panel};
|
||||
eval("require $module") || die $@;
|
||||
my @new_param_list = "$module"->get_param_list();
|
||||
foreach my $item (@new_param_list) {
|
||||
$params{$item->{'name'}} = $item;
|
||||
}
|
||||
push(@param_list, @new_param_list);
|
||||
}
|
||||
}
|
||||
# END INIT CODE
|
||||
|
||||
# Subroutines go here
|
||||
|
||||
sub param_panels {
|
||||
my $param_panels = {};
|
||||
my $libpath = bz_locations()->{'libpath'};
|
||||
foreach my $item ((glob "$libpath/Bugzilla/Config/*.pm")) {
|
||||
$item =~ m#/([^/]+)\.pm$#;
|
||||
my $module = $1;
|
||||
$param_panels->{$module} = "Bugzilla::Config::$module" unless $module eq 'Common';
|
||||
}
|
||||
# Now check for any hooked params
|
||||
Bugzilla::Hook::process('config', { config => $param_panels });
|
||||
return $param_panels;
|
||||
}
|
||||
|
||||
sub SetParam {
|
||||
my ($name, $value) = @_;
|
||||
|
||||
_load_params unless %params;
|
||||
die "Unknown param $name" unless (exists $params{$name});
|
||||
|
||||
my $entry = $params{$name};
|
||||
|
||||
# sanity check the value
|
||||
|
||||
# XXX - This runs the checks. Which would be good, except that
|
||||
# check_shadowdb creates the database as a side effect, and so the
|
||||
# checker fails the second time around...
|
||||
if ($name ne 'shadowdb' && exists $entry->{'checker'}) {
|
||||
my $err = $entry->{'checker'}->($value, $entry);
|
||||
die "Param $name is not valid: $err" unless $err eq '';
|
||||
}
|
||||
|
||||
Bugzilla->params->{$name} = $value;
|
||||
}
|
||||
|
||||
sub update_params {
|
||||
my ($params) = @_;
|
||||
my $answer = Bugzilla->installation_answers;
|
||||
|
||||
my $param = read_param_file();
|
||||
|
||||
# If we didn't return any param values, then this is a new installation.
|
||||
my $new_install = !(keys %$param);
|
||||
|
||||
# --- UPDATE OLD PARAMS ---
|
||||
|
||||
# Old Bugzilla versions stored the version number in the params file
|
||||
# We don't want it, so get rid of it
|
||||
delete $param->{'version'};
|
||||
|
||||
# Change from usebrowserinfo to defaultplatform/defaultopsys combo
|
||||
if (exists $param->{'usebrowserinfo'}) {
|
||||
if (!$param->{'usebrowserinfo'}) {
|
||||
if (!exists $param->{'defaultplatform'}) {
|
||||
$param->{'defaultplatform'} = 'Other';
|
||||
}
|
||||
if (!exists $param->{'defaultopsys'}) {
|
||||
$param->{'defaultopsys'} = 'Other';
|
||||
}
|
||||
}
|
||||
delete $param->{'usebrowserinfo'};
|
||||
}
|
||||
|
||||
# Change from a boolean for quips to multi-state
|
||||
if (exists $param->{'usequip'} && !exists $param->{'enablequips'}) {
|
||||
$param->{'enablequips'} = $param->{'usequip'} ? 'on' : 'off';
|
||||
delete $param->{'usequip'};
|
||||
}
|
||||
|
||||
# Change from old product groups to controls for group_control_map
|
||||
# 2002-10-14 bug 147275 bugreport@peshkin.net
|
||||
if (exists $param->{'usebuggroups'} &&
|
||||
!exists $param->{'makeproductgroups'})
|
||||
{
|
||||
$param->{'makeproductgroups'} = $param->{'usebuggroups'};
|
||||
}
|
||||
if (exists $param->{'usebuggroupsentry'}
|
||||
&& !exists $param->{'useentrygroupdefault'}) {
|
||||
$param->{'useentrygroupdefault'} = $param->{'usebuggroupsentry'};
|
||||
}
|
||||
|
||||
# Modularise auth code
|
||||
if (exists $param->{'useLDAP'} && !exists $param->{'loginmethod'}) {
|
||||
$param->{'loginmethod'} = $param->{'useLDAP'} ? "LDAP" : "DB";
|
||||
}
|
||||
|
||||
# set verify method to whatever loginmethod was
|
||||
if (exists $param->{'loginmethod'}
|
||||
&& !exists $param->{'user_verify_class'})
|
||||
{
|
||||
$param->{'user_verify_class'} = $param->{'loginmethod'};
|
||||
delete $param->{'loginmethod'};
|
||||
}
|
||||
|
||||
# Remove quip-display control from parameters
|
||||
# and give it to users via User Settings (Bug 41972)
|
||||
if ( exists $param->{'enablequips'}
|
||||
&& !exists $param->{'quip_list_entry_control'})
|
||||
{
|
||||
my $new_value;
|
||||
($param->{'enablequips'} eq 'on') && do {$new_value = 'open';};
|
||||
($param->{'enablequips'} eq 'approved') && do {$new_value = 'moderated';};
|
||||
($param->{'enablequips'} eq 'frozen') && do {$new_value = 'closed';};
|
||||
($param->{'enablequips'} eq 'off') && do {$new_value = 'closed';};
|
||||
$param->{'quip_list_entry_control'} = $new_value;
|
||||
delete $param->{'enablequips'};
|
||||
}
|
||||
|
||||
# Old mail_delivery_method choices contained no uppercase characters
|
||||
if (exists $param->{'mail_delivery_method'}
|
||||
&& $param->{'mail_delivery_method'} !~ /[A-Z]/) {
|
||||
my $method = $param->{'mail_delivery_method'};
|
||||
my %translation = (
|
||||
'sendmail' => 'Sendmail',
|
||||
'smtp' => 'SMTP',
|
||||
'qmail' => 'Qmail',
|
||||
'testfile' => 'Test',
|
||||
'none' => 'None');
|
||||
$param->{'mail_delivery_method'} = $translation{$method};
|
||||
}
|
||||
|
||||
# --- DEFAULTS FOR NEW PARAMS ---
|
||||
|
||||
_load_params unless %params;
|
||||
foreach my $item (@param_list) {
|
||||
my $name = $item->{'name'};
|
||||
unless (exists $param->{$name}) {
|
||||
print "New parameter: $name\n" unless $new_install;
|
||||
$param->{$name} = $answer->{$name} || $item->{'default'};
|
||||
}
|
||||
}
|
||||
|
||||
$param->{'utf8'} = 1 if $new_install;
|
||||
|
||||
# --- REMOVE OLD PARAMS ---
|
||||
|
||||
my %oldparams;
|
||||
# Remove any old params, put them in old-params.txt
|
||||
foreach my $item (keys %$param) {
|
||||
if (!grep($_ eq $item, map ($_->{'name'}, @param_list))) {
|
||||
$oldparams{$item} = $param->{$item};
|
||||
delete $param->{$item};
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar(keys %oldparams)) {
|
||||
my $op_file = new IO::File('old-params.txt', '>>', 0600)
|
||||
|| die "old-params.txt: $!";
|
||||
|
||||
print "The following parameters are no longer used in Bugzilla,",
|
||||
" and so have been\nmoved from your parameters file into",
|
||||
" old-params.txt:\n";
|
||||
|
||||
local $Data::Dumper::Terse = 1;
|
||||
local $Data::Dumper::Indent = 0;
|
||||
|
||||
my $comma = "";
|
||||
foreach my $item (keys %oldparams) {
|
||||
print $op_file "\n\n$item:\n" . Data::Dumper->Dump([$oldparams{$item}]) . "\n";
|
||||
print "${comma}$item";
|
||||
$comma = ", ";
|
||||
}
|
||||
print "\n";
|
||||
$op_file->close;
|
||||
}
|
||||
|
||||
if (ON_WINDOWS && !-e SENDMAIL_EXE
|
||||
&& $param->{'mail_delivery_method'} eq 'Sendmail')
|
||||
{
|
||||
my $smtp = $answer->{'SMTP_SERVER'};
|
||||
if (!$smtp) {
|
||||
print "\nBugzilla requires an SMTP server to function on",
|
||||
" Windows.\nPlease enter your SMTP server's hostname: ";
|
||||
$smtp = <STDIN>;
|
||||
chomp $smtp;
|
||||
if ($smtp) {
|
||||
$param->{'smtpserver'} = $smtp;
|
||||
}
|
||||
else {
|
||||
print "\nWarning: No SMTP Server provided, defaulting to",
|
||||
" localhost\n";
|
||||
}
|
||||
}
|
||||
|
||||
$param->{'mail_delivery_method'} = 'SMTP';
|
||||
}
|
||||
|
||||
write_params($param);
|
||||
|
||||
# Return deleted params and values so that checksetup.pl has a chance
|
||||
# to convert old params to new data.
|
||||
return %oldparams;
|
||||
}
|
||||
|
||||
sub write_params {
|
||||
my ($param_data) = @_;
|
||||
$param_data ||= Bugzilla->params;
|
||||
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
my $param_file = "$datadir/params";
|
||||
|
||||
local $Data::Dumper::Sortkeys = 1;
|
||||
|
||||
my ($fh, $tmpname) = File::Temp::tempfile('params.XXXXX',
|
||||
DIR => $datadir );
|
||||
|
||||
print $fh (Data::Dumper->Dump([$param_data], ['*param']))
|
||||
|| die "Can't write param file: $!";
|
||||
|
||||
close $fh;
|
||||
|
||||
rename $tmpname, $param_file
|
||||
|| die "Can't rename $tmpname to $param_file: $!";
|
||||
|
||||
ChmodDataFile($param_file, 0666);
|
||||
|
||||
# And now we have to reset the params cache so that Bugzilla will re-read
|
||||
# them.
|
||||
delete Bugzilla->request_cache->{params};
|
||||
}
|
||||
|
||||
# Some files in the data directory must be world readable if and only if
|
||||
# we don't have a webserver group. Call this function to do this.
|
||||
# This will become a private function once all the datafile handling stuff
|
||||
# moves into this package
|
||||
|
||||
# This sub is not perldoc'd for that reason - noone should know about it
|
||||
sub ChmodDataFile {
|
||||
my ($file, $mask) = @_;
|
||||
my $perm = 0770;
|
||||
if ((stat(bz_locations()->{'datadir'}))[2] & 0002) {
|
||||
$perm = 0777;
|
||||
}
|
||||
$perm = $perm & $mask;
|
||||
chmod $perm,$file;
|
||||
}
|
||||
|
||||
sub read_param_file {
|
||||
my %params;
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
if (-e "$datadir/params") {
|
||||
# Note that checksetup.pl sets file permissions on '$datadir/params'
|
||||
|
||||
# Using Safe mode is _not_ a guarantee of safety if someone does
|
||||
# manage to write to the file. However, it won't hurt...
|
||||
# See bug 165144 for not needing to eval this at all
|
||||
my $s = new Safe;
|
||||
|
||||
$s->rdo("$datadir/params");
|
||||
die "Error reading $datadir/params: $!" if $!;
|
||||
die "Error evaluating $datadir/params: $@" if $@;
|
||||
|
||||
# Now read the param back out from the sandbox
|
||||
%params = %{$s->varglob('param')};
|
||||
}
|
||||
elsif ($ENV{'SERVER_SOFTWARE'}) {
|
||||
# We're in a CGI, but the params file doesn't exist. We can't
|
||||
# Template Toolkit, or even install_string, since checksetup
|
||||
# might not have thrown an error. Bugzilla::CGI->new
|
||||
# hasn't even been called yet, so we manually use CGI::Carp here
|
||||
# so that the user sees the error.
|
||||
require CGI::Carp;
|
||||
CGI::Carp->import('fatalsToBrowser');
|
||||
die "The $datadir/params file does not exist."
|
||||
. ' You probably need to run checksetup.pl.',
|
||||
}
|
||||
return \%params;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Config - Configuration parameters for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
# Administration functions
|
||||
use Bugzilla::Config qw(:admin);
|
||||
|
||||
update_params();
|
||||
SetParam($param, $value);
|
||||
write_params();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This package contains ways to access Bugzilla configuration parameters.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=head2 Parameters
|
||||
|
||||
Parameters can be set, retrieved, and updated.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<SetParam($name, $value)>
|
||||
|
||||
Sets the param named $name to $value. Values are checked using the checker
|
||||
function for the given param if one exists.
|
||||
|
||||
=item C<update_params()>
|
||||
|
||||
Updates the parameters, by transitioning old params to new formats, setting
|
||||
defaults for new params, and removing obsolete ones. Used by F<checksetup.pl>
|
||||
in the process of an installation or upgrade.
|
||||
|
||||
Prints out information about what it's doing, if it makes any changes.
|
||||
|
||||
May prompt the user for input, if certain required parameters are not
|
||||
specified.
|
||||
|
||||
=item C<write_params($params)>
|
||||
|
||||
Description: Writes the parameters to disk.
|
||||
|
||||
Params: C<$params> (optional) - A hashref to write to the disk
|
||||
instead of C<Bugzilla->params>. Used only by
|
||||
C<update_params>.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=item C<read_param_file()>
|
||||
|
||||
Description: Most callers should never need this. This is used
|
||||
by C<Bugzilla->params> to directly read C<$datadir/params>
|
||||
and load it into memory. Use C<Bugzilla->params> instead.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: A hashref containing the current params in C<$datadir/params>.
|
||||
|
||||
=back
|
||||
@@ -1,69 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Admin;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Admin::sortkey = "01";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'allowbugdeletion',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'allowemailchange',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'allowuserdeletion',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'supportwatchers',
|
||||
type => 'b',
|
||||
default => 0
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,91 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Attachment;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Attachment::sortkey = "025";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'allow_attachment_deletion',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
{
|
||||
name => 'allow_attach_url',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
{
|
||||
name => 'maxpatchsize',
|
||||
type => 't',
|
||||
default => '1000',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'maxattachmentsize',
|
||||
type => 't',
|
||||
default => '1000',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
# The maximum size (in bytes) for patches and non-patch attachments.
|
||||
# The default limit is 1000KB, which is 24KB less than mysql's default
|
||||
# maximum packet size (which determines how much data can be sent in a
|
||||
# single mysql packet and thus how much data can be inserted into the
|
||||
# database) to provide breathing space for the data in other fields of
|
||||
# the attachment record as well as any mysql packet overhead (I don't
|
||||
# know of any, but I suspect there may be some.)
|
||||
|
||||
{
|
||||
name => 'maxlocalattachment',
|
||||
type => 't',
|
||||
default => '0',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'convert_uncompressed_images',
|
||||
type => 'b',
|
||||
default => 0,
|
||||
checker => \&check_image_converter
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,135 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Auth;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Auth::sortkey = "02";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'auth_env_id',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'auth_env_email',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'auth_env_realname',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
# XXX in the future:
|
||||
#
|
||||
# user_verify_class and user_info_class should have choices gathered from
|
||||
# whatever sits in their respective directories
|
||||
#
|
||||
# rather than comma-separated lists, these two should eventually become
|
||||
# arrays, but that requires alterations to editparams first
|
||||
|
||||
{
|
||||
name => 'user_info_class',
|
||||
type => 's',
|
||||
choices => [ 'CGI', 'Env', 'Env,CGI' ],
|
||||
default => 'CGI',
|
||||
checker => \&check_multi
|
||||
},
|
||||
|
||||
{
|
||||
name => 'user_verify_class',
|
||||
type => 'o',
|
||||
choices => [ 'DB', 'RADIUS', 'LDAP' ],
|
||||
default => 'DB',
|
||||
checker => \&check_user_verify_class
|
||||
},
|
||||
|
||||
{
|
||||
name => 'rememberlogin',
|
||||
type => 's',
|
||||
choices => ['on', 'defaulton', 'defaultoff', 'off'],
|
||||
default => 'on',
|
||||
checker => \&check_multi
|
||||
},
|
||||
|
||||
{
|
||||
name => 'loginnetmask',
|
||||
type => 't',
|
||||
default => '0',
|
||||
checker => \&check_netmask
|
||||
},
|
||||
|
||||
{
|
||||
name => 'requirelogin',
|
||||
type => 'b',
|
||||
default => '0'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'emailregexp',
|
||||
type => 't',
|
||||
default => q:^[\\w\\.\\+\\-=]+@[\\w\\.\\-]+\\.[\\w\\-]+$:,
|
||||
checker => \&check_regexp
|
||||
},
|
||||
|
||||
{
|
||||
name => 'emailregexpdesc',
|
||||
type => 'l',
|
||||
default => 'A legal address must contain exactly one \'@\', and at least ' .
|
||||
'one \'.\' after the @.'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'emailsuffix',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'createemailregexp',
|
||||
type => 't',
|
||||
default => q:.*:,
|
||||
checker => \&check_regexp
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,115 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::BugChange;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
use Bugzilla::Status;
|
||||
|
||||
$Bugzilla::Config::BugChange::sortkey = "03";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
|
||||
# Hardcoded bug statuses which existed before Bugzilla 3.1.
|
||||
my @closed_bug_statuses = ('RESOLVED', 'VERIFIED', 'CLOSED');
|
||||
|
||||
# If we are upgrading from 3.0 or older, bug statuses are not customisable
|
||||
# and bug_status.is_open is not yet defined (hence the eval), so we use
|
||||
# the bug statuses above as they are still hardcoded.
|
||||
eval {
|
||||
my @current_closed_states = map {$_->name} closed_bug_statuses();
|
||||
# If no closed state was found, use the default list above.
|
||||
@closed_bug_statuses = @current_closed_states if scalar(@current_closed_states);
|
||||
};
|
||||
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'duplicate_or_move_bug_status',
|
||||
type => 's',
|
||||
choices => \@closed_bug_statuses,
|
||||
default => $closed_bug_statuses[0],
|
||||
checker => \&check_bug_status
|
||||
},
|
||||
|
||||
{
|
||||
name => 'letsubmitterchoosepriority',
|
||||
type => 'b',
|
||||
default => 1
|
||||
},
|
||||
|
||||
{
|
||||
name => 'letsubmitterchoosemilestone',
|
||||
type => 'b',
|
||||
default => 1
|
||||
},
|
||||
|
||||
{
|
||||
name => 'musthavemilestoneonaccept',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'commentonclearresolution',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'commentonchange_resolution',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'commentonreassignbycomponent',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'commentonduplicate',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'noresolveonopenblockers',
|
||||
type => 'b',
|
||||
default => 0,
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,126 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::BugFields;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
use Bugzilla::Field;
|
||||
|
||||
$Bugzilla::Config::BugFields::sortkey = "04";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
|
||||
my @legal_priorities = @{get_legal_field_values('priority')};
|
||||
my @legal_severities = @{get_legal_field_values('bug_severity')};
|
||||
my @legal_platforms = @{get_legal_field_values('rep_platform')};
|
||||
my @legal_OS = @{get_legal_field_values('op_sys')};
|
||||
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'useclassification',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'showallproducts',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usetargetmilestone',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'useqacontact',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usestatuswhiteboard',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usevotes',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usebugaliases',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultpriority',
|
||||
type => 's',
|
||||
choices => \@legal_priorities,
|
||||
default => $legal_priorities[-1],
|
||||
checker => \&check_priority
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultseverity',
|
||||
type => 's',
|
||||
choices => \@legal_severities,
|
||||
default => $legal_severities[-1],
|
||||
checker => \&check_severity
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultplatform',
|
||||
type => 's',
|
||||
choices => ['', @legal_platforms],
|
||||
default => '',
|
||||
checker => \&check_platform
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultopsys',
|
||||
type => 's',
|
||||
choices => ['', @legal_OS],
|
||||
default => '',
|
||||
checker => \&check_opsys
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,93 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::BugMove;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::BugMove::sortkey = "05";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'move-enabled',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'move-button-text',
|
||||
type => 't',
|
||||
default => 'Move To Bugscape'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'move-to-url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'move-to-address',
|
||||
type => 't',
|
||||
default => 'bugzilla-import'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'moved-from-address',
|
||||
type => 't',
|
||||
default => 'bugzilla-admin'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'movers',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'moved-default-product',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'moved-default-component',
|
||||
type => 't',
|
||||
default => ''
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,449 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Common;
|
||||
|
||||
use strict;
|
||||
|
||||
use Socket;
|
||||
use Time::Zone;
|
||||
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::Group;
|
||||
use Bugzilla::Status;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Config::Common::EXPORT =
|
||||
qw(check_multi check_numeric check_regexp check_url check_group
|
||||
check_sslbase check_priority check_severity check_platform
|
||||
check_opsys check_shadowdb check_urlbase check_webdotbase
|
||||
check_netmask check_user_verify_class check_image_converter
|
||||
check_mail_delivery_method check_notification check_timezone check_utf8
|
||||
check_bug_status check_smtp_auth
|
||||
);
|
||||
|
||||
# Checking functions for the various values
|
||||
|
||||
sub check_multi {
|
||||
my ($value, $param) = (@_);
|
||||
|
||||
if ($param->{'type'} eq "s") {
|
||||
unless (scalar(grep {$_ eq $value} (@{$param->{'choices'}}))) {
|
||||
return "Invalid choice '$value' for single-select list param '$param->{'name'}'";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
elsif ($param->{'type'} eq 'm' || $param->{'type'} eq 'o') {
|
||||
foreach my $chkParam (split(',', $value)) {
|
||||
unless (scalar(grep {$_ eq $chkParam} (@{$param->{'choices'}}))) {
|
||||
return "Invalid choice '$chkParam' for multi-select list param '$param->{'name'}'";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
return "Invalid param type '$param->{'type'}' for check_multi(); " .
|
||||
"contact your Bugzilla administrator";
|
||||
}
|
||||
}
|
||||
|
||||
sub check_numeric {
|
||||
my ($value) = (@_);
|
||||
if ($value !~ /^[0-9]+$/) {
|
||||
return "must be a numeric value";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_regexp {
|
||||
my ($value) = (@_);
|
||||
eval { qr/$value/ };
|
||||
return $@;
|
||||
}
|
||||
|
||||
sub check_sslbase {
|
||||
my $url = shift;
|
||||
if ($url ne '') {
|
||||
if ($url !~ m#^https://([^/]+).*/$#) {
|
||||
return "must be a legal URL, that starts with https and ends with a slash.";
|
||||
}
|
||||
my $host = $1;
|
||||
# Fall back to port 443 if for some reason getservbyname() fails.
|
||||
my $port = getservbyname('https', 'tcp') || 443;
|
||||
if ($host =~ /^(.+):(\d+)$/) {
|
||||
$host = $1;
|
||||
$port = $2;
|
||||
}
|
||||
local *SOCK;
|
||||
my $proto = getprotobyname('tcp');
|
||||
socket(SOCK, PF_INET, SOCK_STREAM, $proto);
|
||||
my $iaddr = inet_aton($host) || return "The host $host cannot be resolved";
|
||||
my $sin = sockaddr_in($port, $iaddr);
|
||||
if (!connect(SOCK, $sin)) {
|
||||
return "Failed to connect to $host:$port; unable to enable SSL";
|
||||
}
|
||||
close(SOCK);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_utf8 {
|
||||
my $utf8 = shift;
|
||||
# You cannot turn off the UTF-8 parameter if you've already converted
|
||||
# your tables to utf-8.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
if ($dbh->isa('Bugzilla::DB::Mysql') && $dbh->bz_db_is_utf8 && !$utf8) {
|
||||
return "You cannot disable UTF-8 support, because your MySQL database"
|
||||
. " is encoded in UTF-8";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_priority {
|
||||
my ($value) = (@_);
|
||||
my $legal_priorities = get_legal_field_values('priority');
|
||||
if (lsearch($legal_priorities, $value) < 0) {
|
||||
return "Must be a legal priority value: one of " .
|
||||
join(", ", @$legal_priorities);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_severity {
|
||||
my ($value) = (@_);
|
||||
my $legal_severities = get_legal_field_values('bug_severity');
|
||||
if (lsearch($legal_severities, $value) < 0) {
|
||||
return "Must be a legal severity value: one of " .
|
||||
join(", ", @$legal_severities);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_platform {
|
||||
my ($value) = (@_);
|
||||
my $legal_platforms = get_legal_field_values('rep_platform');
|
||||
if (lsearch(['', @$legal_platforms], $value) < 0) {
|
||||
return "Must be empty or a legal platform value: one of " .
|
||||
join(", ", @$legal_platforms);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_opsys {
|
||||
my ($value) = (@_);
|
||||
my $legal_OS = get_legal_field_values('op_sys');
|
||||
if (lsearch(['', @$legal_OS], $value) < 0) {
|
||||
return "Must be empty or a legal operating system value: one of " .
|
||||
join(", ", @$legal_OS);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_bug_status {
|
||||
my $bug_status = shift;
|
||||
my @closed_bug_statuses = map {$_->name} closed_bug_statuses();
|
||||
if (lsearch(\@closed_bug_statuses, $bug_status) < 0) {
|
||||
return "Must be a valid closed status: one of " . join(', ', @closed_bug_statuses);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_group {
|
||||
my $group_name = shift;
|
||||
return "" unless $group_name;
|
||||
my $group = new Bugzilla::Group({'name' => $group_name});
|
||||
unless (defined $group) {
|
||||
return "Must be an existing group name";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_shadowdb {
|
||||
my ($value) = (@_);
|
||||
$value = trim($value);
|
||||
if ($value eq "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!Bugzilla->params->{'shadowdbhost'}) {
|
||||
return "You need to specify a host when using a shadow database";
|
||||
}
|
||||
|
||||
# Can't test existence of this because ConnectToDatabase uses the param,
|
||||
# but we can't set this before testing....
|
||||
# This can really only be fixed after we can use the DBI more openly
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_urlbase {
|
||||
my ($url) = (@_);
|
||||
if ($url && $url !~ m:^http.*/$:) {
|
||||
return "must be a legal URL, that starts with http and ends with a slash.";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_url {
|
||||
my ($url) = (@_);
|
||||
return '' if $url eq ''; # Allow empty URLs
|
||||
if ($url !~ m:/$:) {
|
||||
return 'must be a legal URL, absolute or relative, ending with a slash.';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
sub check_webdotbase {
|
||||
my ($value) = (@_);
|
||||
$value = trim($value);
|
||||
if ($value eq "") {
|
||||
return "";
|
||||
}
|
||||
if($value !~ /^https?:/) {
|
||||
if(! -x $value) {
|
||||
return "The file path \"$value\" is not a valid executable. Please specify the complete file path to 'dot' if you intend to generate graphs locally.";
|
||||
}
|
||||
# Check .htaccess allows access to generated images
|
||||
my $webdotdir = bz_locations()->{'webdotdir'};
|
||||
if(-e "$webdotdir/.htaccess") {
|
||||
open HTACCESS, "$webdotdir/.htaccess";
|
||||
if(! grep(/ \\\.png\$/,<HTACCESS>)) {
|
||||
return "Dependency graph images are not accessible.\nAssuming that you have not modified the file, delete $webdotdir/.htaccess and re-run checksetup.pl to rectify.\n";
|
||||
}
|
||||
close HTACCESS;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_netmask {
|
||||
my ($mask) = @_;
|
||||
my $res = check_numeric($mask);
|
||||
return $res if $res;
|
||||
if ($mask < 0 || $mask > 32) {
|
||||
return "an IPv4 netmask must be between 0 and 32 bits";
|
||||
}
|
||||
# Note that if we changed the netmask from anything apart from 32, then
|
||||
# existing logincookies which aren't for a single IP won't work
|
||||
# any more. We can't know which ones they are, though, so they'll just
|
||||
# take space until they're periodically cleared, later.
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_user_verify_class {
|
||||
# doeditparams traverses the list of params, and for each one it checks,
|
||||
# then updates. This means that if one param checker wants to look at
|
||||
# other params, it must be below that other one. So you can't have two
|
||||
# params mutually dependent on each other.
|
||||
# This means that if someone clears the LDAP config params after setting
|
||||
# the login method as LDAP, we won't notice, but all logins will fail.
|
||||
# So don't do that.
|
||||
|
||||
my ($list, $entry) = @_;
|
||||
$list || return 'You need to specify at least one authentication mechanism';
|
||||
for my $class (split /,\s*/, $list) {
|
||||
my $res = check_multi($class, $entry);
|
||||
return $res if $res;
|
||||
if ($class eq 'DB') {
|
||||
# No params
|
||||
}
|
||||
elsif ($class eq 'RADIUS') {
|
||||
eval "require Authen::Radius";
|
||||
return "Error requiring Authen::Radius: '$@'" if $@;
|
||||
return "RADIUS servername (RADIUS_server) is missing" unless Bugzilla->params->{"RADIUS_server"};
|
||||
return "RADIUS_secret is empty" unless Bugzilla->params->{"RADIUS_secret"};
|
||||
}
|
||||
elsif ($class eq 'LDAP') {
|
||||
eval "require Net::LDAP";
|
||||
return "Error requiring Net::LDAP: '$@'" if $@;
|
||||
return "LDAP servername (LDAPserver) is missing" unless Bugzilla->params->{"LDAPserver"};
|
||||
return "LDAPBaseDN is empty" unless Bugzilla->params->{"LDAPBaseDN"};
|
||||
}
|
||||
else {
|
||||
return "Unknown user_verify_class '$class' in check_user_verify_class";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_image_converter {
|
||||
my ($value, $hash) = @_;
|
||||
if ($value == 1){
|
||||
eval "require Image::Magick";
|
||||
return "Error requiring Image::Magick: '$@'" if $@;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_mail_delivery_method {
|
||||
my $check = check_multi(@_);
|
||||
return $check if $check;
|
||||
my $mailer = shift;
|
||||
if ($mailer eq 'sendmail' && $^O =~ /MSWin32/i) {
|
||||
# look for sendmail.exe
|
||||
return "Failed to locate " . SENDMAIL_EXE
|
||||
unless -e SENDMAIL_EXE;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_notification {
|
||||
my $option = shift;
|
||||
my @current_version =
|
||||
(BUGZILLA_VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
|
||||
if ($current_version[1] % 2 && $option eq 'stable_branch_release') {
|
||||
return "You are currently running a development snapshot, and so your " .
|
||||
"installation is not based on a branch. If you want to be notified " .
|
||||
"about the next stable release, you should select " .
|
||||
"'latest_stable_release' instead";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_timezone {
|
||||
my $tz = shift;
|
||||
unless (defined(tz_offset($tz))) {
|
||||
return "must be empty or a legal timezone name, such as PDT or JST";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_smtp_auth {
|
||||
my $username = shift;
|
||||
if ($username) {
|
||||
eval "require Authen::SASL";
|
||||
return "Error requiring Authen::SASL: '$@'" if $@;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
# OK, here are the parameter definitions themselves.
|
||||
#
|
||||
# Each definition is a hash with keys:
|
||||
#
|
||||
# name - name of the param
|
||||
# desc - description of the param (for editparams.cgi)
|
||||
# type - see below
|
||||
# choices - (optional) see below
|
||||
# default - default value for the param
|
||||
# checker - (optional) checking function for validating parameter entry
|
||||
# It is called with the value of the param as the first arg and a
|
||||
# reference to the param's hash as the second argument
|
||||
#
|
||||
# The type value can be one of the following:
|
||||
#
|
||||
# t -- A short text entry field (suitable for a single line)
|
||||
# p -- A short text entry field (as with type = 't'), but the string is
|
||||
# replaced by asterisks (appropriate for passwords)
|
||||
# l -- A long text field (suitable for many lines)
|
||||
# b -- A boolean value (either 1 or 0)
|
||||
# m -- A list of values, with many selectable (shows up as a select box)
|
||||
# To specify the list of values, make the 'choices' key be an array
|
||||
# reference of the valid choices. The 'default' key should be a string
|
||||
# with a list of selected values (as a comma-separated list), i.e.:
|
||||
# {
|
||||
# name => 'multiselect',
|
||||
# desc => 'A list of options, choose many',
|
||||
# type => 'm',
|
||||
# choices => [ 'a', 'b', 'c', 'd' ],
|
||||
# default => [ 'a', 'd' ],
|
||||
# checker => \&check_multi
|
||||
# }
|
||||
#
|
||||
# Here, 'a' and 'd' are the default options, and the user may pick any
|
||||
# combination of a, b, c, and d as valid options.
|
||||
#
|
||||
# &check_multi should always be used as the param verification function
|
||||
# for list (single and multiple) parameter types.
|
||||
#
|
||||
# o -- A list of values, orderable, and with many selectable (shows up as a
|
||||
# JavaScript-enhanced select box if JavaScript is enabled, and a text
|
||||
# entry field if not)
|
||||
# Set up in the same way as type m.
|
||||
#
|
||||
# s -- A list of values, with one selectable (shows up as a select box)
|
||||
# To specify the list of values, make the 'choices' key be an array
|
||||
# reference of the valid choices. The 'default' key should be one of
|
||||
# those values, i.e.:
|
||||
# {
|
||||
# name => 'singleselect',
|
||||
# desc => 'A list of options, choose one',
|
||||
# type => 's',
|
||||
# choices => [ 'a', 'b', 'c' ],
|
||||
# default => 'b',
|
||||
# checker => \&check_multi
|
||||
# }
|
||||
#
|
||||
# Here, 'b' is the default option, and 'a' and 'c' are other possible
|
||||
# options, but only one at a time!
|
||||
#
|
||||
# &check_multi should always be used as the param verification function
|
||||
# for list (single and multiple) parameter types.
|
||||
|
||||
sub get_param_list {
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Config::Common - Parameter checking functions
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
All parameter checking functions are called with two parameters:
|
||||
|
||||
=head2 Functions
|
||||
|
||||
=over
|
||||
|
||||
=item C<check_multi>
|
||||
|
||||
Checks that a multi-valued parameter (ie types C<s>, C<o> or C<m>) satisfies
|
||||
its contraints.
|
||||
|
||||
=item C<check_numeric>
|
||||
|
||||
Checks that the value is a valid number
|
||||
|
||||
=item C<check_regexp>
|
||||
|
||||
Checks that the value is a valid regexp
|
||||
|
||||
=back
|
||||
@@ -1,133 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Core;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Core::sortkey = "00";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'maintainer',
|
||||
type => 't',
|
||||
default => 'THE MAINTAINER HAS NOT YET BEEN SET'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'urlbase',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_urlbase
|
||||
},
|
||||
|
||||
{
|
||||
name => 'docs_urlbase',
|
||||
type => 't',
|
||||
default => 'docs/%lang%/html/',
|
||||
checker => \&check_url
|
||||
},
|
||||
|
||||
{
|
||||
name => 'sslbase',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_sslbase
|
||||
},
|
||||
|
||||
{
|
||||
name => 'ssl',
|
||||
type => 's',
|
||||
choices => ['never', 'authenticated sessions', 'always'],
|
||||
default => 'never'
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
name => 'cookiedomain',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'cookiepath',
|
||||
type => 't',
|
||||
default => '/'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'timezone',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_timezone
|
||||
},
|
||||
|
||||
{
|
||||
name => 'utf8',
|
||||
type => 'b',
|
||||
default => '0',
|
||||
checker => \&check_utf8
|
||||
},
|
||||
|
||||
{
|
||||
name => 'shutdownhtml',
|
||||
type => 'l',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'announcehtml',
|
||||
type => 'l',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'proxy_url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'upgrade_notification',
|
||||
type => 's',
|
||||
choices => ['development_snapshot', 'latest_stable_release',
|
||||
'stable_branch_release', 'disabled'],
|
||||
default => 'latest_stable_release',
|
||||
checker => \&check_notification
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,52 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::DependencyGraph;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::DependencyGraph::sortkey = "06";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'webdotbase',
|
||||
type => 't',
|
||||
default => 'http://www.research.att.com/~north/cgi-bin/webdot.cgi/%urlbase%',
|
||||
checker => \&check_webdotbase
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,108 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::GroupSecurity;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
use Bugzilla::Group;
|
||||
|
||||
$Bugzilla::Config::GroupSecurity::sortkey = "07";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'makeproductgroups',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'useentrygroupdefault',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'chartgroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => 'editbugs',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'insidergroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => '',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'timetrackinggroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => 'editbugs',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'querysharegroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => 'editbugs',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usevisibilitygroups',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'strict_isolation',
|
||||
type => 'b',
|
||||
default => 0
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
sub _get_all_group_names {
|
||||
my @group_names = map {$_->name} Bugzilla::Group->get_all;
|
||||
unshift(@group_names, '');
|
||||
return \@group_names;
|
||||
}
|
||||
1;
|
||||
@@ -1,87 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::LDAP;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::LDAP::sortkey = "09";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'LDAPserver',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPstarttls',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPbinddn',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPBaseDN',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPuidattribute',
|
||||
type => 't',
|
||||
default => 'uid'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPmailattribute',
|
||||
type => 't',
|
||||
default => 'mail'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPfilter',
|
||||
type => 't',
|
||||
default => '',
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,102 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::MTA;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
use Email::Send;
|
||||
|
||||
$Bugzilla::Config::MTA::sortkey = "10";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'mail_delivery_method',
|
||||
type => 's',
|
||||
# Bugzilla is not ready yet to send mails to newsgroups, and 'IO'
|
||||
# is of no use for now as we already have our own 'Test' mode.
|
||||
choices => [grep {$_ ne 'NNTP' && $_ ne 'IO'} Email::Send->new()->all_mailers(), 'None'],
|
||||
default => 'Sendmail',
|
||||
checker => \&check_mail_delivery_method
|
||||
},
|
||||
|
||||
{
|
||||
name => 'mailfrom',
|
||||
type => 't',
|
||||
default => 'bugzilla-daemon'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'sendmailnow',
|
||||
type => 'b',
|
||||
default => 1
|
||||
},
|
||||
|
||||
{
|
||||
name => 'smtpserver',
|
||||
type => 't',
|
||||
default => 'localhost'
|
||||
},
|
||||
{
|
||||
name => 'smtp_username',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_smtp_auth
|
||||
},
|
||||
{
|
||||
name => 'smtp_password',
|
||||
type => 'p',
|
||||
default => ''
|
||||
},
|
||||
{
|
||||
name => 'smtp_debug',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
{
|
||||
name => 'whinedays',
|
||||
type => 't',
|
||||
default => 7,
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'globalwatchers',
|
||||
type => 't',
|
||||
default => '',
|
||||
}, );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,75 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::PatchViewer;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::PatchViewer::sortkey = "11";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'cvsroot',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'cvsroot_get',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'bonsai_url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'lxr_url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'lxr_root',
|
||||
type => 't',
|
||||
default => '',
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,87 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Query;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Query::sortkey = "12";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'quip_list_entry_control',
|
||||
type => 's',
|
||||
choices => ['open', 'moderated', 'closed'],
|
||||
default => 'open',
|
||||
checker => \&check_multi
|
||||
},
|
||||
|
||||
{
|
||||
name => 'mostfreqthreshold',
|
||||
type => 't',
|
||||
default => '2',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'mybugstemplate',
|
||||
type => 't',
|
||||
default => 'buglist.cgi?bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailreporter1=1&emailtype1=exact&email1=%userid%&field0-0-0=bug_status&type0-0-0=notequals&value0-0-0=UNCONFIRMED&field0-0-1=reporter&type0-0-1=equals&value0-0-1=%userid%'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultquery',
|
||||
type => 't',
|
||||
default => 'bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&order=Importance&long_desc_type=substring'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'quicksearch_comment_cutoff',
|
||||
type => 't',
|
||||
default => '4',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'specific_search_allow_empty_words',
|
||||
type => 'b',
|
||||
default => 0
|
||||
}
|
||||
|
||||
);
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- 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 Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::RADIUS;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::RADIUS::sortkey = "09";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'RADIUS_server',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'RADIUS_secret',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'RADIUS_NAS_IP',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'RADIUS_email_suffix',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
);
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,73 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::ShadowDB;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::ShadowDB::sortkey = "13";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'shadowdbhost',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'shadowdbport',
|
||||
type => 't',
|
||||
default => '3306',
|
||||
checker => \&check_numeric,
|
||||
},
|
||||
|
||||
{
|
||||
name => 'shadowdbsock',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
# This entry must be _after_ the shadowdb{host,port,sock} settings so that
|
||||
# they can be used in the validation here
|
||||
{
|
||||
name => 'shadowdb',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_shadowdb
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,71 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::UserMatch;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::UserMatch::sortkey = "14";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'usemenuforusers',
|
||||
type => 'b',
|
||||
default => '0'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usermatchmode',
|
||||
type => 's',
|
||||
choices => ['off', 'wildcard', 'search'],
|
||||
default => 'off'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'maxusermatches',
|
||||
type => 't',
|
||||
default => '1000',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'confirmuniqueusermatch',
|
||||
type => 'b',
|
||||
default => 1,
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,480 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jake <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Shane H. W. Travis <travis@sedsystems.ca>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Constants;
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
# For bz_locations
|
||||
use File::Basename;
|
||||
|
||||
@Bugzilla::Constants::EXPORT = qw(
|
||||
BUGZILLA_VERSION
|
||||
|
||||
bz_locations
|
||||
|
||||
IS_NULL
|
||||
NOT_NULL
|
||||
|
||||
CONTROLMAPNA
|
||||
CONTROLMAPSHOWN
|
||||
CONTROLMAPDEFAULT
|
||||
CONTROLMAPMANDATORY
|
||||
|
||||
AUTH_OK
|
||||
AUTH_NODATA
|
||||
AUTH_ERROR
|
||||
AUTH_LOGINFAILED
|
||||
AUTH_DISABLED
|
||||
AUTH_NO_SUCH_USER
|
||||
|
||||
USER_PASSWORD_MIN_LENGTH
|
||||
USER_PASSWORD_MAX_LENGTH
|
||||
|
||||
LOGIN_OPTIONAL
|
||||
LOGIN_NORMAL
|
||||
LOGIN_REQUIRED
|
||||
|
||||
LOGOUT_ALL
|
||||
LOGOUT_CURRENT
|
||||
LOGOUT_KEEP_CURRENT
|
||||
|
||||
GRANT_DIRECT
|
||||
GRANT_REGEXP
|
||||
|
||||
GROUP_MEMBERSHIP
|
||||
GROUP_BLESS
|
||||
GROUP_VISIBLE
|
||||
|
||||
MAILTO_USER
|
||||
MAILTO_GROUP
|
||||
|
||||
DEFAULT_COLUMN_LIST
|
||||
DEFAULT_QUERY_NAME
|
||||
|
||||
QUERY_LIST
|
||||
LIST_OF_BUGS
|
||||
|
||||
COMMENT_COLS
|
||||
MAX_COMMENT_LENGTH
|
||||
|
||||
CMT_NORMAL
|
||||
CMT_DUPE_OF
|
||||
CMT_HAS_DUPE
|
||||
CMT_POPULAR_VOTES
|
||||
CMT_MOVED_TO
|
||||
|
||||
THROW_ERROR
|
||||
|
||||
RELATIONSHIPS
|
||||
REL_ASSIGNEE REL_QA REL_REPORTER REL_CC REL_VOTER REL_GLOBAL_WATCHER
|
||||
REL_ANY
|
||||
|
||||
POS_EVENTS
|
||||
EVT_OTHER EVT_ADDED_REMOVED EVT_COMMENT EVT_ATTACHMENT EVT_ATTACHMENT_DATA
|
||||
EVT_PROJ_MANAGEMENT EVT_OPENED_CLOSED EVT_KEYWORD EVT_CC EVT_DEPEND_BLOCK
|
||||
|
||||
NEG_EVENTS
|
||||
EVT_UNCONFIRMED EVT_CHANGED_BY_ME
|
||||
|
||||
GLOBAL_EVENTS
|
||||
EVT_FLAG_REQUESTED EVT_REQUESTED_FLAG
|
||||
|
||||
FULLTEXT_BUGLIST_LIMIT
|
||||
|
||||
ADMIN_GROUP_NAME
|
||||
PER_PRODUCT_PRIVILEGES
|
||||
|
||||
SENDMAIL_EXE
|
||||
SENDMAIL_PATH
|
||||
|
||||
FIELD_TYPE_UNKNOWN
|
||||
FIELD_TYPE_FREETEXT
|
||||
FIELD_TYPE_SINGLE_SELECT
|
||||
FIELD_TYPE_MULTI_SELECT
|
||||
FIELD_TYPE_TEXTAREA
|
||||
FIELD_TYPE_DATETIME
|
||||
|
||||
USAGE_MODE_BROWSER
|
||||
USAGE_MODE_CMDLINE
|
||||
USAGE_MODE_WEBSERVICE
|
||||
USAGE_MODE_EMAIL
|
||||
|
||||
ERROR_MODE_WEBPAGE
|
||||
ERROR_MODE_DIE
|
||||
ERROR_MODE_DIE_SOAP_FAULT
|
||||
|
||||
INSTALLATION_MODE_INTERACTIVE
|
||||
INSTALLATION_MODE_NON_INTERACTIVE
|
||||
|
||||
DB_MODULE
|
||||
ROOT_USER
|
||||
ON_WINDOWS
|
||||
|
||||
MAX_TOKEN_AGE
|
||||
|
||||
SAFE_PROTOCOLS
|
||||
|
||||
MIN_SMALLINT
|
||||
MAX_SMALLINT
|
||||
|
||||
MAX_LEN_QUERY_NAME
|
||||
MAX_MILESTONE_SIZE
|
||||
MAX_COMPONENT_SIZE
|
||||
MAX_FREETEXT_LENGTH
|
||||
);
|
||||
|
||||
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
|
||||
|
||||
# CONSTANTS
|
||||
#
|
||||
# Bugzilla version
|
||||
use constant BUGZILLA_VERSION => "3.2";
|
||||
|
||||
# These are unique values that are unlikely to match a string or a number,
|
||||
# to be used in criteria for match() functions and other things. They start
|
||||
# and end with spaces because most Bugzilla stuff has trim() called on it,
|
||||
# so this is unlikely to match anything we get out of the DB.
|
||||
#
|
||||
# We can't use a reference, because Template Toolkit doesn't work with
|
||||
# them properly (constants.IS_NULL => {} just returns an empty string instead
|
||||
# of the reference).
|
||||
use constant IS_NULL => ' __IS_NULL__ ';
|
||||
use constant NOT_NULL => ' __NOT_NULL__ ';
|
||||
|
||||
#
|
||||
# ControlMap constants for group_control_map.
|
||||
# membercontol:othercontrol => meaning
|
||||
# Na:Na => Bugs in this product may not be restricted to this
|
||||
# group.
|
||||
# Shown:Na => Members of the group may restrict bugs
|
||||
# in this product to this group.
|
||||
# Shown:Shown => Members of the group may restrict bugs
|
||||
# in this product to this group.
|
||||
# Anyone who can enter bugs in this product may initially
|
||||
# restrict bugs in this product to this group.
|
||||
# Shown:Mandatory => Members of the group may restrict bugs
|
||||
# in this product to this group.
|
||||
# Non-members who can enter bug in this product
|
||||
# will be forced to restrict it.
|
||||
# Default:Na => Members of the group may restrict bugs in this
|
||||
# product to this group and do so by default.
|
||||
# Default:Default => Members of the group may restrict bugs in this
|
||||
# product to this group and do so by default and
|
||||
# nonmembers have this option on entry.
|
||||
# Default:Mandatory => Members of the group may restrict bugs in this
|
||||
# product to this group and do so by default.
|
||||
# Non-members who can enter bug in this product
|
||||
# will be forced to restrict it.
|
||||
# Mandatory:Mandatory => Bug will be forced into this group regardless.
|
||||
# All other combinations are illegal.
|
||||
|
||||
use constant CONTROLMAPNA => 0;
|
||||
use constant CONTROLMAPSHOWN => 1;
|
||||
use constant CONTROLMAPDEFAULT => 2;
|
||||
use constant CONTROLMAPMANDATORY => 3;
|
||||
|
||||
# See Bugzilla::Auth for docs on AUTH_*, LOGIN_* and LOGOUT_*
|
||||
|
||||
use constant AUTH_OK => 0;
|
||||
use constant AUTH_NODATA => 1;
|
||||
use constant AUTH_ERROR => 2;
|
||||
use constant AUTH_LOGINFAILED => 3;
|
||||
use constant AUTH_DISABLED => 4;
|
||||
use constant AUTH_NO_SUCH_USER => 5;
|
||||
|
||||
# The minimum and maximum lengths a password must have.
|
||||
use constant USER_PASSWORD_MIN_LENGTH => 3;
|
||||
use constant USER_PASSWORD_MAX_LENGTH => 16;
|
||||
|
||||
use constant LOGIN_OPTIONAL => 0;
|
||||
use constant LOGIN_NORMAL => 1;
|
||||
use constant LOGIN_REQUIRED => 2;
|
||||
|
||||
use constant LOGOUT_ALL => 0;
|
||||
use constant LOGOUT_CURRENT => 1;
|
||||
use constant LOGOUT_KEEP_CURRENT => 2;
|
||||
|
||||
use constant contenttypes =>
|
||||
{
|
||||
"html"=> "text/html" ,
|
||||
"rdf" => "application/rdf+xml" ,
|
||||
"atom"=> "application/atom+xml" ,
|
||||
"xml" => "application/xml" ,
|
||||
"js" => "application/x-javascript" ,
|
||||
"csv" => "text/csv" ,
|
||||
"png" => "image/png" ,
|
||||
"ics" => "text/calendar" ,
|
||||
};
|
||||
|
||||
use constant GRANT_DIRECT => 0;
|
||||
use constant GRANT_REGEXP => 2;
|
||||
|
||||
use constant GROUP_MEMBERSHIP => 0;
|
||||
use constant GROUP_BLESS => 1;
|
||||
use constant GROUP_VISIBLE => 2;
|
||||
|
||||
use constant MAILTO_USER => 0;
|
||||
use constant MAILTO_GROUP => 1;
|
||||
|
||||
# The default list of columns for buglist.cgi
|
||||
use constant DEFAULT_COLUMN_LIST => (
|
||||
"bug_severity", "priority", "op_sys","assigned_to",
|
||||
"bug_status", "resolution", "short_desc"
|
||||
);
|
||||
|
||||
# Used by query.cgi and buglist.cgi as the named-query name
|
||||
# for the default settings.
|
||||
use constant DEFAULT_QUERY_NAME => '(Default query)';
|
||||
|
||||
# The possible types for saved searches.
|
||||
use constant QUERY_LIST => 0;
|
||||
use constant LIST_OF_BUGS => 1;
|
||||
|
||||
# The column length for displayed (and wrapped) bug comments.
|
||||
use constant COMMENT_COLS => 80;
|
||||
# Used in _check_comment(). Gives the max length allowed for a comment.
|
||||
use constant MAX_COMMENT_LENGTH => 65535;
|
||||
|
||||
# The type of bug comments.
|
||||
use constant CMT_NORMAL => 0;
|
||||
use constant CMT_DUPE_OF => 1;
|
||||
use constant CMT_HAS_DUPE => 2;
|
||||
use constant CMT_POPULAR_VOTES => 3;
|
||||
use constant CMT_MOVED_TO => 4;
|
||||
|
||||
# Determine whether a validation routine should return 0 or throw
|
||||
# an error when the validation fails.
|
||||
use constant THROW_ERROR => 1;
|
||||
|
||||
use constant REL_ASSIGNEE => 0;
|
||||
use constant REL_QA => 1;
|
||||
use constant REL_REPORTER => 2;
|
||||
use constant REL_CC => 3;
|
||||
use constant REL_VOTER => 4;
|
||||
use constant REL_GLOBAL_WATCHER => 5;
|
||||
|
||||
use constant RELATIONSHIPS => REL_ASSIGNEE, REL_QA, REL_REPORTER, REL_CC,
|
||||
REL_VOTER, REL_GLOBAL_WATCHER;
|
||||
|
||||
# Used for global events like EVT_FLAG_REQUESTED
|
||||
use constant REL_ANY => 100;
|
||||
|
||||
# There are two sorts of event - positive and negative. Positive events are
|
||||
# those for which the user says "I want mail if this happens." Negative events
|
||||
# are those for which the user says "I don't want mail if this happens."
|
||||
#
|
||||
# Exactly when each event fires is defined in wants_bug_mail() in User.pm; I'm
|
||||
# not commenting them here in case the comments and the code get out of sync.
|
||||
use constant EVT_OTHER => 0;
|
||||
use constant EVT_ADDED_REMOVED => 1;
|
||||
use constant EVT_COMMENT => 2;
|
||||
use constant EVT_ATTACHMENT => 3;
|
||||
use constant EVT_ATTACHMENT_DATA => 4;
|
||||
use constant EVT_PROJ_MANAGEMENT => 5;
|
||||
use constant EVT_OPENED_CLOSED => 6;
|
||||
use constant EVT_KEYWORD => 7;
|
||||
use constant EVT_CC => 8;
|
||||
use constant EVT_DEPEND_BLOCK => 9;
|
||||
|
||||
use constant POS_EVENTS => EVT_OTHER, EVT_ADDED_REMOVED, EVT_COMMENT,
|
||||
EVT_ATTACHMENT, EVT_ATTACHMENT_DATA,
|
||||
EVT_PROJ_MANAGEMENT, EVT_OPENED_CLOSED, EVT_KEYWORD,
|
||||
EVT_CC, EVT_DEPEND_BLOCK;
|
||||
|
||||
use constant EVT_UNCONFIRMED => 50;
|
||||
use constant EVT_CHANGED_BY_ME => 51;
|
||||
|
||||
use constant NEG_EVENTS => EVT_UNCONFIRMED, EVT_CHANGED_BY_ME;
|
||||
|
||||
# These are the "global" flags, which aren't tied to a particular relationship.
|
||||
# and so use REL_ANY.
|
||||
use constant EVT_FLAG_REQUESTED => 100; # Flag has been requested of me
|
||||
use constant EVT_REQUESTED_FLAG => 101; # I have requested a flag
|
||||
|
||||
use constant GLOBAL_EVENTS => EVT_FLAG_REQUESTED, EVT_REQUESTED_FLAG;
|
||||
|
||||
# Number of bugs to return in a buglist when performing
|
||||
# a fulltext search.
|
||||
use constant FULLTEXT_BUGLIST_LIMIT => 200;
|
||||
|
||||
# Default administration group name.
|
||||
use constant ADMIN_GROUP_NAME => 'admin';
|
||||
|
||||
# Privileges which can be per-product.
|
||||
use constant PER_PRODUCT_PRIVILEGES => ('editcomponents', 'editbugs', 'canconfirm');
|
||||
|
||||
# Path to sendmail.exe (Windows only)
|
||||
use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe';
|
||||
# Paths to search for the sendmail binary (non-Windows)
|
||||
use constant SENDMAIL_PATH => '/usr/lib:/usr/sbin:/usr/ucblib';
|
||||
|
||||
# Field types. Match values in fielddefs.type column. These are purposely
|
||||
# not named after database column types, since Bugzilla fields comprise not
|
||||
# only storage but also logic. For example, we might add a "user" field type
|
||||
# whose values are stored in an integer column in the database but for which
|
||||
# we do more than we would do for a standard integer type (f.e. we might
|
||||
# display a user picker).
|
||||
|
||||
use constant FIELD_TYPE_UNKNOWN => 0;
|
||||
use constant FIELD_TYPE_FREETEXT => 1;
|
||||
use constant FIELD_TYPE_SINGLE_SELECT => 2;
|
||||
use constant FIELD_TYPE_MULTI_SELECT => 3;
|
||||
use constant FIELD_TYPE_TEXTAREA => 4;
|
||||
use constant FIELD_TYPE_DATETIME => 5;
|
||||
|
||||
# The maximum number of days a token will remain valid.
|
||||
use constant MAX_TOKEN_AGE => 3;
|
||||
|
||||
# Protocols which are considered as safe.
|
||||
use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
|
||||
'irc', 'mid', 'news', 'nntp', 'prospero', 'telnet',
|
||||
'view-source', 'wais');
|
||||
|
||||
# Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode.
|
||||
use constant USAGE_MODE_BROWSER => 0;
|
||||
use constant USAGE_MODE_CMDLINE => 1;
|
||||
use constant USAGE_MODE_WEBSERVICE => 2;
|
||||
use constant USAGE_MODE_EMAIL => 3;
|
||||
|
||||
# Error modes. Default set by Bugzilla->usage_mode (so ERROR_MODE_WEBPAGE
|
||||
# usually). Use with Bugzilla->error_mode.
|
||||
use constant ERROR_MODE_WEBPAGE => 0;
|
||||
use constant ERROR_MODE_DIE => 1;
|
||||
use constant ERROR_MODE_DIE_SOAP_FAULT => 2;
|
||||
|
||||
# The various modes that checksetup.pl can run in.
|
||||
use constant INSTALLATION_MODE_INTERACTIVE => 0;
|
||||
use constant INSTALLATION_MODE_NON_INTERACTIVE => 1;
|
||||
|
||||
# Data about what we require for different databases.
|
||||
use constant DB_MODULE => {
|
||||
'mysql' => {db => 'Bugzilla::DB::Mysql', db_version => '4.1.2',
|
||||
dbd => {
|
||||
package => 'DBD-mysql',
|
||||
module => 'DBD::mysql',
|
||||
# Disallow development versions
|
||||
blacklist => ['_'],
|
||||
# For UTF-8 support
|
||||
version => '4.00',
|
||||
},
|
||||
name => 'MySQL'},
|
||||
'pg' => {db => 'Bugzilla::DB::Pg', db_version => '8.00.0000',
|
||||
dbd => {
|
||||
package => 'DBD-Pg',
|
||||
module => 'DBD::Pg',
|
||||
version => '1.45',
|
||||
},
|
||||
name => 'PostgreSQL'},
|
||||
'oracle'=> {db => 'Bugzilla::DB::Oracle', db_version => '10.02.0',
|
||||
dbd => {
|
||||
package => 'DBD-Oracle',
|
||||
module => 'DBD::Oracle',
|
||||
version => '1.19',
|
||||
},
|
||||
name => 'Oracle'},
|
||||
};
|
||||
|
||||
# The user who should be considered "root" when we're giving
|
||||
# instructions to Bugzilla administrators.
|
||||
use constant ROOT_USER => $^O =~ /MSWin32/i ? 'Administrator' : 'root';
|
||||
|
||||
# True if we're on Win32.
|
||||
use constant ON_WINDOWS => ($^O =~ /MSWin32/i);
|
||||
|
||||
use constant MIN_SMALLINT => -32768;
|
||||
use constant MAX_SMALLINT => 32767;
|
||||
|
||||
# The longest that a saved search name can be.
|
||||
use constant MAX_LEN_QUERY_NAME => 64;
|
||||
|
||||
# The longest milestone name allowed.
|
||||
use constant MAX_MILESTONE_SIZE => 20;
|
||||
|
||||
# The longest component name allowed.
|
||||
use constant MAX_COMPONENT_SIZE => 64;
|
||||
|
||||
# Maximum length allowed for free text fields.
|
||||
use constant MAX_FREETEXT_LENGTH => 255;
|
||||
|
||||
sub bz_locations {
|
||||
# We know that Bugzilla/Constants.pm must be in %INC at this point.
|
||||
# So the only question is, what's the name of the directory
|
||||
# above it? This is the most reliable way to get our current working
|
||||
# directory under both mod_cgi and mod_perl. We call dirname twice
|
||||
# to get the name of the directory above the "Bugzilla/" directory.
|
||||
#
|
||||
# Calling dirname twice like that won't work on VMS or AmigaOS
|
||||
# but I doubt anybody runs Bugzilla on those.
|
||||
#
|
||||
# On mod_cgi this will be a relative path. On mod_perl it will be an
|
||||
# absolute path.
|
||||
my $libpath = dirname(dirname($INC{'Bugzilla/Constants.pm'}));
|
||||
# We have to detaint $libpath, but we can't use Bugzilla::Util here.
|
||||
$libpath =~ /(.*)/;
|
||||
$libpath = $1;
|
||||
|
||||
my ($project, $localconfig, $datadir);
|
||||
if ($ENV{'PROJECT'} && $ENV{'PROJECT'} =~ /^(\w+)$/) {
|
||||
$project = $1;
|
||||
$localconfig = "localconfig.$project";
|
||||
$datadir = "data/$project";
|
||||
} else {
|
||||
$localconfig = "localconfig";
|
||||
$datadir = "data";
|
||||
}
|
||||
|
||||
# We have to return absolute paths for mod_perl.
|
||||
# That means that if you modify these paths, they must be absolute paths.
|
||||
return {
|
||||
'libpath' => $libpath,
|
||||
'ext_libpath' => "$libpath/lib",
|
||||
# If you put the libraries in a different location than the CGIs,
|
||||
# make sure this still points to the CGIs.
|
||||
'cgi_path' => $libpath,
|
||||
'templatedir' => "$libpath/template",
|
||||
'project' => $project,
|
||||
'localconfig' => "$libpath/$localconfig",
|
||||
'datadir' => "$libpath/$datadir",
|
||||
'attachdir' => "$libpath/$datadir/attachments",
|
||||
'skinsdir' => "$libpath/skins",
|
||||
# $webdotdir must be in the web server's tree somewhere. Even if you use a
|
||||
# local dot, we output images to there. Also, if $webdotdir is
|
||||
# not relative to the bugzilla root directory, you'll need to
|
||||
# change showdependencygraph.cgi to set image_url to the correct
|
||||
# location.
|
||||
# The script should really generate these graphs directly...
|
||||
'webdotdir' => "$libpath/$datadir/webdot",
|
||||
'extensionsdir' => "$libpath/extensions",
|
||||
};
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,959 +0,0 @@
|
||||
# -*- 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): Dave Miller <davem00@aol.com>
|
||||
# Gayathri Swaminath <gayathrik00@aol.com>
|
||||
# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
|
||||
# Dave Lawrence <dkl@redhat.com>
|
||||
# Tomas Kopal <Tomas.Kopal@altap.cz>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Lance Larsh <lance.larsh@oracle.com>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::DB::Mysql - Bugzilla database compatibility layer for MySQL
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module overrides methods of the Bugzilla::DB module with MySQL specific
|
||||
implementation. It is instantiated by the Bugzilla::DB module and should never
|
||||
be used directly.
|
||||
|
||||
For interface details see L<Bugzilla::DB> and L<DBI>.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::DB::Mysql;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::DB::Schema::Mysql;
|
||||
|
||||
use List::Util qw(max);
|
||||
|
||||
# This is how many comments of MAX_COMMENT_LENGTH we expect on a single bug.
|
||||
# In reality, you could have a LOT more comments than this, because
|
||||
# MAX_COMMENT_LENGTH is big.
|
||||
use constant MAX_COMMENTS => 50;
|
||||
|
||||
# This module extends the DB interface via inheritance
|
||||
use base qw(Bugzilla::DB);
|
||||
|
||||
sub new {
|
||||
my ($class, $user, $pass, $host, $dbname, $port, $sock) = @_;
|
||||
|
||||
# construct the DSN from the parameters we got
|
||||
my $dsn = "DBI:mysql:host=$host;database=$dbname";
|
||||
$dsn .= ";port=$port" if $port;
|
||||
$dsn .= ";mysql_socket=$sock" if $sock;
|
||||
|
||||
my $attrs = { mysql_enable_utf8 => Bugzilla->params->{'utf8'} };
|
||||
|
||||
my $self = $class->db_new($dsn, $user, $pass, $attrs);
|
||||
|
||||
# This makes sure that if the tables are encoded as UTF-8, we
|
||||
# return their data correctly.
|
||||
$self->do("SET NAMES utf8") if Bugzilla->params->{'utf8'};
|
||||
|
||||
# all class local variables stored in DBI derived class needs to have
|
||||
# a prefix 'private_'. See DBI documentation.
|
||||
$self->{private_bz_tables_locked} = "";
|
||||
|
||||
bless ($self, $class);
|
||||
|
||||
# Bug 321645 - disable MySQL strict mode, if set
|
||||
my ($var, $sql_mode) = $self->selectrow_array(
|
||||
"SHOW VARIABLES LIKE 'sql\\_mode'");
|
||||
|
||||
if ($sql_mode) {
|
||||
# STRICT_TRANS_TABLE or STRICT_ALL_TABLES enable MySQL strict mode,
|
||||
# causing bug 321645. TRADITIONAL sets these modes (among others) as
|
||||
# well, so it has to be stipped as well
|
||||
my $new_sql_mode =
|
||||
join(",", grep {$_ !~ /^STRICT_(?:TRANS|ALL)_TABLES|TRADITIONAL$/}
|
||||
split(/,/, $sql_mode));
|
||||
|
||||
if ($sql_mode ne $new_sql_mode) {
|
||||
$self->do("SET SESSION sql_mode = ?", undef, $new_sql_mode);
|
||||
}
|
||||
}
|
||||
|
||||
# The "comments" field of the bugs_fulltext table could easily exceed
|
||||
# MySQL's default max_allowed_packet. Also, MySQL should never have
|
||||
# a max_allowed_packet smaller than our max_attachment_size. However,
|
||||
# if we've already set a max_allowed_packet in MySQL bigger than all
|
||||
# of those, we should keep it.
|
||||
my (undef, $current_max_allowed) = $self->selectrow_array(
|
||||
q{SHOW VARIABLES LIKE 'max\_allowed\_packet'});
|
||||
my $min_max_allowed_packet = MAX_COMMENTS * MAX_COMMENT_LENGTH;
|
||||
my $max_allowed_packet = max($min_max_allowed_packet,
|
||||
$current_max_allowed,
|
||||
# This parameter is not yet defined when the DB
|
||||
# is being built for the very first time.
|
||||
Bugzilla->params->{'maxattachmentsize'} || 0);
|
||||
$self->do("SET SESSION max_allowed_packet = $max_allowed_packet");
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# when last_insert_id() is supported on MySQL by lowest DBI/DBD version
|
||||
# required by Bugzilla, this implementation can be removed.
|
||||
sub bz_last_key {
|
||||
my ($self) = @_;
|
||||
|
||||
my ($last_insert_id) = $self->selectrow_array('SELECT LAST_INSERT_ID()');
|
||||
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
sub sql_group_concat {
|
||||
my ($self, $column, $separator) = @_;
|
||||
my $sep_sql;
|
||||
if ($separator) {
|
||||
$sep_sql = " SEPARATOR $separator";
|
||||
}
|
||||
return "GROUP_CONCAT($column$sep_sql)";
|
||||
}
|
||||
|
||||
sub sql_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "$expr REGEXP $pattern";
|
||||
}
|
||||
|
||||
sub sql_not_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "$expr NOT REGEXP $pattern";
|
||||
}
|
||||
|
||||
sub sql_limit {
|
||||
my ($self, $limit, $offset) = @_;
|
||||
|
||||
if (defined($offset)) {
|
||||
return "LIMIT $offset, $limit";
|
||||
} else {
|
||||
return "LIMIT $limit";
|
||||
}
|
||||
}
|
||||
|
||||
sub sql_string_concat {
|
||||
my ($self, @params) = @_;
|
||||
|
||||
return 'CONCAT(' . join(', ', @params) . ')';
|
||||
}
|
||||
|
||||
sub sql_fulltext_search {
|
||||
my ($self, $column, $text) = @_;
|
||||
|
||||
# Add the boolean mode modifier if the search string contains
|
||||
# boolean operators.
|
||||
my $mode = ($text =~ /[+\-<>()~*"]/ ? "IN BOOLEAN MODE" : "");
|
||||
|
||||
# quote the text for use in the MATCH AGAINST expression
|
||||
$text = $self->quote($text);
|
||||
|
||||
# untaint the text, since it's safe to use now that we've quoted it
|
||||
trick_taint($text);
|
||||
|
||||
return "MATCH($column) AGAINST($text $mode)";
|
||||
}
|
||||
|
||||
sub sql_istring {
|
||||
my ($self, $string) = @_;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
sub sql_from_days {
|
||||
my ($self, $days) = @_;
|
||||
|
||||
return "FROM_DAYS($days)";
|
||||
}
|
||||
|
||||
sub sql_to_days {
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return "TO_DAYS($date)";
|
||||
}
|
||||
|
||||
sub sql_date_format {
|
||||
my ($self, $date, $format) = @_;
|
||||
|
||||
$format = "%Y.%m.%d %H:%i:%s" if !$format;
|
||||
|
||||
return "DATE_FORMAT($date, " . $self->quote($format) . ")";
|
||||
}
|
||||
|
||||
sub sql_interval {
|
||||
my ($self, $interval, $units) = @_;
|
||||
|
||||
return "INTERVAL $interval $units";
|
||||
}
|
||||
|
||||
sub sql_iposition {
|
||||
my ($self, $fragment, $text) = @_;
|
||||
return "INSTR($text, $fragment)";
|
||||
}
|
||||
|
||||
sub sql_position {
|
||||
my ($self, $fragment, $text) = @_;
|
||||
|
||||
return "INSTR(CAST($text AS BINARY), CAST($fragment AS BINARY))";
|
||||
}
|
||||
|
||||
sub sql_group_by {
|
||||
my ($self, $needed_columns, $optional_columns) = @_;
|
||||
|
||||
# MySQL allows you to specify the minimal subset of columns to get
|
||||
# a unique result. While it does allow specifying all columns as
|
||||
# ANSI SQL requires, according to MySQL documentation, the fewer
|
||||
# columns you specify, the faster the query runs.
|
||||
return "GROUP BY $needed_columns";
|
||||
}
|
||||
|
||||
|
||||
sub _bz_get_initial_schema {
|
||||
my ($self) = @_;
|
||||
return $self->_bz_build_schema_from_disk();
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Database Setup
|
||||
#####################################################################
|
||||
|
||||
sub bz_setup_database {
|
||||
my ($self) = @_;
|
||||
|
||||
# Make sure the installation has InnoDB turned on, or we're going to be
|
||||
# doing silly things like making foreign keys on MyISAM tables, which is
|
||||
# hard to fix later. We do this up here because none of the code below
|
||||
# works if InnoDB is off. (Particularly if we've already converted the
|
||||
# tables to InnoDB.)
|
||||
my ($innodb_on) = @{$self->selectcol_arrayref(
|
||||
q{SHOW VARIABLES LIKE '%have_innodb%'}, {Columns=>[2]})};
|
||||
if ($innodb_on ne 'YES') {
|
||||
print <<EOT;
|
||||
InnoDB is disabled in your MySQL installation.
|
||||
Bugzilla requires InnoDB to be enabled.
|
||||
Please enable it and then re-run checksetup.pl.
|
||||
|
||||
EOT
|
||||
exit 3;
|
||||
}
|
||||
|
||||
|
||||
# Figure out if any existing tables are of type ISAM and convert them
|
||||
# to type MyISAM if so. ISAM tables are deprecated in MySQL 3.23,
|
||||
# which Bugzilla now requires, and they don't support more than 16
|
||||
# indexes per table, which Bugzilla needs.
|
||||
my $table_status = $self->selectall_arrayref("SHOW TABLE STATUS");
|
||||
my @isam_tables;
|
||||
foreach my $row (@$table_status) {
|
||||
my ($name, $type) = @$row;
|
||||
push(@isam_tables, $name) if $type eq "ISAM";
|
||||
}
|
||||
|
||||
if(scalar(@isam_tables)) {
|
||||
print "One or more of the tables in your existing MySQL database are\n"
|
||||
. "of type ISAM. ISAM tables are deprecated in MySQL 3.23 and\n"
|
||||
. "don't support more than 16 indexes per table, which \n"
|
||||
. "Bugzilla needs.\n Converting your ISAM tables to type"
|
||||
. " MyISAM:\n\n";
|
||||
foreach my $table (@isam_tables) {
|
||||
print "Converting table $table... ";
|
||||
$self->do("ALTER TABLE $table TYPE = MYISAM");
|
||||
print "done.\n";
|
||||
}
|
||||
print "\nISAM->MyISAM table conversion done.\n\n";
|
||||
}
|
||||
|
||||
my ($sd_index_deleted, $longdescs_index_deleted);
|
||||
my @tables = $self->bz_table_list_real();
|
||||
# We want to convert tables to InnoDB, but it's possible that they have
|
||||
# fulltext indexes on them, and conversion will fail unless we remove
|
||||
# the indexes.
|
||||
if (grep($_ eq 'bugs', @tables)) {
|
||||
if ($self->bz_index_info_real('bugs', 'short_desc')) {
|
||||
$self->bz_drop_index_raw('bugs', 'short_desc');
|
||||
}
|
||||
if ($self->bz_index_info_real('bugs', 'bugs_short_desc_idx')) {
|
||||
$self->bz_drop_index_raw('bugs', 'bugs_short_desc_idx');
|
||||
$sd_index_deleted = 1; # Used for later schema cleanup.
|
||||
}
|
||||
}
|
||||
if (grep($_ eq 'longdescs', @tables)) {
|
||||
if ($self->bz_index_info_real('longdescs', 'thetext')) {
|
||||
$self->bz_drop_index_raw('longdescs', 'thetext');
|
||||
}
|
||||
if ($self->bz_index_info_real('longdescs', 'longdescs_thetext_idx')) {
|
||||
$self->bz_drop_index_raw('longdescs', 'longdescs_thetext_idx');
|
||||
$longdescs_index_deleted = 1; # For later schema cleanup.
|
||||
}
|
||||
}
|
||||
|
||||
# Upgrade tables from MyISAM to InnoDB
|
||||
my @myisam_tables;
|
||||
foreach my $row (@$table_status) {
|
||||
my ($name, $type) = @$row;
|
||||
if ($type =~ /^MYISAM$/i
|
||||
&& !grep($_ eq $name, Bugzilla::DB::Schema::Mysql::MYISAM_TABLES))
|
||||
{
|
||||
push(@myisam_tables, $name) ;
|
||||
}
|
||||
}
|
||||
if (scalar @myisam_tables) {
|
||||
print "Bugzilla now uses the InnoDB storage engine in MySQL for",
|
||||
" most tables.\nConverting tables to InnoDB:\n";
|
||||
foreach my $table (@myisam_tables) {
|
||||
print "Converting table $table... ";
|
||||
$self->do("ALTER TABLE $table TYPE = InnoDB");
|
||||
print "done.\n";
|
||||
}
|
||||
}
|
||||
|
||||
$self->_after_table_status(\@tables);
|
||||
|
||||
# Versions of Bugzilla before the existence of Bugzilla::DB::Schema did
|
||||
# not provide explicit names for the table indexes. This means
|
||||
# that our upgrades will not be reliable, because we look for the name
|
||||
# of the index, not what fields it is on, when doing upgrades.
|
||||
# (using the name is much better for cross-database compatibility
|
||||
# and general reliability). It's also very important that our
|
||||
# Schema object be consistent with what is on the disk.
|
||||
#
|
||||
# While we're at it, we also fix some inconsistent index naming
|
||||
# from the original checkin of Bugzilla::DB::Schema.
|
||||
|
||||
# We check for the existence of a particular "short name" index that
|
||||
# has existed at least since Bugzilla 2.8, and probably earlier.
|
||||
# For fixing the inconsistent naming of Schema indexes,
|
||||
# we also check for one of those inconsistently-named indexes.
|
||||
if (grep($_ eq 'bugs', @tables)
|
||||
&& ($self->bz_index_info_real('bugs', 'assigned_to')
|
||||
|| $self->bz_index_info_real('flags', 'flags_bidattid_idx')) )
|
||||
{
|
||||
|
||||
# This is a check unrelated to the indexes, to see if people are
|
||||
# upgrading from 2.18 or below, but somehow have a bz_schema table
|
||||
# already. This only happens if they have done a mysqldump into
|
||||
# a database without doing a DROP DATABASE first.
|
||||
# We just do the check here since this check is a reliable way
|
||||
# of telling that we are upgrading from a version pre-2.20.
|
||||
if (grep($_ eq 'bz_schema', $self->bz_table_list_real())) {
|
||||
die("\nYou are upgrading from a version before 2.20, but the"
|
||||
. " bz_schema\ntable already exists. This means that you"
|
||||
. " restored a mysqldump into\nthe Bugzilla database without"
|
||||
. " first dropping the already-existing\nBugzilla database,"
|
||||
. " at some point. Whenever you restore a Bugzilla\ndatabase"
|
||||
. " backup, you must always drop the entire database first.\n\n"
|
||||
. "Please drop your Bugzilla database and restore it from a"
|
||||
. " backup that\ndoes not contain the bz_schema table. If for"
|
||||
. " some reason you cannot\ndo this, you can connect to your"
|
||||
. " MySQL database and drop the bz_schema\ntable, as a last"
|
||||
. " resort.\n");
|
||||
}
|
||||
|
||||
my $bug_count = $self->selectrow_array("SELECT COUNT(*) FROM bugs");
|
||||
# We estimate one minute for each 3000 bugs, plus 3 minutes just
|
||||
# to handle basic MySQL stuff.
|
||||
my $rename_time = int($bug_count / 3000) + 3;
|
||||
# And 45 minutes for every 15,000 attachments, per some experiments.
|
||||
my ($attachment_count) =
|
||||
$self->selectrow_array("SELECT COUNT(*) FROM attachments");
|
||||
$rename_time += int(($attachment_count * 45) / 15000);
|
||||
# If we're going to take longer than 5 minutes, we let the user know
|
||||
# and allow them to abort.
|
||||
if ($rename_time > 5) {
|
||||
print "\nWe are about to rename old indexes.\n"
|
||||
. "The estimated time to complete renaming is "
|
||||
. "$rename_time minutes.\n"
|
||||
. "You cannot interrupt this action once it has begun.\n"
|
||||
. "If you would like to cancel, press Ctrl-C now..."
|
||||
. " (Waiting 45 seconds...)\n\n";
|
||||
# Wait 45 seconds for them to respond.
|
||||
sleep(45) unless Bugzilla->installation_answers->{NO_PAUSE};
|
||||
}
|
||||
print "Renaming indexes...\n";
|
||||
|
||||
# We can't be interrupted, because of how the "if"
|
||||
# works above.
|
||||
local $SIG{INT} = 'IGNORE';
|
||||
local $SIG{TERM} = 'IGNORE';
|
||||
local $SIG{PIPE} = 'IGNORE';
|
||||
|
||||
# Certain indexes had names in Schema that did not easily conform
|
||||
# to a standard. We store those names here, so that they
|
||||
# can be properly renamed.
|
||||
# Also, sometimes an old mysqldump would incorrectly rename
|
||||
# unique indexes to "PRIMARY", so we address that here, also.
|
||||
my $bad_names = {
|
||||
# 'when' is a possible leftover from Bugzillas before 2.8
|
||||
bugs_activity => ['when', 'bugs_activity_bugid_idx',
|
||||
'bugs_activity_bugwhen_idx'],
|
||||
cc => ['PRIMARY'],
|
||||
longdescs => ['longdescs_bugid_idx',
|
||||
'longdescs_bugwhen_idx'],
|
||||
flags => ['flags_bidattid_idx'],
|
||||
flaginclusions => ['flaginclusions_tpcid_idx'],
|
||||
flagexclusions => ['flagexclusions_tpc_id_idx'],
|
||||
keywords => ['PRIMARY'],
|
||||
milestones => ['PRIMARY'],
|
||||
profiles_activity => ['profiles_activity_when_idx'],
|
||||
group_control_map => ['group_control_map_gid_idx', 'PRIMARY'],
|
||||
user_group_map => ['PRIMARY'],
|
||||
group_group_map => ['PRIMARY'],
|
||||
email_setting => ['PRIMARY'],
|
||||
bug_group_map => ['PRIMARY'],
|
||||
category_group_map => ['PRIMARY'],
|
||||
watch => ['PRIMARY'],
|
||||
namedqueries => ['PRIMARY'],
|
||||
series_data => ['PRIMARY'],
|
||||
# series_categories is dealt with below, not here.
|
||||
};
|
||||
|
||||
# The series table is broken and needs to have one index
|
||||
# dropped before we begin the renaming, because it had a
|
||||
# useless index on it that would cause a naming conflict here.
|
||||
if (grep($_ eq 'series', @tables)) {
|
||||
my $dropname;
|
||||
# This is what the bad index was called before Schema.
|
||||
if ($self->bz_index_info_real('series', 'creator_2')) {
|
||||
$dropname = 'creator_2';
|
||||
}
|
||||
# This is what the bad index is called in Schema.
|
||||
elsif ($self->bz_index_info_real('series', 'series_creator_idx')) {
|
||||
$dropname = 'series_creator_idx';
|
||||
}
|
||||
$self->bz_drop_index_raw('series', $dropname) if $dropname;
|
||||
}
|
||||
|
||||
# The email_setting table also had the same problem.
|
||||
if( grep($_ eq 'email_setting', @tables)
|
||||
&& $self->bz_index_info_real('email_setting',
|
||||
'email_settings_user_id_idx') )
|
||||
{
|
||||
$self->bz_drop_index_raw('email_setting',
|
||||
'email_settings_user_id_idx');
|
||||
}
|
||||
|
||||
# Go through all the tables.
|
||||
foreach my $table (@tables) {
|
||||
# Will contain the names of old indexes as keys, and the
|
||||
# definition of the new indexes as a value. The values
|
||||
# include an extra hash key, NAME, with the new name of
|
||||
# the index.
|
||||
my %rename_indexes;
|
||||
# And go through all the columns on each table.
|
||||
my @columns = $self->bz_table_columns_real($table);
|
||||
|
||||
# We also want to fix the silly naming of unique indexes
|
||||
# that happened when we first checked-in Bugzilla::DB::Schema.
|
||||
if ($table eq 'series_categories') {
|
||||
# The series_categories index had a nonstandard name.
|
||||
push(@columns, 'series_cats_unique_idx');
|
||||
}
|
||||
elsif ($table eq 'email_setting') {
|
||||
# The email_setting table had a similar problem.
|
||||
push(@columns, 'email_settings_unique_idx');
|
||||
}
|
||||
else {
|
||||
push(@columns, "${table}_unique_idx");
|
||||
}
|
||||
# And this is how we fix the other inconsistent Schema naming.
|
||||
push(@columns, @{$bad_names->{$table}})
|
||||
if (exists $bad_names->{$table});
|
||||
foreach my $column (@columns) {
|
||||
# If we have an index named after this column, it's an
|
||||
# old-style-name index.
|
||||
if (my $index = $self->bz_index_info_real($table, $column)) {
|
||||
# Fix the name to fit in with the new naming scheme.
|
||||
$index->{NAME} = $table . "_" .
|
||||
$index->{FIELDS}->[0] . "_idx";
|
||||
print "Renaming index $column to "
|
||||
. $index->{NAME} . "...\n";
|
||||
$rename_indexes{$column} = $index;
|
||||
} # if
|
||||
} # foreach column
|
||||
|
||||
my @rename_sql = $self->_bz_schema->get_rename_indexes_ddl(
|
||||
$table, %rename_indexes);
|
||||
$self->do($_) foreach (@rename_sql);
|
||||
|
||||
} # foreach table
|
||||
} # if old-name indexes
|
||||
|
||||
# If there are no tables, but the DB isn't utf8 and it should be,
|
||||
# then we should alter the database to be utf8. We know it should be
|
||||
# if the utf8 parameter is true or there are no params at all.
|
||||
# This kind of situation happens when people create the database
|
||||
# themselves, and if we don't do this they will get the big
|
||||
# scary WARNING statement about conversion to UTF8.
|
||||
if ( !$self->bz_db_is_utf8 && !@tables
|
||||
&& (Bugzilla->params->{'utf8'} || !scalar keys %{Bugzilla->params}) )
|
||||
{
|
||||
$self->_alter_db_charset_to_utf8();
|
||||
}
|
||||
|
||||
# And now we create the tables and the Schema object.
|
||||
$self->SUPER::bz_setup_database();
|
||||
|
||||
if ($sd_index_deleted) {
|
||||
$self->_bz_real_schema->delete_index('bugs', 'bugs_short_desc_idx');
|
||||
$self->_bz_store_real_schema;
|
||||
}
|
||||
if ($longdescs_index_deleted) {
|
||||
$self->_bz_real_schema->delete_index('longdescs',
|
||||
'longdescs_thetext_idx');
|
||||
$self->_bz_store_real_schema;
|
||||
}
|
||||
|
||||
# The old timestamp fields need to be adjusted here instead of in
|
||||
# checksetup. Otherwise the UPDATE statements inside of bz_add_column
|
||||
# will cause accidental timestamp updates.
|
||||
# The code that does this was moved here from checksetup.
|
||||
|
||||
# 2002-08-14 - bbaetz@student.usyd.edu.au - bug 153578
|
||||
# attachments creation time needs to be a datetime, not a timestamp
|
||||
my $attach_creation =
|
||||
$self->bz_column_info("attachments", "creation_ts");
|
||||
if ($attach_creation && $attach_creation->{TYPE} =~ /^TIMESTAMP/i) {
|
||||
print "Fixing creation time on attachments...\n";
|
||||
|
||||
my $sth = $self->prepare("SELECT COUNT(attach_id) FROM attachments");
|
||||
$sth->execute();
|
||||
my ($attach_count) = $sth->fetchrow_array();
|
||||
|
||||
if ($attach_count > 1000) {
|
||||
print "This may take a while...\n";
|
||||
}
|
||||
my $i = 0;
|
||||
|
||||
# This isn't just as simple as changing the field type, because
|
||||
# the creation_ts was previously updated when an attachment was made
|
||||
# obsolete from the attachment creation screen. So we have to go
|
||||
# and recreate these times from the comments..
|
||||
$sth = $self->prepare("SELECT bug_id, attach_id, submitter_id " .
|
||||
"FROM attachments");
|
||||
$sth->execute();
|
||||
|
||||
# Restrict this as much as possible in order to avoid false
|
||||
# positives, and keep the db search time down
|
||||
my $sth2 = $self->prepare("SELECT bug_when FROM longdescs
|
||||
WHERE bug_id=? AND who=?
|
||||
AND thetext LIKE ?
|
||||
ORDER BY bug_when " . $self->sql_limit(1));
|
||||
while (my ($bug_id, $attach_id, $submitter_id)
|
||||
= $sth->fetchrow_array())
|
||||
{
|
||||
$sth2->execute($bug_id, $submitter_id,
|
||||
"Created an attachment (id=$attach_id)%");
|
||||
my ($when) = $sth2->fetchrow_array();
|
||||
if ($when) {
|
||||
$self->do("UPDATE attachments " .
|
||||
"SET creation_ts='$when' " .
|
||||
"WHERE attach_id=$attach_id");
|
||||
} else {
|
||||
print "Warning - could not determine correct creation"
|
||||
. " time for attachment $attach_id on bug $bug_id\n";
|
||||
}
|
||||
++$i;
|
||||
print "Converted $i of $attach_count attachments\n" if !($i % 1000);
|
||||
}
|
||||
print "Done - converted $i attachments\n";
|
||||
|
||||
$self->bz_alter_column("attachments", "creation_ts",
|
||||
{TYPE => 'DATETIME', NOTNULL => 1});
|
||||
}
|
||||
|
||||
# 2004-08-29 - Tomas.Kopal@altap.cz, bug 257303
|
||||
# Change logincookies.lastused type from timestamp to datetime
|
||||
my $login_lastused = $self->bz_column_info("logincookies", "lastused");
|
||||
if ($login_lastused && $login_lastused->{TYPE} =~ /^TIMESTAMP/i) {
|
||||
$self->bz_alter_column('logincookies', 'lastused',
|
||||
{ TYPE => 'DATETIME', NOTNULL => 1});
|
||||
}
|
||||
|
||||
# 2005-01-17 - Tomas.Kopal@altap.cz, bug 257315
|
||||
# Change bugs.delta_ts type from timestamp to datetime
|
||||
my $bugs_deltats = $self->bz_column_info("bugs", "delta_ts");
|
||||
if ($bugs_deltats && $bugs_deltats->{TYPE} =~ /^TIMESTAMP/i) {
|
||||
$self->bz_alter_column('bugs', 'delta_ts',
|
||||
{TYPE => 'DATETIME', NOTNULL => 1});
|
||||
}
|
||||
|
||||
# 2005-09-24 - bugreport@peshkin.net, bug 307602
|
||||
# Make sure that default 4G table limit is overridden
|
||||
my $row = $self->selectrow_hashref("SHOW TABLE STATUS LIKE 'attach_data'");
|
||||
if ($$row{'Create_options'} !~ /MAX_ROWS/i) {
|
||||
print "Converting attach_data maximum size to 100G...\n";
|
||||
$self->do("ALTER TABLE attach_data
|
||||
AVG_ROW_LENGTH=1000000,
|
||||
MAX_ROWS=100000");
|
||||
}
|
||||
|
||||
# Convert the database to UTF-8 if the utf8 parameter is on.
|
||||
# We check if any table isn't utf8, because lots of crazy
|
||||
# partial-conversion situations can happen, and this handles anything
|
||||
# that could come up (including having the DB charset be utf8 but not
|
||||
# the table charsets.
|
||||
my $utf_table_status =
|
||||
$self->selectall_arrayref("SHOW TABLE STATUS", {Slice=>{}});
|
||||
$self->_after_table_status([map($_->{Name}, @$utf_table_status)]);
|
||||
my @non_utf8_tables = grep($_->{Collation} !~ /^utf8/, @$utf_table_status);
|
||||
|
||||
if (Bugzilla->params->{'utf8'} && scalar @non_utf8_tables) {
|
||||
print <<EOT;
|
||||
|
||||
WARNING: We are about to convert your table storage format to UTF8. This
|
||||
allows Bugzilla to correctly store and sort international characters.
|
||||
However, if you have any non-UTF-8 data in your database,
|
||||
it ***WILL BE DELETED*** by this process. So, before
|
||||
you continue with checksetup.pl, if you have any non-UTF-8
|
||||
data (or even if you're not sure) you should press Ctrl-C now
|
||||
to interrupt checksetup.pl, and run contrib/recode.pl to make all
|
||||
the data in your database into UTF-8. You should also back up your
|
||||
database before continuing. This will affect every single table
|
||||
in the database, even non-Bugzilla tables.
|
||||
|
||||
If you ever used a version of Bugzilla before 2.22, we STRONGLY
|
||||
recommend that you stop checksetup.pl NOW and run contrib/recode.pl.
|
||||
|
||||
EOT
|
||||
|
||||
if (!Bugzilla->installation_answers->{NO_PAUSE}) {
|
||||
if (Bugzilla->installation_mode ==
|
||||
INSTALLATION_MODE_NON_INTERACTIVE)
|
||||
{
|
||||
print <<EOT;
|
||||
Re-run checksetup.pl in interactive mode (without an 'answers' file)
|
||||
to continue.
|
||||
EOT
|
||||
exit;
|
||||
}
|
||||
else {
|
||||
print " Press Enter to continue or Ctrl-C to exit...";
|
||||
getc;
|
||||
}
|
||||
}
|
||||
|
||||
print "Converting table storage format to UTF-8. This may take a",
|
||||
" while.\n";
|
||||
foreach my $table ($self->bz_table_list_real) {
|
||||
my $info_sth = $self->prepare("SHOW FULL COLUMNS FROM $table");
|
||||
$info_sth->execute();
|
||||
while (my $column = $info_sth->fetchrow_hashref) {
|
||||
# Our conversion code doesn't work on enum fields, but they
|
||||
# all go away later in checksetup anyway.
|
||||
next if $column->{Type} =~ /enum/i;
|
||||
|
||||
# If this particular column isn't stored in utf-8
|
||||
if ($column->{Collation}
|
||||
&& $column->{Collation} ne 'NULL'
|
||||
&& $column->{Collation} !~ /utf8/)
|
||||
{
|
||||
my $name = $column->{Field};
|
||||
|
||||
# The code below doesn't work on a field with a FULLTEXT
|
||||
# index. So we drop it, which we'd do later anyway.
|
||||
if ($table eq 'longdescs' && $name eq 'thetext') {
|
||||
$self->bz_drop_index('longdescs',
|
||||
'longdescs_thetext_idx');
|
||||
}
|
||||
if ($table eq 'bugs' && $name eq 'short_desc') {
|
||||
$self->bz_drop_index('bugs', 'bugs_short_desc_idx');
|
||||
}
|
||||
my %ft_indexes;
|
||||
if ($table eq 'bugs_fulltext') {
|
||||
%ft_indexes = $self->_bz_real_schema->get_indexes_on_column_abstract(
|
||||
'bugs_fulltext', $name);
|
||||
foreach my $index (keys %ft_indexes) {
|
||||
$self->bz_drop_index('bugs_fulltext', $index);
|
||||
}
|
||||
}
|
||||
|
||||
print "Converting $table.$name to be stored as UTF-8...\n";
|
||||
my $col_info =
|
||||
$self->bz_column_info_real($table, $name);
|
||||
|
||||
# CHANGE COLUMN doesn't take PRIMARY KEY
|
||||
delete $col_info->{PRIMARYKEY};
|
||||
|
||||
my $sql_def = $self->_bz_schema->get_type_ddl($col_info);
|
||||
# We don't want MySQL to actually try to *convert*
|
||||
# from our current charset to UTF-8, we just want to
|
||||
# transfer the bytes directly. This is how we do that.
|
||||
|
||||
# The CHARACTER SET part of the definition has to come
|
||||
# right after the type, which will always come first.
|
||||
my ($binary, $utf8) = ($sql_def, $sql_def);
|
||||
my $type = $self->_bz_schema->convert_type($col_info->{TYPE});
|
||||
$binary =~ s/(\Q$type\E)/$1 CHARACTER SET binary/;
|
||||
$utf8 =~ s/(\Q$type\E)/$1 CHARACTER SET utf8/;
|
||||
$self->do("ALTER TABLE $table CHANGE COLUMN $name $name
|
||||
$binary");
|
||||
$self->do("ALTER TABLE $table CHANGE COLUMN $name $name
|
||||
$utf8");
|
||||
|
||||
if ($table eq 'bugs_fulltext') {
|
||||
foreach my $index (keys %ft_indexes) {
|
||||
$self->bz_add_index('bugs_fulltext', $index,
|
||||
$ft_indexes{$index});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->do("ALTER TABLE $table DEFAULT CHARACTER SET utf8");
|
||||
} # foreach my $table (@tables)
|
||||
}
|
||||
|
||||
# Sometimes you can have a situation where all the tables are utf8,
|
||||
# but the database isn't. (This tends to happen when you've done
|
||||
# a mysqldump.) So we have this change outside of the above block,
|
||||
# so that it just happens silently if no actual *table* conversion
|
||||
# needs to happen.
|
||||
if (Bugzilla->params->{'utf8'} && !$self->bz_db_is_utf8) {
|
||||
$self->_alter_db_charset_to_utf8();
|
||||
}
|
||||
}
|
||||
|
||||
# There is a bug in MySQL 4.1.0 - 4.1.15 that makes certain SELECT
|
||||
# statements fail after a SHOW TABLE STATUS:
|
||||
# http://bugs.mysql.com/bug.php?id=13535
|
||||
# This is a workaround, a dummy SELECT to reset the LAST_INSERT_ID.
|
||||
sub _after_table_status {
|
||||
my ($self, $tables) = @_;
|
||||
if (grep($_ eq 'bugs', @$tables)
|
||||
&& $self->bz_column_info_real("bugs", "bug_id"))
|
||||
{
|
||||
$self->do('SELECT 1 FROM bugs WHERE bug_id IS NULL');
|
||||
}
|
||||
}
|
||||
|
||||
sub _alter_db_charset_to_utf8 {
|
||||
my $self = shift;
|
||||
my $db_name = Bugzilla->localconfig->{db_name};
|
||||
$self->do("ALTER DATABASE $db_name CHARACTER SET utf8");
|
||||
}
|
||||
|
||||
sub bz_db_is_utf8 {
|
||||
my $self = shift;
|
||||
my $db_collation = $self->selectrow_arrayref(
|
||||
"SHOW VARIABLES LIKE 'character_set_database'");
|
||||
# First column holds the variable name, second column holds the value.
|
||||
return $db_collation->[1] =~ /utf8/ ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
sub bz_enum_initial_values {
|
||||
my ($self) = @_;
|
||||
my %enum_values = %{$self->ENUM_DEFAULTS};
|
||||
# Get a complete description of the 'bugs' table; with DBD::MySQL
|
||||
# there isn't a column-by-column way of doing this. Could use
|
||||
# $dbh->column_info, but it would go slower and we would have to
|
||||
# use the undocumented mysql_type_name accessor to get the type
|
||||
# of each row.
|
||||
my $sth = $self->prepare("DESCRIBE bugs");
|
||||
$sth->execute();
|
||||
# Look for the particular columns we are interested in.
|
||||
while (my ($thiscol, $thistype) = $sth->fetchrow_array()) {
|
||||
if (defined $enum_values{$thiscol}) {
|
||||
# this is a column of interest.
|
||||
my @value_list;
|
||||
if ($thistype and ($thistype =~ /^enum\(/)) {
|
||||
# it has an enum type; get the set of values.
|
||||
while ($thistype =~ /'([^']*)'(.*)/) {
|
||||
push(@value_list, $1);
|
||||
$thistype = $2;
|
||||
}
|
||||
}
|
||||
if (@value_list) {
|
||||
# record the enum values found.
|
||||
$enum_values{$thiscol} = \@value_list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \%enum_values;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# MySQL-specific Database-Reading Methods
|
||||
#####################################################################
|
||||
|
||||
=begin private
|
||||
|
||||
=head1 MYSQL-SPECIFIC DATABASE-READING METHODS
|
||||
|
||||
These methods read information about the database from the disk,
|
||||
instead of from a Schema object. They are only reliable for MySQL
|
||||
(see bug 285111 for the reasons why not all DBs use/have functions
|
||||
like this), but that's OK because we only need them for
|
||||
backwards-compatibility anyway, for versions of Bugzilla before 2.20.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<bz_column_info_real($table, $column)>
|
||||
|
||||
Description: Returns an abstract column definition for a column
|
||||
as it actually exists on disk in the database.
|
||||
Params: $table - The name of the table the column is on.
|
||||
$column - The name of the column you want info about.
|
||||
Returns: An abstract column definition.
|
||||
If the column does not exist, returns undef.
|
||||
|
||||
=cut
|
||||
|
||||
sub bz_column_info_real {
|
||||
my ($self, $table, $column) = @_;
|
||||
|
||||
# DBD::mysql does not support selecting a specific column,
|
||||
# so we have to get all the columns on the table and find
|
||||
# the one we want.
|
||||
my $info_sth = $self->column_info(undef, undef, $table, '%');
|
||||
|
||||
# Don't use fetchall_hashref as there's a Win32 DBI bug (292821)
|
||||
my $col_data;
|
||||
while ($col_data = $info_sth->fetchrow_hashref) {
|
||||
last if $col_data->{'COLUMN_NAME'} eq $column;
|
||||
}
|
||||
|
||||
if (!defined $col_data) {
|
||||
return undef;
|
||||
}
|
||||
return $self->_bz_schema->column_info_to_column($col_data);
|
||||
}
|
||||
|
||||
=item C<bz_index_info_real($table, $index)>
|
||||
|
||||
Description: Returns information about an index on a table in the database.
|
||||
Params: $table = name of table containing the index
|
||||
$index = name of an index
|
||||
Returns: An abstract index definition, always in hashref format.
|
||||
If the index does not exist, the function returns undef.
|
||||
=cut
|
||||
sub bz_index_info_real {
|
||||
my ($self, $table, $index) = @_;
|
||||
|
||||
my $sth = $self->prepare("SHOW INDEX FROM $table");
|
||||
$sth->execute;
|
||||
|
||||
my @fields;
|
||||
my $index_type;
|
||||
# $raw_def will be an arrayref containing the following information:
|
||||
# 0 = name of the table that the index is on
|
||||
# 1 = 0 if unique, 1 if not unique
|
||||
# 2 = name of the index
|
||||
# 3 = seq_in_index (The order of the current field in the index).
|
||||
# 4 = Name of ONE column that the index is on
|
||||
# 5 = 'Collation' of the index. Usually 'A'.
|
||||
# 6 = Cardinality. Either a number or undef.
|
||||
# 7 = sub_part. Usually undef. Sometimes 1.
|
||||
# 8 = "packed". Usually undef.
|
||||
# 9 = Null. Sometimes undef, sometimes 'YES'.
|
||||
# 10 = Index_type. The type of the index. Usually either 'BTREE' or 'FULLTEXT'
|
||||
# 11 = 'Comment.' Usually undef.
|
||||
while (my $raw_def = $sth->fetchrow_arrayref) {
|
||||
if ($raw_def->[2] eq $index) {
|
||||
push(@fields, $raw_def->[4]);
|
||||
# No index can be both UNIQUE and FULLTEXT, that's why
|
||||
# this is written this way.
|
||||
$index_type = $raw_def->[1] ? '' : 'UNIQUE';
|
||||
$index_type = $raw_def->[10] eq 'FULLTEXT'
|
||||
? 'FULLTEXT' : $index_type;
|
||||
}
|
||||
}
|
||||
|
||||
my $retval;
|
||||
if (scalar(@fields)) {
|
||||
$retval = {FIELDS => \@fields, TYPE => $index_type};
|
||||
}
|
||||
return $retval;
|
||||
}
|
||||
|
||||
=item C<bz_index_list_real($table)>
|
||||
|
||||
Description: Returns a list of index names on a table in
|
||||
the database, as it actually exists on disk.
|
||||
Params: $table - The name of the table you want info about.
|
||||
Returns: An array of index names.
|
||||
|
||||
=cut
|
||||
|
||||
sub bz_index_list_real {
|
||||
my ($self, $table) = @_;
|
||||
my $sth = $self->prepare("SHOW INDEX FROM $table");
|
||||
# Column 3 of a SHOW INDEX statement contains the name of the index.
|
||||
return @{ $self->selectcol_arrayref($sth, {Columns => [3]}) };
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# MySQL-Specific "Schema Builder"
|
||||
#####################################################################
|
||||
|
||||
=back
|
||||
|
||||
=head1 MYSQL-SPECIFIC "SCHEMA BUILDER"
|
||||
|
||||
MySQL needs to be able to read in a legacy database (from before
|
||||
Schema existed) and create a Schema object out of it. That's what
|
||||
this code does.
|
||||
|
||||
=end private
|
||||
|
||||
=cut
|
||||
|
||||
# This sub itself is actually written generically, but the subroutines
|
||||
# that it depends on are database-specific. In particular, the
|
||||
# bz_column_info_real function would be very difficult to create
|
||||
# properly for any other DB besides MySQL.
|
||||
sub _bz_build_schema_from_disk {
|
||||
my ($self) = @_;
|
||||
|
||||
print "Building Schema object from database...\n";
|
||||
|
||||
my $schema = $self->_bz_schema->get_empty_schema();
|
||||
|
||||
my @tables = $self->bz_table_list_real();
|
||||
foreach my $table (@tables) {
|
||||
$schema->add_table($table);
|
||||
my @columns = $self->bz_table_columns_real($table);
|
||||
foreach my $column (@columns) {
|
||||
my $type_info = $self->bz_column_info_real($table, $column);
|
||||
$schema->set_column($table, $column, $type_info);
|
||||
}
|
||||
|
||||
my @indexes = $self->bz_index_list_real($table);
|
||||
foreach my $index (@indexes) {
|
||||
unless ($index eq 'PRIMARY') {
|
||||
my $index_info = $self->bz_index_info_real($table, $index);
|
||||
($index_info = $index_info->{FIELDS})
|
||||
if (!$index_info->{TYPE});
|
||||
$schema->set_index($table, $index, $index_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
1;
|
||||
@@ -1,608 +0,0 @@
|
||||
# -*- 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 Oracle Corporation.
|
||||
# Portions created by Oracle are Copyright (C) 2007 Oracle Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Lance Larsh <lance.larsh@oracle.com>
|
||||
# Xiaoou Wu <xiaoou.wu@oracle.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::DB::Oracle - Bugzilla database compatibility layer for Oracle
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module overrides methods of the Bugzilla::DB module with Oracle
|
||||
specific implementation. It is instantiated by the Bugzilla::DB module
|
||||
and should never be used directly.
|
||||
|
||||
For interface details see L<Bugzilla::DB> and L<DBI>.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::DB::Oracle;
|
||||
|
||||
use strict;
|
||||
|
||||
use DBD::Oracle;
|
||||
use DBD::Oracle qw(:ora_types);
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
# This module extends the DB interface via inheritance
|
||||
use base qw(Bugzilla::DB);
|
||||
|
||||
#####################################################################
|
||||
# Constants
|
||||
#####################################################################
|
||||
use constant EMPTY_STRING => '__BZ_EMPTY_STR__';
|
||||
use constant ISOLATION_LEVEL => 'READ COMMITTED';
|
||||
use constant BLOB_TYPE => { ora_type => ORA_BLOB };
|
||||
use constant GROUPBY_REGEXP => '((CASE\s+WHEN.+END)|(TO_CHAR\(.+\))|(\(SCORE.+\))|(\(MATCH.+\))|(\w+(\.\w+)?))(\s+AS\s+)?(.*)?$';
|
||||
|
||||
sub new {
|
||||
my ($class, $user, $pass, $host, $dbname, $port) = @_;
|
||||
|
||||
# You can never connect to Oracle without a DB name,
|
||||
# and there is no default DB.
|
||||
$dbname ||= Bugzilla->localconfig->{db_name};
|
||||
|
||||
# Set the language enviroment
|
||||
$ENV{'NLS_LANG'} = '.AL32UTF8' if Bugzilla->params->{'utf8'};
|
||||
|
||||
# construct the DSN from the parameters we got
|
||||
my $dsn = "DBI:Oracle:host=$host;sid=$dbname";
|
||||
$dsn .= ";port=$port" if $port;
|
||||
my $attrs = { FetchHashKeyName => 'NAME_lc',
|
||||
LongReadLen => ( Bugzilla->params->{'maxattachmentsize'}
|
||||
|| 1000 ) * 1024,
|
||||
};
|
||||
my $self = $class->db_new($dsn, $user, $pass, $attrs);
|
||||
|
||||
bless ($self, $class);
|
||||
|
||||
# Set the session's default date format to match MySQL
|
||||
$self->do("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
|
||||
$self->do("ALTER SESSION SET NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'");
|
||||
$self->do("ALTER SESSION SET NLS_LENGTH_SEMANTICS='CHAR'")
|
||||
if Bugzilla->params->{'utf8'};
|
||||
# To allow case insensitive query.
|
||||
$self->do("ALTER SESSION SET NLS_COMP='ANSI'");
|
||||
$self->do("ALTER SESSION SET NLS_SORT='BINARY_AI'");
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub bz_last_key {
|
||||
my ($self, $table, $column) = @_;
|
||||
|
||||
my $seq = $table . "_" . $column . "_SEQ";
|
||||
my ($last_insert_id) = $self->selectrow_array("SELECT $seq.CURRVAL "
|
||||
. " FROM DUAL");
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
sub sql_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "REGEXP_LIKE($expr, $pattern)";
|
||||
}
|
||||
|
||||
sub sql_not_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "NOT REGEXP_LIKE($expr, $pattern)"
|
||||
}
|
||||
|
||||
sub sql_limit {
|
||||
my ($self, $limit, $offset) = @_;
|
||||
|
||||
if(defined $offset) {
|
||||
return "/* LIMIT $limit $offset */";
|
||||
}
|
||||
return "/* LIMIT $limit */";
|
||||
}
|
||||
|
||||
sub sql_string_concat {
|
||||
my ($self, @params) = @_;
|
||||
|
||||
return 'CONCAT(' . join(', ', @params) . ')';
|
||||
}
|
||||
|
||||
sub sql_to_days {
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return " TO_CHAR(TO_DATE($date),'J') ";
|
||||
}
|
||||
sub sql_from_days{
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return " TO_DATE($date,'J') ";
|
||||
}
|
||||
sub sql_fulltext_search {
|
||||
my ($self, $column, $text, $label) = @_;
|
||||
$text = $self->quote($text);
|
||||
trick_taint($text);
|
||||
return "CONTAINS($column,$text,$label)", "SCORE($label)";
|
||||
}
|
||||
|
||||
sub sql_date_format {
|
||||
my ($self, $date, $format) = @_;
|
||||
|
||||
$format = "%Y.%m.%d %H:%i:%s" if !$format;
|
||||
|
||||
$format =~ s/\%Y/YYYY/g;
|
||||
$format =~ s/\%y/YY/g;
|
||||
$format =~ s/\%m/MM/g;
|
||||
$format =~ s/\%d/DD/g;
|
||||
$format =~ s/\%a/Dy/g;
|
||||
$format =~ s/\%H/HH24/g;
|
||||
$format =~ s/\%i/MI/g;
|
||||
$format =~ s/\%s/SS/g;
|
||||
|
||||
return "TO_CHAR($date, " . $self->quote($format) . ")";
|
||||
}
|
||||
|
||||
sub sql_interval {
|
||||
my ($self, $interval, $units) = @_;
|
||||
if ($units =~ /YEAR|MONTH/i) {
|
||||
return "NUMTOYMINTERVAL($interval,'$units')";
|
||||
} else{
|
||||
return "NUMTODSINTERVAL($interval,'$units')";
|
||||
}
|
||||
}
|
||||
|
||||
sub sql_position {
|
||||
my ($self, $fragment, $text) = @_;
|
||||
return "INSTR($text, $fragment)";
|
||||
}
|
||||
|
||||
sub sql_in {
|
||||
my ($self, $column_name, $in_list_ref) = @_;
|
||||
my @in_list = @$in_list_ref;
|
||||
return $self->SUPER::sql_in($column_name, $in_list_ref) if $#in_list < 1000;
|
||||
my @in_str;
|
||||
while (@in_list) {
|
||||
my $length = $#in_list + 1;
|
||||
my $splice = $length > 1000 ? 1000 : $length;
|
||||
my @sub_in_list = splice(@in_list, 0, $splice);
|
||||
push(@in_str,
|
||||
$self->SUPER::sql_in($column_name, \@sub_in_list));
|
||||
}
|
||||
return "( " . join(" OR ", @in_str) . " )";
|
||||
}
|
||||
|
||||
sub bz_drop_table {
|
||||
my ($self, $name) = @_;
|
||||
my $table_exists = $self->bz_table_info($name);
|
||||
if ($table_exists) {
|
||||
$self->_bz_drop_fks($name);
|
||||
$self->SUPER::bz_drop_table($name);
|
||||
}
|
||||
}
|
||||
|
||||
# Dropping all FKs for a specified table.
|
||||
sub _bz_drop_fks {
|
||||
my ($self, $table) = @_;
|
||||
my @columns = $self->_bz_real_schema->get_table_columns($table);
|
||||
foreach my $column (@columns) {
|
||||
$self->bz_drop_fk($table, $column);
|
||||
}
|
||||
}
|
||||
|
||||
sub _fix_empty {
|
||||
my ($string) = @_;
|
||||
$string = '' if $string eq EMPTY_STRING;
|
||||
return $string;
|
||||
}
|
||||
|
||||
sub _fix_arrayref {
|
||||
my ($row) = @_;
|
||||
return undef if !defined $row;
|
||||
foreach my $field (@$row) {
|
||||
$field = _fix_empty($field) if defined $field;
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
sub _fix_hashref {
|
||||
my ($row) = @_;
|
||||
return undef if !defined $row;
|
||||
foreach my $value (values %$row) {
|
||||
$value = _fix_empty($value) if defined $value;
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
sub adjust_statement {
|
||||
my ($sql) = @_;
|
||||
|
||||
# We can't just assume any occurrence of "''" in $sql is an empty
|
||||
# string, since "''" can occur inside a string literal as a way of
|
||||
# escaping a single "'" in the literal. Therefore we must be trickier...
|
||||
|
||||
# split the statement into parts by single-quotes. The negative value
|
||||
# at the end to the split operator from dropping trailing empty strings
|
||||
# (e.g., when $sql ends in "''")
|
||||
my @parts = split /'/, $sql, -1;
|
||||
|
||||
if( !(@parts % 2) ) {
|
||||
# Either the string is empty or the quotes are mismatched
|
||||
# Returning input unmodified.
|
||||
return $sql;
|
||||
}
|
||||
|
||||
# We already verified that we have an odd number of parts. If we take
|
||||
# the first part off now, we know we're entering the loop with an even
|
||||
# number of parts
|
||||
my @result;
|
||||
my $part = shift @parts;
|
||||
|
||||
# Oracle requires a FROM clause in all SELECT statements, so append
|
||||
# "FROM dual" to queries without one (e.g., "SELECT NOW()")
|
||||
my $is_select = ($part =~ m/^\s*SELECT\b/io);
|
||||
my $has_from = ($part =~ m/\bFROM\b/io) if $is_select;
|
||||
|
||||
# Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
|
||||
$part =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
|
||||
|
||||
# Oracle use SUBSTR instead of SUBSTRING
|
||||
$part =~ s/\bSUBSTRING\b/SUBSTR/io;
|
||||
|
||||
# Oracle need no 'AS'
|
||||
$part =~ s/\bAS\b//ig;
|
||||
|
||||
# Oracle doesn't have LIMIT, so if we find the LIMIT comment, wrap the
|
||||
# query with "SELECT * FROM (...) WHERE rownum < $limit"
|
||||
my ($limit,$offset) = ($part =~ m{/\* LIMIT (\d*) (\d*) \*/}o);
|
||||
|
||||
push @result, $part;
|
||||
while( @parts ) {
|
||||
my $string = shift @parts;
|
||||
my $nonstring = shift @parts;
|
||||
|
||||
# if the non-string part is zero-length and there are more parts left,
|
||||
# then this is an escaped quote inside a string literal
|
||||
while( !(length $nonstring) && @parts ) {
|
||||
# we know it's safe to remove two parts at a time, since we
|
||||
# entered the loop with an even number of parts
|
||||
$string .= "''" . shift @parts;
|
||||
$nonstring = shift @parts;
|
||||
}
|
||||
|
||||
# Look for a FROM if this is a SELECT and we haven't found one yet
|
||||
$has_from = ($nonstring =~ m/\bFROM\b/io)
|
||||
if ($is_select and !$has_from);
|
||||
|
||||
# Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
|
||||
$nonstring =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
|
||||
|
||||
# Oracle use SUBSTR instead of SUBSTRING
|
||||
$nonstring =~ s/\bSUBSTRING\b/SUBSTR/io;
|
||||
|
||||
# Oracle need no 'AS'
|
||||
$nonstring =~ s/\bAS\b//ig;
|
||||
|
||||
# Look for a LIMIT clause
|
||||
($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o);
|
||||
|
||||
if(!length($string)){
|
||||
push @result, EMPTY_STRING;
|
||||
push @result, $nonstring;
|
||||
} else {
|
||||
push @result, $string;
|
||||
push @result, $nonstring;
|
||||
}
|
||||
}
|
||||
|
||||
my $new_sql = join "'", @result;
|
||||
|
||||
# Append "FROM dual" if this is a SELECT without a FROM clause
|
||||
$new_sql .= " FROM DUAL" if ($is_select and !$has_from);
|
||||
|
||||
# Wrap the query with a "WHERE rownum <= ..." if we found LIMIT
|
||||
|
||||
if (defined($limit)) {
|
||||
if ($new_sql !~ /\bWHERE\b/) {
|
||||
$new_sql = $new_sql." WHERE 1=1";
|
||||
}
|
||||
my ($before_where, $after_where) = split /\bWHERE\b/i,$new_sql;
|
||||
if (defined($offset)) {
|
||||
if ($new_sql =~ /(.*\s+)FROM(\s+.*)/i) {
|
||||
my ($before_from,$after_from) = ($1,$2);
|
||||
$before_where = "$before_from FROM ($before_from,"
|
||||
. " ROW_NUMBER() OVER (ORDER BY 1) R "
|
||||
. " FROM $after_from ) ";
|
||||
$after_where = " R BETWEEN $offset+1 AND $limit+$offset";
|
||||
}
|
||||
} else {
|
||||
$after_where = " rownum <=$limit AND ".$after_where;
|
||||
}
|
||||
|
||||
$new_sql = $before_where." WHERE ".$after_where;
|
||||
}
|
||||
return $new_sql;
|
||||
}
|
||||
|
||||
sub do {
|
||||
my $self = shift;
|
||||
my $sql = shift;
|
||||
$sql = adjust_statement($sql);
|
||||
unshift @_, $sql;
|
||||
return $self->SUPER::do(@_);
|
||||
}
|
||||
|
||||
sub selectrow_array {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
if ( wantarray ) {
|
||||
my @row = $self->SUPER::selectrow_array(@_);
|
||||
_fix_arrayref(\@row);
|
||||
return @row;
|
||||
} else {
|
||||
my $row = $self->SUPER::selectrow_array(@_);
|
||||
$row = _fix_empty($row) if defined $row;
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
sub selectrow_arrayref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectrow_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
|
||||
_fix_arrayref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub selectrow_hashref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectrow_hashref(@_);
|
||||
return undef if !defined $ref;
|
||||
|
||||
_fix_hashref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub selectall_arrayref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectall_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
|
||||
foreach my $row (@$ref) {
|
||||
if (ref($row) eq 'ARRAY') {
|
||||
_fix_arrayref($row);
|
||||
}
|
||||
elsif (ref($row) eq 'HASH') {
|
||||
_fix_hashref($row);
|
||||
}
|
||||
}
|
||||
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub selectall_hashref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $rows = $self->SUPER::selectall_hashref(@_);
|
||||
return undef if !defined $rows;
|
||||
foreach my $row (values %$rows) {
|
||||
_fix_hashref($row);
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
sub selectcol_arrayref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectcol_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
_fix_arrayref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub prepare {
|
||||
my $self = shift;
|
||||
my $sql = shift;
|
||||
my $new_sql = adjust_statement($sql);
|
||||
unshift @_, $new_sql;
|
||||
return bless $self->SUPER::prepare(@_),
|
||||
'Bugzilla::DB::Oracle::st';
|
||||
}
|
||||
|
||||
sub prepare_cached {
|
||||
my $self = shift;
|
||||
my $sql = shift;
|
||||
my $new_sql = adjust_statement($sql);
|
||||
unshift @_, $new_sql;
|
||||
return bless $self->SUPER::prepare_cached(@_),
|
||||
'Bugzilla::DB::Oracle::st';
|
||||
}
|
||||
|
||||
sub quote_identifier {
|
||||
my ($self,$id) = @_;
|
||||
return $id;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Protected "Real Database" Schema Information Methods
|
||||
#####################################################################
|
||||
|
||||
sub bz_table_columns_real {
|
||||
my ($self, $table) = @_;
|
||||
$table = uc($table);
|
||||
my $cols = $self->selectcol_arrayref(
|
||||
"SELECT LOWER(COLUMN_NAME) FROM USER_TAB_COLUMNS WHERE
|
||||
TABLE_NAME = ? ORDER BY COLUMN_NAME", undef, $table);
|
||||
return @$cols;
|
||||
}
|
||||
|
||||
sub bz_table_list_real {
|
||||
my ($self) = @_;
|
||||
my $tables = $self->selectcol_arrayref(
|
||||
"SELECT LOWER(TABLE_NAME) FROM USER_TABLES WHERE
|
||||
TABLE_NAME NOT LIKE ? ORDER BY TABLE_NAME", undef, 'DR$%');
|
||||
return @$tables;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Custom Database Setup
|
||||
#####################################################################
|
||||
|
||||
sub bz_setup_database {
|
||||
my $self = shift;
|
||||
|
||||
# Create a function that returns SYSDATE to emulate MySQL's "NOW()".
|
||||
# Function NOW() is used widely in Bugzilla SQLs, but Oracle does not
|
||||
# have that function, So we have to create one ourself.
|
||||
$self->do("CREATE OR REPLACE FUNCTION NOW "
|
||||
. " RETURN DATE IS BEGIN RETURN SYSDATE; END;");
|
||||
$self->do("CREATE OR REPLACE FUNCTION CHAR_LENGTH(COLUMN_NAME VARCHAR2)"
|
||||
. " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
|
||||
# Create a WORLD_LEXER named BZ_LEX for multilingual fulltext search
|
||||
my $lexer = $self->selectcol_arrayref(
|
||||
"SELECT pre_name FROM CTXSYS.CTX_PREFERENCES WHERE pre_name = ? AND
|
||||
pre_owner = ?",
|
||||
undef,'BZ_LEX',uc(Bugzilla->localconfig->{db_user}));
|
||||
if(!@$lexer) {
|
||||
$self->do("BEGIN CTX_DDL.CREATE_PREFERENCE
|
||||
('BZ_LEX', 'WORLD_LEXER'); END;");
|
||||
}
|
||||
|
||||
$self->SUPER::bz_setup_database(@_);
|
||||
|
||||
my @tables = $self->bz_table_list_real();
|
||||
foreach my $table (@tables) {
|
||||
my @columns = $self->bz_table_columns_real($table);
|
||||
foreach my $column (@columns) {
|
||||
my $def = $self->bz_column_info($table, $column);
|
||||
if ($def->{REFERENCES}) {
|
||||
my $references = $def->{REFERENCES};
|
||||
my $update = $references->{UPDATE} || 'CASCADE';
|
||||
my $to_table = $references->{TABLE};
|
||||
my $to_column = $references->{COLUMN};
|
||||
my $fk_name = $self->_bz_schema->_get_fk_name($table,
|
||||
$column,
|
||||
$references);
|
||||
if ( $update =~ /CASCADE/i ){
|
||||
my $trigger_name = uc($fk_name . "_UC");
|
||||
my $exist_trigger = $self->selectcol_arrayref(
|
||||
"SELECT OBJECT_NAME FROM USER_OBJECTS
|
||||
WHERE OBJECT_NAME = ?", undef, $trigger_name);
|
||||
if(@$exist_trigger) {
|
||||
$self->do("DROP TRIGGER $trigger_name");
|
||||
}
|
||||
|
||||
my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
|
||||
. " AFTER UPDATE ON ". $to_table
|
||||
. " REFERENCING "
|
||||
. " NEW AS NEW "
|
||||
. " OLD AS OLD "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " UPDATE $table"
|
||||
. " SET $column = :NEW.$to_column"
|
||||
. " WHERE $column = :OLD.$to_column;"
|
||||
. " END $trigger_name;";
|
||||
$self->do($tr_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package Bugzilla::DB::Oracle::st;
|
||||
use base qw(DBI::st);
|
||||
|
||||
sub fetchrow_arrayref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchrow_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
Bugzilla::DB::Oracle::_fix_arrayref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetchrow_array {
|
||||
my $self = shift;
|
||||
if ( wantarray ) {
|
||||
my @row = $self->SUPER::fetchrow_array(@_);
|
||||
Bugzilla::DB::Oracle::_fix_arrayref(\@row);
|
||||
return @row;
|
||||
} else {
|
||||
my $row = $self->SUPER::fetchrow_array(@_);
|
||||
$row = Bugzilla::DB::Oracle::_fix_empty($row) if defined $row;
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
sub fetchrow_hashref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchrow_hashref(@_);
|
||||
return undef if !defined $ref;
|
||||
Bugzilla::DB::Oracle::_fix_hashref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetchall_arrayref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchall_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
foreach my $row (@$ref) {
|
||||
if (ref($row) eq 'ARRAY') {
|
||||
Bugzilla::DB::Oracle::_fix_arrayref($row);
|
||||
}
|
||||
elsif (ref($row) eq 'HASH') {
|
||||
Bugzilla::DB::Oracle::_fix_hashref($row);
|
||||
}
|
||||
}
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetchall_hashref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchall_hashref(@_);
|
||||
return undef if !defined $ref;
|
||||
foreach my $row (values %$ref) {
|
||||
Bugzilla::DB::Oracle::_fix_hashref($row);
|
||||
}
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetch {
|
||||
my $self = shift;
|
||||
my $row = $self->SUPER::fetch(@_);
|
||||
if ($row) {
|
||||
Bugzilla::DB::Oracle::_fix_arrayref($row);
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
1;
|
||||
@@ -1,261 +0,0 @@
|
||||
# -*- 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): Dave Miller <davem00@aol.com>
|
||||
# Gayathri Swaminath <gayathrik00@aol.com>
|
||||
# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
|
||||
# Dave Lawrence <dkl@redhat.com>
|
||||
# Tomas Kopal <Tomas.Kopal@altap.cz>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Lance Larsh <lance.larsh@oracle.com>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::DB::Pg - Bugzilla database compatibility layer for PostgreSQL
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module overrides methods of the Bugzilla::DB module with PostgreSQL
|
||||
specific implementation. It is instantiated by the Bugzilla::DB module
|
||||
and should never be used directly.
|
||||
|
||||
For interface details see L<Bugzilla::DB> and L<DBI>.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::DB::Pg;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use DBD::Pg;
|
||||
|
||||
# This module extends the DB interface via inheritance
|
||||
use base qw(Bugzilla::DB);
|
||||
|
||||
use constant BLOB_TYPE => { pg_type => DBD::Pg::PG_BYTEA };
|
||||
|
||||
sub new {
|
||||
my ($class, $user, $pass, $host, $dbname, $port) = @_;
|
||||
|
||||
# The default database name for PostgreSQL. We have
|
||||
# to connect to SOME database, even if we have
|
||||
# no $dbname parameter.
|
||||
$dbname ||= 'template1';
|
||||
|
||||
# construct the DSN from the parameters we got
|
||||
my $dsn = "DBI:Pg:dbname=$dbname";
|
||||
$dsn .= ";host=$host" if $host;
|
||||
$dsn .= ";port=$port" if $port;
|
||||
|
||||
# This stops Pg from printing out lots of "NOTICE" messages when
|
||||
# creating tables.
|
||||
$dsn .= ";options='-c client_min_messages=warning'";
|
||||
|
||||
my $attrs = { pg_enable_utf8 => Bugzilla->params->{'utf8'} };
|
||||
|
||||
my $self = $class->db_new($dsn, $user, $pass, $attrs);
|
||||
|
||||
# all class local variables stored in DBI derived class needs to have
|
||||
# a prefix 'private_'. See DBI documentation.
|
||||
$self->{private_bz_tables_locked} = "";
|
||||
|
||||
bless ($self, $class);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# if last_insert_id is supported on PostgreSQL by lowest DBI/DBD version
|
||||
# supported by Bugzilla, this implementation can be removed.
|
||||
sub bz_last_key {
|
||||
my ($self, $table, $column) = @_;
|
||||
|
||||
my $seq = $table . "_" . $column . "_seq";
|
||||
my ($last_insert_id) = $self->selectrow_array("SELECT CURRVAL('$seq')");
|
||||
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
sub sql_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "$expr ~* $pattern";
|
||||
}
|
||||
|
||||
sub sql_not_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "$expr !~* $pattern"
|
||||
}
|
||||
|
||||
sub sql_limit {
|
||||
my ($self, $limit, $offset) = @_;
|
||||
|
||||
if (defined($offset)) {
|
||||
return "LIMIT $limit OFFSET $offset";
|
||||
} else {
|
||||
return "LIMIT $limit";
|
||||
}
|
||||
}
|
||||
|
||||
sub sql_from_days {
|
||||
my ($self, $days) = @_;
|
||||
|
||||
return "TO_TIMESTAMP(${days}::int, 'J')::date";
|
||||
}
|
||||
|
||||
sub sql_to_days {
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return "TO_CHAR(${date}::date, 'J')::int";
|
||||
}
|
||||
|
||||
sub sql_date_format {
|
||||
my ($self, $date, $format) = @_;
|
||||
|
||||
$format = "%Y.%m.%d %H:%i:%s" if !$format;
|
||||
|
||||
$format =~ s/\%Y/YYYY/g;
|
||||
$format =~ s/\%y/YY/g;
|
||||
$format =~ s/\%m/MM/g;
|
||||
$format =~ s/\%d/DD/g;
|
||||
$format =~ s/\%a/Dy/g;
|
||||
$format =~ s/\%H/HH24/g;
|
||||
$format =~ s/\%i/MI/g;
|
||||
$format =~ s/\%s/SS/g;
|
||||
|
||||
return "TO_CHAR($date, " . $self->quote($format) . ")";
|
||||
}
|
||||
|
||||
sub sql_interval {
|
||||
my ($self, $interval, $units) = @_;
|
||||
|
||||
return "$interval * INTERVAL '1 $units'";
|
||||
}
|
||||
|
||||
sub sql_string_concat {
|
||||
my ($self, @params) = @_;
|
||||
|
||||
# Postgres 7.3 does not support concatenating of different types, so we
|
||||
# need to cast both parameters to text. Version 7.4 seems to handle this
|
||||
# properly, so when we stop support 7.3, this can be removed.
|
||||
return '(CAST(' . join(' AS text) || CAST(', @params) . ' AS text))';
|
||||
}
|
||||
|
||||
# Tell us whether or not a particular sequence exists in the DB.
|
||||
sub bz_sequence_exists {
|
||||
my ($self, $seq_name) = @_;
|
||||
my $exists = $self->selectrow_array(
|
||||
'SELECT 1 FROM pg_statio_user_sequences WHERE relname = ?',
|
||||
undef, $seq_name);
|
||||
return $exists || 0;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Custom Database Setup
|
||||
#####################################################################
|
||||
|
||||
sub bz_setup_database {
|
||||
my $self = shift;
|
||||
$self->SUPER::bz_setup_database(@_);
|
||||
|
||||
# PostgreSQL doesn't like having *any* index on the thetext
|
||||
# field, because it can't have index data longer than 2770
|
||||
# characters on that field.
|
||||
$self->bz_drop_index('longdescs', 'longdescs_thetext_idx');
|
||||
# Same for all the comments fields in the fulltext table.
|
||||
$self->bz_drop_index('bugs_fulltext', 'bugs_fulltext_comments_idx');
|
||||
$self->bz_drop_index('bugs_fulltext',
|
||||
'bugs_fulltext_comments_noprivate_idx');
|
||||
|
||||
# PostgreSQL also wants an index for calling LOWER on
|
||||
# login_name, which we do with sql_istrcmp all over the place.
|
||||
$self->bz_add_index('profiles', 'profiles_login_name_lower_idx',
|
||||
{FIELDS => ['LOWER(login_name)'], TYPE => 'UNIQUE'});
|
||||
|
||||
# Now that Bugzilla::Object uses sql_istrcmp, other tables
|
||||
# also need a LOWER() index.
|
||||
_fix_case_differences('fielddefs', 'name');
|
||||
$self->bz_add_index('fielddefs', 'fielddefs_name_lower_idx',
|
||||
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
|
||||
_fix_case_differences('keyworddefs', 'name');
|
||||
$self->bz_add_index('keyworddefs', 'keyworddefs_name_lower_idx',
|
||||
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
|
||||
_fix_case_differences('products', 'name');
|
||||
$self->bz_add_index('products', 'products_name_lower_idx',
|
||||
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
|
||||
|
||||
# bz_rename_column didn't correctly rename the sequence.
|
||||
if ($self->bz_column_info('fielddefs', 'id')
|
||||
&& $self->bz_sequence_exists('fielddefs_fieldid_seq'))
|
||||
{
|
||||
print "Fixing fielddefs_fieldid_seq sequence...\n";
|
||||
$self->do("ALTER TABLE fielddefs_fieldid_seq RENAME TO fielddefs_id_seq");
|
||||
$self->do("ALTER TABLE fielddefs ALTER COLUMN id
|
||||
SET DEFAULT NEXTVAL('fielddefs_id_seq')");
|
||||
}
|
||||
}
|
||||
|
||||
# Renames things that differ only in case.
|
||||
sub _fix_case_differences {
|
||||
my ($table, $field) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $duplicates = $dbh->selectcol_arrayref(
|
||||
"SELECT DISTINCT LOWER($field) FROM $table
|
||||
GROUP BY LOWER($field) HAVING COUNT(LOWER($field)) > 1");
|
||||
|
||||
foreach my $name (@$duplicates) {
|
||||
my $dups = $dbh->selectcol_arrayref(
|
||||
"SELECT $field FROM $table WHERE LOWER($field) = ?",
|
||||
undef, $name);
|
||||
my $primary = shift @$dups;
|
||||
foreach my $dup (@$dups) {
|
||||
my $new_name = "${dup}_";
|
||||
# Make sure the new name isn't *also* a duplicate.
|
||||
while (1) {
|
||||
last if (!$dbh->selectrow_array(
|
||||
"SELECT 1 FROM $table WHERE LOWER($field) = ?",
|
||||
undef, lc($new_name)));
|
||||
$new_name .= "_";
|
||||
}
|
||||
print "$table '$primary' and '$dup' have names that differ",
|
||||
" only in case.\nRenaming '$dup' to '$new_name'...\n";
|
||||
$dbh->do("UPDATE $table SET $field = ? WHERE $field = ?",
|
||||
undef, $new_name, $dup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Custom Schema Information Functions
|
||||
#####################################################################
|
||||
|
||||
# Pg includes the PostgreSQL system tables in table_list_real, so
|
||||
# we need to remove those.
|
||||
sub bz_table_list_real {
|
||||
my $self = shift;
|
||||
|
||||
my @full_table_list = $self->SUPER::bz_table_list_real(@_);
|
||||
# All PostgreSQL system tables start with "pg_" or "sql_"
|
||||
my @table_list = grep(!/(^pg_)|(^sql_)/, @full_table_list);
|
||||
return @table_list;
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,369 +0,0 @@
|
||||
# -*- 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): Andrew Dunstan <andrew@dunslane.net>,
|
||||
# Edward J. Sabol <edwardjsabol@iname.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::DB::Schema::Mysql;
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB::Schema implementation for MySQL
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use base qw(Bugzilla::DB::Schema);
|
||||
|
||||
# This is for column_info_to_column, to know when a tinyint is a
|
||||
# boolean and when it's really a tinyint. This only has to be accurate
|
||||
# up to and through 2.19.3, because that's the only time we need
|
||||
# column_info_to_column.
|
||||
#
|
||||
# This is basically a hash of tables/columns, with one entry for each column
|
||||
# that should be interpreted as a BOOLEAN instead of as an INT1 when
|
||||
# reading in the Schema from the disk. The values are discarded; I just
|
||||
# used "1" for simplicity.
|
||||
use constant BOOLEAN_MAP => {
|
||||
bugs => {everconfirmed => 1, reporter_accessible => 1,
|
||||
cclist_accessible => 1, qacontact_accessible => 1,
|
||||
assignee_accessible => 1},
|
||||
longdescs => {isprivate => 1, already_wrapped => 1},
|
||||
attachments => {ispatch => 1, isobsolete => 1, isprivate => 1},
|
||||
flags => {is_active => 1},
|
||||
flagtypes => {is_active => 1, is_requestable => 1,
|
||||
is_requesteeble => 1, is_multiplicable => 1},
|
||||
fielddefs => {mailhead => 1, obsolete => 1},
|
||||
bug_status => {isactive => 1},
|
||||
resolution => {isactive => 1},
|
||||
bug_severity => {isactive => 1},
|
||||
priority => {isactive => 1},
|
||||
rep_platform => {isactive => 1},
|
||||
op_sys => {isactive => 1},
|
||||
profiles => {mybugslink => 1, newemailtech => 1},
|
||||
namedqueries => {linkinfooter => 1, watchfordiffs => 1},
|
||||
groups => {isbuggroup => 1, isactive => 1},
|
||||
group_control_map => {entry => 1, membercontrol => 1, othercontrol => 1,
|
||||
canedit => 1},
|
||||
group_group_map => {isbless => 1},
|
||||
user_group_map => {isbless => 1, isderived => 1},
|
||||
products => {disallownew => 1},
|
||||
series => {public => 1},
|
||||
whine_queries => {onemailperbug => 1},
|
||||
quips => {approved => 1},
|
||||
setting => {is_enabled => 1}
|
||||
};
|
||||
|
||||
# Maps the db_specific hash backwards, for use in column_info_to_column.
|
||||
use constant REVERSE_MAPPING => {
|
||||
# Boolean and the SERIAL fields are handled in column_info_to_column,
|
||||
# and so don't have an entry here.
|
||||
TINYINT => 'INT1',
|
||||
SMALLINT => 'INT2',
|
||||
MEDIUMINT => 'INT3',
|
||||
INTEGER => 'INT4',
|
||||
|
||||
# All the other types have the same name in their abstract version
|
||||
# as in their db-specific version, so no reverse mapping is needed.
|
||||
};
|
||||
|
||||
use constant MYISAM_TABLES => qw(bugs_fulltext);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sub _initialize {
|
||||
|
||||
my $self = shift;
|
||||
|
||||
$self = $self->SUPER::_initialize(@_);
|
||||
|
||||
$self->{db_specific} = {
|
||||
|
||||
BOOLEAN => 'tinyint',
|
||||
FALSE => '0',
|
||||
TRUE => '1',
|
||||
|
||||
INT1 => 'tinyint',
|
||||
INT2 => 'smallint',
|
||||
INT3 => 'mediumint',
|
||||
INT4 => 'integer',
|
||||
|
||||
SMALLSERIAL => 'smallint auto_increment',
|
||||
MEDIUMSERIAL => 'mediumint auto_increment',
|
||||
INTSERIAL => 'integer auto_increment',
|
||||
|
||||
TINYTEXT => 'tinytext',
|
||||
MEDIUMTEXT => 'mediumtext',
|
||||
LONGTEXT => 'mediumtext',
|
||||
|
||||
LONGBLOB => 'longblob',
|
||||
|
||||
DATETIME => 'datetime',
|
||||
|
||||
};
|
||||
|
||||
$self->_adjust_schema;
|
||||
|
||||
return $self;
|
||||
|
||||
} #eosub--_initialize
|
||||
#------------------------------------------------------------------------------
|
||||
sub _get_create_table_ddl {
|
||||
# Extend superclass method to specify the MYISAM storage engine.
|
||||
# Returns a "create table" SQL statement.
|
||||
|
||||
my($self, $table) = @_;
|
||||
|
||||
my $charset = Bugzilla->dbh->bz_db_is_utf8 ? "CHARACTER SET utf8" : '';
|
||||
my $type = grep($_ eq $table, MYISAM_TABLES) ? 'MYISAM' : 'InnoDB';
|
||||
return($self->SUPER::_get_create_table_ddl($table)
|
||||
. " ENGINE = $type $charset");
|
||||
|
||||
} #eosub--_get_create_table_ddl
|
||||
#------------------------------------------------------------------------------
|
||||
sub _get_create_index_ddl {
|
||||
# Extend superclass method to create FULLTEXT indexes on text fields.
|
||||
# Returns a "create index" SQL statement.
|
||||
|
||||
my($self, $table_name, $index_name, $index_fields, $index_type) = @_;
|
||||
|
||||
my $sql = "CREATE ";
|
||||
$sql .= "$index_type " if ($index_type eq 'UNIQUE'
|
||||
|| $index_type eq 'FULLTEXT');
|
||||
$sql .= "INDEX \`$index_name\` ON $table_name \(" .
|
||||
join(", ", @$index_fields) . "\)";
|
||||
|
||||
return($sql);
|
||||
|
||||
} #eosub--_get_create_index_ddl
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
sub get_create_database_sql {
|
||||
my ($self, $name) = @_;
|
||||
# We only create as utf8 if we have no params (meaning we're doing
|
||||
# a new installation) or if the utf8 param is on.
|
||||
my $create_utf8 = Bugzilla->params->{'utf8'}
|
||||
|| !defined Bugzilla->params->{'utf8'};
|
||||
my $charset = $create_utf8 ? "CHARACTER SET utf8" : '';
|
||||
return ("CREATE DATABASE $name $charset");
|
||||
}
|
||||
|
||||
# MySQL has a simpler ALTER TABLE syntax than ANSI.
|
||||
sub get_alter_column_ddl {
|
||||
my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
|
||||
my $old_def = $self->get_column($table, $column);
|
||||
my %new_def_copy = %$new_def;
|
||||
if ($old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
|
||||
# If a column stays a primary key do NOT specify PRIMARY KEY in the
|
||||
# ALTER TABLE statement. This avoids a MySQL error that two primary
|
||||
# keys are not allowed.
|
||||
delete $new_def_copy{PRIMARYKEY};
|
||||
}
|
||||
|
||||
my $new_ddl = $self->get_type_ddl(\%new_def_copy);
|
||||
my @statements;
|
||||
|
||||
push(@statements, "UPDATE $table SET $column = $set_nulls_to
|
||||
WHERE $column IS NULL") if defined $set_nulls_to;
|
||||
push(@statements, "ALTER TABLE $table CHANGE COLUMN
|
||||
$column $column $new_ddl");
|
||||
if ($old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY}) {
|
||||
# Dropping a PRIMARY KEY needs an explicit DROP PRIMARY KEY
|
||||
push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
|
||||
}
|
||||
|
||||
return @statements;
|
||||
}
|
||||
|
||||
sub get_drop_fk_sql {
|
||||
my ($self, $table, $column, $references) = @_;
|
||||
my $fk_name = $self->_get_fk_name($table, $column, $references);
|
||||
my @sql = ("ALTER TABLE $table DROP FOREIGN KEY $fk_name");
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# MySQL requires, and will create, an index on any column with
|
||||
# an FK. It will name it after the fk, which we never do.
|
||||
# So if there's an index named after the fk, we also have to delete it.
|
||||
if ($dbh->bz_index_info_real($table, $fk_name)) {
|
||||
push(@sql, $self->get_drop_index_ddl($table, $fk_name));
|
||||
}
|
||||
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub get_drop_index_ddl {
|
||||
my ($self, $table, $name) = @_;
|
||||
return ("DROP INDEX \`$name\` ON $table");
|
||||
}
|
||||
|
||||
# A special function for MySQL, for renaming a lot of indexes.
|
||||
# Index renames is a hash, where the key is a string - the
|
||||
# old names of the index, and the value is a hash - the index
|
||||
# definition that we're renaming to, with an extra key of "NAME"
|
||||
# that contains the new index name.
|
||||
# The indexes in %indexes must be in hashref format.
|
||||
sub get_rename_indexes_ddl {
|
||||
my ($self, $table, %indexes) = @_;
|
||||
my @keys = keys %indexes or return ();
|
||||
|
||||
my $sql = "ALTER TABLE $table ";
|
||||
|
||||
foreach my $old_name (@keys) {
|
||||
my $name = $indexes{$old_name}->{NAME};
|
||||
my $type = $indexes{$old_name}->{TYPE};
|
||||
$type ||= 'INDEX';
|
||||
my $fields = join(',', @{$indexes{$old_name}->{FIELDS}});
|
||||
# $old_name needs to be escaped, sometimes, because it was
|
||||
# a reserved word.
|
||||
$old_name = '`' . $old_name . '`';
|
||||
$sql .= " ADD $type $name ($fields), DROP INDEX $old_name,";
|
||||
}
|
||||
# Remove the last comma.
|
||||
chop($sql);
|
||||
return ($sql);
|
||||
}
|
||||
|
||||
# Converts a DBI column_info output to an abstract column definition.
|
||||
# Expects to only be called by Bugzila::DB::Mysql::_bz_build_schema_from_disk,
|
||||
# although there's a chance that it will also work properly if called
|
||||
# elsewhere.
|
||||
sub column_info_to_column {
|
||||
my ($self, $column_info) = @_;
|
||||
|
||||
# Unfortunately, we have to break Schema's normal "no database"
|
||||
# barrier a few times in this function.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $table = $column_info->{TABLE_NAME};
|
||||
my $col_name = $column_info->{COLUMN_NAME};
|
||||
|
||||
my $column = {};
|
||||
|
||||
($column->{NOTNULL} = 1) if $column_info->{NULLABLE} == 0;
|
||||
|
||||
if ($column_info->{mysql_is_pri_key}) {
|
||||
# In MySQL, if a table has no PK, but it has a UNIQUE index,
|
||||
# that index will show up as the PK. So we have to eliminate
|
||||
# that possibility.
|
||||
# Unfortunately, the only way to definitely solve this is
|
||||
# to break Schema's standard of not touching the live database
|
||||
# and check if the index called PRIMARY is on that field.
|
||||
my $pri_index = $dbh->bz_index_info_real($table, 'PRIMARY');
|
||||
if ( $pri_index && grep($_ eq $col_name, @{$pri_index->{FIELDS}}) ) {
|
||||
$column->{PRIMARYKEY} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
# MySQL frequently defines a default for a field even when we
|
||||
# didn't explicitly set one. So we have to have some special
|
||||
# hacks to determine whether or not we should actually put
|
||||
# a default in the abstract schema for this field.
|
||||
if (defined $column_info->{COLUMN_DEF}) {
|
||||
# The defaults that MySQL inputs automatically are usually
|
||||
# something that would be considered "false" by perl, either
|
||||
# a 0 or an empty string. (Except for datetime and decimal
|
||||
# fields, which have their own special auto-defaults.)
|
||||
#
|
||||
# Here's how we handle this: If it exists in the schema
|
||||
# without a default, then we don't use the default. If it
|
||||
# doesn't exist in the schema, then we're either going to
|
||||
# be dropping it soon, or it's a custom end-user column, in which
|
||||
# case having a bogus default won't harm anything.
|
||||
my $schema_column = $self->get_column($table, $col_name);
|
||||
unless ( (!$column_info->{COLUMN_DEF}
|
||||
|| $column_info->{COLUMN_DEF} eq '0000-00-00 00:00:00'
|
||||
|| $column_info->{COLUMN_DEF} eq '0.00')
|
||||
&& $schema_column
|
||||
&& !exists $schema_column->{DEFAULT}) {
|
||||
|
||||
my $default = $column_info->{COLUMN_DEF};
|
||||
# Schema uses '0' for the defaults for decimal fields.
|
||||
$default = 0 if $default =~ /^0\.0+$/;
|
||||
# If we're not a number, we're a string and need to be
|
||||
# quoted.
|
||||
$default = $dbh->quote($default) if !($default =~ /^(-)?(\d+)(.\d+)?$/);
|
||||
$column->{DEFAULT} = $default;
|
||||
}
|
||||
}
|
||||
|
||||
my $type = $column_info->{TYPE_NAME};
|
||||
|
||||
# Certain types of columns need the size/precision appended.
|
||||
if ($type =~ /CHAR$/ || $type eq 'DECIMAL') {
|
||||
# This is nicely lowercase and has the size/precision appended.
|
||||
$type = $column_info->{mysql_type_name};
|
||||
}
|
||||
|
||||
# If we're a tinyint, we could be either a BOOLEAN or an INT1.
|
||||
# Only the BOOLEAN_MAP knows the difference.
|
||||
elsif ($type eq 'TINYINT' && exists BOOLEAN_MAP->{$table}
|
||||
&& exists BOOLEAN_MAP->{$table}->{$col_name}) {
|
||||
$type = 'BOOLEAN';
|
||||
if (exists $column->{DEFAULT}) {
|
||||
$column->{DEFAULT} = $column->{DEFAULT} ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
}
|
||||
|
||||
# We also need to check if we're an auto_increment field.
|
||||
elsif ($type =~ /INT/) {
|
||||
# Unfortunately, the only way to do this in DBI is to query the
|
||||
# database, so we have to break the rule here that Schema normally
|
||||
# doesn't touch the live DB.
|
||||
my $ref_sth = $dbh->prepare(
|
||||
"SELECT $col_name FROM $table LIMIT 1");
|
||||
$ref_sth->execute;
|
||||
if ($ref_sth->{mysql_is_auto_increment}->[0]) {
|
||||
if ($type eq 'MEDIUMINT') {
|
||||
$type = 'MEDIUMSERIAL';
|
||||
}
|
||||
elsif ($type eq 'SMALLINT') {
|
||||
$type = 'SMALLSERIAL';
|
||||
}
|
||||
else {
|
||||
$type = 'INTSERIAL';
|
||||
}
|
||||
}
|
||||
$ref_sth->finish;
|
||||
|
||||
}
|
||||
|
||||
# For all other db-specific types, check if they exist in
|
||||
# REVERSE_MAPPING and use the type found there.
|
||||
if (exists REVERSE_MAPPING->{$type}) {
|
||||
$type = REVERSE_MAPPING->{$type};
|
||||
}
|
||||
|
||||
$column->{TYPE} = $type;
|
||||
|
||||
#print "$table.$col_name: " . Data::Dumper->Dump([$column]) . "\n";
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
sub get_rename_column_ddl {
|
||||
my ($self, $table, $old_name, $new_name) = @_;
|
||||
my $def = $self->get_type_ddl($self->get_column($table, $old_name));
|
||||
# MySQL doesn't like having the PRIMARY KEY statement in a rename.
|
||||
$def =~ s/PRIMARY KEY//i;
|
||||
return ("ALTER TABLE $table CHANGE COLUMN $old_name $new_name $def");
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,403 +0,0 @@
|
||||
# -*- 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 Oracle Corporation.
|
||||
# Portions created by Oracle are Copyright (C) 2007 Oracle Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Lance Larsh <lance.larsh@oracle.com>
|
||||
# Xiaoou Wu <xiaoou.wu@oracle.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::DB::Schema::Oracle;
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB::Schema implementation for Oracle
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Bugzilla::DB::Schema);
|
||||
use Carp qw(confess);
|
||||
use Bugzilla::Util;
|
||||
|
||||
use constant ADD_COLUMN => 'ADD';
|
||||
# Whether this is true or not, this is what it needs to be in order for
|
||||
# hash_identifier to maintain backwards compatibility with versions before
|
||||
# 3.2rc2.
|
||||
use constant MAX_IDENTIFIER_LEN => 27;
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sub _initialize {
|
||||
|
||||
my $self = shift;
|
||||
|
||||
$self = $self->SUPER::_initialize(@_);
|
||||
|
||||
$self->{db_specific} = {
|
||||
|
||||
BOOLEAN => 'integer',
|
||||
FALSE => '0',
|
||||
TRUE => '1',
|
||||
|
||||
INT1 => 'integer',
|
||||
INT2 => 'integer',
|
||||
INT3 => 'integer',
|
||||
INT4 => 'integer',
|
||||
|
||||
SMALLSERIAL => 'integer',
|
||||
MEDIUMSERIAL => 'integer',
|
||||
INTSERIAL => 'integer',
|
||||
|
||||
TINYTEXT => 'varchar(255)',
|
||||
MEDIUMTEXT => 'varchar(4000)',
|
||||
LONGTEXT => 'clob',
|
||||
|
||||
LONGBLOB => 'blob',
|
||||
|
||||
DATETIME => 'date',
|
||||
|
||||
};
|
||||
|
||||
$self->_adjust_schema;
|
||||
|
||||
return $self;
|
||||
|
||||
} #eosub--_initialize
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
sub get_table_ddl {
|
||||
my $self = shift;
|
||||
my $table = shift;
|
||||
unshift @_, $table;
|
||||
my @ddl = $self->SUPER::get_table_ddl(@_);
|
||||
|
||||
my @fields = @{ $self->{abstract_schema}{$table}{FIELDS} || [] };
|
||||
while (@fields) {
|
||||
my $field_name = shift @fields;
|
||||
my $field_info = shift @fields;
|
||||
# Create triggers to deal with empty string.
|
||||
if ( $field_info->{TYPE} =~ /varchar|TEXT/i
|
||||
&& $field_info->{NOTNULL} ) {
|
||||
push (@ddl, _get_notnull_trigger_ddl($table, $field_name));
|
||||
}
|
||||
# Create sequences and triggers to emulate SERIAL datatypes.
|
||||
if ( $field_info->{TYPE} =~ /SERIAL/i ) {
|
||||
push (@ddl, $self->_get_create_seq_ddl($table, $field_name));
|
||||
}
|
||||
}
|
||||
return @ddl;
|
||||
|
||||
} #eosub--get_table_ddl
|
||||
|
||||
# Extend superclass method to create Oracle Text indexes if index type
|
||||
# is FULLTEXT from schema. Returns a "create index" SQL statement.
|
||||
sub _get_create_index_ddl {
|
||||
|
||||
my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
|
||||
$index_name = "idx_" . $self->_hash_identifier($index_name);
|
||||
if ($index_type eq 'FULLTEXT') {
|
||||
my $sql = "CREATE INDEX $index_name ON $table_name ("
|
||||
. join(',',@$index_fields)
|
||||
. ") INDEXTYPE IS CTXSYS.CONTEXT "
|
||||
. " PARAMETERS('LEXER BZ_LEX SYNC(ON COMMIT)')" ;
|
||||
return $sql;
|
||||
}
|
||||
|
||||
return($self->SUPER::_get_create_index_ddl($table_name, $index_name,
|
||||
$index_fields, $index_type));
|
||||
|
||||
}
|
||||
|
||||
sub get_drop_index_ddl {
|
||||
my $self = shift;
|
||||
my ($table, $name) = @_;
|
||||
|
||||
$name = 'idx_' . $self->_hash_identifier($name);
|
||||
return $self->SUPER::get_drop_index_ddl($table, $name);
|
||||
}
|
||||
|
||||
# Oracle supports the use of FOREIGN KEY integrity constraints
|
||||
# to define the referential integrity actions, including:
|
||||
# - Update and delete No Action (default)
|
||||
# - Delete CASCADE
|
||||
# - Delete SET NULL
|
||||
sub get_fk_ddl {
|
||||
my ($self, $table, $column, $references) = @_;
|
||||
return "" if !$references;
|
||||
|
||||
my $update = $references->{UPDATE} || 'CASCADE';
|
||||
my $delete = $references->{DELETE};
|
||||
my $to_table = $references->{TABLE} || confess "No table in reference";
|
||||
my $to_column = $references->{COLUMN} || confess "No column in reference";
|
||||
my $fk_name = $self->_get_fk_name($table, $column, $references);
|
||||
|
||||
my $fk_string = "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
|
||||
. " REFERENCES $to_table($to_column)\n";
|
||||
|
||||
$fk_string = $fk_string . " ON DELETE $delete" if $delete;
|
||||
|
||||
if ( $update =~ /CASCADE/i ){
|
||||
my $tr_str = "CREATE OR REPLACE TRIGGER ${fk_name}_UC"
|
||||
. " AFTER UPDATE ON ". $to_table
|
||||
. " REFERENCING "
|
||||
. " NEW AS NEW "
|
||||
. " OLD AS OLD "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " UPDATE $table"
|
||||
. " SET $column = :NEW.$to_column"
|
||||
. " WHERE $column = :OLD.$to_column;"
|
||||
. " END ${fk_name}_UC;";
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->do($tr_str);
|
||||
}
|
||||
|
||||
return $fk_string;
|
||||
}
|
||||
|
||||
sub get_drop_fk_sql {
|
||||
my $self = shift;
|
||||
my ($table, $column, $references) = @_;
|
||||
my $fk_name = $self->_get_fk_name(@_);
|
||||
my @sql;
|
||||
if (!$references->{UPDATE} || $references->{UPDATE} =~ /CASCADE/i) {
|
||||
push(@sql, "DROP TRIGGER ${fk_name}_uc");
|
||||
}
|
||||
push(@sql, $self->SUPER::get_drop_fk_sql(@_));
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub _get_fk_name {
|
||||
my ($self, $table, $column, $references) = @_;
|
||||
my $to_table = $references->{TABLE};
|
||||
my $to_column = $references->{COLUMN};
|
||||
my $fk_name = "${table}_${column}_${to_table}_${to_column}";
|
||||
$fk_name = "fk_" . $self->_hash_identifier($fk_name);
|
||||
|
||||
return $fk_name;
|
||||
}
|
||||
|
||||
sub get_alter_column_ddl {
|
||||
my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
|
||||
|
||||
my @statements;
|
||||
my $old_def = $self->get_column_abstract($table, $column);
|
||||
my $specific = $self->{db_specific};
|
||||
|
||||
# If the types have changed, we have to deal with that.
|
||||
if (uc(trim($old_def->{TYPE})) ne uc(trim($new_def->{TYPE}))) {
|
||||
push(@statements, $self->_get_alter_type_sql($table, $column,
|
||||
$new_def, $old_def));
|
||||
}
|
||||
|
||||
my $default = $new_def->{DEFAULT};
|
||||
my $default_old = $old_def->{DEFAULT};
|
||||
# This first condition prevents "uninitialized value" errors.
|
||||
if (!defined $default && !defined $default_old) {
|
||||
# Do Nothing
|
||||
}
|
||||
# If we went from having a default to not having one
|
||||
elsif (!defined $default && defined $default_old) {
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column"
|
||||
. " DEFAULT NULL");
|
||||
}
|
||||
# If we went from no default to a default, or we changed the default.
|
||||
elsif ( (defined $default && !defined $default_old) ||
|
||||
($default ne $default_old) )
|
||||
{
|
||||
$default = $specific->{$default} if exists $specific->{$default};
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column "
|
||||
. " DEFAULT $default");
|
||||
}
|
||||
|
||||
# If we went from NULL to NOT NULL.
|
||||
if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
|
||||
my $setdefault;
|
||||
# Handle any fields that were NULL before, if we have a default,
|
||||
$setdefault = $new_def->{DEFAULT} if exists $new_def->{DEFAULT};
|
||||
# But if we have a set_nulls_to, that overrides the DEFAULT
|
||||
# (although nobody would usually specify both a default and
|
||||
# a set_nulls_to.)
|
||||
$setdefault = $set_nulls_to if defined $set_nulls_to;
|
||||
if (defined $setdefault) {
|
||||
push(@statements, "UPDATE $table SET $column = $setdefault"
|
||||
. " WHERE $column IS NULL");
|
||||
}
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column"
|
||||
. " NOT NULL");
|
||||
push (@statements, _get_notnull_trigger_ddl($table, $column))
|
||||
if $old_def->{TYPE} =~ /varchar|text/i
|
||||
&& $new_def->{TYPE} =~ /varchar|text/i;
|
||||
}
|
||||
# If we went from NOT NULL to NULL
|
||||
elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column"
|
||||
. " NULL");
|
||||
push(@statements, "DROP TRIGGER ${table}_${column}")
|
||||
if $new_def->{TYPE} =~ /varchar|text/i
|
||||
&& $old_def->{TYPE} =~ /varchar|text/i;
|
||||
}
|
||||
|
||||
# If we went from not being a PRIMARY KEY to being a PRIMARY KEY.
|
||||
if (!$old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
|
||||
push(@statements, "ALTER TABLE $table ADD PRIMARY KEY ($column)");
|
||||
}
|
||||
# If we went from being a PK to not being a PK
|
||||
elsif ( $old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY} ) {
|
||||
push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
|
||||
}
|
||||
|
||||
return @statements;
|
||||
}
|
||||
|
||||
sub _get_alter_type_sql {
|
||||
my ($self, $table, $column, $new_def, $old_def) = @_;
|
||||
my @statements;
|
||||
|
||||
my $type = $new_def->{TYPE};
|
||||
$type = $self->{db_specific}->{$type}
|
||||
if exists $self->{db_specific}->{$type};
|
||||
|
||||
if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
die("You cannot specify a DEFAULT on a SERIAL-type column.")
|
||||
if $new_def->{DEFAULT};
|
||||
}
|
||||
|
||||
if ( ($old_def->{TYPE} =~ /LONGTEXT/i && $new_def->{TYPE} !~ /LONGTEXT/i)
|
||||
|| ($old_def->{TYPE} !~ /LONGTEXT/i && $new_def->{TYPE} =~ /LONGTEXT/i)
|
||||
) {
|
||||
# LONG to VARCHAR or VARCHAR to LONG is not allowed in Oracle,
|
||||
# just a way to work around.
|
||||
# Determine whether column_temp is already exist.
|
||||
my $dbh=Bugzilla->dbh;
|
||||
my $column_exist = $dbh->selectcol_arrayref(
|
||||
"SELECT CNAME FROM COL WHERE TNAME = UPPER(?) AND
|
||||
CNAME = UPPER(?)", undef,$table,$column . "_temp");
|
||||
if(!@$column_exist) {
|
||||
push(@statements,
|
||||
"ALTER TABLE $table ADD ${column}_temp $type");
|
||||
}
|
||||
push(@statements, "UPDATE $table SET ${column}_temp = $column");
|
||||
push(@statements, "COMMIT");
|
||||
push(@statements, "ALTER TABLE $table DROP COLUMN $column");
|
||||
push(@statements,
|
||||
"ALTER TABLE $table RENAME COLUMN ${column}_temp TO $column");
|
||||
} else {
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column $type");
|
||||
}
|
||||
|
||||
if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, _get_create_seq_ddl($table, $column));
|
||||
}
|
||||
|
||||
# If this column is no longer SERIAL, we need to drop the sequence
|
||||
# that went along with it.
|
||||
if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, "DROP SEQUENCE ${table}_${column}_SEQ");
|
||||
push(@statements, "DROP TRIGGER ${table}_${column}_TR");
|
||||
}
|
||||
|
||||
# If this column is changed to type TEXT/VARCHAR, we need to deal with
|
||||
# empty string.
|
||||
if ( $old_def->{TYPE} !~ /varchar|text/i
|
||||
&& $new_def->{TYPE} =~ /varchar|text/i
|
||||
&& $new_def->{NOTNULL} )
|
||||
{
|
||||
push (@statements, _get_notnull_trigger_ddl($table, $column));
|
||||
}
|
||||
# If this column is no longer TEXT/VARCHAR, we need to drop the trigger
|
||||
# that went along with it.
|
||||
if ( $old_def->{TYPE} =~ /varchar|text/i
|
||||
&& $old_def->{NOTNULL}
|
||||
&& $new_def->{TYPE} !~ /varchar|text/i )
|
||||
{
|
||||
push(@statements, "DROP TRIGGER ${table}_${column}");
|
||||
}
|
||||
return @statements;
|
||||
}
|
||||
|
||||
sub get_rename_column_ddl {
|
||||
my ($self, $table, $old_name, $new_name) = @_;
|
||||
if (lc($old_name) eq lc($new_name)) {
|
||||
# if the only change is a case change, return an empty list.
|
||||
return ();
|
||||
}
|
||||
my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
|
||||
my $def = $self->get_column_abstract($table, $old_name);
|
||||
if ($def->{TYPE} =~ /SERIAL/i) {
|
||||
# We have to rename the series also, and fix the default of the series.
|
||||
push(@sql, "RENAME ${table}_${old_name}_SEQ TO
|
||||
${table}_${new_name}_seq");
|
||||
my $serial_sql =
|
||||
"CREATE OR REPLACE TRIGGER ${table}_${new_name}_TR "
|
||||
. " BEFORE INSERT ON ${table} "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " SELECT ${table}_${new_name}_SEQ.NEXTVAL "
|
||||
. " INTO :NEW.${new_name} FROM DUAL; "
|
||||
. " END;";
|
||||
push(@sql, $serial_sql);
|
||||
push(@sql, "DROP TRIGGER ${table}_${old_name}_TR");
|
||||
}
|
||||
if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL} ) {
|
||||
push(@sql, _get_notnull_trigger_ddl($table,$new_name));
|
||||
push(@sql, "DROP TRIGGER ${table}_${old_name}");
|
||||
}
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub _get_notnull_trigger_ddl {
|
||||
my ($table, $column) = @_;
|
||||
|
||||
my $notnull_sql = "CREATE OR REPLACE TRIGGER "
|
||||
. " ${table}_${column}"
|
||||
. " BEFORE INSERT OR UPDATE ON ". $table
|
||||
. " FOR EACH ROW"
|
||||
. " BEGIN "
|
||||
. " IF :NEW.". $column ." IS NULL THEN "
|
||||
. " SELECT '" . Bugzilla::DB::Oracle->EMPTY_STRING
|
||||
. "' INTO :NEW.". $column ." FROM DUAL; "
|
||||
. " END IF; "
|
||||
. " END ".$table.";";
|
||||
return $notnull_sql;
|
||||
}
|
||||
|
||||
sub _get_create_seq_ddl {
|
||||
my ($self, $table, $column, $start_with) = @_;
|
||||
$start_with ||= 1;
|
||||
my @ddl;
|
||||
my $seq_name = "${table}_${column}_SEQ";
|
||||
my $seq_sql = "CREATE SEQUENCE $seq_name "
|
||||
. " INCREMENT BY 1 "
|
||||
. " START WITH $start_with "
|
||||
. " NOMAXVALUE "
|
||||
. " NOCYCLE "
|
||||
. " NOCACHE";
|
||||
my $serial_sql = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
|
||||
. " BEFORE INSERT ON ${table} "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " SELECT ${seq_name}.NEXTVAL "
|
||||
. " INTO :NEW.${column} FROM DUAL; "
|
||||
. " END;";
|
||||
push (@ddl, $seq_sql);
|
||||
push (@ddl, $serial_sql);
|
||||
|
||||
return @ddl;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,166 +0,0 @@
|
||||
# -*- 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): Andrew Dunstan <andrew@dunslane.net>,
|
||||
# Edward J. Sabol <edwardjsabol@iname.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::DB::Schema::Pg;
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB::Schema implementation for PostgreSQL
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::DB::Schema);
|
||||
use Storable qw(dclone);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sub _initialize {
|
||||
|
||||
my $self = shift;
|
||||
|
||||
$self = $self->SUPER::_initialize(@_);
|
||||
|
||||
# Remove FULLTEXT index types from the schemas.
|
||||
foreach my $table (keys %{ $self->{schema} }) {
|
||||
if ($self->{schema}{$table}{INDEXES}) {
|
||||
foreach my $index (@{ $self->{schema}{$table}{INDEXES} }) {
|
||||
if (ref($index) eq 'HASH') {
|
||||
delete($index->{TYPE}) if (exists $index->{TYPE}
|
||||
&& $index->{TYPE} eq 'FULLTEXT');
|
||||
}
|
||||
}
|
||||
foreach my $index (@{ $self->{abstract_schema}{$table}{INDEXES} }) {
|
||||
if (ref($index) eq 'HASH') {
|
||||
delete($index->{TYPE}) if (exists $index->{TYPE}
|
||||
&& $index->{TYPE} eq 'FULLTEXT');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->{db_specific} = {
|
||||
|
||||
BOOLEAN => 'smallint',
|
||||
FALSE => '0',
|
||||
TRUE => '1',
|
||||
|
||||
INT1 => 'integer',
|
||||
INT2 => 'integer',
|
||||
INT3 => 'integer',
|
||||
INT4 => 'integer',
|
||||
|
||||
SMALLSERIAL => 'serial unique',
|
||||
MEDIUMSERIAL => 'serial unique',
|
||||
INTSERIAL => 'serial unique',
|
||||
|
||||
TINYTEXT => 'varchar(255)',
|
||||
MEDIUMTEXT => 'text',
|
||||
LONGTEXT => 'text',
|
||||
|
||||
LONGBLOB => 'bytea',
|
||||
|
||||
DATETIME => 'timestamp(0) without time zone',
|
||||
|
||||
};
|
||||
|
||||
$self->_adjust_schema;
|
||||
|
||||
return $self;
|
||||
|
||||
} #eosub--_initialize
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
sub get_rename_column_ddl {
|
||||
my ($self, $table, $old_name, $new_name) = @_;
|
||||
if (lc($old_name) eq lc($new_name)) {
|
||||
# if the only change is a case change, return an empty list, since Pg
|
||||
# is case-insensitive and will return an error about a duplicate name
|
||||
return ();
|
||||
}
|
||||
my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
|
||||
my $def = $self->get_column_abstract($table, $old_name);
|
||||
if ($def->{TYPE} =~ /SERIAL/i) {
|
||||
# We have to rename the series also, and fix the default of the series.
|
||||
push(@sql, "ALTER TABLE ${table}_${old_name}_seq
|
||||
RENAME TO ${table}_${new_name}_seq");
|
||||
push(@sql, "ALTER TABLE $table ALTER COLUMN $new_name
|
||||
SET DEFAULT NEXTVAL('${table}_${new_name}_seq')");
|
||||
}
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub get_rename_table_sql {
|
||||
my ($self, $old_name, $new_name) = @_;
|
||||
if (lc($old_name) eq lc($new_name)) {
|
||||
# if the only change is a case change, return an empty list, since Pg
|
||||
# is case-insensitive and will return an error about a duplicate name
|
||||
return ();
|
||||
}
|
||||
return ("ALTER TABLE $old_name RENAME TO $new_name");
|
||||
}
|
||||
|
||||
sub _get_alter_type_sql {
|
||||
my ($self, $table, $column, $new_def, $old_def) = @_;
|
||||
my @statements;
|
||||
|
||||
my $type = $new_def->{TYPE};
|
||||
$type = $self->{db_specific}->{$type}
|
||||
if exists $self->{db_specific}->{$type};
|
||||
|
||||
if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
die("You cannot specify a DEFAULT on a SERIAL-type column.")
|
||||
if $new_def->{DEFAULT};
|
||||
$type =~ s/serial/integer/i;
|
||||
}
|
||||
|
||||
# On Pg, you don't need UNIQUE if you're a PK--it creates
|
||||
# two identical indexes otherwise.
|
||||
$type =~ s/unique//i if $new_def->{PRIMARYKEY};
|
||||
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
|
||||
TYPE $type");
|
||||
|
||||
if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, "CREATE SEQUENCE ${table}_${column}_seq");
|
||||
push(@statements, "SELECT setval('${table}_${column}_seq',
|
||||
MAX($table.$column))
|
||||
FROM $table");
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
|
||||
SET DEFAULT nextval('${table}_${column}_seq')");
|
||||
}
|
||||
|
||||
# If this column is no longer SERIAL, we need to drop the sequence
|
||||
# that went along with it.
|
||||
if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
|
||||
DROP DEFAULT");
|
||||
# XXX Pg actually won't let us drop the sequence, even though it's
|
||||
# no longer in use. So we harmlessly leave behind a sequence
|
||||
# that does nothing.
|
||||
#push(@statements, "DROP SEQUENCE ${table}_${column}_seq");
|
||||
}
|
||||
|
||||
return @statements;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,234 +0,0 @@
|
||||
# -*- 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): Bradley Baetz <bbaetz@acm.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
package Bugzilla::Error;
|
||||
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
@Bugzilla::Error::EXPORT = qw(ThrowCodeError ThrowTemplateError ThrowUserError);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::WebService::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Date::Format;
|
||||
|
||||
# We cannot use $^S to detect if we are in an eval(), because mod_perl
|
||||
# already eval'uates everything, so $^S = 1 in all cases under mod_perl!
|
||||
sub _in_eval {
|
||||
my $in_eval = 0;
|
||||
for (my $stack = 1; my $sub = (caller($stack))[3]; $stack++) {
|
||||
last if $sub =~ /^ModPerl/;
|
||||
$in_eval = 1 if $sub =~ /^\(eval\)/;
|
||||
}
|
||||
return $in_eval;
|
||||
}
|
||||
|
||||
sub _throw_error {
|
||||
my ($name, $error, $vars) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$vars ||= {};
|
||||
|
||||
$vars->{error} = $error;
|
||||
|
||||
# Make sure any transaction is rolled back (if supported).
|
||||
# If we are within an eval(), do not roll back transactions as we are
|
||||
# eval'uating some test on purpose.
|
||||
$dbh->bz_rollback_transaction() if ($dbh->bz_in_transaction() && !_in_eval());
|
||||
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
# If a writable $datadir/errorlog exists, log error details there.
|
||||
if (-w "$datadir/errorlog") {
|
||||
require Data::Dumper;
|
||||
my $mesg = "";
|
||||
for (1..75) { $mesg .= "-"; };
|
||||
$mesg .= "\n[$$] " . time2str("%D %H:%M:%S ", time());
|
||||
$mesg .= "$name $error ";
|
||||
$mesg .= "$ENV{REMOTE_ADDR} " if $ENV{REMOTE_ADDR};
|
||||
$mesg .= Bugzilla->user->login;
|
||||
$mesg .= (' actually ' . Bugzilla->sudoer->login) if Bugzilla->sudoer;
|
||||
$mesg .= "\n";
|
||||
my %params = Bugzilla->cgi->Vars;
|
||||
$Data::Dumper::Useqq = 1;
|
||||
for my $param (sort keys %params) {
|
||||
my $val = $params{$param};
|
||||
# obscure passwords
|
||||
$val = "*****" if $param =~ /password/i;
|
||||
# limit line length
|
||||
$val =~ s/^(.{512}).*$/$1\[CHOP\]/;
|
||||
$mesg .= "[$$] " . Data::Dumper->Dump([$val],["param($param)"]);
|
||||
}
|
||||
for my $var (sort keys %ENV) {
|
||||
my $val = $ENV{$var};
|
||||
$val = "*****" if $val =~ /password|http_pass/i;
|
||||
$mesg .= "[$$] " . Data::Dumper->Dump([$val],["env($var)"]);
|
||||
}
|
||||
open(ERRORLOGFID, ">>$datadir/errorlog");
|
||||
print ERRORLOGFID "$mesg\n";
|
||||
close ERRORLOGFID;
|
||||
}
|
||||
|
||||
my $template = Bugzilla->template;
|
||||
if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
|
||||
print Bugzilla->cgi->header();
|
||||
$template->process($name, $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
else {
|
||||
my $message;
|
||||
$template->process($name, $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
if (Bugzilla->error_mode == ERROR_MODE_DIE) {
|
||||
die("$message\n");
|
||||
}
|
||||
elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
|
||||
# Clone the hash so we aren't modifying the constant.
|
||||
my %error_map = %{ WS_ERROR_CODE() };
|
||||
require Bugzilla::Hook;
|
||||
Bugzilla::Hook::process('webservice-error_codes',
|
||||
{ error_map => \%error_map });
|
||||
my $code = $error_map{$error};
|
||||
if (!$code) {
|
||||
$code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
|
||||
$code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
|
||||
}
|
||||
die SOAP::Fault->faultcode($code)->faultstring($message);
|
||||
}
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
sub ThrowUserError {
|
||||
_throw_error("global/user-error.html.tmpl", @_);
|
||||
}
|
||||
|
||||
sub ThrowCodeError {
|
||||
_throw_error("global/code-error.html.tmpl", @_);
|
||||
}
|
||||
|
||||
sub ThrowTemplateError {
|
||||
my ($template_err) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Make sure the transaction is rolled back (if supported).
|
||||
$dbh->bz_rollback_transaction() if $dbh->bz_in_transaction();
|
||||
|
||||
my $vars = {};
|
||||
if (Bugzilla->error_mode == ERROR_MODE_DIE) {
|
||||
die("error: template error: $template_err");
|
||||
}
|
||||
|
||||
$vars->{'template_error_msg'} = $template_err;
|
||||
$vars->{'error'} = "template_error";
|
||||
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
# Try a template first; but if this one fails too, fall back
|
||||
# on plain old print statements.
|
||||
if (!$template->process("global/code-error.html.tmpl", $vars)) {
|
||||
my $maintainer = Bugzilla->params->{'maintainer'};
|
||||
my $error = html_quote($vars->{'template_error_msg'});
|
||||
my $error2 = html_quote($template->error());
|
||||
print <<END;
|
||||
<tt>
|
||||
<p>
|
||||
Bugzilla has suffered an internal error. Please save this page and
|
||||
send it to $maintainer with details of what you were doing at the
|
||||
time this message appeared.
|
||||
</p>
|
||||
<script type="text/javascript"> <!--
|
||||
document.write("<p>URL: " +
|
||||
document.location.href.replace(/&/g,"&")
|
||||
.replace(/</g,"<")
|
||||
.replace(/>/g,">") + "</p>");
|
||||
// -->
|
||||
</script>
|
||||
<p>Template->process() failed twice.<br>
|
||||
First error: $error<br>
|
||||
Second error: $error2</p>
|
||||
</tt>
|
||||
END
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Error - Error handling utilities for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Error;
|
||||
|
||||
ThrowUserError("error_tag",
|
||||
{ foo => 'bar' });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Various places throughout the Bugzilla codebase need to report errors to the
|
||||
user. The C<Throw*Error> family of functions allow this to be done in a
|
||||
generic and localizable manner.
|
||||
|
||||
These functions automatically unlock the database tables, if there were any
|
||||
locked. They will also roll back the transaction, if it is supported by
|
||||
the underlying DB.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<ThrowUserError>
|
||||
|
||||
This function takes an error tag as the first argument, and an optional hashref
|
||||
of variables as a second argument. These are used by the
|
||||
I<global/user-error.html.tmpl> template to format the error, using the passed
|
||||
in variables as required.
|
||||
|
||||
=item C<ThrowCodeError>
|
||||
|
||||
This function is used when an internal check detects an error of some sort.
|
||||
This usually indicates a bug in Bugzilla, although it can occur if the user
|
||||
manually constructs urls without correct parameters.
|
||||
|
||||
This function's behaviour is similar to C<ThrowUserError>, except that the
|
||||
template used to display errors is I<global/code-error.html.tmpl>. In addition
|
||||
if the hashref used as the optional second argument contains a key I<variables>
|
||||
then the contents of the hashref (which is expected to be another hashref) will
|
||||
be displayed after the error message, as a debugging aid.
|
||||
|
||||
=item C<ThrowTemplateError>
|
||||
|
||||
This function should only be called if a C<template-<gt>process()> fails.
|
||||
It tries another template first, because often one template being
|
||||
broken or missing doesn't mean that they all are. But it falls back to
|
||||
a print statement as a last-ditch error.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla|Bugzilla>
|
||||
@@ -1,803 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Dan Mosedale <dmose@mozilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Myk Melez <myk@mozilla.org>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Field - a particular piece of information about bugs
|
||||
and useful routines for form field manipulation
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla;
|
||||
use Data::Dumper;
|
||||
|
||||
# Display information about all fields.
|
||||
print Dumper(Bugzilla->get_fields());
|
||||
|
||||
# Display information about non-obsolete custom fields.
|
||||
print Dumper(Bugzilla->active_custom_fields);
|
||||
|
||||
use Bugzilla::Field;
|
||||
|
||||
# Display information about non-obsolete custom fields.
|
||||
# Bugzilla->get_fields() is a wrapper around Bugzilla::Field->match(),
|
||||
# so both methods take the same arguments.
|
||||
print Dumper(Bugzilla::Field->match({ obsolete => 0, custom => 1 }));
|
||||
|
||||
# Create or update a custom field or field definition.
|
||||
my $field = Bugzilla::Field->create(
|
||||
{name => 'cf_silly', description => 'Silly', custom => 1});
|
||||
|
||||
# Instantiate a Field object for an existing field.
|
||||
my $field = new Bugzilla::Field({name => 'qacontact_accessible'});
|
||||
if ($field->obsolete) {
|
||||
print $field->description . " is obsolete\n";
|
||||
}
|
||||
|
||||
# Validation Routines
|
||||
check_field($name, $value, \@legal_values, $no_warn);
|
||||
$fieldid = get_field_id($fieldname);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Field.pm defines field objects, which represent the particular pieces
|
||||
of information that Bugzilla stores about bugs.
|
||||
|
||||
This package also provides functions for dealing with CGI form fields.
|
||||
|
||||
C<Bugzilla::Field> is an implementation of L<Bugzilla::Object>, and
|
||||
so provides all of the methods available in L<Bugzilla::Object>,
|
||||
in addition to what is documented here.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::Field;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter Bugzilla::Object);
|
||||
@Bugzilla::Field::EXPORT = qw(check_field get_field_id get_legal_field_values);
|
||||
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_TABLE => 'fielddefs';
|
||||
use constant LIST_ORDER => 'sortkey, name';
|
||||
|
||||
use constant DB_COLUMNS => (
|
||||
'id',
|
||||
'name',
|
||||
'description',
|
||||
'type',
|
||||
'custom',
|
||||
'mailhead',
|
||||
'sortkey',
|
||||
'obsolete',
|
||||
'enter_bug',
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(name description);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
custom => \&_check_custom,
|
||||
description => \&_check_description,
|
||||
enter_bug => \&_check_enter_bug,
|
||||
mailhead => \&_check_mailhead,
|
||||
obsolete => \&_check_obsolete,
|
||||
sortkey => \&_check_sortkey,
|
||||
type => \&_check_type,
|
||||
};
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
description
|
||||
mailhead
|
||||
sortkey
|
||||
obsolete
|
||||
enter_bug
|
||||
);
|
||||
|
||||
# How various field types translate into SQL data definitions.
|
||||
use constant SQL_DEFINITIONS => {
|
||||
# Using commas because these are constants and they shouldn't
|
||||
# be auto-quoted by the "=>" operator.
|
||||
FIELD_TYPE_FREETEXT, { TYPE => 'varchar(255)' },
|
||||
FIELD_TYPE_SINGLE_SELECT, { TYPE => 'varchar(64)', NOTNULL => 1,
|
||||
DEFAULT => "'---'" },
|
||||
FIELD_TYPE_TEXTAREA, { TYPE => 'MEDIUMTEXT' },
|
||||
FIELD_TYPE_DATETIME, { TYPE => 'DATETIME' },
|
||||
};
|
||||
|
||||
# Field definitions for the fields that ship with Bugzilla.
|
||||
# These are used by populate_field_definitions to populate
|
||||
# the fielddefs table.
|
||||
use constant DEFAULT_FIELDS => (
|
||||
{name => 'bug_id', desc => 'Bug #', in_new_bugmail => 1},
|
||||
{name => 'short_desc', desc => 'Summary', in_new_bugmail => 1},
|
||||
{name => 'classification', desc => 'Classification', in_new_bugmail => 1},
|
||||
{name => 'product', desc => 'Product', in_new_bugmail => 1},
|
||||
{name => 'version', desc => 'Version', in_new_bugmail => 1},
|
||||
{name => 'rep_platform', desc => 'Platform', in_new_bugmail => 1},
|
||||
{name => 'bug_file_loc', desc => 'URL', in_new_bugmail => 1},
|
||||
{name => 'op_sys', desc => 'OS/Version', in_new_bugmail => 1},
|
||||
{name => 'bug_status', desc => 'Status', in_new_bugmail => 1},
|
||||
{name => 'status_whiteboard', desc => 'Status Whiteboard',
|
||||
in_new_bugmail => 1},
|
||||
{name => 'keywords', desc => 'Keywords', in_new_bugmail => 1},
|
||||
{name => 'resolution', desc => 'Resolution'},
|
||||
{name => 'bug_severity', desc => 'Severity', in_new_bugmail => 1},
|
||||
{name => 'priority', desc => 'Priority', in_new_bugmail => 1},
|
||||
{name => 'component', desc => 'Component', in_new_bugmail => 1},
|
||||
{name => 'assigned_to', desc => 'AssignedTo', in_new_bugmail => 1},
|
||||
{name => 'reporter', desc => 'ReportedBy', in_new_bugmail => 1},
|
||||
{name => 'votes', desc => 'Votes'},
|
||||
{name => 'qa_contact', desc => 'QAContact', in_new_bugmail => 1},
|
||||
{name => 'cc', desc => 'CC', in_new_bugmail => 1},
|
||||
{name => 'dependson', desc => 'Depends on', in_new_bugmail => 1},
|
||||
{name => 'blocked', desc => 'Blocks', in_new_bugmail => 1},
|
||||
|
||||
{name => 'attachments.description', desc => 'Attachment description'},
|
||||
{name => 'attachments.filename', desc => 'Attachment filename'},
|
||||
{name => 'attachments.mimetype', desc => 'Attachment mime type'},
|
||||
{name => 'attachments.ispatch', desc => 'Attachment is patch'},
|
||||
{name => 'attachments.isobsolete', desc => 'Attachment is obsolete'},
|
||||
{name => 'attachments.isprivate', desc => 'Attachment is private'},
|
||||
{name => 'attachments.submitter', desc => 'Attachment creator'},
|
||||
|
||||
{name => 'target_milestone', desc => 'Target Milestone'},
|
||||
{name => 'creation_ts', desc => 'Creation date', in_new_bugmail => 1},
|
||||
{name => 'delta_ts', desc => 'Last changed date', in_new_bugmail => 1},
|
||||
{name => 'longdesc', desc => 'Comment'},
|
||||
{name => 'longdescs.isprivate', desc => 'Comment is private'},
|
||||
{name => 'alias', desc => 'Alias'},
|
||||
{name => 'everconfirmed', desc => 'Ever Confirmed'},
|
||||
{name => 'reporter_accessible', desc => 'Reporter Accessible'},
|
||||
{name => 'cclist_accessible', desc => 'CC Accessible'},
|
||||
{name => 'bug_group', desc => 'Group'},
|
||||
{name => 'estimated_time', desc => 'Estimated Hours', in_new_bugmail => 1},
|
||||
{name => 'remaining_time', desc => 'Remaining Hours'},
|
||||
{name => 'deadline', desc => 'Deadline', in_new_bugmail => 1},
|
||||
{name => 'commenter', desc => 'Commenter'},
|
||||
{name => 'flagtypes.name', desc => 'Flag'},
|
||||
{name => 'requestees.login_name', desc => 'Flag Requestee'},
|
||||
{name => 'setters.login_name', desc => 'Flag Setter'},
|
||||
{name => 'work_time', desc => 'Hours Worked'},
|
||||
{name => 'percentage_complete', desc => 'Percentage Complete'},
|
||||
{name => 'content', desc => 'Content'},
|
||||
{name => 'attach_data.thedata', desc => 'Attachment data'},
|
||||
{name => 'attachments.isurl', desc => 'Attachment is a URL'},
|
||||
{name => "owner_idle_time", desc => "Time Since Assignee Touched"},
|
||||
);
|
||||
|
||||
##############
|
||||
# Validators #
|
||||
##############
|
||||
|
||||
sub _check_custom { return $_[1] ? 1 : 0; }
|
||||
|
||||
sub _check_description {
|
||||
my ($invocant, $desc) = @_;
|
||||
$desc = clean_text($desc);
|
||||
$desc || ThrowUserError('field_missing_description');
|
||||
return $desc;
|
||||
}
|
||||
|
||||
sub _check_enter_bug { return $_[1] ? 1 : 0; }
|
||||
|
||||
sub _check_mailhead { return $_[1] ? 1 : 0; }
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name, $is_custom) = @_;
|
||||
$name = lc(clean_text($name));
|
||||
$name || ThrowUserError('field_missing_name');
|
||||
|
||||
# Don't want to allow a name that might mess up SQL.
|
||||
my $name_regex = qr/^[\w\.]+$/;
|
||||
# Custom fields have more restrictive name requirements than
|
||||
# standard fields.
|
||||
$name_regex = qr/^\w+$/ if $is_custom;
|
||||
# Custom fields can't be named just "cf_", and there is no normal
|
||||
# field named just "cf_".
|
||||
($name =~ $name_regex && $name ne "cf_")
|
||||
|| ThrowUserError('field_invalid_name', { name => $name });
|
||||
|
||||
# If it's custom, prepend cf_ to the custom field name to distinguish
|
||||
# it from standard fields.
|
||||
if ($name !~ /^cf_/ && $is_custom) {
|
||||
$name = 'cf_' . $name;
|
||||
}
|
||||
|
||||
# Assure the name is unique. Names can't be changed, so we don't have
|
||||
# to worry about what to do on updates.
|
||||
my $field = new Bugzilla::Field({ name => $name });
|
||||
ThrowUserError('field_already_exists', {'field' => $field }) if $field;
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_obsolete { return $_[1] ? 1 : 0; }
|
||||
|
||||
sub _check_sortkey {
|
||||
my ($invocant, $sortkey) = @_;
|
||||
my $skey = $sortkey;
|
||||
if (!defined $skey || $skey eq '') {
|
||||
($sortkey) = Bugzilla->dbh->selectrow_array(
|
||||
'SELECT MAX(sortkey) + 100 FROM fielddefs') || 100;
|
||||
}
|
||||
detaint_natural($sortkey)
|
||||
|| ThrowUserError('field_invalid_sortkey', { sortkey => $skey });
|
||||
return $sortkey;
|
||||
}
|
||||
|
||||
sub _check_type {
|
||||
my ($invocant, $type) = @_;
|
||||
my $saved_type = $type;
|
||||
# The constant here should be updated every time a new,
|
||||
# higher field type is added.
|
||||
(detaint_natural($type) && $type <= FIELD_TYPE_DATETIME)
|
||||
|| ThrowCodeError('invalid_customfield_type', { type => $saved_type });
|
||||
return $type;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Instance Properties
|
||||
|
||||
=over
|
||||
|
||||
=item C<name>
|
||||
|
||||
the name of the field in the database; begins with "cf_" if field
|
||||
is a custom field, but test the value of the boolean "custom" property
|
||||
to determine if a given field is a custom field;
|
||||
|
||||
=item C<description>
|
||||
|
||||
a short string describing the field; displayed to Bugzilla users
|
||||
in several places within Bugzilla's UI, f.e. as the form field label
|
||||
on the "show bug" page;
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub description { return $_[0]->{description} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<type>
|
||||
|
||||
an integer specifying the kind of field this is; values correspond to
|
||||
the FIELD_TYPE_* constants in Constants.pm
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub type { return $_[0]->{type} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<custom>
|
||||
|
||||
a boolean specifying whether or not the field is a custom field;
|
||||
if true, field name should start "cf_", but use this property to determine
|
||||
which fields are custom fields;
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub custom { return $_[0]->{custom} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<in_new_bugmail>
|
||||
|
||||
a boolean specifying whether or not the field is displayed in bugmail
|
||||
for newly-created bugs;
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub in_new_bugmail { return $_[0]->{mailhead} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<sortkey>
|
||||
|
||||
an integer specifying the sortkey of the field.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub sortkey { return $_[0]->{sortkey} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<obsolete>
|
||||
|
||||
a boolean specifying whether or not the field is obsolete;
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub obsolete { return $_[0]->{obsolete} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<enter_bug>
|
||||
|
||||
A boolean specifying whether or not this field should appear on
|
||||
enter_bug.cgi
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub enter_bug { return $_[0]->{enter_bug} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<legal_values>
|
||||
|
||||
A reference to an array with valid active values for this field.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub legal_values {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'legal_values'}) {
|
||||
$self->{'legal_values'} = get_legal_field_values($self->name);
|
||||
}
|
||||
return $self->{'legal_values'};
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Instance Mutators
|
||||
|
||||
These set the particular field that they are named after.
|
||||
|
||||
They take a single value--the new value for that field.
|
||||
|
||||
They will throw an error if you try to set the values to something invalid.
|
||||
|
||||
=over
|
||||
|
||||
=item C<set_description>
|
||||
|
||||
=item C<set_enter_bug>
|
||||
|
||||
=item C<set_obsolete>
|
||||
|
||||
=item C<set_sortkey>
|
||||
|
||||
=item C<set_in_new_bugmail>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
sub set_enter_bug { $_[0]->set('enter_bug', $_[1]); }
|
||||
sub set_obsolete { $_[0]->set('obsolete', $_[1]); }
|
||||
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
|
||||
sub set_in_new_bugmail { $_[0]->set('mailhead', $_[1]); }
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Instance Method
|
||||
|
||||
=over
|
||||
|
||||
=item C<remove_from_db>
|
||||
|
||||
Attempts to remove the passed in field from the database.
|
||||
Deleting a field is only successful if the field is obsolete and
|
||||
there are no values specified (or EVER specified) for the field.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $name = $self->name;
|
||||
|
||||
if (!$self->custom) {
|
||||
ThrowCodeError('field_not_custom', {'name' => $name });
|
||||
}
|
||||
|
||||
if (!$self->obsolete) {
|
||||
ThrowUserError('customfield_not_obsolete', {'name' => $self->name });
|
||||
}
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
# Check to see if bug activity table has records (should be fast with index)
|
||||
my $has_activity = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs_activity
|
||||
WHERE fieldid = ?", undef, $self->id);
|
||||
if ($has_activity) {
|
||||
ThrowUserError('customfield_has_activity', {'name' => $name });
|
||||
}
|
||||
|
||||
# Check to see if bugs table has records (slow)
|
||||
my $bugs_query = "";
|
||||
|
||||
if ($self->type == FIELD_TYPE_MULTI_SELECT) {
|
||||
$bugs_query = "SELECT COUNT(*) FROM bug_$name";
|
||||
}
|
||||
else {
|
||||
$bugs_query = "SELECT COUNT(*) FROM bugs WHERE $name IS NOT NULL
|
||||
AND $name != ''";
|
||||
# Ignore the default single select value
|
||||
if ($self->type == FIELD_TYPE_SINGLE_SELECT) {
|
||||
$bugs_query .= " AND $name != '---'";
|
||||
}
|
||||
# Ignore blank dates.
|
||||
if ($self->type == FIELD_TYPE_DATETIME) {
|
||||
$bugs_query .= " AND $name != '00-00-00 00:00:00'";
|
||||
}
|
||||
}
|
||||
|
||||
my $has_bugs = $dbh->selectrow_array($bugs_query);
|
||||
if ($has_bugs) {
|
||||
ThrowUserError('customfield_has_contents', {'name' => $name });
|
||||
}
|
||||
|
||||
# Once we reach here, we should be OK to delete.
|
||||
$dbh->do('DELETE FROM fielddefs WHERE id = ?', undef, $self->id);
|
||||
|
||||
my $type = $self->type;
|
||||
|
||||
# the values for multi-select are stored in a seperate table
|
||||
if ($type != FIELD_TYPE_MULTI_SELECT) {
|
||||
$dbh->bz_drop_column('bugs', $name);
|
||||
}
|
||||
|
||||
if ($type == FIELD_TYPE_SINGLE_SELECT
|
||||
|| $type == FIELD_TYPE_MULTI_SELECT)
|
||||
{
|
||||
# Delete the table that holds the legal values for this field.
|
||||
$dbh->bz_drop_field_tables($self);
|
||||
}
|
||||
|
||||
$dbh->bz_commit_transaction()
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Class Methods
|
||||
|
||||
=over
|
||||
|
||||
=item C<create>
|
||||
|
||||
Just like L<Bugzilla::Object/create>. Takes the following parameters:
|
||||
|
||||
=over
|
||||
|
||||
=item C<name> B<Required> - The name of the field.
|
||||
|
||||
=item C<description> B<Required> - The field label to display in the UI.
|
||||
|
||||
=item C<mailhead> - boolean - Whether this field appears at the
|
||||
top of the bugmail for a newly-filed bug. Defaults to 0.
|
||||
|
||||
=item C<custom> - boolean - True if this is a Custom Field. The field
|
||||
will be added to the C<bugs> table if it does not exist. Defaults to 0.
|
||||
|
||||
=item C<sortkey> - integer - The sortkey of the field. Defaults to 0.
|
||||
|
||||
=item C<enter_bug> - boolean - Whether this field is
|
||||
editable on the bug creation form. Defaults to 0.
|
||||
|
||||
C<obsolete> - boolean - Whether this field is obsolete. Defaults to 0.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my $field = $class->SUPER::create(@_);
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
if ($field->custom) {
|
||||
my $name = $field->name;
|
||||
my $type = $field->type;
|
||||
if (SQL_DEFINITIONS->{$type}) {
|
||||
# Create the database column that stores the data for this field.
|
||||
$dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type});
|
||||
}
|
||||
|
||||
if ($type == FIELD_TYPE_SINGLE_SELECT
|
||||
|| $type == FIELD_TYPE_MULTI_SELECT)
|
||||
{
|
||||
# Create the table that holds the legal values for this field.
|
||||
$dbh->bz_add_field_tables($field);
|
||||
}
|
||||
|
||||
if ($type == FIELD_TYPE_SINGLE_SELECT) {
|
||||
# Insert a default value of "---" into the legal values table.
|
||||
$dbh->do("INSERT INTO $name (value) VALUES ('---')");
|
||||
}
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
sub run_create_validators {
|
||||
my $class = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $params = $class->SUPER::run_create_validators(@_);
|
||||
|
||||
$params->{name} = $class->_check_name($params->{name}, $params->{custom});
|
||||
if (!exists $params->{sortkey}) {
|
||||
$params->{sortkey} = $dbh->selectrow_array(
|
||||
"SELECT MAX(sortkey) + 100 FROM fielddefs") || 100;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_legal_field_values($field)>
|
||||
|
||||
Description: returns all the legal values for a field that has a
|
||||
list of legal values, like rep_platform or resolution.
|
||||
The table where these values are stored must at least have
|
||||
the following columns: value, isactive, sortkey.
|
||||
|
||||
Params: C<$field> - Name of the table where valid values are.
|
||||
|
||||
Returns: a reference to a list of valid values.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_legal_field_values {
|
||||
my ($field) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $result_ref = $dbh->selectcol_arrayref(
|
||||
"SELECT value FROM $field
|
||||
WHERE isactive = ?
|
||||
ORDER BY sortkey, value", undef, (1));
|
||||
return $result_ref;
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<populate_field_definitions()>
|
||||
|
||||
Description: Populates the fielddefs table during an installation
|
||||
or upgrade.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub populate_field_definitions {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# ADD and UPDATE field definitions
|
||||
foreach my $def (DEFAULT_FIELDS) {
|
||||
my $field = new Bugzilla::Field({ name => $def->{name} });
|
||||
if ($field) {
|
||||
$field->set_description($def->{desc});
|
||||
$field->set_in_new_bugmail($def->{in_new_bugmail});
|
||||
$field->update();
|
||||
}
|
||||
else {
|
||||
if (exists $def->{in_new_bugmail}) {
|
||||
$def->{mailhead} = $def->{in_new_bugmail};
|
||||
delete $def->{in_new_bugmail};
|
||||
}
|
||||
$def->{description} = $def->{desc};
|
||||
delete $def->{desc};
|
||||
Bugzilla::Field->create($def);
|
||||
}
|
||||
}
|
||||
|
||||
# DELETE fields which were added only accidentally, or which
|
||||
# were never tracked in bugs_activity. Note that you can never
|
||||
# delete fields which are used by bugs_activity.
|
||||
|
||||
# Oops. Bug 163299
|
||||
$dbh->do("DELETE FROM fielddefs WHERE name='cc_accessible'");
|
||||
# Oops. Bug 215319
|
||||
$dbh->do("DELETE FROM fielddefs WHERE name='requesters.login_name'");
|
||||
# This field was never tracked in bugs_activity, so it's safe to delete.
|
||||
$dbh->do("DELETE FROM fielddefs WHERE name='attachments.thedata'");
|
||||
|
||||
# MODIFY old field definitions
|
||||
|
||||
# 2005-11-13 LpSolit@gmail.com - Bug 302599
|
||||
# One of the field names was a fragment of SQL code, which is DB dependent.
|
||||
# We have to rename it to a real name, which is DB independent.
|
||||
my $new_field_name = 'days_elapsed';
|
||||
my $field_description = 'Days since bug changed';
|
||||
|
||||
my ($old_field_id, $old_field_name) =
|
||||
$dbh->selectrow_array('SELECT id, name FROM fielddefs
|
||||
WHERE description = ?',
|
||||
undef, $field_description);
|
||||
|
||||
if ($old_field_id && ($old_field_name ne $new_field_name)) {
|
||||
print "SQL fragment found in the 'fielddefs' table...\n";
|
||||
print "Old field name: " . $old_field_name . "\n";
|
||||
# We have to fix saved searches first. Queries have been escaped
|
||||
# before being saved. We have to do the same here to find them.
|
||||
$old_field_name = url_quote($old_field_name);
|
||||
my $broken_named_queries =
|
||||
$dbh->selectall_arrayref('SELECT userid, name, query
|
||||
FROM namedqueries WHERE ' .
|
||||
$dbh->sql_istrcmp('query', '?', 'LIKE'),
|
||||
undef, "%=$old_field_name%");
|
||||
|
||||
my $sth_UpdateQueries = $dbh->prepare('UPDATE namedqueries SET query = ?
|
||||
WHERE userid = ? AND name = ?');
|
||||
|
||||
print "Fixing saved searches...\n" if scalar(@$broken_named_queries);
|
||||
foreach my $named_query (@$broken_named_queries) {
|
||||
my ($userid, $name, $query) = @$named_query;
|
||||
$query =~ s/=\Q$old_field_name\E(&|$)/=$new_field_name$1/gi;
|
||||
$sth_UpdateQueries->execute($query, $userid, $name);
|
||||
}
|
||||
|
||||
# We now do the same with saved chart series.
|
||||
my $broken_series =
|
||||
$dbh->selectall_arrayref('SELECT series_id, query
|
||||
FROM series WHERE ' .
|
||||
$dbh->sql_istrcmp('query', '?', 'LIKE'),
|
||||
undef, "%=$old_field_name%");
|
||||
|
||||
my $sth_UpdateSeries = $dbh->prepare('UPDATE series SET query = ?
|
||||
WHERE series_id = ?');
|
||||
|
||||
print "Fixing saved chart series...\n" if scalar(@$broken_series);
|
||||
foreach my $series (@$broken_series) {
|
||||
my ($series_id, $query) = @$series;
|
||||
$query =~ s/=\Q$old_field_name\E(&|$)/=$new_field_name$1/gi;
|
||||
$sth_UpdateSeries->execute($query, $series_id);
|
||||
}
|
||||
# Now that saved searches have been fixed, we can fix the field name.
|
||||
print "Fixing the 'fielddefs' table...\n";
|
||||
print "New field name: " . $new_field_name . "\n";
|
||||
$dbh->do('UPDATE fielddefs SET name = ? WHERE id = ?',
|
||||
undef, ($new_field_name, $old_field_id));
|
||||
}
|
||||
|
||||
# This field has to be created separately, or the above upgrade code
|
||||
# might not run properly.
|
||||
Bugzilla::Field->create({ name => $new_field_name,
|
||||
description => $field_description })
|
||||
unless new Bugzilla::Field({ name => $new_field_name });
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head2 Data Validation
|
||||
|
||||
=over
|
||||
|
||||
=item C<check_field($name, $value, \@legal_values, $no_warn)>
|
||||
|
||||
Description: Makes sure the field $name is defined and its $value
|
||||
is non empty. If @legal_values is defined, this routine
|
||||
checks whether its value is one of the legal values
|
||||
associated with this field, else it checks against
|
||||
the default valid values for this field obtained by
|
||||
C<get_legal_field_values($name)>. If the test is successful,
|
||||
the function returns 1. If the test fails, an error
|
||||
is thrown (by default), unless $no_warn is true, in which
|
||||
case the function returns 0.
|
||||
|
||||
Params: $name - the field name
|
||||
$value - the field value
|
||||
@legal_values - (optional) list of legal values
|
||||
$no_warn - (optional) do not throw an error if true
|
||||
|
||||
Returns: 1 on success; 0 on failure if $no_warn is true (else an
|
||||
error is thrown).
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub check_field {
|
||||
my ($name, $value, $legalsRef, $no_warn) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# If $legalsRef is undefined, we use the default valid values.
|
||||
unless (defined $legalsRef) {
|
||||
$legalsRef = get_legal_field_values($name);
|
||||
}
|
||||
|
||||
if (!defined($value)
|
||||
|| trim($value) eq ""
|
||||
|| lsearch($legalsRef, $value) < 0)
|
||||
{
|
||||
return 0 if $no_warn; # We don't want an error to be thrown; return.
|
||||
trick_taint($name);
|
||||
|
||||
my $field = new Bugzilla::Field({ name => $name });
|
||||
my $field_desc = $field ? $field->description : $name;
|
||||
ThrowCodeError('illegal_field', { field => $field_desc });
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_field_id($fieldname)>
|
||||
|
||||
Description: Returns the ID of the specified field name and throws
|
||||
an error if this field does not exist.
|
||||
|
||||
Params: $name - a field name
|
||||
|
||||
Returns: the corresponding field ID or an error if the field name
|
||||
does not exist.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_field_id {
|
||||
my ($name) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
trick_taint($name);
|
||||
my $id = $dbh->selectrow_array('SELECT id FROM fielddefs
|
||||
WHERE name = ?', undef, $name);
|
||||
|
||||
ThrowCodeError('invalid_field_name', {field => $name}) unless $id;
|
||||
return $id
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,482 +0,0 @@
|
||||
# -*- 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): Myk Melez <myk@mozilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::FlagType;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::FlagType - A module to deal with Bugzilla flag types.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
FlagType.pm provides an interface to flag types as stored in Bugzilla.
|
||||
See below for more information.
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
=over
|
||||
|
||||
=item *
|
||||
|
||||
Use of private functions/variables outside this module may lead to
|
||||
unexpected results after an upgrade. Please avoid using private
|
||||
functions in other files/modules. Private functions are functions
|
||||
whose names start with _ or are specifically noted as being private.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Group;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
=begin private
|
||||
|
||||
=head1 PRIVATE VARIABLES/CONSTANTS
|
||||
|
||||
=over
|
||||
|
||||
=item C<DB_COLUMNS>
|
||||
|
||||
basic sets of columns and tables for getting flag types from the
|
||||
database.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
flagtypes.id
|
||||
flagtypes.name
|
||||
flagtypes.description
|
||||
flagtypes.cc_list
|
||||
flagtypes.target_type
|
||||
flagtypes.sortkey
|
||||
flagtypes.is_active
|
||||
flagtypes.is_requestable
|
||||
flagtypes.is_requesteeble
|
||||
flagtypes.is_multiplicable
|
||||
flagtypes.grant_group_id
|
||||
flagtypes.request_group_id
|
||||
);
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<DB_TABLE>
|
||||
|
||||
Which database(s) is the data coming from?
|
||||
|
||||
Note: when adding tables to DB_TABLE, make sure to include the separator
|
||||
(i.e. words like "LEFT OUTER JOIN") before the table name, since tables take
|
||||
multiple separators based on the join type, and therefore it is not possible
|
||||
to join them later using a single known separator.
|
||||
|
||||
=back
|
||||
|
||||
=end private
|
||||
|
||||
=cut
|
||||
|
||||
use constant DB_TABLE => 'flagtypes';
|
||||
use constant LIST_ORDER => 'flagtypes.sortkey, flagtypes.name';
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
=head2 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<id>
|
||||
|
||||
Returns the ID of the flagtype.
|
||||
|
||||
=item C<name>
|
||||
|
||||
Returns the name of the flagtype.
|
||||
|
||||
=item C<description>
|
||||
|
||||
Returns the description of the flagtype.
|
||||
|
||||
=item C<cc_list>
|
||||
|
||||
Returns the concatenated CC list for the flagtype, as a single string.
|
||||
|
||||
=item C<target_type>
|
||||
|
||||
Returns whether the flagtype applies to bugs or attachments.
|
||||
|
||||
=item C<is_active>
|
||||
|
||||
Returns whether the flagtype is active or disabled. Flags being
|
||||
in a disabled flagtype are not deleted. It only prevents you from
|
||||
adding new flags to it.
|
||||
|
||||
=item C<is_requestable>
|
||||
|
||||
Returns whether you can request for the given flagtype
|
||||
(i.e. whether the '?' flag is available or not).
|
||||
|
||||
=item C<is_requesteeble>
|
||||
|
||||
Returns whether you can ask someone specifically or not.
|
||||
|
||||
=item C<is_multiplicable>
|
||||
|
||||
Returns whether you can have more than one flag for the given
|
||||
flagtype in a given bug/attachment.
|
||||
|
||||
=item C<sortkey>
|
||||
|
||||
Returns the sortkey of the flagtype.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub id { return $_[0]->{'id'}; }
|
||||
sub name { return $_[0]->{'name'}; }
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub cc_list { return $_[0]->{'cc_list'}; }
|
||||
sub target_type { return $_[0]->{'target_type'} eq 'b' ? 'bug' : 'attachment'; }
|
||||
sub is_active { return $_[0]->{'is_active'}; }
|
||||
sub is_requestable { return $_[0]->{'is_requestable'}; }
|
||||
sub is_requesteeble { return $_[0]->{'is_requesteeble'}; }
|
||||
sub is_multiplicable { return $_[0]->{'is_multiplicable'}; }
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
sub request_group_id { return $_[0]->{'request_group_id'}; }
|
||||
sub grant_group_id { return $_[0]->{'grant_group_id'}; }
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<grant_list>
|
||||
|
||||
Returns a reference to an array of users who have permission to grant this flag type.
|
||||
The arrays are populated with hashrefs containing the login, identity and visibility of users.
|
||||
|
||||
=item C<grant_group>
|
||||
|
||||
Returns the group (as a Bugzilla::Group object) in which a user
|
||||
must be in order to grant or deny a request.
|
||||
|
||||
=item C<request_group>
|
||||
|
||||
Returns the group (as a Bugzilla::Group object) in which a user
|
||||
must be in order to request or clear a flag.
|
||||
|
||||
=item C<flag_count>
|
||||
|
||||
Returns the number of flags belonging to the flagtype.
|
||||
|
||||
=item C<inclusions>
|
||||
|
||||
Return a hash of product/component IDs and names
|
||||
explicitly associated with the flagtype.
|
||||
|
||||
=item C<exclusions>
|
||||
|
||||
Return a hash of product/component IDs and names
|
||||
explicitly excluded from the flagtype.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub grant_list {
|
||||
my $self = shift;
|
||||
my @custusers;
|
||||
my @allusers = @{Bugzilla->user->get_userlist};
|
||||
foreach my $user (@allusers) {
|
||||
my $user_obj = new Bugzilla::User({name => $user->{login}});
|
||||
push(@custusers, $user) if $user_obj->can_set_flag($self);
|
||||
}
|
||||
return \@custusers;
|
||||
}
|
||||
|
||||
sub grant_group {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'grant_group'} && $self->{'grant_group_id'}) {
|
||||
$self->{'grant_group'} = new Bugzilla::Group($self->{'grant_group_id'});
|
||||
}
|
||||
return $self->{'grant_group'};
|
||||
}
|
||||
|
||||
sub request_group {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'request_group'} && $self->{'request_group_id'}) {
|
||||
$self->{'request_group'} = new Bugzilla::Group($self->{'request_group_id'});
|
||||
}
|
||||
return $self->{'request_group'};
|
||||
}
|
||||
|
||||
sub flag_count {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'flag_count'}) {
|
||||
$self->{'flag_count'} =
|
||||
Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM flags
|
||||
WHERE type_id = ?', undef, $self->{'id'});
|
||||
}
|
||||
return $self->{'flag_count'};
|
||||
}
|
||||
|
||||
sub inclusions {
|
||||
my $self = shift;
|
||||
|
||||
$self->{'inclusions'} ||= get_clusions($self->id, 'in');
|
||||
return $self->{'inclusions'};
|
||||
}
|
||||
|
||||
sub exclusions {
|
||||
my $self = shift;
|
||||
|
||||
$self->{'exclusions'} ||= get_clusions($self->id, 'ex');
|
||||
return $self->{'exclusions'};
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Public Functions
|
||||
######################################################################
|
||||
|
||||
=pod
|
||||
|
||||
=head1 PUBLIC FUNCTIONS/METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_clusions($id, $type)>
|
||||
|
||||
Return a hash of product/component IDs and names
|
||||
associated with the flagtype:
|
||||
$clusions{'product_name:component_name'} = "product_ID:component_ID"
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_clusions {
|
||||
my ($id, $type) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $list =
|
||||
$dbh->selectall_arrayref("SELECT products.id, products.name, " .
|
||||
" components.id, components.name " .
|
||||
"FROM flagtypes, flag${type}clusions " .
|
||||
"LEFT OUTER JOIN products " .
|
||||
" ON flag${type}clusions.product_id = products.id " .
|
||||
"LEFT OUTER JOIN components " .
|
||||
" ON flag${type}clusions.component_id = components.id " .
|
||||
"WHERE flagtypes.id = ? " .
|
||||
" AND flag${type}clusions.type_id = flagtypes.id",
|
||||
undef, $id);
|
||||
my %clusions;
|
||||
foreach my $data (@$list) {
|
||||
my ($product_id, $product_name, $component_id, $component_name) = @$data;
|
||||
$product_id ||= 0;
|
||||
$product_name ||= "__Any__";
|
||||
$component_id ||= 0;
|
||||
$component_name ||= "__Any__";
|
||||
$clusions{"$product_name:$component_name"} = "$product_id:$component_id";
|
||||
}
|
||||
return \%clusions;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<match($criteria)>
|
||||
|
||||
Queries the database for flag types matching the given criteria
|
||||
and returns a list of matching flagtype objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub match {
|
||||
my ($criteria) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Depending on the criteria, we may have to append additional tables.
|
||||
my $tables = [DB_TABLE];
|
||||
my @criteria = sqlify_criteria($criteria, $tables);
|
||||
$tables = join(' ', @$tables);
|
||||
$criteria = join(' AND ', @criteria);
|
||||
|
||||
my $flagtype_ids = $dbh->selectcol_arrayref("SELECT id FROM $tables WHERE $criteria");
|
||||
|
||||
return Bugzilla::FlagType->new_from_list($flagtype_ids);
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<count($criteria)>
|
||||
|
||||
Returns the total number of flag types matching the given criteria.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub count {
|
||||
my ($criteria) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Depending on the criteria, we may have to append additional tables.
|
||||
my $tables = [DB_TABLE];
|
||||
my @criteria = sqlify_criteria($criteria, $tables);
|
||||
$tables = join(' ', @$tables);
|
||||
$criteria = join(' AND ', @criteria);
|
||||
|
||||
my $count = $dbh->selectrow_array("SELECT COUNT(flagtypes.id)
|
||||
FROM $tables WHERE $criteria");
|
||||
return $count;
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Private Functions
|
||||
######################################################################
|
||||
|
||||
=begin private
|
||||
|
||||
=head1 PRIVATE FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item C<sqlify_criteria($criteria, $tables)>
|
||||
|
||||
Converts a hash of criteria into a list of SQL criteria.
|
||||
$criteria is a reference to the criteria (field => value),
|
||||
$tables is a reference to an array of tables being accessed
|
||||
by the query.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub sqlify_criteria {
|
||||
my ($criteria, $tables) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# the generated list of SQL criteria; "1=1" is a clever way of making sure
|
||||
# there's something in the list so calling code doesn't have to check list
|
||||
# size before building a WHERE clause out of it
|
||||
my @criteria = ("1=1");
|
||||
|
||||
if ($criteria->{name}) {
|
||||
my $name = $dbh->quote($criteria->{name});
|
||||
trick_taint($name); # Detaint data as we have quoted it.
|
||||
push(@criteria, "flagtypes.name = $name");
|
||||
}
|
||||
if ($criteria->{target_type}) {
|
||||
# The target type is stored in the database as a one-character string
|
||||
# ("a" for attachment and "b" for bug), but this function takes complete
|
||||
# names ("attachment" and "bug") for clarity, so we must convert them.
|
||||
my $target_type = $criteria->{target_type} eq 'bug'? 'b' : 'a';
|
||||
push(@criteria, "flagtypes.target_type = '$target_type'");
|
||||
}
|
||||
if (exists($criteria->{is_active})) {
|
||||
my $is_active = $criteria->{is_active} ? "1" : "0";
|
||||
push(@criteria, "flagtypes.is_active = $is_active");
|
||||
}
|
||||
if ($criteria->{product_id} && $criteria->{'component_id'}) {
|
||||
my $product_id = $criteria->{product_id};
|
||||
my $component_id = $criteria->{component_id};
|
||||
|
||||
# Add inclusions to the query, which simply involves joining the table
|
||||
# by flag type ID and target product/component.
|
||||
push(@$tables, "INNER JOIN flaginclusions AS i ON flagtypes.id = i.type_id");
|
||||
push(@criteria, "(i.product_id = $product_id OR i.product_id IS NULL)");
|
||||
push(@criteria, "(i.component_id = $component_id OR i.component_id IS NULL)");
|
||||
|
||||
# Add exclusions to the query, which is more complicated. First of all,
|
||||
# we do a LEFT JOIN so we don't miss flag types with no exclusions.
|
||||
# Then, as with inclusions, we join on flag type ID and target product/
|
||||
# component. However, since we want flag types that *aren't* on the
|
||||
# exclusions list, we add a WHERE criteria to use only records with
|
||||
# NULL exclusion type, i.e. without any exclusions.
|
||||
my $join_clause = "flagtypes.id = e.type_id " .
|
||||
"AND (e.product_id = $product_id OR e.product_id IS NULL) " .
|
||||
"AND (e.component_id = $component_id OR e.component_id IS NULL)";
|
||||
push(@$tables, "LEFT JOIN flagexclusions AS e ON ($join_clause)");
|
||||
push(@criteria, "e.type_id IS NULL");
|
||||
}
|
||||
if ($criteria->{group}) {
|
||||
my $gid = $criteria->{group};
|
||||
detaint_natural($gid);
|
||||
push(@criteria, "(flagtypes.grant_group_id = $gid " .
|
||||
" OR flagtypes.request_group_id = $gid)");
|
||||
}
|
||||
|
||||
return @criteria;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=end private
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
=over
|
||||
|
||||
=item B<Bugzilla::Flags>
|
||||
|
||||
=back
|
||||
|
||||
=head1 CONTRIBUTORS
|
||||
|
||||
=over
|
||||
|
||||
=item Myk Melez <myk@mozilla.org>
|
||||
|
||||
=item Kevin Benton <kevin.benton@amd.com>
|
||||
|
||||
=item Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,403 +0,0 @@
|
||||
# -*- 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): Joel Peshkin <bugreport@peshkin.net>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Tiago R. Mello <timello@async.com.br>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Group;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Config qw(:admin);
|
||||
|
||||
###############################
|
||||
##### Module Initialization ###
|
||||
###############################
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
groups.id
|
||||
groups.name
|
||||
groups.description
|
||||
groups.isbuggroup
|
||||
groups.userregexp
|
||||
groups.isactive
|
||||
groups.icon_url
|
||||
);
|
||||
|
||||
use constant DB_TABLE => 'groups';
|
||||
|
||||
use constant LIST_ORDER => 'isbuggroup, name';
|
||||
|
||||
use constant VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
description => \&_check_description,
|
||||
userregexp => \&_check_user_regexp,
|
||||
isactive => \&_check_is_active,
|
||||
isbuggroup => \&_check_is_bug_group,
|
||||
icon_url => \&_check_icon_url,
|
||||
};
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(name description isbuggroup);
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
name
|
||||
description
|
||||
userregexp
|
||||
isactive
|
||||
icon_url
|
||||
);
|
||||
|
||||
# Parameters that are lists of groups.
|
||||
use constant GROUP_PARAMS => qw(chartgroup insidergroup timetrackinggroup
|
||||
querysharegroup);
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub is_bug_group { return $_[0]->{'isbuggroup'}; }
|
||||
sub user_regexp { return $_[0]->{'userregexp'}; }
|
||||
sub is_active { return $_[0]->{'isactive'}; }
|
||||
sub icon_url { return $_[0]->{'icon_url'}; }
|
||||
|
||||
sub members_direct {
|
||||
my ($self) = @_;
|
||||
return $self->{members_direct} if defined $self->{members_direct};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $user_ids = $dbh->selectcol_arrayref(
|
||||
"SELECT user_group_map.user_id
|
||||
FROM user_group_map
|
||||
WHERE user_group_map.group_id = ?
|
||||
AND grant_type = " . GRANT_DIRECT . "
|
||||
AND isbless = 0", undef, $self->id);
|
||||
require Bugzilla::User;
|
||||
$self->{members_direct} = Bugzilla::User->new_from_list($user_ids);
|
||||
return $self->{members_direct};
|
||||
}
|
||||
|
||||
sub grant_direct {
|
||||
my ($self, $type) = @_;
|
||||
$self->{grant_direct} ||= {};
|
||||
return $self->{grant_direct}->{$type}
|
||||
if defined $self->{members_direct}->{$type};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $ids = $dbh->selectcol_arrayref(
|
||||
"SELECT member_id FROM group_group_map
|
||||
WHERE grantor_id = ? AND grant_type = $type",
|
||||
undef, $self->id) || [];
|
||||
|
||||
$self->{grant_direct}->{$type} = $self->new_from_list($ids);
|
||||
return $self->{grant_direct}->{$type};
|
||||
}
|
||||
|
||||
sub granted_by_direct {
|
||||
my ($self, $type) = @_;
|
||||
$self->{granted_by_direct} ||= {};
|
||||
return $self->{granted_by_direct}->{$type}
|
||||
if defined $self->{granted_by_direct}->{$type};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $ids = $dbh->selectcol_arrayref(
|
||||
"SELECT grantor_id FROM group_group_map
|
||||
WHERE member_id = ? AND grant_type = $type",
|
||||
undef, $self->id) || [];
|
||||
|
||||
$self->{granted_by_direct}->{$type} = $self->new_from_list($ids);
|
||||
return $self->{granted_by_direct}->{$type};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
sub set_is_active { $_[0]->set('isactive', $_[1]); }
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_user_regexp { $_[0]->set('userregexp', $_[1]); }
|
||||
sub set_icon_url { $_[0]->set('icon_url', $_[1]); }
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my $changes = $self->SUPER::update(@_);
|
||||
|
||||
if (exists $changes->{name}) {
|
||||
my ($old_name, $new_name) = @{$changes->{name}};
|
||||
my $update_params;
|
||||
foreach my $group (GROUP_PARAMS) {
|
||||
if ($old_name eq Bugzilla->params->{$group}) {
|
||||
SetParam($group, $new_name);
|
||||
$update_params = 1;
|
||||
}
|
||||
}
|
||||
write_params() if $update_params;
|
||||
}
|
||||
|
||||
# If we've changed this group to be active, fix any Mandatory groups.
|
||||
$self->_enforce_mandatory if (exists $changes->{isactive}
|
||||
&& $changes->{isactive}->[1]);
|
||||
|
||||
$self->_rederive_regexp() if exists $changes->{userregexp};
|
||||
return $changes;
|
||||
}
|
||||
|
||||
# Add missing entries in bug_group_map for bugs created while
|
||||
# a mandatory group was disabled and which is now enabled again.
|
||||
sub _enforce_mandatory {
|
||||
my ($self) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $gid = $self->id;
|
||||
|
||||
my $bug_ids =
|
||||
$dbh->selectcol_arrayref('SELECT bugs.bug_id
|
||||
FROM bugs
|
||||
INNER JOIN group_control_map
|
||||
ON group_control_map.product_id = bugs.product_id
|
||||
LEFT JOIN bug_group_map
|
||||
ON bug_group_map.bug_id = bugs.bug_id
|
||||
AND bug_group_map.group_id = group_control_map.group_id
|
||||
WHERE group_control_map.group_id = ?
|
||||
AND group_control_map.membercontrol = ?
|
||||
AND bug_group_map.group_id IS NULL',
|
||||
undef, ($gid, CONTROLMAPMANDATORY));
|
||||
|
||||
my $sth = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
|
||||
foreach my $bug_id (@$bug_ids) {
|
||||
$sth->execute($bug_id, $gid);
|
||||
}
|
||||
}
|
||||
|
||||
sub is_active_bug_group {
|
||||
my $self = shift;
|
||||
return $self->is_active && $self->is_bug_group;
|
||||
}
|
||||
|
||||
sub _rederive_regexp {
|
||||
my ($self) = @_;
|
||||
RederiveRegexp($self->user_regexp, $self->id);
|
||||
}
|
||||
|
||||
sub members_non_inherited {
|
||||
my ($self) = @_;
|
||||
return $self->{members_non_inherited}
|
||||
if exists $self->{members_non_inherited};
|
||||
|
||||
my $member_ids = Bugzilla->dbh->selectcol_arrayref(
|
||||
'SELECT DISTINCT user_id FROM user_group_map
|
||||
WHERE isbless = 0 AND group_id = ?',
|
||||
undef, $self->id) || [];
|
||||
require Bugzilla::User;
|
||||
$self->{members_non_inherited} = Bugzilla::User->new_from_list($member_ids);
|
||||
return $self->{members_non_inherited};
|
||||
}
|
||||
|
||||
################################
|
||||
##### Module Subroutines ###
|
||||
################################
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my ($params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
print get_text('install_group_create', { name => $params->{name} }) . "\n"
|
||||
if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
|
||||
|
||||
my $group = $class->SUPER::create(@_);
|
||||
|
||||
# Since we created a new group, give the "admin" group all privileges
|
||||
# initially.
|
||||
my $admin = new Bugzilla::Group({name => 'admin'});
|
||||
# This function is also used to create the "admin" group itself,
|
||||
# so there's a chance it won't exist yet.
|
||||
if ($admin) {
|
||||
my $sth = $dbh->prepare('INSERT INTO group_group_map
|
||||
(member_id, grantor_id, grant_type)
|
||||
VALUES (?, ?, ?)');
|
||||
$sth->execute($admin->id, $group->id, GROUP_MEMBERSHIP);
|
||||
$sth->execute($admin->id, $group->id, GROUP_BLESS);
|
||||
$sth->execute($admin->id, $group->id, GROUP_VISIBLE);
|
||||
}
|
||||
|
||||
$group->_rederive_regexp() if $group->user_regexp;
|
||||
return $group;
|
||||
}
|
||||
|
||||
sub ValidateGroupName {
|
||||
my ($name, @users) = (@_);
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $query = "SELECT id FROM groups " .
|
||||
"WHERE name = ?";
|
||||
if (Bugzilla->params->{'usevisibilitygroups'}) {
|
||||
my @visible = (-1);
|
||||
foreach my $user (@users) {
|
||||
$user && push @visible, @{$user->visible_groups_direct};
|
||||
}
|
||||
my $visible = join(', ', @visible);
|
||||
$query .= " AND id IN($visible)";
|
||||
}
|
||||
my $sth = $dbh->prepare($query);
|
||||
$sth->execute($name);
|
||||
my ($ret) = $sth->fetchrow_array();
|
||||
return $ret;
|
||||
}
|
||||
|
||||
# This sub is not perldoc'ed because we expect it to go away and
|
||||
# just become the _rederive_regexp private method.
|
||||
sub RederiveRegexp {
|
||||
my ($regexp, $gid) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->prepare("SELECT userid, login_name, group_id
|
||||
FROM profiles
|
||||
LEFT JOIN user_group_map
|
||||
ON user_group_map.user_id = profiles.userid
|
||||
AND group_id = ?
|
||||
AND grant_type = ?
|
||||
AND isbless = 0");
|
||||
my $sthadd = $dbh->prepare("INSERT INTO user_group_map
|
||||
(user_id, group_id, grant_type, isbless)
|
||||
VALUES (?, ?, ?, 0)");
|
||||
my $sthdel = $dbh->prepare("DELETE FROM user_group_map
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND grant_type = ? and isbless = 0");
|
||||
$sth->execute($gid, GRANT_REGEXP);
|
||||
while (my ($uid, $login, $present) = $sth->fetchrow_array()) {
|
||||
if (($regexp =~ /\S+/) && ($login =~ m/$regexp/i))
|
||||
{
|
||||
$sthadd->execute($uid, $gid, GRANT_REGEXP) unless $present;
|
||||
} else {
|
||||
$sthdel->execute($uid, $gid, GRANT_REGEXP) if $present;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
###############################
|
||||
### Validators ###
|
||||
###############################
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name) = @_;
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError("empty_group_name");
|
||||
# If we're creating a Group or changing the name...
|
||||
if (!ref($invocant) || $invocant->name ne $name) {
|
||||
my $exists = new Bugzilla::Group({name => $name });
|
||||
ThrowUserError("group_exists", { name => $name }) if $exists;
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_description {
|
||||
my ($invocant, $desc) = @_;
|
||||
$desc = trim($desc);
|
||||
$desc || ThrowUserError("empty_group_description");
|
||||
return $desc;
|
||||
}
|
||||
|
||||
sub _check_user_regexp {
|
||||
my ($invocant, $regex) = @_;
|
||||
$regex = trim($regex) || '';
|
||||
ThrowUserError("invalid_regexp") unless (eval {qr/$regex/});
|
||||
return $regex;
|
||||
}
|
||||
|
||||
sub _check_is_active { return $_[1] ? 1 : 0; }
|
||||
sub _check_is_bug_group {
|
||||
return $_[1] ? 1 : 0;
|
||||
}
|
||||
|
||||
sub _check_icon_url { return $_[1] ? clean_text($_[1]) : undef; }
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Group - Bugzilla group class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Group;
|
||||
|
||||
my $group = new Bugzilla::Group(1);
|
||||
my $group = new Bugzilla::Group({name => 'AcmeGroup'});
|
||||
|
||||
my $id = $group->id;
|
||||
my $name = $group->name;
|
||||
my $description = $group->description;
|
||||
my $user_reg_exp = $group->user_reg_exp;
|
||||
my $is_active = $group->is_active;
|
||||
my $icon_url = $group->icon_url;
|
||||
my $is_active_bug_group = $group->is_active_bug_group;
|
||||
|
||||
my $group_id = Bugzilla::Group::ValidateGroupName('admin', @users);
|
||||
my @groups = Bugzilla::Group->get_all;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Group.pm represents a Bugzilla Group object. It is an implementation
|
||||
of L<Bugzilla::Object>, and thus has all the methods that L<Bugzilla::Object>
|
||||
provides, in addition to any methods documented below.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<create>
|
||||
|
||||
Note that in addition to what L<Bugzilla::Object/create($params)>
|
||||
normally does, this function also makes the new group be inherited
|
||||
by the C<admin> group. That is, the C<admin> group will automatically
|
||||
be a member of this group.
|
||||
|
||||
=item C<ValidateGroupName($name, @users)>
|
||||
|
||||
Description: ValidateGroupName checks to see if ANY of the users
|
||||
in the provided list of user objects can see the
|
||||
named group.
|
||||
|
||||
Params: $name - String with the group name.
|
||||
@users - An array with Bugzilla::User objects.
|
||||
|
||||
Returns: It returns the group id if successful
|
||||
and undef otherwise.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<members_non_inherited>
|
||||
|
||||
Returns an arrayref of L<Bugzilla::User> objects representing people who are
|
||||
"directly" in this group, meaning that they're in it because they match
|
||||
the group regular expression, or they have been actually added to the
|
||||
group manually.
|
||||
|
||||
=back
|
||||
@@ -1,389 +0,0 @@
|
||||
# -*- 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): Zach Lipton <zach@zachlipton.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Hook;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use strict;
|
||||
|
||||
sub process {
|
||||
my ($name, $args) = @_;
|
||||
|
||||
# get a list of all extensions
|
||||
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
|
||||
|
||||
# check each extension to see if it uses the hook
|
||||
# if so, invoke the extension source file:
|
||||
foreach my $extension (@extensions) {
|
||||
# all of these variables come directly from code or directory names.
|
||||
# If there's malicious data here, we have much bigger issues to
|
||||
# worry about, so we can safely detaint them:
|
||||
trick_taint($extension);
|
||||
# Skip CVS directories and any hidden files/dirs.
|
||||
next if $extension =~ m{/CVS$} || $extension =~ m{/\.[^/]+$};
|
||||
next if -e "$extension/disabled";
|
||||
if (-e $extension.'/code/'.$name.'.pl') {
|
||||
Bugzilla->hook_args($args);
|
||||
# Allow extensions to load their own libraries.
|
||||
local @INC = ("$extension/lib", @INC);
|
||||
do($extension.'/code/'.$name.'.pl');
|
||||
ThrowCodeError('extension_invalid',
|
||||
{ errstr => $@, name => $name, extension => $extension }) if $@;
|
||||
# Flush stored data.
|
||||
Bugzilla->hook_args({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub enabled_plugins {
|
||||
my $extdir = bz_locations()->{'extensionsdir'};
|
||||
my @extensions = glob("$extdir/*");
|
||||
my %enabled;
|
||||
foreach my $extension (@extensions) {
|
||||
trick_taint($extension);
|
||||
my $extname = $extension;
|
||||
$extname =~ s{^\Q$extdir\E/}{};
|
||||
next if $extname eq 'CVS' || $extname =~ /^\./;
|
||||
next if -e "$extension/disabled";
|
||||
# Allow extensions to load their own libraries.
|
||||
local @INC = ("$extension/lib", @INC);
|
||||
$enabled{$extname} = do("$extension/info.pl");
|
||||
ThrowCodeError('extension_invalid',
|
||||
{ errstr => $@, name => 'version',
|
||||
extension => $extension }) if $@;
|
||||
|
||||
}
|
||||
|
||||
return \%enabled;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Hook - Extendable extension hooks for Bugzilla code
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Hook;
|
||||
|
||||
Bugzilla::Hook::process("hookname", { arg => $value, arg2 => $value2 });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla allows extension modules to drop in and add routines at
|
||||
arbitrary points in Bugzilla code. These points are referred to as
|
||||
hooks. When a piece of standard Bugzilla code wants to allow an extension
|
||||
to perform additional functions, it uses Bugzilla::Hook's L</process>
|
||||
subroutine to invoke any extension code if installed.
|
||||
|
||||
There is a sample extension in F<extensions/example/> that demonstrates
|
||||
most of the things described in this document, as well as many of the
|
||||
hooks available.
|
||||
|
||||
=head2 How Hooks Work
|
||||
|
||||
When a hook named C<HOOK_NAME> is run, Bugzilla will attempt to invoke any
|
||||
source files named F<extensions/*/code/HOOK_NAME.pl>.
|
||||
|
||||
So, for example, if your extension is called "testopia", and you
|
||||
want to have code run during the L</install-update_db> hook, you
|
||||
would have a file called F<extensions/testopia/code/install-update_db.pl>
|
||||
that contained perl code to run during that hook.
|
||||
|
||||
=head2 Arguments Passed to Hooks
|
||||
|
||||
Some L<hooks|/HOOKS> have params that are passed to them.
|
||||
|
||||
These params are accessible through L<Bugzilla/hook_args>.
|
||||
That returns a hashref. Very frequently, if you want your
|
||||
hook to do anything, you have to modify these variables.
|
||||
|
||||
=head2 Versioning Extensions
|
||||
|
||||
Every extension must have a file in its root called F<info.pl>.
|
||||
This file must return a hash when called with C<do>.
|
||||
The hash must contain a 'version' key with the current version of the
|
||||
extension. Extension authors can also add any extra infomration to this hash if
|
||||
required, by adding a new key beginning with x_ which will not be used the
|
||||
core Bugzilla code.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<process>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Invoke any code hooks with a matching name from any installed extensions.
|
||||
|
||||
See C<customization.xml> in the Bugzilla Guide for more information on
|
||||
Bugzilla's extension mechanism.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$name> - The name of the hook to invoke.
|
||||
|
||||
=item C<$args> - A hashref. The named args to pass to the hook.
|
||||
They will be accessible to the hook via L<Bugzilla/hook_args>.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head1 HOOKS
|
||||
|
||||
This describes what hooks exist in Bugzilla currently. They are mostly
|
||||
in alphabetical order, but some related hooks are near each other instead
|
||||
of being alphabetical.
|
||||
|
||||
=head2 bug-end_of_update
|
||||
|
||||
This happens at the end of L<Bugzilla::Bug/update>, after all other changes are
|
||||
made to the database. This generally occurs inside a database transaction.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<bug> - The changed bug object, with all fields set to their updated
|
||||
values.
|
||||
|
||||
=item C<timestamp> - The timestamp used for all updates in this transaction.
|
||||
|
||||
=item C<changes> - The hash of changed fields.
|
||||
C<$changes-E<gt>{field} = [old, new]>
|
||||
|
||||
=back
|
||||
|
||||
=head2 buglist-columns
|
||||
|
||||
This happens in buglist.cgi after the standard columns have been defined and
|
||||
right before the display column determination. It gives you the opportunity
|
||||
to add additional display columns.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<columns> - A hashref, where the keys are unique string identifiers
|
||||
for the column being defined and the values are hashrefs with the
|
||||
following fields:
|
||||
|
||||
=over
|
||||
|
||||
=item C<name> - The name of the column in the database.
|
||||
|
||||
=item C<title> - The title of the column as displayed to users.
|
||||
|
||||
=back
|
||||
|
||||
The definition is structured as:
|
||||
|
||||
$columns->{$id} = { name => $name, title => $title };
|
||||
|
||||
=back
|
||||
|
||||
=head2 colchange-columns
|
||||
|
||||
This happens in F<colchange.cgi> right after the list of possible display
|
||||
columns have been defined and gives you the opportunity to add additional
|
||||
display columns to the list of selectable columns.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<columns> - An arrayref containing an array of column IDs. Any IDs
|
||||
added by this hook must have been defined in the the buglist-columns hook.
|
||||
See L</buglist-columns>.
|
||||
|
||||
=back
|
||||
|
||||
=head2 enter_bug-entrydefaultvars
|
||||
|
||||
This happens right before the template is loaded on enter_bug.cgi.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<vars> - A hashref. The variables that will be passed into the template.
|
||||
|
||||
=back
|
||||
|
||||
=head2 flag-end_of_update
|
||||
|
||||
This happens at the end of L<Bugzilla::Flag/process>, after all other changes
|
||||
are made to the database and after emails are sent. It gives you a before/after
|
||||
snapshot of flags so you can react to specific flag changes.
|
||||
This generally occurs inside a database transaction.
|
||||
|
||||
Note that the interface to this hook is B<UNSTABLE> and it may change in the
|
||||
future.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<bug> - The changed bug object.
|
||||
|
||||
=item C<timestamp> - The timestamp used for all updates in this transaction.
|
||||
|
||||
=item C<old_flags> - The snapshot of flag summaries from before the change.
|
||||
|
||||
=item C<new_flags> - The snapshot of flag summaries after the change. Call
|
||||
C<my ($removed, $added) = diff_arrays(old_flags, new_flags)> to get the list of
|
||||
changed flags, and search for a specific condition like C<added eq 'review-'>.
|
||||
|
||||
=back
|
||||
|
||||
=head2 install-before_final_checks
|
||||
|
||||
Allows execution of custom code before the final checks are done in
|
||||
checksetup.pl.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<silent>
|
||||
|
||||
A flag that indicates whether or not checksetup is running in silent mode.
|
||||
|
||||
=back
|
||||
|
||||
=head2 install-requirements
|
||||
|
||||
Because of the way Bugzilla installation works, there can't be a normal
|
||||
hook during the time that F<checksetup.pl> checks what modules are
|
||||
installed. (C<Bugzilla::Hook> needs to have those modules installed--it's
|
||||
a chicken-and-egg problem.)
|
||||
|
||||
So instead of the way hooks normally work, this hook just looks for two
|
||||
subroutines (or constants, since all constants are just subroutines) in
|
||||
your file, called C<OPTIONAL_MODULES> and C<REQUIRED_MODULES>,
|
||||
which should return arrayrefs in the same format as C<OPTIONAL_MODULES> and
|
||||
C<REQUIRED_MODULES> in L<Bugzilla::Install::Requirements>.
|
||||
|
||||
These subroutines will be passed an arrayref that contains the current
|
||||
Bugzilla requirements of the same type, in case you want to modify
|
||||
Bugzilla's requirements somehow. (Probably the most common would be to
|
||||
alter a version number or the "feature" element of C<OPTIONAL_MODULES>.)
|
||||
|
||||
F<checksetup.pl> will add these requirements to its own.
|
||||
|
||||
Please remember--if you put something in C<REQUIRED_MODULES>, then
|
||||
F<checksetup.pl> B<cannot complete> unless the user has that module
|
||||
installed! So use C<OPTIONAL_MODULES> whenever you can.
|
||||
|
||||
=head2 install-update_db
|
||||
|
||||
This happens at the very end of all the tables being updated
|
||||
during an installation or upgrade. If you need to modify your custom
|
||||
schema, do it here. No params are passed.
|
||||
|
||||
=head2 db_schema-abstract_schema
|
||||
|
||||
This allows you to add tables to Bugzilla. Note that we recommend that you
|
||||
prefix the names of your tables with some word, so that they don't conflict
|
||||
with any future Bugzilla tables.
|
||||
|
||||
If you wish to add new I<columns> to existing Bugzilla tables, do that
|
||||
in L</install-update_db>.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<schema> - A hashref, in the format of
|
||||
L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>. Add new hash keys to make new table
|
||||
definitions. F<checksetup.pl> will automatically add these tables to the
|
||||
database when run.
|
||||
|
||||
=back
|
||||
|
||||
=head2 webservice
|
||||
|
||||
This hook allows you to add your own modules to the WebService. (See
|
||||
L<Bugzilla::WebService>.)
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<dispatch>
|
||||
|
||||
A hashref that you can specify the names of your modules and what Perl
|
||||
module handles the functions for that module. (This is actually sent to
|
||||
L<SOAP::Lite/dispatch_with>. You can see how that's used in F<xmlrpc.cgi>.)
|
||||
|
||||
The Perl module name must start with C<extensions::yourextension::lib::>
|
||||
(replace C<yourextension> with the name of your extension). The C<package>
|
||||
declaration inside that module must also start with
|
||||
C<extensions::yourextension::lib::> in that module's code.
|
||||
|
||||
Example:
|
||||
|
||||
$dispatch->{Example} = "extensions::example::lib::Example";
|
||||
|
||||
And then you'd have a module F<extensions/example/lib/Example.pm>
|
||||
|
||||
It's recommended that all the keys you put in C<dispatch> start with the
|
||||
name of your extension, so that you don't conflict with the standard Bugzilla
|
||||
WebService functions (and so that you also don't conflict with other
|
||||
plugins).
|
||||
|
||||
=back
|
||||
|
||||
=head2 webservice-error_codes
|
||||
|
||||
If your webservice extension throws custom errors, you can set numeric
|
||||
codes for those errors here.
|
||||
|
||||
Extensions should use error codes above 10000, unless they are re-using
|
||||
an already-existing error code.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<error_map>
|
||||
|
||||
A hash that maps the names of errors (like C<invalid_param>) to numbers.
|
||||
See L<Bugzilla::WebService::Constants/WS_ERROR_CODE> for an example.
|
||||
|
||||
=back
|
||||
@@ -1,452 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Install;
|
||||
|
||||
# Functions in this this package can assume that the database
|
||||
# has been set up, params are available, localconfig is
|
||||
# available, and any module can be used.
|
||||
#
|
||||
# If you want to write an installation function that can't
|
||||
# make those assumptions, then it should go into one of the
|
||||
# packages under the Bugzilla::Install namespace.
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Group;
|
||||
use Bugzilla::Product;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::User::Setting;
|
||||
use Bugzilla::Util qw(get_text);
|
||||
use Bugzilla::Version;
|
||||
|
||||
sub SETTINGS {
|
||||
return {
|
||||
# 2005-03-03 travis@sedsystems.ca -- Bug 41972
|
||||
display_quips => { options => ["on", "off"], default => "on" },
|
||||
# 2005-03-10 travis@sedsystems.ca -- Bug 199048
|
||||
comment_sort_order => { options => ["oldest_to_newest", "newest_to_oldest",
|
||||
"newest_to_oldest_desc_first"],
|
||||
default => "oldest_to_newest" },
|
||||
# 2005-05-12 bugzilla@glob.com.au -- Bug 63536
|
||||
post_bug_submit_action => { options => ["next_bug", "same_bug", "nothing"],
|
||||
default => "next_bug" },
|
||||
# 2005-06-29 wurblzap@gmail.com -- Bug 257767
|
||||
csv_colsepchar => { options => [',',';'], default => ',' },
|
||||
# 2005-10-26 wurblzap@gmail.com -- Bug 291459
|
||||
zoom_textareas => { options => ["on", "off"], default => "on" },
|
||||
# 2005-10-21 LpSolit@gmail.com -- Bug 313020
|
||||
per_bug_queries => { options => ['on', 'off'], default => 'off' },
|
||||
# 2006-05-01 olav@bkor.dhs.org -- Bug 7710
|
||||
state_addselfcc => { options => ['always', 'never', 'cc_unless_role'],
|
||||
default => 'cc_unless_role' },
|
||||
# 2006-08-04 wurblzap@gmail.com -- Bug 322693
|
||||
skin => { subclass => 'Skin', default => 'Dusk' },
|
||||
# 2006-12-10 LpSolit@gmail.com -- Bug 297186
|
||||
lang => { subclass => 'Lang',
|
||||
default => ${Bugzilla->languages}[0] },
|
||||
# 2007-07-02 altlist@gmail.com -- Bug 225731
|
||||
quote_replies => { options => ['quoted_reply', 'simple_reply', 'off'],
|
||||
default => "quoted_reply" }
|
||||
}
|
||||
};
|
||||
|
||||
use constant SYSTEM_GROUPS => (
|
||||
{
|
||||
name => 'admin',
|
||||
description => 'Administrators'
|
||||
},
|
||||
{
|
||||
name => 'tweakparams',
|
||||
description => 'Can change Parameters'
|
||||
},
|
||||
{
|
||||
name => 'editusers',
|
||||
description => 'Can edit or disable users'
|
||||
},
|
||||
{
|
||||
name => 'creategroups',
|
||||
description => 'Can create and destroy groups'
|
||||
},
|
||||
{
|
||||
name => 'editclassifications',
|
||||
description => 'Can create, destroy, and edit classifications'
|
||||
},
|
||||
{
|
||||
name => 'editcomponents',
|
||||
description => 'Can create, destroy, and edit components'
|
||||
},
|
||||
{
|
||||
name => 'editkeywords',
|
||||
description => 'Can create, destroy, and edit keywords'
|
||||
},
|
||||
{
|
||||
name => 'editbugs',
|
||||
description => 'Can edit all bug fields',
|
||||
userregexp => '.*'
|
||||
},
|
||||
{
|
||||
name => 'canconfirm',
|
||||
description => 'Can confirm a bug or mark it a duplicate'
|
||||
},
|
||||
{
|
||||
name => 'bz_canusewhines',
|
||||
description => 'User can configure whine reports for self'
|
||||
},
|
||||
{
|
||||
name => 'bz_sudoers',
|
||||
description => 'Can perform actions as other users'
|
||||
},
|
||||
# There are also other groups created in update_system_groups.
|
||||
);
|
||||
|
||||
use constant DEFAULT_CLASSIFICATION => {
|
||||
name => 'Unclassified',
|
||||
description => 'Not assigned to any classification'
|
||||
};
|
||||
|
||||
use constant DEFAULT_PRODUCT => {
|
||||
name => 'TestProduct',
|
||||
description => 'This is a test product.'
|
||||
. ' This ought to be blown away and replaced with real stuff in a'
|
||||
. ' finished installation of bugzilla.'
|
||||
};
|
||||
|
||||
use constant DEFAULT_COMPONENT => {
|
||||
name => 'TestComponent',
|
||||
description => '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.'
|
||||
};
|
||||
|
||||
sub update_settings {
|
||||
my %settings = %{SETTINGS()};
|
||||
foreach my $setting (keys %settings) {
|
||||
add_setting($setting,
|
||||
$settings{$setting}->{options},
|
||||
$settings{$setting}->{default},
|
||||
$settings{$setting}->{subclass});
|
||||
}
|
||||
}
|
||||
|
||||
sub update_system_groups {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Create most of the system groups
|
||||
foreach my $definition (SYSTEM_GROUPS) {
|
||||
my $exists = new Bugzilla::Group({ name => $definition->{name} });
|
||||
$definition->{isbuggroup} = 0;
|
||||
Bugzilla::Group->create($definition) unless $exists;
|
||||
}
|
||||
|
||||
# Certain groups need something done after they are created. We do
|
||||
# that here.
|
||||
|
||||
# Make sure people who can whine at others can also whine.
|
||||
if (!new Bugzilla::Group({name => 'bz_canusewhineatothers'})) {
|
||||
my $whineatothers = Bugzilla::Group->create({
|
||||
name => 'bz_canusewhineatothers',
|
||||
description => 'Can configure whine reports for other users',
|
||||
isbuggroup => 0 });
|
||||
my $whine = new Bugzilla::Group({ name => 'bz_canusewhines' });
|
||||
|
||||
$dbh->do('INSERT INTO group_group_map (grantor_id, member_id)
|
||||
VALUES (?,?)', undef, $whine->id, $whineatothers->id);
|
||||
}
|
||||
|
||||
# Make sure sudoers are automatically protected from being sudoed.
|
||||
if (!new Bugzilla::Group({name => 'bz_sudo_protect'})) {
|
||||
my $sudo_protect = Bugzilla::Group->create({
|
||||
name => 'bz_sudo_protect',
|
||||
description => 'Can not be impersonated by other users',
|
||||
isbuggroup => 0 });
|
||||
my $sudo = new Bugzilla::Group({ name => 'bz_sudoers' });
|
||||
$dbh->do('INSERT INTO group_group_map (grantor_id, member_id)
|
||||
VALUES (?,?)', undef, $sudo_protect->id, $sudo->id);
|
||||
}
|
||||
|
||||
# Re-evaluate all regexps, to keep them up-to-date.
|
||||
my $sth = $dbh->prepare(
|
||||
"SELECT profiles.userid, profiles.login_name, groups.id,
|
||||
groups.userregexp, user_group_map.group_id
|
||||
FROM (profiles CROSS JOIN groups)
|
||||
LEFT JOIN user_group_map
|
||||
ON user_group_map.user_id = profiles.userid
|
||||
AND user_group_map.group_id = groups.id
|
||||
AND user_group_map.grant_type = ?
|
||||
WHERE userregexp != '' OR user_group_map.group_id IS NOT NULL");
|
||||
|
||||
my $sth_add = $dbh->prepare(
|
||||
"INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
|
||||
VALUES (?, ?, 0, " . GRANT_REGEXP . ")");
|
||||
|
||||
my $sth_del = $dbh->prepare(
|
||||
"DELETE FROM user_group_map
|
||||
WHERE user_id = ? AND group_id = ? AND isbless = 0
|
||||
AND grant_type = " . GRANT_REGEXP);
|
||||
|
||||
$sth->execute(GRANT_REGEXP);
|
||||
while (my ($uid, $login, $gid, $rexp, $present) = $sth->fetchrow_array()) {
|
||||
if ($login =~ m/$rexp/i) {
|
||||
$sth_add->execute($uid, $gid) unless $present;
|
||||
} else {
|
||||
$sth_del->execute($uid, $gid) if $present;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# This function should be called only after creating the admin user.
|
||||
sub create_default_product {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Make the default Classification if it doesn't already exist.
|
||||
if (!$dbh->selectrow_array('SELECT 1 FROM classifications')) {
|
||||
my $class = DEFAULT_CLASSIFICATION;
|
||||
print get_text('install_default_classification',
|
||||
{ name => $class->{name} }) . "\n";
|
||||
$dbh->do('INSERT INTO classifications (name, description)
|
||||
VALUES (?, ?)',
|
||||
undef, $class->{name}, $class->{description});
|
||||
}
|
||||
|
||||
# And same for the default product/component.
|
||||
if (!$dbh->selectrow_array('SELECT 1 FROM products')) {
|
||||
my $default_prod = DEFAULT_PRODUCT;
|
||||
print get_text('install_default_product',
|
||||
{ name => $default_prod->{name} }) . "\n";
|
||||
|
||||
$dbh->do(q{INSERT INTO products (name, description)
|
||||
VALUES (?,?)},
|
||||
undef, $default_prod->{name}, $default_prod->{description});
|
||||
|
||||
my $product = new Bugzilla::Product({name => $default_prod->{name}});
|
||||
|
||||
# The default version.
|
||||
Bugzilla::Version::create(Bugzilla::Version::DEFAULT_VERSION, $product);
|
||||
|
||||
# And we automatically insert the default milestone.
|
||||
$dbh->do(q{INSERT INTO milestones (product_id, value, sortkey)
|
||||
SELECT id, defaultmilestone, 0
|
||||
FROM products});
|
||||
|
||||
# Get the user who will be the owner of the Product.
|
||||
# We pick the admin with the lowest id, or we insert
|
||||
# an invalid "0" into the database, just so that we can
|
||||
# create the component.
|
||||
my $admin_group = new Bugzilla::Group({name => 'admin'});
|
||||
my ($admin_id) = $dbh->selectrow_array(
|
||||
'SELECT user_id FROM user_group_map WHERE group_id = ?
|
||||
ORDER BY user_id ' . $dbh->sql_limit(1),
|
||||
undef, $admin_group->id) || 0;
|
||||
|
||||
my $default_comp = DEFAULT_COMPONENT;
|
||||
|
||||
$dbh->do("INSERT INTO components (name, product_id, description,
|
||||
initialowner)
|
||||
VALUES (?, ?, ?, ?)", undef, $default_comp->{name},
|
||||
$product->id, $default_comp->{description}, $admin_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub create_admin {
|
||||
my ($params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
my $admin_group = new Bugzilla::Group({ name => 'admin' });
|
||||
my $admin_inheritors =
|
||||
Bugzilla::User->flatten_group_membership($admin_group->id);
|
||||
my $admin_group_ids = join(',', @$admin_inheritors);
|
||||
|
||||
my ($admin_count) = $dbh->selectrow_array(
|
||||
"SELECT COUNT(*) FROM user_group_map
|
||||
WHERE group_id IN ($admin_group_ids)");
|
||||
|
||||
return if $admin_count;
|
||||
|
||||
my %answer = %{Bugzilla->installation_answers};
|
||||
my $login = $answer{'ADMIN_EMAIL'};
|
||||
my $password = $answer{'ADMIN_PASSWORD'};
|
||||
my $full_name = $answer{'ADMIN_REALNAME'};
|
||||
|
||||
if (!$login || !$password || !$full_name) {
|
||||
print "\n" . get_text('install_admin_setup') . "\n\n";
|
||||
}
|
||||
|
||||
while (!$login) {
|
||||
print get_text('install_admin_get_email') . ' ';
|
||||
$login = <STDIN>;
|
||||
chomp $login;
|
||||
eval { Bugzilla::User->check_login_name_for_creation($login); };
|
||||
if ($@) {
|
||||
print $@ . "\n";
|
||||
undef $login;
|
||||
}
|
||||
}
|
||||
|
||||
while (!defined $full_name) {
|
||||
print get_text('install_admin_get_name') . ' ';
|
||||
$full_name = <STDIN>;
|
||||
chomp($full_name);
|
||||
}
|
||||
|
||||
if (!$password) {
|
||||
$password = _prompt_for_password(
|
||||
get_text('install_admin_get_password'));
|
||||
}
|
||||
|
||||
my $admin = Bugzilla::User->create({ login_name => $login,
|
||||
realname => $full_name,
|
||||
cryptpassword => $password });
|
||||
make_admin($admin);
|
||||
}
|
||||
|
||||
sub make_admin {
|
||||
my ($user) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$user = ref($user) ? $user
|
||||
: new Bugzilla::User(login_to_id($user, THROW_ERROR));
|
||||
|
||||
my $admin_group = new Bugzilla::Group({ name => 'admin' });
|
||||
|
||||
# Admins get explicit membership and bless capability for the admin group
|
||||
$dbh->selectrow_array("SELECT id FROM groups WHERE name = 'admin'");
|
||||
|
||||
my $group_insert = $dbh->prepare(
|
||||
'INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
|
||||
VALUES (?, ?, ?, ?)');
|
||||
# These are run in an eval so that we can ignore the error of somebody
|
||||
# already being granted these things.
|
||||
eval {
|
||||
$group_insert->execute($user->id, $admin_group->id, 0, GRANT_DIRECT);
|
||||
};
|
||||
eval {
|
||||
$group_insert->execute($user->id, $admin_group->id, 1, GRANT_DIRECT);
|
||||
};
|
||||
|
||||
# Admins should also have editusers directly, even though they'll usually
|
||||
# inherit it. People could have changed their inheritance structure.
|
||||
my $editusers = new Bugzilla::Group({ name => 'editusers' });
|
||||
eval {
|
||||
$group_insert->execute($user->id, $editusers->id, 0, GRANT_DIRECT);
|
||||
};
|
||||
|
||||
print "\n", get_text('install_admin_created', { user => $user }), "\n";
|
||||
}
|
||||
|
||||
sub _prompt_for_password {
|
||||
my $prompt = shift;
|
||||
|
||||
my $password;
|
||||
while (!$password) {
|
||||
# trap a few interrupts so we can fix the echo if we get aborted.
|
||||
local $SIG{HUP} = \&_password_prompt_exit;
|
||||
local $SIG{INT} = \&_password_prompt_exit;
|
||||
local $SIG{QUIT} = \&_password_prompt_exit;
|
||||
local $SIG{TERM} = \&_password_prompt_exit;
|
||||
|
||||
system("stty","-echo") unless ON_WINDOWS; # disable input echoing
|
||||
|
||||
print $prompt, ' ';
|
||||
$password = <STDIN>;
|
||||
chomp $password;
|
||||
print "\n", get_text('install_confirm_password'), ' ';
|
||||
my $pass2 = <STDIN>;
|
||||
chomp $pass2;
|
||||
eval { validate_password($password, $pass2); };
|
||||
if ($@) {
|
||||
print "\n$@\n";
|
||||
undef $password;
|
||||
}
|
||||
system("stty","echo") unless ON_WINDOWS;
|
||||
}
|
||||
return $password;
|
||||
}
|
||||
|
||||
# This is just in case we get interrupted while getting a password.
|
||||
sub _password_prompt_exit {
|
||||
# re-enable input echoing
|
||||
system("stty","echo") unless ON_WINDOWS;
|
||||
exit 1;
|
||||
}
|
||||
|
||||
sub reset_password {
|
||||
my $login = shift;
|
||||
my $user = Bugzilla::User->check($login);
|
||||
my $prompt = "\n" . get_text('install_reset_password', { user => $user });
|
||||
my $password = _prompt_for_password($prompt);
|
||||
$user->set_password($password);
|
||||
$user->update();
|
||||
print "\n", get_text('install_reset_password_done'), "\n";
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install - Functions and variables having to do with
|
||||
installation.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Install;
|
||||
Bugzilla::Install::update_settings();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by L<checksetup.pl> during installation.
|
||||
This module contains functions that deal with general installation
|
||||
issues after the database is completely set up and configured.
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
=over
|
||||
|
||||
=item C<SETTINGS>
|
||||
|
||||
Contains information about Settings, used by L</update_settings()>.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<update_settings()>
|
||||
|
||||
Description: Adds and updates Settings for users.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing.
|
||||
|
||||
=item C<create_default_product()>
|
||||
|
||||
Description: Creates the default product and classification if
|
||||
they don't exist.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,251 +0,0 @@
|
||||
# -*- 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 Everything Solved, Inc.
|
||||
# Portions created by Everything Solved are Copyright (C) 2007
|
||||
# Everything Solved, Inc. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Install::CPAN;
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
our @EXPORT = qw(set_cpan_config install_module BZ_LIB);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Util qw(bin_loc install_string);
|
||||
|
||||
use CPAN;
|
||||
use Cwd qw(abs_path);
|
||||
use File::Path qw(rmtree);
|
||||
use List::Util qw(shuffle);
|
||||
|
||||
# We need the absolute path of ext_libpath, because CPAN chdirs around
|
||||
# and so we can't use a relative directory.
|
||||
#
|
||||
# We need it often enough (and at compile time, in install-module.pl) so
|
||||
# we make it a constant.
|
||||
use constant BZ_LIB => abs_path(bz_locations()->{ext_libpath});
|
||||
|
||||
# CPAN requires nearly all of its parameters to be set, or it will start
|
||||
# asking questions to the user. We want to avoid that, so we have
|
||||
# defaults here for most of the required parameters we know about, in case
|
||||
# any of them aren't set. The rest are handled by set_cpan_defaults().
|
||||
use constant CPAN_DEFAULTS => {
|
||||
auto_commit => 0,
|
||||
# We always force builds, so there's no reason to cache them.
|
||||
build_cache => 0,
|
||||
cache_metadata => 1,
|
||||
index_expire => 1,
|
||||
scan_cache => 'atstart',
|
||||
|
||||
inhibit_startup_message => 1,
|
||||
mbuild_install_build_command => './Build',
|
||||
|
||||
curl => bin_loc('curl'),
|
||||
gzip => bin_loc('gzip'),
|
||||
links => bin_loc('links'),
|
||||
lynx => bin_loc('lynx'),
|
||||
make => bin_loc('make'),
|
||||
pager => bin_loc('less'),
|
||||
tar => bin_loc('tar'),
|
||||
unzip => bin_loc('unzip'),
|
||||
wget => bin_loc('wget'),
|
||||
|
||||
urllist => [shuffle qw(
|
||||
http://cpan.pair.com/
|
||||
http://mirror.hiwaay.net/CPAN/
|
||||
ftp://ftp.dc.aleron.net/pub/CPAN/
|
||||
http://perl.secsup.org/
|
||||
http://mirrors.kernel.org/cpan/)],
|
||||
};
|
||||
|
||||
sub install_module {
|
||||
my ($name, $notest) = @_;
|
||||
my $bzlib = BZ_LIB;
|
||||
|
||||
# Certain modules require special stuff in order to not prompt us.
|
||||
my $original_makepl = $CPAN::Config->{makepl_arg};
|
||||
# This one's a regex in case we're doing Template::Plugin::GD and it
|
||||
# pulls in Template-Toolkit as a dependency.
|
||||
if ($name =~ /^Template/) {
|
||||
$CPAN::Config->{makepl_arg} .= " TT_ACCEPT=y TT_EXTRAS=n";
|
||||
}
|
||||
elsif ($name eq 'XML::Twig') {
|
||||
$CPAN::Config->{makepl_arg} = "-n $original_makepl";
|
||||
}
|
||||
elsif ($name eq 'Net::LDAP') {
|
||||
$CPAN::Config->{makepl_arg} .= " --skipdeps";
|
||||
}
|
||||
elsif ($name eq 'SOAP::Lite') {
|
||||
$CPAN::Config->{makepl_arg} .= " --noprompt";
|
||||
}
|
||||
|
||||
my $module = CPAN::Shell->expand('Module', $name);
|
||||
print install_string('install_module',
|
||||
{ module => $name, version => $module->cpan_version }) . "\n";
|
||||
if ($notest) {
|
||||
CPAN::Shell->notest('install', $name);
|
||||
}
|
||||
else {
|
||||
CPAN::Shell->force('install', $name);
|
||||
}
|
||||
|
||||
# If it installed any binaries in the Bugzilla directory, delete them.
|
||||
if (-d "$bzlib/bin") {
|
||||
File::Path::rmtree("$bzlib/bin");
|
||||
}
|
||||
|
||||
$CPAN::Config->{makepl_arg} = $original_makepl;
|
||||
}
|
||||
|
||||
sub set_cpan_config {
|
||||
my $do_global = shift;
|
||||
my $bzlib = BZ_LIB;
|
||||
|
||||
# We set defaults before we do anything, otherwise CPAN will
|
||||
# start asking us questions as soon as we load its configuration.
|
||||
eval { require CPAN::Config; };
|
||||
_set_cpan_defaults();
|
||||
|
||||
# Calling a senseless autoload that does nothing makes us
|
||||
# automatically load any existing configuration.
|
||||
# We want to avoid the "invalid command" message.
|
||||
open(my $saveout, ">&STDOUT");
|
||||
open(STDOUT, '>/dev/null');
|
||||
eval { CPAN->ignore_this_error_message_from_bugzilla; };
|
||||
undef $@;
|
||||
close(STDOUT);
|
||||
open(STDOUT, '>&', $saveout);
|
||||
|
||||
my $dir = $CPAN::Config->{cpan_home};
|
||||
if (!defined $dir || !-w $dir) {
|
||||
# If we can't use the standard CPAN build dir, we try to make one.
|
||||
$dir = "$ENV{HOME}/.cpan";
|
||||
mkdir $dir;
|
||||
|
||||
# If we can't make one, we finally try to use the Bugzilla directory.
|
||||
if (!-w $dir) {
|
||||
print "WARNING: Using the Bugzilla directory as the CPAN home.\n";
|
||||
$dir = "$bzlib/.cpan";
|
||||
}
|
||||
}
|
||||
$CPAN::Config->{cpan_home} = $dir;
|
||||
$CPAN::Config->{build_dir} = "$dir/build";
|
||||
# We always force builds, so there's no reason to cache them.
|
||||
$CPAN::Config->{keep_source_where} = "$dir/source";
|
||||
# This is set both here and in defaults so that it's always true.
|
||||
$CPAN::Config->{inhibit_startup_message} = 1;
|
||||
# Automatically install dependencies.
|
||||
$CPAN::Config->{prerequisites_policy} = 'follow';
|
||||
|
||||
# Unless specified, we install the modules into the Bugzilla directory.
|
||||
if (!$do_global) {
|
||||
$CPAN::Config->{makepl_arg} .= " LIB=\"$bzlib\""
|
||||
. " INSTALLMAN1DIR=\"$bzlib/man/man1\""
|
||||
. " INSTALLMAN3DIR=\"$bzlib/man/man3\""
|
||||
# The bindirs are here because otherwise we'll try to write to
|
||||
# the system binary dirs, and that will cause CPAN to die.
|
||||
. " INSTALLBIN=\"$bzlib/bin\""
|
||||
. " INSTALLSCRIPT=\"$bzlib/bin\""
|
||||
# INSTALLDIRS=perl is set because that makes sure that MakeMaker
|
||||
# always uses the directories we've specified here.
|
||||
. " INSTALLDIRS=perl";
|
||||
$CPAN::Config->{mbuild_arg} = "--install_base \"$bzlib\"";
|
||||
|
||||
# When we're not root, sometimes newer versions of CPAN will
|
||||
# try to read/modify things that belong to root, unless we set
|
||||
# certain config variables.
|
||||
$CPAN::Config->{histfile} = "$dir/histfile";
|
||||
$CPAN::Config->{use_sqlite} = 0;
|
||||
$CPAN::Config->{prefs_dir} = "$dir/prefs";
|
||||
|
||||
# Unless we actually set PERL5LIB, some modules can't install
|
||||
# themselves, like DBD::mysql, DBD::Pg, and XML::Twig.
|
||||
my $current_lib = $ENV{PERL5LIB} ? $ENV{PERL5LIB} . ':' : '';
|
||||
$ENV{PERL5LIB} = $current_lib . $bzlib;
|
||||
}
|
||||
}
|
||||
|
||||
sub _set_cpan_defaults {
|
||||
# If CPAN hasn't been configured, we try to use some reasonable defaults.
|
||||
foreach my $key (keys %{CPAN_DEFAULTS()}) {
|
||||
$CPAN::Config->{$key} = CPAN_DEFAULTS->{$key}
|
||||
if !defined $CPAN::Config->{$key};
|
||||
}
|
||||
|
||||
my @missing;
|
||||
# In newer CPANs, this is in HandleConfig. In older CPANs, it's in
|
||||
# Config.
|
||||
if (eval { require CPAN::HandleConfig }) {
|
||||
@missing = CPAN::HandleConfig->missing_config_data;
|
||||
}
|
||||
else {
|
||||
@missing = CPAN::Config->missing_config_data;
|
||||
}
|
||||
|
||||
foreach my $key (@missing) {
|
||||
$CPAN::Config->{$key} = '';
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::CPAN - Routines to install Perl modules from CPAN.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Install::CPAN;
|
||||
|
||||
set_cpan_config();
|
||||
install_module('Module::Name', 1);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is primarily used by L<install-module> to do the "hard work" of
|
||||
installing CPAN modules.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<set_cpan_config>
|
||||
|
||||
Sets up the configuration of CPAN for this session. Must be called
|
||||
before L</install_module>. Takes one boolean parameter. If true,
|
||||
L</install_module> will install modules globally instead of to the
|
||||
local F<lib/> directory. On most systems, you have to be root to do that.
|
||||
|
||||
=item C<install_module>
|
||||
|
||||
Installs a module from CPAN. Takes two arguments:
|
||||
|
||||
=over
|
||||
|
||||
=item C<$name> - The name of the module, just like you'd pass to the
|
||||
C<install> command in the CPAN shell.
|
||||
|
||||
=item C<$notest> - If true, we skip running tests on this module. This
|
||||
can greatly speed up the installation time.
|
||||
|
||||
=back
|
||||
|
||||
Note that calling this function prints a B<lot> of information to
|
||||
STDOUT and STDERR.
|
||||
|
||||
=back
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,690 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Bill Barry <after.fallout@gmail.com>
|
||||
|
||||
package Bugzilla::Install::Filesystem;
|
||||
|
||||
# NOTE: This package may "use" any modules that it likes,
|
||||
# and localconfig is available. However, all functions in this
|
||||
# package should assume that:
|
||||
#
|
||||
# * Templates are not available.
|
||||
# * Files do not have the correct permissions.
|
||||
# * The database does not exist.
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Install::Localconfig;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use File::Find;
|
||||
use File::Path;
|
||||
use File::Basename;
|
||||
use IO::File;
|
||||
use POSIX ();
|
||||
|
||||
use base qw(Exporter);
|
||||
our @EXPORT = qw(
|
||||
update_filesystem
|
||||
create_htaccess
|
||||
fix_all_file_permissions
|
||||
);
|
||||
|
||||
# This looks like a constant because it effectively is, but
|
||||
# it has to call other subroutines and read the current filesystem,
|
||||
# so it's defined as a sub. This is not exported, so it doesn't have
|
||||
# a perldoc. However, look at the various hashes defined inside this
|
||||
# function to understand what it returns. (There are comments throughout.)
|
||||
#
|
||||
# The rationale for the file permissions is that the web server generally
|
||||
# runs as apache, so the cgi scripts should not be writable for apache,
|
||||
# otherwise someone may find it possible to change the cgis when exploiting
|
||||
# some security flaw somewhere (not necessarily in Bugzilla!)
|
||||
sub FILESYSTEM {
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
my $attachdir = bz_locations()->{'attachdir'};
|
||||
my $extensionsdir = bz_locations()->{'extensionsdir'};
|
||||
my $webdotdir = bz_locations()->{'webdotdir'};
|
||||
my $templatedir = bz_locations()->{'templatedir'};
|
||||
my $libdir = bz_locations()->{'libpath'};
|
||||
my $extlib = bz_locations()->{'ext_libpath'};
|
||||
my $skinsdir = bz_locations()->{'skinsdir'};
|
||||
|
||||
my $ws_group = Bugzilla->localconfig->{'webservergroup'};
|
||||
|
||||
# The set of permissions that we use:
|
||||
|
||||
# FILES
|
||||
# Executable by the web server
|
||||
my $ws_executable = $ws_group ? 0750 : 0755;
|
||||
# Executable by the owner only.
|
||||
my $owner_executable = 0700;
|
||||
# Readable by the web server.
|
||||
my $ws_readable = $ws_group ? 0640 : 0644;
|
||||
# Readable by the owner only.
|
||||
my $owner_readable = 0600;
|
||||
# Writeable by the web server.
|
||||
my $ws_writeable = $ws_group ? 0660 : 0666;
|
||||
|
||||
# DIRECTORIES
|
||||
# Readable by the web server.
|
||||
my $ws_dir_readable = $ws_group ? 0750 : 0755;
|
||||
# Readable only by the owner.
|
||||
my $owner_dir_readable = 0700;
|
||||
# Writeable by the web server.
|
||||
my $ws_dir_writeable = $ws_group ? 0770 : 01777;
|
||||
# The web server can overwrite files owned by other users,
|
||||
# in this directory.
|
||||
my $ws_dir_full_control = $ws_group ? 0770 : 0777;
|
||||
|
||||
# Note: When being processed by checksetup, these have their permissions
|
||||
# set in this order: %all_dirs, %recurse_dirs, %all_files.
|
||||
#
|
||||
# Each is processed in alphabetical order of keys, so shorter keys
|
||||
# will have their permissions set before longer keys (thus setting
|
||||
# the permissions on parent directories before setting permissions
|
||||
# on their children).
|
||||
|
||||
# --- FILE PERMISSIONS (Non-created files) --- #
|
||||
my %files = (
|
||||
'*' => { perms => $ws_readable },
|
||||
'*.cgi' => { perms => $ws_executable },
|
||||
'whineatnews.pl' => { perms => $ws_executable },
|
||||
'collectstats.pl' => { perms => $ws_executable },
|
||||
'checksetup.pl' => { perms => $owner_executable },
|
||||
'importxml.pl' => { perms => $ws_executable },
|
||||
'runtests.pl' => { perms => $owner_executable },
|
||||
'testserver.pl' => { perms => $ws_executable },
|
||||
'whine.pl' => { perms => $ws_executable },
|
||||
'customfield.pl' => { perms => $owner_executable },
|
||||
'email_in.pl' => { perms => $ws_executable },
|
||||
'sanitycheck.pl' => { perms => $ws_executable },
|
||||
'install-module.pl' => { perms => $owner_executable },
|
||||
|
||||
'docs/makedocs.pl' => { perms => $owner_executable },
|
||||
'docs/style.css' => { perms => $ws_readable },
|
||||
'docs/*/rel_notes.txt' => { perms => $ws_readable },
|
||||
'docs/*/README.docs' => { perms => $owner_readable },
|
||||
"$datadir/bugzilla-update.xml" => { perms => $ws_writeable },
|
||||
"$datadir/params" => { perms => $ws_writeable },
|
||||
"$datadir/mailer.testfile" => { perms => $ws_writeable },
|
||||
);
|
||||
|
||||
# Directories that we want to set the perms on, but not
|
||||
# recurse through. These are directories we didn't create
|
||||
# in checkesetup.pl.
|
||||
my %non_recurse_dirs = (
|
||||
'.' => $ws_dir_readable,
|
||||
docs => $ws_dir_readable,
|
||||
);
|
||||
|
||||
# This sets the permissions for each item inside each of these
|
||||
# directories, including the directory itself.
|
||||
# 'CVS' directories are special, though, and are never readable by
|
||||
# the webserver.
|
||||
my %recurse_dirs = (
|
||||
# Writeable directories
|
||||
"$datadir/template" => { files => $ws_readable,
|
||||
dirs => $ws_dir_full_control },
|
||||
$attachdir => { files => $ws_writeable,
|
||||
dirs => $ws_dir_writeable },
|
||||
$webdotdir => { files => $ws_writeable,
|
||||
dirs => $ws_dir_writeable },
|
||||
graphs => { files => $ws_writeable,
|
||||
dirs => $ws_dir_writeable },
|
||||
|
||||
# Readable directories
|
||||
"$datadir/mining" => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
"$datadir/duplicates" => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
"$libdir/Bugzilla" => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
$extlib => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
$templatedir => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
$extensionsdir => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
images => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
css => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
js => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
skins => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
t => { files => $owner_readable,
|
||||
dirs => $owner_dir_readable },
|
||||
'docs/*/html' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/*/pdf' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/*/txt' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/*/images' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/lib' => { files => $owner_readable,
|
||||
dirs => $owner_dir_readable },
|
||||
'docs/*/xml' => { files => $owner_readable,
|
||||
dirs => $owner_dir_readable },
|
||||
);
|
||||
|
||||
# --- FILES TO CREATE --- #
|
||||
|
||||
# The name of each directory that we should actually *create*,
|
||||
# pointing at its default permissions.
|
||||
my %create_dirs = (
|
||||
$datadir => $ws_dir_full_control,
|
||||
"$datadir/mining" => $ws_dir_readable,
|
||||
"$datadir/duplicates" => $ws_dir_readable,
|
||||
$attachdir => $ws_dir_writeable,
|
||||
$extensionsdir => $ws_dir_readable,
|
||||
graphs => $ws_dir_writeable,
|
||||
$webdotdir => $ws_dir_writeable,
|
||||
'skins/custom' => $ws_dir_readable,
|
||||
'skins/contrib' => $ws_dir_readable,
|
||||
);
|
||||
|
||||
# The name of each file, pointing at its default permissions and
|
||||
# default contents.
|
||||
my %create_files = ();
|
||||
|
||||
# Each standard stylesheet has an associated custom stylesheet that
|
||||
# we create. Also, we create placeholders for standard stylesheets
|
||||
# for contrib skins which don't provide them themselves.
|
||||
foreach my $skin_dir ("$skinsdir/custom", <$skinsdir/contrib/*>) {
|
||||
next unless -d $skin_dir;
|
||||
next if basename($skin_dir) =~ /^cvs$/i;
|
||||
$create_dirs{"$skin_dir/yui"} = $ws_dir_readable;
|
||||
foreach my $base_css (<$skinsdir/standard/*.css>) {
|
||||
_add_custom_css($skin_dir, basename($base_css), \%create_files, $ws_readable);
|
||||
}
|
||||
foreach my $dir_css (<$skinsdir/standard/*/*.css>) {
|
||||
$dir_css =~ s{.+?([^/]+/[^/]+)$}{$1};
|
||||
_add_custom_css($skin_dir, $dir_css, \%create_files, $ws_readable);
|
||||
}
|
||||
}
|
||||
|
||||
# Because checksetup controls the creation of index.html separately
|
||||
# from all other files, it gets its very own hash.
|
||||
my %index_html = (
|
||||
'index.html' => { perms => $ws_readable, contents => <<EOT
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Refresh" content="0; URL=index.cgi">
|
||||
</head>
|
||||
<body>
|
||||
<h1>I think you are looking for <a href="index.cgi">index.cgi</a></h1>
|
||||
</body>
|
||||
</html>
|
||||
EOT
|
||||
}
|
||||
);
|
||||
|
||||
# Because checksetup controls the .htaccess creation separately
|
||||
# by a localconfig variable, these go in a separate variable from
|
||||
# %create_files.
|
||||
my $ht_default_deny = <<EOT;
|
||||
# nothing in this directory is retrievable unless overridden by an .htaccess
|
||||
# in a subdirectory
|
||||
deny from all
|
||||
EOT
|
||||
|
||||
my %htaccess = (
|
||||
"$attachdir/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
"$libdir/Bugzilla/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
"$extlib/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
"$templatedir/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
|
||||
'.htaccess' => { perms => $ws_readable, contents => <<EOT
|
||||
# Don't allow people to retrieve non-cgi executable files or our private data
|
||||
<FilesMatch ^(.*\\.pm|.*\\.pl|.*localconfig.*)\$>
|
||||
deny from all
|
||||
</FilesMatch>
|
||||
EOT
|
||||
},
|
||||
|
||||
"$webdotdir/.htaccess" => { perms => $ws_readable, contents => <<EOT
|
||||
# Restrict access to .dot files to the public webdot server at research.att.com
|
||||
# if research.att.com ever changes their IP, or if you use a different
|
||||
# webdot server, you'll need to edit this
|
||||
<FilesMatch \\.dot\$>
|
||||
Allow from 192.20.225.0/24
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
# Allow access to .png files created by a local copy of 'dot'
|
||||
<FilesMatch \\.png\$>
|
||||
Allow from all
|
||||
</FilesMatch>
|
||||
|
||||
# And no directory listings, either.
|
||||
Deny from all
|
||||
EOT
|
||||
},
|
||||
|
||||
# Even though $datadir may not (and should not) be accessible from the
|
||||
# web server, we can't know for sure, so create the .htaccess anyway.
|
||||
# It's harmless if it isn't accessible...
|
||||
"$datadir/.htaccess" => { perms => $ws_readable, contents => <<EOT
|
||||
# Nothing in this directory is retrievable unless overridden by an .htaccess
|
||||
# in a subdirectory; the only exception is duplicates.rdf, which is used by
|
||||
# duplicates.xul and must be accessible from the web server
|
||||
deny from all
|
||||
<Files duplicates.rdf>
|
||||
allow from all
|
||||
</Files>
|
||||
EOT
|
||||
|
||||
|
||||
},
|
||||
);
|
||||
|
||||
my %all_files = (%create_files, %htaccess, %index_html, %files);
|
||||
my %all_dirs = (%create_dirs, %non_recurse_dirs);
|
||||
|
||||
return {
|
||||
create_dirs => \%create_dirs,
|
||||
recurse_dirs => \%recurse_dirs,
|
||||
all_dirs => \%all_dirs,
|
||||
|
||||
create_files => \%create_files,
|
||||
htaccess => \%htaccess,
|
||||
index_html => \%index_html,
|
||||
all_files => \%all_files,
|
||||
};
|
||||
}
|
||||
|
||||
sub update_filesystem {
|
||||
my ($params) = @_;
|
||||
my $fs = FILESYSTEM();
|
||||
my %dirs = %{$fs->{create_dirs}};
|
||||
my %files = %{$fs->{create_files}};
|
||||
|
||||
my $datadir = bz_locations->{'datadir'};
|
||||
# If the graphs/ directory doesn't exist, we're upgrading from
|
||||
# a version old enough that we need to update the $datadir/mining
|
||||
# format.
|
||||
if (-d "$datadir/mining" && !-d 'graphs') {
|
||||
_update_old_charts($datadir);
|
||||
}
|
||||
|
||||
# By sorting the dirs, we assure that shorter-named directories
|
||||
# (meaning parent directories) are always created before their
|
||||
# child directories.
|
||||
foreach my $dir (sort keys %dirs) {
|
||||
unless (-d $dir) {
|
||||
print "Creating $dir directory...\n";
|
||||
mkdir $dir || die $!;
|
||||
# For some reason, passing in the permissions to "mkdir"
|
||||
# doesn't work right, but doing a "chmod" does.
|
||||
chmod $dirs{$dir}, $dir || die $!;
|
||||
}
|
||||
}
|
||||
|
||||
_create_files(%files);
|
||||
if ($params->{index_html}) {
|
||||
_create_files(%{$fs->{index_html}});
|
||||
}
|
||||
elsif (-e 'index.html') {
|
||||
my $templatedir = bz_locations()->{'templatedir'};
|
||||
print <<EOT;
|
||||
|
||||
*** It appears that you still have an old index.html hanging around.
|
||||
Either the contents of this file should be moved into a template and
|
||||
placed in the '$templatedir/en/custom' directory, or you should delete
|
||||
the file.
|
||||
|
||||
EOT
|
||||
}
|
||||
|
||||
# Delete old files that no longer need to exist
|
||||
|
||||
# 2001-04-29 jake@bugzilla.org - Remove oldemailtech
|
||||
# http://bugzilla.mozilla.org/show_bugs.cgi?id=71552
|
||||
if (-d 'shadow') {
|
||||
print "Removing shadow directory...\n";
|
||||
rmtree("shadow");
|
||||
}
|
||||
|
||||
if (-e "$datadir/versioncache") {
|
||||
print "Removing versioncache...\n";
|
||||
unlink "$datadir/versioncache";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# A simple helper for creating "empty" CSS files.
|
||||
sub _add_custom_css {
|
||||
my ($skin_dir, $path, $create_files, $perms) = @_;
|
||||
$create_files->{"$skin_dir/$path"} = { perms => $perms, contents => <<EOT
|
||||
/*
|
||||
* Custom rules for $path.
|
||||
* The rules you put here override rules in that stylesheet.
|
||||
*/
|
||||
EOT
|
||||
};
|
||||
}
|
||||
|
||||
sub create_htaccess {
|
||||
_create_files(%{FILESYSTEM()->{htaccess}});
|
||||
|
||||
# Repair old .htaccess files
|
||||
my $htaccess = new IO::File('.htaccess', 'r') || die ".htaccess: $!";
|
||||
my $old_data;
|
||||
{ local $/; $old_data = <$htaccess>; }
|
||||
$htaccess->close;
|
||||
|
||||
my $repaired = 0;
|
||||
if ($old_data =~ s/\|localconfig\|/\|.*localconfig.*\|/) {
|
||||
$repaired = 1;
|
||||
}
|
||||
if ($old_data !~ /\(\.\*\\\.pm\|/) {
|
||||
$old_data =~ s/\(/(.*\\.pm\|/;
|
||||
$repaired = 1;
|
||||
}
|
||||
if ($repaired) {
|
||||
print "Repairing .htaccess...\n";
|
||||
$htaccess = new IO::File('.htaccess', 'w') || die $!;
|
||||
print $htaccess $old_data;
|
||||
$htaccess->close;
|
||||
}
|
||||
|
||||
|
||||
my $webdot_dir = bz_locations()->{'webdotdir'};
|
||||
# The public webdot IP address changed.
|
||||
my $webdot = new IO::File("$webdot_dir/.htaccess", 'r')
|
||||
|| die "$webdot_dir/.htaccess: $!";
|
||||
my $webdot_data;
|
||||
{ local $/; $webdot_data = <$webdot>; }
|
||||
$webdot->close;
|
||||
if ($webdot_data =~ /192\.20\.225\.10/) {
|
||||
print "Repairing $webdot_dir/.htaccess...\n";
|
||||
$webdot_data =~ s/192\.20\.225\.10/192.20.225.0\/24/g;
|
||||
$webdot = new IO::File("$webdot_dir/.htaccess", 'w') || die $!;
|
||||
print $webdot $webdot_data;
|
||||
$webdot->close;
|
||||
}
|
||||
}
|
||||
|
||||
# A helper for the above functions.
|
||||
sub _create_files {
|
||||
my (%files) = @_;
|
||||
|
||||
# It's not necessary to sort these, but it does make the
|
||||
# output of checksetup.pl look a bit nicer.
|
||||
foreach my $file (sort keys %files) {
|
||||
unless (-e $file) {
|
||||
print "Creating $file...\n";
|
||||
my $info = $files{$file};
|
||||
my $fh = new IO::File($file, O_WRONLY | O_CREAT, $info->{perms})
|
||||
|| die $!;
|
||||
print $fh $info->{contents} if $info->{contents};
|
||||
$fh->close;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# If you ran a REALLY old version of Bugzilla, your chart files are in the
|
||||
# wrong format. This code is a little messy, because it's very old, and
|
||||
# when moving it into this module, I couldn't test it so I left it almost
|
||||
# completely alone.
|
||||
sub _update_old_charts {
|
||||
my ($datadir) = @_;
|
||||
print "Updating old chart storage format...\n";
|
||||
foreach my $in_file (glob("$datadir/mining/*")) {
|
||||
# Don't try and upgrade image or db files!
|
||||
next if (($in_file =~ /\.gif$/i) ||
|
||||
($in_file =~ /\.png$/i) ||
|
||||
($in_file =~ /\.db$/i) ||
|
||||
($in_file =~ /\.orig$/i));
|
||||
|
||||
rename("$in_file", "$in_file.orig") or next;
|
||||
open(IN, "$in_file.orig") or next;
|
||||
open(OUT, '>', $in_file) or next;
|
||||
|
||||
# Fields in the header
|
||||
my @declared_fields;
|
||||
|
||||
# Fields we changed to half way through by mistake
|
||||
# This list comes from an old version of collectstats.pl
|
||||
# This part is only for people who ran later versions of 2.11 (devel)
|
||||
my @intermediate_fields = qw(DATE UNCONFIRMED NEW ASSIGNED REOPENED
|
||||
RESOLVED VERIFIED CLOSED);
|
||||
|
||||
# Fields we actually want (matches the current collectstats.pl)
|
||||
my @out_fields = qw(DATE NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED
|
||||
VERIFIED CLOSED FIXED INVALID WONTFIX LATER REMIND
|
||||
DUPLICATE WORKSFORME MOVED);
|
||||
|
||||
while (<IN>) {
|
||||
if (/^# fields?: (.*)\s$/) {
|
||||
@declared_fields = map uc, (split /\||\r/, $1);
|
||||
print OUT "# fields: ", join('|', @out_fields), "\n";
|
||||
}
|
||||
elsif (/^(\d+\|.*)/) {
|
||||
my @data = split(/\||\r/, $1);
|
||||
my %data;
|
||||
if (@data == @declared_fields) {
|
||||
# old format
|
||||
for my $i (0 .. $#declared_fields) {
|
||||
$data{$declared_fields[$i]} = $data[$i];
|
||||
}
|
||||
}
|
||||
elsif (@data == @intermediate_fields) {
|
||||
# Must have changed over at this point
|
||||
for my $i (0 .. $#intermediate_fields) {
|
||||
$data{$intermediate_fields[$i]} = $data[$i];
|
||||
}
|
||||
}
|
||||
elsif (@data == @out_fields) {
|
||||
# This line's fine - it has the right number of entries
|
||||
for my $i (0 .. $#out_fields) {
|
||||
$data{$out_fields[$i]} = $data[$i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
print "Oh dear, input line $. of $in_file had " .
|
||||
scalar(@data) . " fields\nThis was unexpected.",
|
||||
" You may want to check your data files.\n";
|
||||
}
|
||||
|
||||
print OUT join('|',
|
||||
map { defined ($data{$_}) ? ($data{$_}) : "" } @out_fields),
|
||||
"\n";
|
||||
}
|
||||
else {
|
||||
print OUT;
|
||||
}
|
||||
}
|
||||
|
||||
close(IN);
|
||||
close(OUT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub fix_all_file_permissions {
|
||||
my ($output) = @_;
|
||||
|
||||
my $ws_group = Bugzilla->localconfig->{'webservergroup'};
|
||||
my $group_id = _check_web_server_group($ws_group, $output);
|
||||
|
||||
return if ON_WINDOWS;
|
||||
|
||||
my $fs = FILESYSTEM();
|
||||
my %files = %{$fs->{all_files}};
|
||||
my %dirs = %{$fs->{all_dirs}};
|
||||
my %recurse_dirs = %{$fs->{recurse_dirs}};
|
||||
|
||||
print get_text('install_file_perms_fix') . "\n" if $output;
|
||||
|
||||
my $owner_id = POSIX::getuid();
|
||||
$group_id = POSIX::getgid() unless defined $group_id;
|
||||
|
||||
foreach my $dir (sort keys %dirs) {
|
||||
next unless -d $dir;
|
||||
_fix_perms($dir, $owner_id, $group_id, $dirs{$dir});
|
||||
}
|
||||
|
||||
foreach my $dir (sort keys %recurse_dirs) {
|
||||
next unless -d $dir;
|
||||
# Set permissions on the directory itself.
|
||||
my $perms = $recurse_dirs{$dir};
|
||||
_fix_perms($dir, $owner_id, $group_id, $perms->{dirs});
|
||||
# Now recurse through the directory and set the correct permissions
|
||||
# on subdirectories and files.
|
||||
find({ no_chdir => 1, wanted => sub {
|
||||
my $name = $File::Find::name;
|
||||
if (-d $name) {
|
||||
_fix_perms($name, $owner_id, $group_id, $perms->{dirs});
|
||||
}
|
||||
else {
|
||||
_fix_perms($name, $owner_id, $group_id, $perms->{files});
|
||||
}
|
||||
}}, $dir);
|
||||
}
|
||||
|
||||
foreach my $file (sort keys %files) {
|
||||
# %files supports globs
|
||||
foreach my $filename (glob $file) {
|
||||
# Don't touch directories.
|
||||
next if -d $filename || !-e $filename;
|
||||
_fix_perms($filename, $owner_id, $group_id,
|
||||
$files{$file}->{perms});
|
||||
}
|
||||
}
|
||||
|
||||
_fix_cvs_dirs($owner_id, '.');
|
||||
}
|
||||
|
||||
# A helper for fix_all_file_permissions
|
||||
sub _fix_cvs_dirs {
|
||||
my ($owner_id, $dir) = @_;
|
||||
my $owner_gid = POSIX::getgid();
|
||||
find({ no_chdir => 1, wanted => sub {
|
||||
my $name = $File::Find::name;
|
||||
if ($File::Find::dir =~ /\/CVS/ || $_ eq '.cvsignore'
|
||||
|| (-d $name && $_ eq 'CVS')) {
|
||||
_fix_perms($name, $owner_id, $owner_gid, 0700);
|
||||
}
|
||||
}}, $dir);
|
||||
}
|
||||
|
||||
sub _fix_perms {
|
||||
my ($name, $owner, $group, $perms) = @_;
|
||||
#printf ("Changing $name to %o\n", $perms);
|
||||
chown $owner, $group, $name
|
||||
|| warn "Failed to change ownership of $name: $!";
|
||||
chmod $perms, $name
|
||||
|| warn "Failed to change permissions of $name: $!";
|
||||
}
|
||||
|
||||
sub _check_web_server_group {
|
||||
my ($group, $output) = @_;
|
||||
|
||||
my $filename = bz_locations()->{'localconfig'};
|
||||
my $group_id;
|
||||
|
||||
# If we are on Windows, webservergroup does nothing
|
||||
if (ON_WINDOWS && $group && $output) {
|
||||
print "\n\n" . get_text('install_webservergroup_windows') . "\n\n";
|
||||
}
|
||||
|
||||
# If we're not on Windows, make sure that webservergroup isn't
|
||||
# empty.
|
||||
elsif (!ON_WINDOWS && !$group && $output) {
|
||||
print "\n\n" . get_text('install_webservergroup_empty') . "\n\n";
|
||||
}
|
||||
|
||||
# If we're not on Windows, make sure we are actually a member of
|
||||
# the webservergroup.
|
||||
elsif (!ON_WINDOWS && $group) {
|
||||
$group_id = getgrnam($group);
|
||||
ThrowCodeError('invalid_webservergroup', { group => $group })
|
||||
unless defined $group_id;
|
||||
|
||||
# If on unix, see if we need to print a warning about a webservergroup
|
||||
# that we can't chgrp to
|
||||
if ($output && $< != 0 && !grep($_ eq $group_id, split(" ", $)))) {
|
||||
print "\n\n" . get_text('install_webservergroup_not_in') . "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $group_id;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::Filesystem - Fix up the filesystem during
|
||||
installation.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by L<checksetup.pl> to modify the
|
||||
filesystem during installation, including creating the data/ directory.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<update_filesystem({ index_html => 0 })>
|
||||
|
||||
Description: Creates all the directories and files that Bugzilla
|
||||
needs to function but doesn't ship with. Also does
|
||||
any updates to these files as necessary during an
|
||||
upgrade.
|
||||
|
||||
Params: C<index_html> - Whether or not we should create
|
||||
the F<index.html> file.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=item C<create_htaccess()>
|
||||
|
||||
Description: Creates all of the .htaccess files for Apache,
|
||||
in the various Bugzilla directories. Also updates
|
||||
the .htaccess files if they need updating.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=item C<fix_all_file_permissions($output)>
|
||||
|
||||
Description: Sets all the file permissions on all of Bugzilla's files
|
||||
to what they should be. Note that permissions are different
|
||||
depending on whether or not C<$webservergroup> is set
|
||||
in F<localconfig>.
|
||||
|
||||
Params: C<$output> - C<true> if you want this function to print
|
||||
out information about what it's doing.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,440 +0,0 @@
|
||||
# -*- 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 Initial Developer of the Original Code is Everything Solved.
|
||||
# Portions created by Everything Solved are Copyright (C) 2006
|
||||
# Everything Solved. All Rights Reserved.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Install::Localconfig;
|
||||
|
||||
# NOTE: This package may "use" any modules that it likes. However,
|
||||
# all functions in this package should assume that:
|
||||
#
|
||||
# * The data/ directory does not exist.
|
||||
# * Templates are not available.
|
||||
# * Files do not have the correct permissions
|
||||
# * The database is not up to date
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Util qw(bin_loc);
|
||||
|
||||
use Data::Dumper;
|
||||
use File::Basename qw(dirname);
|
||||
use IO::File;
|
||||
use Safe;
|
||||
|
||||
use base qw(Exporter);
|
||||
|
||||
our @EXPORT_OK = qw(
|
||||
read_localconfig
|
||||
update_localconfig
|
||||
);
|
||||
|
||||
use constant LOCALCONFIG_VARS => (
|
||||
{
|
||||
name => 'create_htaccess',
|
||||
default => 1,
|
||||
desc => <<EOT
|
||||
# If you are using Apache as your web server, Bugzilla can create .htaccess
|
||||
# files for you that will instruct Apache not to serve files that shouldn't
|
||||
# be accessed from the web browser (like your local configuration data and non-cgi
|
||||
# executable files). For this to work, the directory your Bugzilla
|
||||
# installation is in must be within the jurisdiction of a <Directory> block
|
||||
# in the httpd.conf file that has 'AllowOverride Limit' in it. If it has
|
||||
# 'AllowOverride All' or other options with Limit, that's fine.
|
||||
# (Older Apache installations may use an access.conf file to store these
|
||||
# <Directory> blocks.)
|
||||
# If this is set to 1, Bugzilla will create these files if they don't exist.
|
||||
# If this is set to 0, Bugzilla will not create these files.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'webservergroup',
|
||||
default => ON_WINDOWS ? '' : 'apache',
|
||||
desc => q{# This is the group your web server runs as.
|
||||
# If you have a Windows box, ignore this setting.
|
||||
# If you do not have access to the group your web server runs under,
|
||||
# set this to "". If you do set this to "", then your Bugzilla installation
|
||||
# will be _VERY_ insecure, because some files will be world readable/writable,
|
||||
# and so anyone who can get local access to your machine can do whatever they
|
||||
# want. You should only have this set to "" if this is a testing installation
|
||||
# and you cannot set this up any other way. YOU HAVE BEEN WARNED!
|
||||
# If you set this to anything other than "", you will need to run checksetup.pl
|
||||
# as} . ROOT_USER . qq{, or as a user who is a member of the specified group.\n}
|
||||
},
|
||||
{
|
||||
name => 'db_driver',
|
||||
default => 'mysql',
|
||||
desc => <<EOT
|
||||
# What SQL database to use. Default is mysql. List of supported databases
|
||||
# can be obtained by listing Bugzilla/DB directory - every module corresponds
|
||||
# to one supported database and the name corresponds to a driver name.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_host',
|
||||
default => 'localhost',
|
||||
desc =>
|
||||
"# The DNS name of the host that the database server runs on.\n"
|
||||
},
|
||||
{
|
||||
name => 'db_name',
|
||||
default => 'bugs',
|
||||
desc => "# The name of the database\n"
|
||||
},
|
||||
{
|
||||
name => 'db_user',
|
||||
default => 'bugs',
|
||||
desc => "# Who we connect to the database as.\n"
|
||||
},
|
||||
{
|
||||
name => 'db_pass',
|
||||
default => '',
|
||||
desc => <<EOT
|
||||
# Enter your database password here. It's normally advisable to specify
|
||||
# a password for your bugzilla database user.
|
||||
# If you use apostrophe (') or a backslash (\\) in your password, you'll
|
||||
# need to escape it by preceding it with a '\\' character. (\\') or (\\)
|
||||
# (Far simpler just not to use those characters.)
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_port',
|
||||
default => 0,
|
||||
desc => <<EOT
|
||||
# Sometimes the database server is running on a non-standard port. If that's
|
||||
# the case for your database server, set this to the port number that your
|
||||
# database server is running on. Setting this to 0 means "use the default
|
||||
# port for my database server."
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_sock',
|
||||
default => '',
|
||||
desc => <<EOT
|
||||
# MySQL Only: Enter a path to the unix socket for MySQL. If this is
|
||||
# blank, then MySQL's compiled-in default will be used. You probably
|
||||
# want that.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_check',
|
||||
default => 1,
|
||||
desc => <<EOT
|
||||
# Should checksetup.pl try to verify that your database setup is correct?
|
||||
# (with some combinations of database servers/Perl modules/moonphase this
|
||||
# doesn't work)
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'index_html',
|
||||
default => 0,
|
||||
desc => <<EOT
|
||||
# With the introduction of a configurable index page using the
|
||||
# template toolkit, Bugzilla's main index page is now index.cgi.
|
||||
# Most web servers will allow you to use index.cgi as a directory
|
||||
# index, and many come preconfigured that way, but if yours doesn't
|
||||
# then you'll need an index.html file that provides redirection
|
||||
# to index.cgi. Setting \$index_html to 1 below will allow
|
||||
# checksetup.pl to create one for you if it doesn't exist.
|
||||
# NOTE: checksetup.pl will not replace an existing file, so if you
|
||||
# wish to have checksetup.pl create one for you, you must
|
||||
# make sure that index.html doesn't already exist
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'cvsbin',
|
||||
default => \&_get_default_cvsbin,
|
||||
desc => <<EOT
|
||||
# For some optional functions of Bugzilla (such as the pretty-print patch
|
||||
# viewer), we need the cvs binary to access files and revisions.
|
||||
# Because it's possible that this program is not in your path, you can specify
|
||||
# its location here. Please specify the full path to the executable.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'interdiffbin',
|
||||
default => \&_get_default_interdiffbin,
|
||||
desc => <<EOT
|
||||
# For some optional functions of Bugzilla (such as the pretty-print patch
|
||||
# viewer), we need the interdiff binary to make diffs between two patches.
|
||||
# Because it's possible that this program is not in your path, you can specify
|
||||
# its location here. Please specify the full path to the executable.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'diffpath',
|
||||
default => \&_get_default_diffpath,
|
||||
desc => <<EOT
|
||||
# The interdiff feature needs diff, so we have to have that path.
|
||||
# Please specify the directory name only; do not use trailing slash.
|
||||
EOT
|
||||
},
|
||||
);
|
||||
|
||||
use constant OLD_LOCALCONFIG_VARS => qw(
|
||||
mysqlpath
|
||||
contenttypes
|
||||
pages
|
||||
severities platforms opsys priorities
|
||||
);
|
||||
|
||||
sub read_localconfig {
|
||||
my ($include_deprecated) = @_;
|
||||
my $filename = bz_locations()->{'localconfig'};
|
||||
|
||||
my %localconfig;
|
||||
if (-e $filename) {
|
||||
my $s = new Safe;
|
||||
# Some people like to store their database password in another file.
|
||||
$s->permit('dofile');
|
||||
|
||||
$s->rdo($filename);
|
||||
if ($@ || $!) {
|
||||
my $err_msg = $@ ? $@ : $!;
|
||||
die <<EOT;
|
||||
An error has occurred while reading your 'localconfig' file. The text of
|
||||
the error message is:
|
||||
|
||||
$err_msg
|
||||
|
||||
Please fix the error in your 'localconfig' file. Alternately, rename your
|
||||
'localconfig' file, rerun checksetup.pl, and re-enter your answers.
|
||||
|
||||
\$ mv -f localconfig localconfig.old
|
||||
\$ ./checksetup.pl
|
||||
EOT
|
||||
}
|
||||
|
||||
my @vars = map($_->{name}, LOCALCONFIG_VARS);
|
||||
push(@vars, OLD_LOCALCONFIG_VARS) if $include_deprecated;
|
||||
foreach my $var (@vars) {
|
||||
my $glob = $s->varglob($var);
|
||||
# We can't get the type of a variable out of a Safe automatically.
|
||||
# We can only get the glob itself. So we figure out its type this
|
||||
# way, by trying first a scalar, then an array, then a hash.
|
||||
#
|
||||
# The interesting thing is that this converts all deprecated
|
||||
# array or hash vars into hashrefs or arrayrefs, but that's
|
||||
# fine since as I write this all modern localconfig vars are
|
||||
# actually scalars.
|
||||
if (defined $$glob) {
|
||||
$localconfig{$var} = $$glob;
|
||||
}
|
||||
elsif (defined @$glob) {
|
||||
$localconfig{$var} = \@$glob;
|
||||
}
|
||||
elsif (defined %$glob) {
|
||||
$localconfig{$var} = \%$glob;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \%localconfig;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# This is quite tricky. But fun!
|
||||
#
|
||||
# First we read the file 'localconfig'. Then we check if the variables we
|
||||
# need are defined. If not, we will append the new settings to
|
||||
# localconfig, instruct the user to check them, and stop.
|
||||
#
|
||||
# Why do it this way?
|
||||
#
|
||||
# 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. You
|
||||
# know, we never want to overwrite your own version of 'localconfig', so
|
||||
# we can't put it into the CVS/tarfile, can we?
|
||||
#
|
||||
# Now, when we need a new variable, we simply add the necessary stuff to
|
||||
# LOCALCONFIG_VARS. When the user gets the new version of Bugzilla from CVS and
|
||||
# runs checksetup, it 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 it is what the user wants.
|
||||
#
|
||||
# Cute, ey?
|
||||
#
|
||||
sub update_localconfig {
|
||||
my ($params) = @_;
|
||||
|
||||
my $output = $params->{output} || 0;
|
||||
my $answer = Bugzilla->installation_answers;
|
||||
my $localconfig = read_localconfig('include deprecated');
|
||||
|
||||
my @new_vars;
|
||||
foreach my $var (LOCALCONFIG_VARS) {
|
||||
my $name = $var->{name};
|
||||
if (!defined $localconfig->{$name}) {
|
||||
push(@new_vars, $name);
|
||||
$var->{default} = &{$var->{default}} if ref($var->{default}) eq 'CODE';
|
||||
$localconfig->{$name} = $answer->{$name} || $var->{default};
|
||||
}
|
||||
}
|
||||
|
||||
my @old_vars;
|
||||
foreach my $name (OLD_LOCALCONFIG_VARS) {
|
||||
push(@old_vars, $name) if defined $localconfig->{$name};
|
||||
}
|
||||
|
||||
if (!$localconfig->{'interdiffbin'} && $output) {
|
||||
print <<EOT
|
||||
|
||||
OPTIONAL NOTE: If you want to be able to use the 'difference between two
|
||||
patches' feature of Bugzilla (which requires the PatchReader Perl module
|
||||
as well), you should install patchutils from:
|
||||
|
||||
http://cyberelk.net/tim/patchutils/
|
||||
|
||||
EOT
|
||||
}
|
||||
|
||||
my $filename = bz_locations->{'localconfig'};
|
||||
|
||||
if (scalar @old_vars) {
|
||||
my $oldstuff = join(', ', @old_vars);
|
||||
print <<EOT
|
||||
|
||||
The following variables are no longer used in $filename, and
|
||||
should be removed: $oldstuff
|
||||
|
||||
EOT
|
||||
}
|
||||
|
||||
if (scalar @new_vars) {
|
||||
my $filename = bz_locations->{'localconfig'};
|
||||
my $fh = new IO::File($filename, '>>') || die "$filename: $!";
|
||||
$fh->seek(0, SEEK_END);
|
||||
foreach my $var (LOCALCONFIG_VARS) {
|
||||
if (grep($_ eq $var->{name}, @new_vars)) {
|
||||
print $fh "\n", $var->{desc},
|
||||
Data::Dumper->Dump([$localconfig->{$var->{name}}],
|
||||
["*$var->{name}"]);
|
||||
}
|
||||
}
|
||||
|
||||
my $newstuff = join(', ', @new_vars);
|
||||
print <<EOT;
|
||||
|
||||
This version of Bugzilla contains some variables that you may want to
|
||||
change and adapt to your local settings. Please edit the file
|
||||
$filename and rerun checksetup.pl.
|
||||
|
||||
The following variables are new to $filename since you last ran
|
||||
checksetup.pl: $newstuff
|
||||
|
||||
EOT
|
||||
exit;
|
||||
}
|
||||
|
||||
# Reset the cache for Bugzilla->localconfig so that it will be re-read
|
||||
delete Bugzilla->request_cache->{localconfig};
|
||||
|
||||
return { old_vars => \@old_vars, new_vars => \@new_vars };
|
||||
}
|
||||
|
||||
sub _get_default_cvsbin { return bin_loc('cvs') }
|
||||
sub _get_default_interdiffbin { return bin_loc('interdiff') }
|
||||
sub _get_default_diffpath {
|
||||
my $diff_bin = bin_loc('diff');
|
||||
return dirname($diff_bin);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::Localconfig - Functions and variables dealing
|
||||
with the manipulation and creation of the F<localconfig> file.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Install::Requirements qw(update_localconfig);
|
||||
update_localconfig({ output => 1 });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by L<checksetup.pl> to create and
|
||||
modify the localconfig file. Most scripts should use L<Bugzilla/localconfig>
|
||||
to access localconfig variables.
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
=over
|
||||
|
||||
=item C<LOCALCONFIG_VARS>
|
||||
|
||||
An array of hashrefs. These hashrefs contain three keys:
|
||||
|
||||
name - The name of the variable.
|
||||
default - The default value for the variable. Should always be
|
||||
something that can fit in a scalar.
|
||||
desc - Additional text to put in localconfig before the variable
|
||||
definition. Must end in a newline. Each line should start
|
||||
with "#" unless you have some REALLY good reason not
|
||||
to do that.
|
||||
|
||||
=item C<OLD_LOCALCONFIG_VARS>
|
||||
|
||||
An array of names of variables. If C<update_localconfig> finds these
|
||||
variables defined in localconfig, it will print out a warning.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<read_localconfig($include_deprecated)>
|
||||
|
||||
Description: Reads the localconfig file and returns all valid
|
||||
values in a hashref.
|
||||
|
||||
Params: C<$include_deprecated> - C<true> if you want the returned
|
||||
hashref to also include variables listed in
|
||||
C<OLD_LOCALCONFIG_VARS>, if they exist. Generally
|
||||
this is only for use by C<update_localconfig>.
|
||||
|
||||
Returns: A hashref of the localconfig variables. If an array
|
||||
is defined, it will be an arrayref in the returned hash. If a
|
||||
hash is defined, it will be a hashref in the returned hash.
|
||||
Only includes variables specified in C<LOCALCONFIG_VARS>
|
||||
(and C<OLD_LOCALCONFIG_VARS> if C<$include_deprecated> is
|
||||
specified).
|
||||
|
||||
=item C<update_localconfig({ output =E<gt> 1 })>
|
||||
|
||||
Description: Adds any new variables to localconfig that aren't
|
||||
currently defined there. Also optionally prints out
|
||||
a message about vars that *should* be there and aren't.
|
||||
Exits the program if it adds any new vars.
|
||||
|
||||
Params: C<output> - C<true> if the function should display informational
|
||||
output and warnings. It will always display errors or
|
||||
any message which would cause program execution to halt.
|
||||
|
||||
Returns: A hashref, with C<old_vals> being an array of names of variables
|
||||
that were removed, and C<new_vals> being an array of names
|
||||
of variables that were added to localconfig.
|
||||
|
||||
=back
|
||||
@@ -1,695 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Install::Requirements;
|
||||
|
||||
# NOTE: This package MUST NOT "use" any Bugzilla modules other than
|
||||
# Bugzilla::Constants, anywhere. We may "use" standard perl modules.
|
||||
#
|
||||
# Subroutines may "require" and "import" from modules, but they
|
||||
# MUST NOT "use."
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Install::Util qw(vers_cmp install_string);
|
||||
use List::Util qw(max);
|
||||
use Safe;
|
||||
|
||||
use base qw(Exporter);
|
||||
our @EXPORT = qw(
|
||||
REQUIRED_MODULES
|
||||
OPTIONAL_MODULES
|
||||
|
||||
check_requirements
|
||||
check_graphviz
|
||||
have_vers
|
||||
install_command
|
||||
);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
# The below two constants are subroutines so that they can implement
|
||||
# a hook. Other than that they are actually constants.
|
||||
|
||||
# "package" is the perl package we're checking for. "module" is the name
|
||||
# of the actual module we load with "require" to see if the package is
|
||||
# installed or not. "version" is the version we need, or 0 if we'll accept
|
||||
# any version.
|
||||
#
|
||||
# "blacklist" is an arrayref of regular expressions that describe versions that
|
||||
# are 'blacklisted'--that is, even if the version is high enough, Bugzilla
|
||||
# will refuse to say that it's OK to run with that version.
|
||||
sub REQUIRED_MODULES {
|
||||
my $perl_ver = sprintf('%vd', $^V);
|
||||
my @modules = (
|
||||
{
|
||||
package => 'CGI.pm',
|
||||
module => 'CGI',
|
||||
# Perl 5.10 requires CGI 3.33 due to a taint issue when
|
||||
# uploading attachments, see bug 416382.
|
||||
# Require CGI 3.21 for -httponly support, see bug 368502.
|
||||
version => (vers_cmp($perl_ver, '5.10') > -1) ? '3.33' : '3.21'
|
||||
},
|
||||
{
|
||||
package => 'TimeDate',
|
||||
module => 'Date::Format',
|
||||
version => '2.21'
|
||||
},
|
||||
{
|
||||
package => 'PathTools',
|
||||
module => 'File::Spec',
|
||||
version => '0.84'
|
||||
},
|
||||
{
|
||||
package => 'DBI',
|
||||
module => 'DBI',
|
||||
version => '1.41'
|
||||
},
|
||||
{
|
||||
package => 'Template-Toolkit',
|
||||
module => 'Template',
|
||||
version => '2.15'
|
||||
},
|
||||
{
|
||||
package => 'Email-Send',
|
||||
module => 'Email::Send',
|
||||
version => ON_WINDOWS ? '2.16' : '2.00'
|
||||
},
|
||||
{
|
||||
package => 'Email-MIME',
|
||||
module => 'Email::MIME',
|
||||
version => '1.861'
|
||||
},
|
||||
{
|
||||
package => 'Email-MIME-Modifier',
|
||||
module => 'Email::MIME::Modifier',
|
||||
version => '1.442'
|
||||
},
|
||||
);
|
||||
|
||||
my $all_modules = _get_extension_requirements(
|
||||
'REQUIRED_MODULES', \@modules);
|
||||
return $all_modules;
|
||||
};
|
||||
|
||||
sub OPTIONAL_MODULES {
|
||||
my @modules = (
|
||||
{
|
||||
package => 'GD',
|
||||
module => 'GD',
|
||||
version => '1.20',
|
||||
feature => 'Graphical Reports, New Charts, Old Charts'
|
||||
},
|
||||
{
|
||||
package => 'Chart',
|
||||
module => 'Chart::Base',
|
||||
version => '1.0',
|
||||
feature => 'New Charts, Old Charts'
|
||||
},
|
||||
{
|
||||
package => 'Template-GD',
|
||||
# This module tells us whether or not Template-GD is installed
|
||||
# on Template-Toolkits after 2.14, and still works with 2.14 and lower.
|
||||
module => 'Template::Plugin::GD::Image',
|
||||
version => 0,
|
||||
feature => 'Graphical Reports'
|
||||
},
|
||||
{
|
||||
package => 'GDTextUtil',
|
||||
module => 'GD::Text',
|
||||
version => 0,
|
||||
feature => 'Graphical Reports'
|
||||
},
|
||||
{
|
||||
package => 'GDGraph',
|
||||
module => 'GD::Graph',
|
||||
version => 0,
|
||||
feature => 'Graphical Reports'
|
||||
},
|
||||
{
|
||||
package => 'XML-Twig',
|
||||
module => 'XML::Twig',
|
||||
version => 0,
|
||||
feature => 'Move Bugs Between Installations'
|
||||
},
|
||||
{
|
||||
package => 'MIME-tools',
|
||||
# MIME::Parser is packaged as MIME::Tools on ActiveState Perl
|
||||
module => ON_WINDOWS ? 'MIME::Tools' : 'MIME::Parser',
|
||||
version => '5.406',
|
||||
feature => 'Move Bugs Between Installations'
|
||||
},
|
||||
{
|
||||
package => 'libwww-perl',
|
||||
module => 'LWP::UserAgent',
|
||||
version => 0,
|
||||
feature => 'Automatic Update Notifications'
|
||||
},
|
||||
{
|
||||
package => 'PatchReader',
|
||||
module => 'PatchReader',
|
||||
version => '0.9.4',
|
||||
feature => 'Patch Viewer'
|
||||
},
|
||||
{
|
||||
package => 'PerlMagick',
|
||||
module => 'Image::Magick',
|
||||
version => 0,
|
||||
feature => 'Optionally Convert BMP Attachments to PNGs'
|
||||
},
|
||||
{
|
||||
package => 'perl-ldap',
|
||||
module => 'Net::LDAP',
|
||||
version => 0,
|
||||
feature => 'LDAP Authentication'
|
||||
},
|
||||
{
|
||||
package => 'Authen-SASL',
|
||||
module => 'Authen::SASL',
|
||||
version => 0,
|
||||
feature => 'SMTP Authentication'
|
||||
},
|
||||
{
|
||||
package => 'RadiusPerl',
|
||||
module => 'Authen::Radius',
|
||||
version => 0,
|
||||
feature => 'RADIUS Authentication'
|
||||
},
|
||||
{
|
||||
package => 'SOAP-Lite',
|
||||
module => 'SOAP::Lite',
|
||||
version => 0,
|
||||
feature => 'XML-RPC Interface'
|
||||
},
|
||||
{
|
||||
# We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
|
||||
package => 'HTML-Parser',
|
||||
module => 'HTML::Parser',
|
||||
version => '3.40',
|
||||
feature => 'More HTML in Product/Group Descriptions'
|
||||
},
|
||||
{
|
||||
package => 'HTML-Scrubber',
|
||||
module => 'HTML::Scrubber',
|
||||
version => 0,
|
||||
feature => 'More HTML in Product/Group Descriptions'
|
||||
},
|
||||
|
||||
# Inbound Email
|
||||
{
|
||||
package => 'Email-MIME-Attachment-Stripper',
|
||||
module => 'Email::MIME::Attachment::Stripper',
|
||||
version => 0,
|
||||
feature => 'Inbound Email'
|
||||
},
|
||||
{
|
||||
package => 'Email-Reply',
|
||||
module => 'Email::Reply',
|
||||
version => 0,
|
||||
feature => 'Inbound Email'
|
||||
},
|
||||
|
||||
# mod_perl
|
||||
{
|
||||
package => 'mod_perl',
|
||||
module => 'mod_perl2',
|
||||
version => '1.999022',
|
||||
feature => 'mod_perl'
|
||||
},
|
||||
);
|
||||
|
||||
# Even very new releases of perl (5.8.5) don't come with this version,
|
||||
# so I didn't want to make it a general requirement just for
|
||||
# running under mod_cgi.
|
||||
# If Perl 5.10 is installed, then CGI 3.33 is already required. So this
|
||||
# check is only relevant with Perl 5.8.x.
|
||||
my $perl_ver = sprintf('%vd', $^V);
|
||||
if (vers_cmp($perl_ver, '5.10') < 0) {
|
||||
push(@modules, { package => 'CGI.pm',
|
||||
module => 'CGI',
|
||||
version => '3.11',
|
||||
feature => 'mod_perl' });
|
||||
}
|
||||
|
||||
my $all_modules = _get_extension_requirements(
|
||||
'OPTIONAL_MODULES', \@modules);
|
||||
return $all_modules;
|
||||
};
|
||||
|
||||
# This implements the install-requirements hook described in Bugzilla::Hook.
|
||||
sub _get_extension_requirements {
|
||||
my ($function, $base_modules) = @_;
|
||||
my @all_modules;
|
||||
# get a list of all extensions
|
||||
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
|
||||
foreach my $extension (@extensions) {
|
||||
my $file = "$extension/code/install-requirements.pl";
|
||||
if (-e $file) {
|
||||
my $safe = new Safe;
|
||||
# This is a very liberal Safe.
|
||||
$safe->permit(qw(:browse require entereval caller));
|
||||
$safe->rdo($file);
|
||||
if ($@) {
|
||||
warn $@;
|
||||
next;
|
||||
}
|
||||
my $modules = eval { &{$safe->varglob($function)}($base_modules) };
|
||||
next unless $modules;
|
||||
push(@all_modules, @$modules);
|
||||
}
|
||||
}
|
||||
|
||||
unshift(@all_modules, @$base_modules);
|
||||
return \@all_modules;
|
||||
};
|
||||
|
||||
sub check_requirements {
|
||||
my ($output) = @_;
|
||||
|
||||
print "\n", install_string('checking_modules'), "\n" if $output;
|
||||
my $root = ROOT_USER;
|
||||
my $missing = _check_missing(REQUIRED_MODULES, $output);
|
||||
|
||||
print "\n", install_string('checking_dbd'), "\n" if $output;
|
||||
my $have_one_dbd = 0;
|
||||
my $db_modules = DB_MODULE;
|
||||
foreach my $db (keys %$db_modules) {
|
||||
my $dbd = $db_modules->{$db}->{dbd};
|
||||
$have_one_dbd = 1 if have_vers($dbd, $output);
|
||||
}
|
||||
|
||||
print "\n", install_string('checking_optional'), "\n" if $output;
|
||||
my $missing_optional = _check_missing(OPTIONAL_MODULES, $output);
|
||||
|
||||
# If we're running on Windows, reset the input line terminator so that
|
||||
# console input works properly - loading CGI tends to mess it up
|
||||
$/ = "\015\012" if ON_WINDOWS;
|
||||
|
||||
my $pass = !scalar(@$missing) && $have_one_dbd;
|
||||
return {
|
||||
pass => $pass,
|
||||
one_dbd => $have_one_dbd,
|
||||
missing => $missing,
|
||||
optional => $missing_optional,
|
||||
any_missing => !$pass || scalar(@$missing_optional),
|
||||
};
|
||||
}
|
||||
|
||||
# A helper for check_requirements
|
||||
sub _check_missing {
|
||||
my ($modules, $output) = @_;
|
||||
|
||||
my @missing;
|
||||
foreach my $module (@$modules) {
|
||||
unless (have_vers($module, $output)) {
|
||||
push(@missing, $module);
|
||||
}
|
||||
}
|
||||
|
||||
return \@missing;
|
||||
}
|
||||
|
||||
# Returns the build ID of ActivePerl. If several versions of
|
||||
# ActivePerl are installed, it won't be able to know which one
|
||||
# you are currently running. But that's our best guess.
|
||||
sub _get_activestate_build_id {
|
||||
eval 'use Win32::TieRegistry';
|
||||
return 0 if $@;
|
||||
my $key = Win32::TieRegistry->new('LMachine\Software\ActiveState\ActivePerl')
|
||||
or return 0;
|
||||
return $key->GetValue("CurrentVersion");
|
||||
}
|
||||
|
||||
sub print_module_instructions {
|
||||
my ($check_results, $output) = @_;
|
||||
|
||||
# We only print these notes if we have to.
|
||||
if ((!$output && @{$check_results->{missing}})
|
||||
|| ($output && $check_results->{any_missing}))
|
||||
{
|
||||
|
||||
if (ON_WINDOWS) {
|
||||
|
||||
print "\n* NOTE: You must run any commands listed below as "
|
||||
. ROOT_USER . ".\n\n";
|
||||
|
||||
my $perl_ver = sprintf('%vd', $^V);
|
||||
|
||||
# URL when running Perl 5.8.x.
|
||||
my $url_to_theory58S = 'http://theoryx5.uwinnipeg.ca/ppms';
|
||||
my $repo_up_cmd =
|
||||
'* *';
|
||||
# Packages for Perl 5.10 are not compatible with Perl 5.8.
|
||||
if (vers_cmp($perl_ver, '5.10') > -1) {
|
||||
$url_to_theory58S = 'http://cpan.uwinnipeg.ca/PPMPackages/10xx/';
|
||||
}
|
||||
# ActivePerl older than revision 819 require an additional command.
|
||||
if (_get_activestate_build_id() < 819) {
|
||||
$repo_up_cmd = <<EOT;
|
||||
* *
|
||||
* Then you have to do (also as an Administrator): *
|
||||
* *
|
||||
* ppm repo up theory58S *
|
||||
* *
|
||||
* Do that last command over and over until you see "theory58S" at the *
|
||||
* top of the displayed list. *
|
||||
EOT
|
||||
}
|
||||
print <<EOT;
|
||||
***********************************************************************
|
||||
* Note For Windows Users *
|
||||
***********************************************************************
|
||||
* In order to install the modules listed below, you first have to run *
|
||||
* the following command as an Administrator: *
|
||||
* *
|
||||
* ppm repo add theory58S $url_to_theory58S
|
||||
$repo_up_cmd
|
||||
***********************************************************************
|
||||
EOT
|
||||
}
|
||||
}
|
||||
|
||||
# Required Modules
|
||||
if (my @missing = @{$check_results->{missing}}) {
|
||||
print <<EOT;
|
||||
***********************************************************************
|
||||
* REQUIRED MODULES *
|
||||
***********************************************************************
|
||||
* Bugzilla requires you to install some Perl modules which are either *
|
||||
* missing from your system, or the version on your system is too old. *
|
||||
* *
|
||||
* The latest versions of each module can be installed by running the *
|
||||
* commands below. *
|
||||
***********************************************************************
|
||||
EOT
|
||||
|
||||
print "COMMANDS:\n\n";
|
||||
foreach my $package (@missing) {
|
||||
my $command = install_command($package);
|
||||
print " $command\n";
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
|
||||
if (!$check_results->{one_dbd}) {
|
||||
print <<EOT;
|
||||
***********************************************************************
|
||||
* DATABASE ACCESS *
|
||||
***********************************************************************
|
||||
* In order to access your database, Bugzilla requires that the *
|
||||
* correct "DBD" module be installed for the database that you are *
|
||||
* running. *
|
||||
* *
|
||||
* Pick and run the correct command below for the database that you *
|
||||
* plan to use with Bugzilla. *
|
||||
***********************************************************************
|
||||
COMMANDS:
|
||||
|
||||
EOT
|
||||
|
||||
my %db_modules = %{DB_MODULE()};
|
||||
foreach my $db (keys %db_modules) {
|
||||
my $command = install_command($db_modules{$db}->{dbd});
|
||||
printf "%10s: \%s\n", $db_modules{$db}->{name}, $command;
|
||||
print ' ' x 12 . "Minimum version required: "
|
||||
. $db_modules{$db}->{dbd}->{version} . "\n";
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
|
||||
return unless $output;
|
||||
|
||||
if (my @missing = @{$check_results->{optional}}) {
|
||||
print <<EOT;
|
||||
**********************************************************************
|
||||
* OPTIONAL MODULES *
|
||||
**********************************************************************
|
||||
* Certain Perl modules are not required by Bugzilla, but by *
|
||||
* installing the latest version you gain access to additional *
|
||||
* features. *
|
||||
* *
|
||||
* The optional modules you do not have installed are listed below, *
|
||||
* with the name of the feature they enable. If you want to install *
|
||||
* one of these modules, just run the appropriate command in the *
|
||||
* "COMMANDS TO INSTALL" section. *
|
||||
**********************************************************************
|
||||
|
||||
EOT
|
||||
# Now we have to determine how large the table cols will be.
|
||||
my $longest_name = max(map(length($_->{package}), @missing));
|
||||
|
||||
# The first column header is at least 11 characters long.
|
||||
$longest_name = 11 if $longest_name < 11;
|
||||
|
||||
# The table is 71 characters long. There are seven mandatory
|
||||
# characters (* and space) in the string. So, we have a total
|
||||
# of 64 characters to work with.
|
||||
my $remaining_space = 64 - $longest_name;
|
||||
print '*' x 71 . "\n";
|
||||
printf "* \%${longest_name}s * %-${remaining_space}s *\n",
|
||||
'MODULE NAME', 'ENABLES FEATURE(S)';
|
||||
print '*' x 71 . "\n";
|
||||
foreach my $package (@missing) {
|
||||
printf "* \%${longest_name}s * %-${remaining_space}s *\n",
|
||||
$package->{package}, $package->{feature};
|
||||
}
|
||||
print '*' x 71 . "\n";
|
||||
|
||||
print "COMMANDS TO INSTALL:\n\n";
|
||||
foreach my $module (@missing) {
|
||||
my $command = install_command($module);
|
||||
printf "%15s: $command\n", $module->{package};
|
||||
}
|
||||
}
|
||||
|
||||
if ($output && $check_results->{any_missing} && !ON_WINDOWS) {
|
||||
print install_string('install_all', { perl => $^X });
|
||||
}
|
||||
}
|
||||
|
||||
sub check_graphviz {
|
||||
my ($output) = @_;
|
||||
|
||||
return 1 if (Bugzilla->params->{'webdotbase'} =~ /^https?:/);
|
||||
|
||||
printf("Checking for %15s %-9s ", "GraphViz", "(any)") if $output;
|
||||
|
||||
my $return = 0;
|
||||
if(-x Bugzilla->params->{'webdotbase'}) {
|
||||
print "ok: found\n" if $output;
|
||||
$return = 1;
|
||||
} else {
|
||||
print "not a valid executable: " . Bugzilla->params->{'webdotbase'} . "\n";
|
||||
}
|
||||
|
||||
my $webdotdir = bz_locations()->{'webdotdir'};
|
||||
# Check .htaccess allows access to generated images
|
||||
if (-e "$webdotdir/.htaccess") {
|
||||
my $htaccess = new IO::File("$webdotdir/.htaccess", 'r')
|
||||
|| die "$webdotdir/.htaccess: " . $!;
|
||||
if (!grep(/png/, $htaccess->getlines)) {
|
||||
print "Dependency graph images are not accessible.\n";
|
||||
print "delete $webdotdir/.htaccess and re-run checksetup.pl to fix.\n";
|
||||
}
|
||||
$htaccess->close;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
# This was originally clipped from the libnet Makefile.PL, adapted here to
|
||||
# use the below vers_cmp routine for accurate version checking.
|
||||
sub have_vers {
|
||||
my ($params, $output) = @_;
|
||||
my $module = $params->{module};
|
||||
my $package = $params->{package};
|
||||
if (!$package) {
|
||||
$package = $module;
|
||||
$package =~ s/::/-/g;
|
||||
}
|
||||
my $wanted = $params->{version};
|
||||
|
||||
eval "require $module;";
|
||||
|
||||
# VERSION is provided by UNIVERSAL::
|
||||
my $vnum = eval { $module->VERSION } || -1;
|
||||
|
||||
# CGI's versioning scheme went 2.75, 2.751, 2.752, 2.753, 2.76
|
||||
# That breaks the standard version tests, so we need to manually correct
|
||||
# the version
|
||||
if ($module eq 'CGI' && $vnum =~ /(2\.7\d)(\d+)/) {
|
||||
$vnum = $1 . "." . $2;
|
||||
}
|
||||
|
||||
my $vstr;
|
||||
if ($vnum eq "-1") { # string compare just in case it's non-numeric
|
||||
$vstr = install_string('module_not_found');
|
||||
}
|
||||
elsif (vers_cmp($vnum,"0") > -1) {
|
||||
$vstr = install_string('module_found', { ver => $vnum });
|
||||
}
|
||||
else {
|
||||
$vstr = install_string('module_unknown_version');
|
||||
}
|
||||
|
||||
my $vok = (vers_cmp($vnum,$wanted) > -1);
|
||||
my $blacklisted;
|
||||
if ($vok && $params->{blacklist}) {
|
||||
$blacklisted = grep($vnum =~ /$_/, @{$params->{blacklist}});
|
||||
$vok = 0 if $blacklisted;
|
||||
}
|
||||
|
||||
if ($output) {
|
||||
my $ok = $vok ? install_string('module_ok') : '';
|
||||
my $black_string = $blacklisted ? install_string('blacklisted') : '';
|
||||
my $want_string = $wanted ? "v$wanted" : install_string('any');
|
||||
|
||||
$ok = "$ok:" if $ok;
|
||||
printf "%s %19s %-9s $ok $vstr $black_string\n",
|
||||
install_string('checking_for'), $package, "($want_string)";
|
||||
}
|
||||
|
||||
return $vok ? 1 : 0;
|
||||
}
|
||||
|
||||
sub install_command {
|
||||
my $module = shift;
|
||||
my ($command, $package);
|
||||
|
||||
if (ON_WINDOWS) {
|
||||
$command = 'ppm install %s';
|
||||
$package = $module->{package};
|
||||
}
|
||||
else {
|
||||
$command = "$^X install-module.pl \%s";
|
||||
# Non-Windows installations need to use module names, because
|
||||
# CPAN doesn't understand package names.
|
||||
$package = $module->{module};
|
||||
}
|
||||
return sprintf $command, $package;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::Requirements - Functions and variables dealing
|
||||
with Bugzilla's perl-module requirements.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by C<checksetup.pl> to determine whether
|
||||
or not all of Bugzilla's prerequisites are installed. (That is, all the
|
||||
perl modules it requires.)
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<REQUIRED_MODULES>
|
||||
|
||||
An arrayref of hashrefs that describes the perl modules required by
|
||||
Bugzilla. The hashes have two keys, C<name> and C<version>, which
|
||||
represent the name of the module and the version that we require.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<check_requirements>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This checks what optional or required perl modules are installed, like
|
||||
C<checksetup.pl> does.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$output> - C<true> if you want the function to print out information
|
||||
about what it's doing, and the versions of everything installed.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hashref containing these values:
|
||||
|
||||
=over
|
||||
|
||||
=item C<pass> - Whether or not we have all the mandatory requirements.
|
||||
|
||||
=item C<missing> - An arrayref containing any required modules that
|
||||
are not installed or that are not up-to-date. Each item in the array is
|
||||
a hashref in the format of items from L</REQUIRED_MODULES>.
|
||||
|
||||
=item C<optional> - The same as C<missing>, but for optional modules.
|
||||
|
||||
=item C<have_one_dbd> - True if at least one C<DBD::> module is installed.
|
||||
|
||||
=item C<any_missing> - True if there are any missing modules, even optional
|
||||
modules.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<check_graphviz($output)>
|
||||
|
||||
Description: Checks if the graphviz binary specified in the
|
||||
C<webdotbase> parameter is a valid binary, or a valid URL.
|
||||
|
||||
Params: C<$output> - C<$true> if you want the function to
|
||||
print out information about what it's doing.
|
||||
|
||||
Returns: C<1> if the check was successful, C<0> otherwise.
|
||||
|
||||
=item C<have_vers($module, $output)>
|
||||
|
||||
Description: Tells you whether or not you have the appropriate
|
||||
version of the module requested. It also prints
|
||||
out a message to the user explaining the check
|
||||
and the result.
|
||||
|
||||
Params: C<$module> - A hashref, in the format of an item from
|
||||
L</REQUIRED_MODULES>.
|
||||
C<$output> - Set to true if you want this function to
|
||||
print information to STDOUT about what it's
|
||||
doing.
|
||||
|
||||
Returns: C<1> if you have the module installed and you have the
|
||||
appropriate version. C<0> otherwise.
|
||||
|
||||
=item C<install_command($module)>
|
||||
|
||||
Description: Prints out the appropriate command to install the
|
||||
module specified, depending on whether you're
|
||||
on Windows or Linux.
|
||||
|
||||
Params: C<$module> - A hashref, in the format of an item from
|
||||
L</REQUIRED_MODULES>.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,553 +0,0 @@
|
||||
# -*- 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 Everything Solved.
|
||||
# Portions created by Everything Solved are Copyright (C) 2006
|
||||
# Everything Solved. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Install::Util;
|
||||
|
||||
# The difference between this module and Bugzilla::Util is that this
|
||||
# module may require *only* Bugzilla::Constants and built-in
|
||||
# perl modules.
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
use File::Basename;
|
||||
use POSIX qw(setlocale LC_CTYPE);
|
||||
use Safe;
|
||||
|
||||
use base qw(Exporter);
|
||||
our @EXPORT_OK = qw(
|
||||
bin_loc
|
||||
get_version_and_os
|
||||
indicate_progress
|
||||
install_string
|
||||
include_languages
|
||||
template_include_path
|
||||
vers_cmp
|
||||
get_console_locale
|
||||
);
|
||||
|
||||
sub bin_loc {
|
||||
my ($bin) = @_;
|
||||
return '' if ON_WINDOWS;
|
||||
# Don't print any errors from "which"
|
||||
open(my $saveerr, ">&STDERR");
|
||||
open(STDERR, '>/dev/null');
|
||||
my $loc = `which $bin`;
|
||||
close(STDERR);
|
||||
open(STDERR, ">&", $saveerr);
|
||||
my $exit_code = $? >> 8; # See the perlvar manpage.
|
||||
return '' if $exit_code > 0;
|
||||
chomp($loc);
|
||||
return $loc;
|
||||
}
|
||||
|
||||
sub get_version_and_os {
|
||||
# Display version information
|
||||
my @os_details = POSIX::uname;
|
||||
# 0 is the name of the OS, 2 is the major version,
|
||||
my $os_name = $os_details[0] . ' ' . $os_details[2];
|
||||
if (ON_WINDOWS) {
|
||||
require Win32;
|
||||
$os_name = Win32::GetOSName();
|
||||
}
|
||||
# $os_details[3] is the minor version.
|
||||
return { bz_ver => BUGZILLA_VERSION,
|
||||
perl_ver => sprintf('%vd', $^V),
|
||||
os_name => $os_name,
|
||||
os_ver => $os_details[3] };
|
||||
}
|
||||
|
||||
sub indicate_progress {
|
||||
my ($params) = @_;
|
||||
my $current = $params->{current};
|
||||
my $total = $params->{total};
|
||||
my $every = $params->{every} || 1;
|
||||
|
||||
print "." if !($current % $every);
|
||||
if ($current == $total || $current % ($every * 60) == 0) {
|
||||
print "$current/$total (" . int($current * 100 / $total) . "%)\n";
|
||||
}
|
||||
}
|
||||
|
||||
sub install_string {
|
||||
my ($string_id, $vars) = @_;
|
||||
_cache()->{template_include_path} ||= template_include_path();
|
||||
my $path = _cache()->{template_include_path};
|
||||
|
||||
my $string_template;
|
||||
# Find the first template that defines this string.
|
||||
foreach my $dir (@$path) {
|
||||
my $base = "$dir/setup/strings";
|
||||
$string_template = _get_string_from_file($string_id, "$base.txt.pl")
|
||||
if !defined $string_template;
|
||||
last if defined $string_template;
|
||||
}
|
||||
|
||||
die "No language defines the string '$string_id'"
|
||||
if !defined $string_template;
|
||||
|
||||
$vars ||= {};
|
||||
my @replace_keys = keys %$vars;
|
||||
foreach my $key (@replace_keys) {
|
||||
my $replacement = $vars->{$key};
|
||||
die "'$key' in '$string_id' is tainted: '$replacement'"
|
||||
if is_tainted($replacement);
|
||||
# We don't want people to start getting clever and inserting
|
||||
# ##variable## into their values. So we check if any other
|
||||
# key is listed in the *replacement* string, before doing
|
||||
# the replacement. This is mostly to protect programmers from
|
||||
# making mistakes.
|
||||
if (grep($replacement =~ /##$key##/, @replace_keys)) {
|
||||
die "Unsafe replacement for '$key' in '$string_id': '$replacement'";
|
||||
}
|
||||
$string_template =~ s/\Q##$key##\E/$replacement/g;
|
||||
}
|
||||
|
||||
return $string_template;
|
||||
}
|
||||
|
||||
sub include_languages {
|
||||
my ($params) = @_;
|
||||
$params ||= {};
|
||||
|
||||
# Basically, the way this works is that we have a list of languages
|
||||
# that we *want*, and a list of languages that Bugzilla actually
|
||||
# supports. The caller tells us what languages they want, by setting
|
||||
# $ENV{HTTP_ACCEPT_LANGUAGE} or $params->{only_language}. The languages
|
||||
# we support are those specified in $params->{use_languages}. Otherwise
|
||||
# we support every language installed in the template/ directory.
|
||||
|
||||
my @wanted;
|
||||
if ($params->{only_language}) {
|
||||
@wanted = ($params->{only_language});
|
||||
}
|
||||
else {
|
||||
@wanted = _sort_accept_language($ENV{'HTTP_ACCEPT_LANGUAGE'} || '');
|
||||
}
|
||||
|
||||
my @supported;
|
||||
if (defined $params->{use_languages}) {
|
||||
@supported = @{$params->{use_languages}};
|
||||
}
|
||||
else {
|
||||
my @dirs = glob(bz_locations()->{'templatedir'} . "/*");
|
||||
@dirs = map(basename($_), @dirs);
|
||||
@supported = grep($_ ne 'CVS', @dirs);
|
||||
}
|
||||
|
||||
my @usedlanguages;
|
||||
foreach my $wanted (@wanted) {
|
||||
# If we support the language we want, or *any version* of
|
||||
# the language we want, it gets pushed into @usedlanguages.
|
||||
#
|
||||
# Per RFC 1766 and RFC 2616, things like 'en' match 'en-us' and
|
||||
# 'en-uk', but not the other way around. (This is unfortunately
|
||||
# not very clearly stated in those RFC; see comment just over 14.5
|
||||
# in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)
|
||||
if(my @found = grep /^\Q$wanted\E(-.+)?$/i, @supported) {
|
||||
push (@usedlanguages, @found);
|
||||
}
|
||||
}
|
||||
|
||||
# We always include English at the bottom if it's not there, even if
|
||||
# somebody removed it from use_languages.
|
||||
if (!grep($_ eq 'en', @usedlanguages)) {
|
||||
push(@usedlanguages, 'en');
|
||||
}
|
||||
|
||||
return @usedlanguages;
|
||||
}
|
||||
|
||||
sub template_include_path {
|
||||
my @usedlanguages = include_languages(@_);
|
||||
# Now, we add template directories in the order they will be searched:
|
||||
|
||||
# First, we add extension template directories, because extension templates
|
||||
# override standard templates. Extensions may be localized in the same way
|
||||
# that Bugzilla templates are localized.
|
||||
my @include_path;
|
||||
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
|
||||
foreach my $extension (@extensions) {
|
||||
foreach my $lang (@usedlanguages) {
|
||||
_add_language_set(\@include_path, $lang, "$extension/template");
|
||||
}
|
||||
}
|
||||
|
||||
# Then, we add normal template directories, sorted by language.
|
||||
foreach my $lang (@usedlanguages) {
|
||||
_add_language_set(\@include_path, $lang);
|
||||
}
|
||||
|
||||
return \@include_path;
|
||||
}
|
||||
|
||||
# This is taken straight from Sort::Versions 1.5, which is not included
|
||||
# with perl by default.
|
||||
sub vers_cmp {
|
||||
my ($a, $b) = @_;
|
||||
|
||||
# Remove leading zeroes - Bug 344661
|
||||
$a =~ s/^0*(\d.+)/$1/;
|
||||
$b =~ s/^0*(\d.+)/$1/;
|
||||
|
||||
my @A = ($a =~ /([-.]|\d+|[^-.\d]+)/g);
|
||||
my @B = ($b =~ /([-.]|\d+|[^-.\d]+)/g);
|
||||
|
||||
my ($A, $B);
|
||||
while (@A and @B) {
|
||||
$A = shift @A;
|
||||
$B = shift @B;
|
||||
if ($A eq '-' and $B eq '-') {
|
||||
next;
|
||||
} elsif ( $A eq '-' ) {
|
||||
return -1;
|
||||
} elsif ( $B eq '-') {
|
||||
return 1;
|
||||
} elsif ($A eq '.' and $B eq '.') {
|
||||
next;
|
||||
} elsif ( $A eq '.' ) {
|
||||
return -1;
|
||||
} elsif ( $B eq '.' ) {
|
||||
return 1;
|
||||
} elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) {
|
||||
if ($A =~ /^0/ || $B =~ /^0/) {
|
||||
return $A cmp $B if $A cmp $B;
|
||||
} else {
|
||||
return $A <=> $B if $A <=> $B;
|
||||
}
|
||||
} else {
|
||||
$A = uc $A;
|
||||
$B = uc $B;
|
||||
return $A cmp $B if $A cmp $B;
|
||||
}
|
||||
}
|
||||
@A <=> @B;
|
||||
}
|
||||
|
||||
######################
|
||||
# Helper Subroutines #
|
||||
######################
|
||||
|
||||
# Used by install_string
|
||||
sub _get_string_from_file {
|
||||
my ($string_id, $file) = @_;
|
||||
|
||||
return undef if !-e $file;
|
||||
my $safe = new Safe;
|
||||
$safe->rdo($file);
|
||||
my %strings = %{$safe->varglob('strings')};
|
||||
return $strings{$string_id};
|
||||
}
|
||||
|
||||
# Used by template_include_path.
|
||||
sub _add_language_set {
|
||||
my ($array, $lang, $templatedir) = @_;
|
||||
|
||||
$templatedir ||= bz_locations()->{'templatedir'};
|
||||
my @add = ("$templatedir/$lang/custom", "$templatedir/$lang/default");
|
||||
|
||||
my $project = bz_locations->{'project'};
|
||||
push(@add, "$templatedir/$lang/$project") if $project;
|
||||
|
||||
foreach my $dir (@add) {
|
||||
#if (-d $dir) {
|
||||
trick_taint($dir);
|
||||
push(@$array, $dir);
|
||||
#}
|
||||
}
|
||||
}
|
||||
|
||||
# Make an ordered list out of a HTTP Accept-Language header (see RFC 2616, 14.4)
|
||||
# We ignore '*' and <language-range>;q=0
|
||||
# For languages with the same priority q the order remains unchanged.
|
||||
sub _sort_accept_language {
|
||||
sub sortQvalue { $b->{'qvalue'} <=> $a->{'qvalue'} }
|
||||
my $accept_language = $_[0];
|
||||
|
||||
# clean up string.
|
||||
$accept_language =~ s/[^A-Za-z;q=0-9\.\-,]//g;
|
||||
my @qlanguages;
|
||||
my @languages;
|
||||
foreach(split /,/, $accept_language) {
|
||||
if (m/([A-Za-z\-]+)(?:;q=(\d(?:\.\d+)))?/) {
|
||||
my $lang = $1;
|
||||
my $qvalue = $2;
|
||||
$qvalue = 1 if not defined $qvalue;
|
||||
next if $qvalue == 0;
|
||||
$qvalue = 1 if $qvalue > 1;
|
||||
push(@qlanguages, {'qvalue' => $qvalue, 'language' => $lang});
|
||||
}
|
||||
}
|
||||
|
||||
return map($_->{'language'}, (sort sortQvalue @qlanguages));
|
||||
}
|
||||
|
||||
sub get_console_locale {
|
||||
require Locale::Language;
|
||||
my $locale = setlocale(LC_CTYPE);
|
||||
my $language;
|
||||
# Some distros set e.g. LC_CTYPE = fr_CH.UTF-8. We clean it up.
|
||||
if ($locale =~ /^([^\.]+)/) {
|
||||
$locale = $1;
|
||||
}
|
||||
$locale =~ s/_/-/;
|
||||
# It's pretty sure that there is no language pack of the form fr-CH
|
||||
# installed, so we also include fr as a wanted language.
|
||||
if ($locale =~ /^(\S+)\-/) {
|
||||
$language = $1;
|
||||
$locale .= ",$language";
|
||||
}
|
||||
else {
|
||||
$language = $locale;
|
||||
}
|
||||
|
||||
# Some OSs or distributions may have setlocale return a string of the form
|
||||
# German_Germany.1252 (this example taken from a Windows XP system), which
|
||||
# is unsuitable for our needs because Bugzilla works on language codes.
|
||||
# We try and convert them here.
|
||||
if ($language = Locale::Language::language2code($language)) {
|
||||
$locale .= ",$language";
|
||||
}
|
||||
|
||||
return $locale;
|
||||
}
|
||||
|
||||
|
||||
# This is like request_cache, but it's used only by installation code
|
||||
# for setup.cgi and things like that.
|
||||
our $_cache = {};
|
||||
sub _cache {
|
||||
if ($ENV{MOD_PERL}) {
|
||||
require Apache2::RequestUtil;
|
||||
return Apache2::RequestUtil->request->pnotes();
|
||||
}
|
||||
return $_cache;
|
||||
}
|
||||
|
||||
###############################
|
||||
# Copied from Bugzilla::Util #
|
||||
##############################
|
||||
|
||||
sub trick_taint {
|
||||
require Carp;
|
||||
Carp::confess("Undef to trick_taint") unless defined $_[0];
|
||||
my $match = $_[0] =~ /^(.*)$/s;
|
||||
$_[0] = $match ? $1 : undef;
|
||||
return (defined($_[0]));
|
||||
}
|
||||
|
||||
sub is_tainted {
|
||||
return not eval { my $foo = join('',@_), kill 0; 1; };
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::Util - Utility functions that are useful both during
|
||||
installation and afterwards.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains various subroutines that are used primarily
|
||||
during installation. However, these subroutines can also be useful to
|
||||
non-installation code, so they have been split out into this module.
|
||||
|
||||
The difference between this module and L<Bugzilla::Util> is that this
|
||||
module is safe to C<use> anywhere in Bugzilla, even during installation,
|
||||
because it depends only on L<Bugzilla::Constants> and built-in perl modules.
|
||||
|
||||
None of the subroutines are exported by default--you must explicitly
|
||||
export them.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<bin_loc>
|
||||
|
||||
On *nix systems, given the name of a binary, returns the path to that
|
||||
binary, if the binary is in the C<PATH>.
|
||||
|
||||
=item C<get_version_and_os>
|
||||
|
||||
Returns a hash containing information about what version of Bugzilla we're
|
||||
running, what perl version we're using, and what OS we're running on.
|
||||
|
||||
=item C<get_console_locale>
|
||||
|
||||
Returns the language to use based on the LC_CTYPE value returned by the OS.
|
||||
If LC_CTYPE is of the form fr-CH, then fr is appended to the list.
|
||||
|
||||
=item C<indicate_progress>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This prints out lines of dots as a long update is going on, to let the user
|
||||
know where we are and that we're not frozen. A new line of dots will start
|
||||
every 60 dots.
|
||||
|
||||
Sample usage: C<indicate_progress({ total =E<gt> $total, current =E<gt>
|
||||
$count, every =E<gt> 1 })>
|
||||
|
||||
=item B<Sample Output>
|
||||
|
||||
Here's some sample output with C<total = 1000> and C<every = 10>:
|
||||
|
||||
............................................................600/1000 (60%)
|
||||
........................................
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<total> - The total number of items we're processing.
|
||||
|
||||
=item C<current> - The number of the current item we're processing.
|
||||
|
||||
=item C<every> - How often the function should print out a dot.
|
||||
For example, if this is 10, the function will print out a dot every
|
||||
ten items. Defaults to 1 if not specified.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>: nothing
|
||||
|
||||
=back
|
||||
|
||||
=item C<install_string>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This is a very simple method of templating strings for installation.
|
||||
It should only be used by code that has to run before the Template Toolkit
|
||||
can be used. (See the comments at the top of the various L<Bugzilla::Install>
|
||||
modules to find out when it's safe to use Template Toolkit.)
|
||||
|
||||
It pulls strings out of the F<strings.txt.pl> "template" and replaces
|
||||
any variable surrounded by double-hashes (##) with a value you specify.
|
||||
|
||||
This allows for localization of strings used during installation.
|
||||
|
||||
=item B<Example>
|
||||
|
||||
Let's say your template string looks like this:
|
||||
|
||||
The ##animal## jumped over the ##plant##.
|
||||
|
||||
Let's say that string is called 'animal_jump_plant'. So you call the function
|
||||
like this:
|
||||
|
||||
install_string('animal_jump_plant', { animal => 'fox', plant => 'tree' });
|
||||
|
||||
That will output this:
|
||||
|
||||
The fox jumped over the tree.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$string_id> - The name of the string from F<strings.txt.pl>.
|
||||
|
||||
=item C<$vars> - A hashref containing the replacement values for variables
|
||||
inside of the string.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>: The appropriate string, with variables replaced.
|
||||
|
||||
=back
|
||||
|
||||
=item C<template_include_path>
|
||||
|
||||
Used by L<Bugzilla::Template> and L</install_string> to determine the
|
||||
directories where templates are installed. Templates can be installed
|
||||
in many places. They're listed here in the basic order that they're
|
||||
searched:
|
||||
|
||||
=over
|
||||
|
||||
=item extensions/C<$extension>/template/C<$language>/C<$project>
|
||||
|
||||
=item extensions/C<$extension>/template/C<$language>/custom
|
||||
|
||||
=item extensions/C<$extension>/template/C<$language>/default
|
||||
|
||||
=item template/C<$language>/C<$project>
|
||||
|
||||
=item template/C<$language>/custom
|
||||
|
||||
=item template/C<$language>/default
|
||||
|
||||
=back
|
||||
|
||||
C<$project> has to do with installations that are using the C<$ENV{PROJECT}>
|
||||
variable to have different "views" on a single Bugzilla.
|
||||
|
||||
The F<default> directory includes templates shipped with Bugzilla.
|
||||
|
||||
The F<custom> directory is a directory for local installations to override
|
||||
the F<default> templates. Any individual template in F<custom> will
|
||||
override a template of the same name and path in F<default>.
|
||||
|
||||
C<$language> is a language code, C<en> being the default language shipped
|
||||
with Bugzilla. Localizers ship other languages.
|
||||
|
||||
C<$extension> is the name of any directory in the F<extensions/> directory.
|
||||
Each extension has its own directory.
|
||||
|
||||
Note that languages are sorted by the user's preference (as specified
|
||||
in their browser, usually), and extensions are sorted alphabetically.
|
||||
|
||||
=item C<include_languages>
|
||||
|
||||
Used by L<Bugzilla::Template> to determine the languages' list which
|
||||
are compiled with the browser's I<Accept-Language> and the languages
|
||||
of installed templates.
|
||||
|
||||
=item C<vers_cmp>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This is a comparison function, like you would use in C<sort>, except that
|
||||
it compares two version numbers. So, for example, 2.10 would be greater
|
||||
than 2.2.
|
||||
|
||||
It's based on versioncmp from L<Sort::Versions>, with some Bugzilla-specific
|
||||
fixes.
|
||||
|
||||
=item B<Params>: C<$a> and C<$b> - The versions you want to compare.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
C<-1> if C<$a> is less than C<$b>, C<0> if they are equal, or C<1> if C<$a>
|
||||
is greater than C<$b>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
@@ -1,189 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Keyword;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
keyworddefs.id
|
||||
keyworddefs.name
|
||||
keyworddefs.description
|
||||
);
|
||||
|
||||
use constant DB_TABLE => 'keyworddefs';
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(name description);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
description => \&_check_description,
|
||||
};
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
name
|
||||
description
|
||||
);
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
|
||||
sub bug_count {
|
||||
my ($self) = @_;
|
||||
return $self->{'bug_count'} if defined $self->{'bug_count'};
|
||||
($self->{'bug_count'}) =
|
||||
Bugzilla->dbh->selectrow_array(
|
||||
'SELECT COUNT(*) FROM keywords WHERE keywordid = ?',
|
||||
undef, $self->id);
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Mutators #####
|
||||
###############################
|
||||
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
|
||||
###############################
|
||||
#### Subroutines ######
|
||||
###############################
|
||||
|
||||
sub keyword_count {
|
||||
my ($count) =
|
||||
Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM keyworddefs');
|
||||
return $count;
|
||||
}
|
||||
|
||||
sub get_all_with_bug_count {
|
||||
my $class = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $keywords =
|
||||
$dbh->selectall_arrayref('SELECT ' . join(', ', DB_COLUMNS) . ',
|
||||
COUNT(keywords.bug_id) AS bug_count
|
||||
FROM keyworddefs
|
||||
LEFT JOIN keywords
|
||||
ON keyworddefs.id = keywords.keywordid ' .
|
||||
$dbh->sql_group_by('keyworddefs.id',
|
||||
'keyworddefs.name,
|
||||
keyworddefs.description') . '
|
||||
ORDER BY keyworddefs.name', {'Slice' => {}});
|
||||
if (!$keywords) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach my $keyword (@$keywords) {
|
||||
bless($keyword, $class);
|
||||
}
|
||||
return $keywords;
|
||||
}
|
||||
|
||||
###############################
|
||||
### Validators ###
|
||||
###############################
|
||||
|
||||
sub _check_name {
|
||||
my ($self, $name) = @_;
|
||||
|
||||
$name = trim($name);
|
||||
$name eq "" && ThrowUserError("keyword_blank_name");
|
||||
if ($name =~ /[\s,]/) {
|
||||
ThrowUserError("keyword_invalid_name");
|
||||
}
|
||||
|
||||
# We only want to validate the non-existence of the name if
|
||||
# we're creating a new Keyword or actually renaming the keyword.
|
||||
if (!ref($self) || $self->name ne $name) {
|
||||
my $keyword = new Bugzilla::Keyword({ name => $name });
|
||||
ThrowUserError("keyword_already_exists", { name => $name }) if $keyword;
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_description {
|
||||
my ($self, $desc) = @_;
|
||||
$desc = trim($desc);
|
||||
$desc eq '' && ThrowUserError("keyword_blank_description");
|
||||
return $desc;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Keyword - A Keyword that can be added to a bug.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Keyword;
|
||||
|
||||
my $count = Bugzilla::Keyword::keyword_count;
|
||||
|
||||
my $description = $keyword->description;
|
||||
|
||||
my $keywords = Bugzilla::Keyword->get_all_with_bug_count();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Keyword represents a keyword that can be added to a bug.
|
||||
|
||||
This implements all standard C<Bugzilla::Object> methods. See
|
||||
L<Bugzilla::Object> for more details.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
This is only a list of subroutines specific to C<Bugzilla::Keyword>.
|
||||
See L<Bugzilla::Object> for more subroutines that this object
|
||||
implements.
|
||||
|
||||
=over
|
||||
|
||||
=item C<keyword_count()>
|
||||
|
||||
Description: A utility function to get the total number
|
||||
of keywords defined. Mostly used to see
|
||||
if there are any keywords defined at all.
|
||||
Params: none
|
||||
Returns: An integer, the count of keywords.
|
||||
|
||||
=item C<get_all_with_bug_count()>
|
||||
|
||||
Description: Returns all defined keywords. This is an efficient way
|
||||
to get the associated bug counts, as only one SQL query
|
||||
is executed with this method, instead of one per keyword
|
||||
when calling get_all and then bug_count.
|
||||
Params: none
|
||||
Returns: A reference to an array of Keyword objects, or an empty
|
||||
arrayref if there are no keywords.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,192 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>,
|
||||
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Alan Raetz <al_raetz@yahoo.com>
|
||||
# Jacob Steenhagen <jake@actex.net>
|
||||
# Matthew Tuck <matty@chariot.net.au>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Byron Jones <bugzilla@glob.com.au>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Mailer;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Mailer::EXPORT = qw(MessageToMTA build_thread_marker);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use Date::Format qw(time2str);
|
||||
|
||||
use Encode qw(encode);
|
||||
use Encode::MIME::Header;
|
||||
use Email::Address;
|
||||
use Email::MIME;
|
||||
# Loading this gives us encoding_set.
|
||||
use Email::MIME::Modifier;
|
||||
use Email::Send;
|
||||
|
||||
sub MessageToMTA {
|
||||
my ($msg) = (@_);
|
||||
my $method = Bugzilla->params->{'mail_delivery_method'};
|
||||
return if $method eq 'None';
|
||||
|
||||
my $email = ref($msg) ? $msg : Email::MIME->new($msg);
|
||||
|
||||
# We add this header to mark the mail as "auto-generated" and
|
||||
# thus to hopefully avoid auto replies.
|
||||
$email->header_set('Auto-Submitted', 'auto-generated');
|
||||
|
||||
$email->walk_parts(sub {
|
||||
my ($part) = @_;
|
||||
return if $part->parts > 1; # Top-level
|
||||
my $content_type = $part->content_type || '';
|
||||
if ($content_type !~ /;/) {
|
||||
my $body = $part->body;
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
$part->charset_set('UTF-8');
|
||||
# encoding_set works only with bytes, not with utf8 strings.
|
||||
my $raw = $part->body_raw;
|
||||
if (utf8::is_utf8($raw)) {
|
||||
utf8::encode($raw);
|
||||
$part->body_set($raw);
|
||||
}
|
||||
}
|
||||
$part->encoding_set('quoted-printable') if !is_7bit_clean($body);
|
||||
}
|
||||
});
|
||||
|
||||
# MIME-Version must be set otherwise some mailsystems ignore the charset
|
||||
$email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version');
|
||||
|
||||
# Encode the headers correctly in quoted-printable
|
||||
foreach my $header ($email->header_names) {
|
||||
my @values = $email->header($header);
|
||||
# We don't recode headers that happen multiple times.
|
||||
next if scalar(@values) > 1;
|
||||
if (my $value = $values[0]) {
|
||||
if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($value)) {
|
||||
utf8::decode($value);
|
||||
}
|
||||
|
||||
# avoid excessive line wrapping done by Encode.
|
||||
local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 998;
|
||||
|
||||
my $encoded = encode('MIME-Q', $value);
|
||||
$email->header_set($header, $encoded);
|
||||
}
|
||||
}
|
||||
|
||||
my $from = $email->header('From');
|
||||
|
||||
my ($hostname, @args);
|
||||
if ($method eq "Sendmail") {
|
||||
if (ON_WINDOWS) {
|
||||
$Email::Send::Sendmail::SENDMAIL = SENDMAIL_EXE;
|
||||
}
|
||||
push @args, "-i";
|
||||
# We want to make sure that we pass *only* an email address.
|
||||
if ($from) {
|
||||
my ($email_obj) = Email::Address->parse($from);
|
||||
if ($email_obj) {
|
||||
my $from_email = $email_obj->address;
|
||||
push(@args, "-f$from_email") if $from_email;
|
||||
}
|
||||
}
|
||||
push(@args, "-ODeliveryMode=deferred")
|
||||
if !Bugzilla->params->{"sendmailnow"};
|
||||
}
|
||||
else {
|
||||
# Sendmail will automatically append our hostname to the From
|
||||
# address, but other mailers won't.
|
||||
my $urlbase = Bugzilla->params->{'urlbase'};
|
||||
$urlbase =~ m|//([^:/]+)[:/]?|;
|
||||
$hostname = $1;
|
||||
$from .= "\@$hostname" if $from !~ /@/;
|
||||
$email->header_set('From', $from);
|
||||
|
||||
# Sendmail adds a Date: header also, but others may not.
|
||||
if (!defined $email->header('Date')) {
|
||||
$email->header_set('Date', time2str("%a, %e %b %Y %T %z", time()));
|
||||
}
|
||||
}
|
||||
|
||||
if ($method eq "SMTP") {
|
||||
push @args, Host => Bugzilla->params->{"smtpserver"},
|
||||
username => Bugzilla->params->{"smtp_username"},
|
||||
password => Bugzilla->params->{"smtp_password"},
|
||||
Hello => $hostname,
|
||||
Debug => Bugzilla->params->{'smtp_debug'};
|
||||
}
|
||||
|
||||
if ($method eq "Test") {
|
||||
my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
|
||||
open TESTFILE, '>>', $filename;
|
||||
# From - <date> is required to be a valid mbox file.
|
||||
print TESTFILE "\n\nFrom - " . $email->header('Date') . "\n" . $email->as_string;
|
||||
close TESTFILE;
|
||||
}
|
||||
else {
|
||||
# This is useful for both Sendmail and Qmail, so we put it out here.
|
||||
local $ENV{PATH} = SENDMAIL_PATH;
|
||||
my $mailer = Email::Send->new({ mailer => $method,
|
||||
mailer_args => \@args });
|
||||
my $retval = $mailer->send($email);
|
||||
ThrowCodeError('mail_send_error', { msg => $retval, mail => $email })
|
||||
if !$retval;
|
||||
}
|
||||
}
|
||||
|
||||
# Builds header suitable for use as a threading marker in email notifications
|
||||
sub build_thread_marker {
|
||||
my ($bug_id, $user_id, $is_new) = @_;
|
||||
|
||||
if (!defined $user_id) {
|
||||
$user_id = Bugzilla->user->id;
|
||||
}
|
||||
|
||||
my $sitespec = '@' . Bugzilla->params->{'urlbase'};
|
||||
$sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain
|
||||
$sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate
|
||||
if ($2) {
|
||||
$sitespec = "-$2$sitespec"; # Put the port number back in, before the '@'
|
||||
}
|
||||
|
||||
my $threadingmarker;
|
||||
if ($is_new) {
|
||||
$threadingmarker = "Message-ID: <bug-$bug_id-$user_id$sitespec>";
|
||||
}
|
||||
else {
|
||||
$threadingmarker = "In-Reply-To: <bug-$bug_id-$user_id$sitespec>" .
|
||||
"\nReferences: <bug-$bug_id-$user_id$sitespec>";
|
||||
}
|
||||
|
||||
return $threadingmarker;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,375 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Milestone;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
################################
|
||||
##### Initialization #####
|
||||
################################
|
||||
|
||||
use constant DEFAULT_SORTKEY => 0;
|
||||
|
||||
use constant DB_TABLE => 'milestones';
|
||||
use constant NAME_FIELD => 'value';
|
||||
use constant LIST_ORDER => 'sortkey, value';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
value
|
||||
product_id
|
||||
sortkey
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(
|
||||
name
|
||||
product
|
||||
);
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
value
|
||||
sortkey
|
||||
);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
product => \&_check_product,
|
||||
sortkey => \&_check_sortkey,
|
||||
};
|
||||
|
||||
use constant UPDATE_VALIDATORS => {
|
||||
value => \&_check_value,
|
||||
};
|
||||
|
||||
################################
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $param = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $product;
|
||||
if (ref $param) {
|
||||
$product = $param->{product};
|
||||
my $name = $param->{name};
|
||||
if (!defined $product) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'product',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
if (!defined $name) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'name',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
|
||||
my $condition = 'product_id = ? AND value = ?';
|
||||
my @values = ($product->id, $name);
|
||||
$param = { condition => $condition, values => \@values };
|
||||
}
|
||||
|
||||
unshift @_, $param;
|
||||
return $class->SUPER::new(@_);
|
||||
}
|
||||
|
||||
sub run_create_validators {
|
||||
my $class = shift;
|
||||
my $params = $class->SUPER::run_create_validators(@_);
|
||||
|
||||
my $product = delete $params->{product};
|
||||
$params->{product_id} = $product->id;
|
||||
$params->{value} = $class->_check_value($params->{name}, $product);
|
||||
delete $params->{name};
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my $changes = $self->SUPER::update(@_);
|
||||
|
||||
if (exists $changes->{value}) {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
# The milestone value is stored in the bugs table instead of its ID.
|
||||
$dbh->do('UPDATE bugs SET target_milestone = ?
|
||||
WHERE target_milestone = ? AND product_id = ?',
|
||||
undef, ($self->name, $changes->{value}->[0], $self->product_id));
|
||||
|
||||
# The default milestone also stores the value instead of the ID.
|
||||
$dbh->do('UPDATE products SET defaultmilestone = ?
|
||||
WHERE id = ? AND defaultmilestone = ?',
|
||||
undef, ($self->name, $self->product_id, $changes->{value}->[0]));
|
||||
}
|
||||
return $changes;
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# The default milestone cannot be deleted.
|
||||
if ($self->name eq $self->product->default_milestone) {
|
||||
ThrowUserError('milestone_is_default', { milestone => $self });
|
||||
}
|
||||
|
||||
if ($self->bug_count) {
|
||||
# We don't want to delete bugs when deleting a milestone.
|
||||
# Bugs concerned are reassigned to the default milestone.
|
||||
my $bug_ids =
|
||||
$dbh->selectcol_arrayref('SELECT bug_id FROM bugs
|
||||
WHERE product_id = ? AND target_milestone = ?',
|
||||
undef, ($self->product->id, $self->name));
|
||||
|
||||
my $timestamp = $dbh->selectrow_array('SELECT NOW()');
|
||||
|
||||
$dbh->do('UPDATE bugs SET target_milestone = ?, delta_ts = ?
|
||||
WHERE ' . $dbh->sql_in('bug_id', $bug_ids),
|
||||
undef, ($self->product->default_milestone, $timestamp));
|
||||
|
||||
require Bugzilla::Bug;
|
||||
import Bugzilla::Bug qw(LogActivityEntry);
|
||||
foreach my $bug_id (@$bug_ids) {
|
||||
LogActivityEntry($bug_id, 'target_milestone',
|
||||
$self->name,
|
||||
$self->product->default_milestone,
|
||||
Bugzilla->user->id, $timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->do('DELETE FROM milestones WHERE id = ?', undef, $self->id);
|
||||
}
|
||||
|
||||
################################
|
||||
# Validators
|
||||
################################
|
||||
|
||||
sub _check_value {
|
||||
my ($invocant, $name, $product) = @_;
|
||||
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError('milestone_blank_name');
|
||||
if (length($name) > MAX_MILESTONE_SIZE) {
|
||||
ThrowUserError('milestone_name_too_long', {name => $name});
|
||||
}
|
||||
|
||||
$product = $invocant->product if (ref $invocant);
|
||||
my $milestone = new Bugzilla::Milestone({product => $product, name => $name});
|
||||
if ($milestone && (!ref $invocant || $milestone->id != $invocant->id)) {
|
||||
ThrowUserError('milestone_already_exists', { name => $milestone->name,
|
||||
product => $product->name });
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_sortkey {
|
||||
my ($invocant, $sortkey) = @_;
|
||||
|
||||
# Keep a copy in case detaint_signed() clears the sortkey
|
||||
my $stored_sortkey = $sortkey;
|
||||
|
||||
if (!detaint_signed($sortkey) || $sortkey < MIN_SMALLINT || $sortkey > MAX_SMALLINT) {
|
||||
ThrowUserError('milestone_sortkey_invalid', {sortkey => $stored_sortkey});
|
||||
}
|
||||
return $sortkey;
|
||||
}
|
||||
|
||||
sub _check_product {
|
||||
my ($invocant, $product) = @_;
|
||||
return Bugzilla->user->check_can_admin_product($product->name);
|
||||
}
|
||||
|
||||
################################
|
||||
# Methods
|
||||
################################
|
||||
|
||||
sub set_name { $_[0]->set('value', $_[1]); }
|
||||
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_count'}) {
|
||||
$self->{'bug_count'} = $dbh->selectrow_array(q{
|
||||
SELECT COUNT(*) FROM bugs
|
||||
WHERE product_id = ? AND target_milestone = ?},
|
||||
undef, $self->product_id, $self->name) || 0;
|
||||
}
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
################################
|
||||
##### Accessors ######
|
||||
################################
|
||||
|
||||
sub name { return $_[0]->{'value'}; }
|
||||
sub product_id { return $_[0]->{'product_id'}; }
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
|
||||
sub product {
|
||||
my $self = shift;
|
||||
|
||||
require Bugzilla::Product;
|
||||
$self->{'product'} ||= new Bugzilla::Product($self->product_id);
|
||||
return $self->{'product'};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Milestone - Bugzilla product milestone class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Milestone;
|
||||
|
||||
my $milestone = new Bugzilla::Milestone({ name => $name, product => $product });
|
||||
|
||||
my $name = $milestone->name;
|
||||
my $product_id = $milestone->product_id;
|
||||
my $product = $milestone->product;
|
||||
my $sortkey = $milestone->sortkey;
|
||||
|
||||
my $milestone = Bugzilla::Milestone->create(
|
||||
{ name => $name, product => $product, sortkey => $sortkey });
|
||||
|
||||
$milestone->set_name($new_name);
|
||||
$milestone->set_sortkey($new_sortkey);
|
||||
$milestone->update();
|
||||
|
||||
$milestone->remove_from_db;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Milestone.pm represents a Product Milestone object.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<new({name => $name, product => $product})>
|
||||
|
||||
Description: The constructor is used to load an existing milestone
|
||||
by passing a product object and a milestone name.
|
||||
|
||||
Params: $product - a Bugzilla::Product object.
|
||||
$name - the name of a milestone (string).
|
||||
|
||||
Returns: A Bugzilla::Milestone object.
|
||||
|
||||
=item C<name()>
|
||||
|
||||
Description: Name (value) of the milestone.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: The name of the milestone.
|
||||
|
||||
=item C<product_id()>
|
||||
|
||||
Description: ID of the product the milestone belongs to.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: The ID of a product.
|
||||
|
||||
=item C<product()>
|
||||
|
||||
Description: The product object of the product the milestone belongs to.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::Product object.
|
||||
|
||||
=item C<sortkey()>
|
||||
|
||||
Description: Sortkey of the milestone.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: The sortkey of the milestone.
|
||||
|
||||
=item C<bug_count()>
|
||||
|
||||
Description: Returns the total of bugs that belong to the milestone.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer with the number of bugs.
|
||||
|
||||
=item C<set_name($new_name)>
|
||||
|
||||
Description: Changes the name of the milestone.
|
||||
|
||||
Params: $new_name - new name of the milestone (string). This name
|
||||
must be unique within the product.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_sortkey($new_sortkey)>
|
||||
|
||||
Description: Changes the sortkey of the milestone.
|
||||
|
||||
Params: $new_sortkey - new sortkey of the milestone (signed integer).
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<update()>
|
||||
|
||||
Description: Writes the new name and/or the new sortkey into the DB.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A hashref with changes made to the milestone object.
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: Deletes the current milestone from the DB. The object itself
|
||||
is not destroyed.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<create({name => $name, product => $product, sortkey => $sortkey})>
|
||||
|
||||
Description: Create a new milestone for the given product.
|
||||
|
||||
Params: $name - name of the new milestone (string). This name
|
||||
must be unique within the product.
|
||||
$product - a Bugzilla::Product object.
|
||||
$sortkey - the sortkey of the new milestone (signed integer)
|
||||
|
||||
Returns: A Bugzilla::Milestone object.
|
||||
|
||||
=back
|
||||
@@ -1,789 +0,0 @@
|
||||
# -*- 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 Everything Solved.
|
||||
# Portions created by Everything Solved are Copyright (C) 2006
|
||||
# Everything Solved. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Object;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use Date::Parse;
|
||||
|
||||
use constant NAME_FIELD => 'name';
|
||||
use constant ID_FIELD => 'id';
|
||||
use constant LIST_ORDER => NAME_FIELD;
|
||||
|
||||
use constant UPDATE_VALIDATORS => {};
|
||||
use constant NUMERIC_COLUMNS => ();
|
||||
use constant DATE_COLUMNS => ();
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my $object = $class->_init(@_);
|
||||
bless($object, $class) if $object;
|
||||
return $object;
|
||||
}
|
||||
|
||||
|
||||
# Note: Because this uses sql_istrcmp, if you make a new object use
|
||||
# Bugzilla::Object, make sure that you modify bz_setup_database
|
||||
# in Bugzilla::DB::Pg appropriately, to add the right LOWER
|
||||
# index. You can see examples already there.
|
||||
sub _init {
|
||||
my $class = shift;
|
||||
my ($param) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $columns = join(',', $class->DB_COLUMNS);
|
||||
my $table = $class->DB_TABLE;
|
||||
my $name_field = $class->NAME_FIELD;
|
||||
my $id_field = $class->ID_FIELD;
|
||||
|
||||
my $id = $param unless (ref $param eq 'HASH');
|
||||
my $object;
|
||||
|
||||
if (defined $id) {
|
||||
# We special-case if somebody specifies an ID, so that we can
|
||||
# validate it as numeric.
|
||||
detaint_natural($id)
|
||||
|| ThrowCodeError('param_must_be_numeric',
|
||||
{function => $class . '::_init'});
|
||||
|
||||
$object = $dbh->selectrow_hashref(qq{
|
||||
SELECT $columns FROM $table
|
||||
WHERE $id_field = ?}, undef, $id);
|
||||
} else {
|
||||
unless (defined $param->{name} || (defined $param->{'condition'}
|
||||
&& defined $param->{'values'}))
|
||||
{
|
||||
ThrowCodeError('bad_arg', { argument => 'param',
|
||||
function => $class . '::new' });
|
||||
}
|
||||
|
||||
my ($condition, @values);
|
||||
if (defined $param->{name}) {
|
||||
$condition = $dbh->sql_istrcmp($name_field, '?');
|
||||
push(@values, $param->{name});
|
||||
}
|
||||
elsif (defined $param->{'condition'} && defined $param->{'values'}) {
|
||||
caller->isa('Bugzilla::Object')
|
||||
|| ThrowCodeError('protection_violation',
|
||||
{ caller => caller,
|
||||
function => $class . '::new',
|
||||
argument => 'condition/values' });
|
||||
$condition = $param->{'condition'};
|
||||
push(@values, @{$param->{'values'}});
|
||||
}
|
||||
|
||||
map { trick_taint($_) } @values;
|
||||
$object = $dbh->selectrow_hashref(
|
||||
"SELECT $columns FROM $table WHERE $condition", undef, @values);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
sub check {
|
||||
my ($invocant, $param) = @_;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
# If we were just passed a name, then just use the name.
|
||||
if (!ref $param) {
|
||||
$param = { name => $param };
|
||||
}
|
||||
# Don't allow empty names.
|
||||
if (exists $param->{name}) {
|
||||
$param->{name} = trim($param->{name});
|
||||
$param->{name} || ThrowUserError('object_name_not_specified',
|
||||
{ class => $class });
|
||||
}
|
||||
my $obj = $class->new($param)
|
||||
|| ThrowUserError('object_does_not_exist', {%$param, class => $class});
|
||||
return $obj;
|
||||
}
|
||||
|
||||
sub new_from_list {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my ($id_list) = @_;
|
||||
my $id_field = $class->ID_FIELD;
|
||||
|
||||
my @detainted_ids;
|
||||
foreach my $id (@$id_list) {
|
||||
detaint_natural($id) ||
|
||||
ThrowCodeError('param_must_be_numeric',
|
||||
{function => $class . '::new_from_list'});
|
||||
push(@detainted_ids, $id);
|
||||
}
|
||||
# We don't do $invocant->match because some classes have
|
||||
# their own implementation of match which is not compatible
|
||||
# with this one. However, match() still needs to have the right $invocant
|
||||
# in order to do $class->DB_TABLE and so on.
|
||||
return match($invocant, { $id_field => \@detainted_ids });
|
||||
}
|
||||
|
||||
# Note: Future extensions to this could be:
|
||||
# * Add a MATCH_JOIN constant so that we can join against
|
||||
# certain other tables for the WHERE criteria.
|
||||
sub match {
|
||||
my ($invocant, $criteria) = @_;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return [$class->get_all] if !$criteria;
|
||||
|
||||
my (@terms, @values);
|
||||
foreach my $field (keys %$criteria) {
|
||||
my $value = $criteria->{$field};
|
||||
if (ref $value eq 'ARRAY') {
|
||||
# IN () is invalid SQL, and if we have an empty list
|
||||
# to match against, we're just returning an empty
|
||||
# array anyhow.
|
||||
return [] if !scalar @$value;
|
||||
|
||||
my @qmarks = ("?") x @$value;
|
||||
push(@terms, $dbh->sql_in($field, \@qmarks));
|
||||
push(@values, @$value);
|
||||
}
|
||||
elsif ($value eq NOT_NULL) {
|
||||
push(@terms, "$field IS NOT NULL");
|
||||
}
|
||||
elsif ($value eq IS_NULL) {
|
||||
push(@terms, "$field IS NULL");
|
||||
}
|
||||
else {
|
||||
push(@terms, "$field = ?");
|
||||
push(@values, $value);
|
||||
}
|
||||
}
|
||||
|
||||
my $where = join(' AND ', @terms);
|
||||
return $class->_do_list_select($where, \@values);
|
||||
}
|
||||
|
||||
sub _do_list_select {
|
||||
my ($class, $where, $values) = @_;
|
||||
my $table = $class->DB_TABLE;
|
||||
my $cols = join(',', $class->DB_COLUMNS);
|
||||
my $order = $class->LIST_ORDER;
|
||||
|
||||
my $sql = "SELECT $cols FROM $table";
|
||||
if (defined $where) {
|
||||
$sql .= " WHERE $where ";
|
||||
}
|
||||
$sql .= " ORDER BY $order";
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @$values);
|
||||
bless ($_, $class) foreach @$objects;
|
||||
return $objects
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
sub id { return $_[0]->{$_[0]->ID_FIELD}; }
|
||||
sub name { return $_[0]->{$_[0]->NAME_FIELD}; }
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub set {
|
||||
my ($self, $field, $value) = @_;
|
||||
|
||||
# This method is protected. It's used to help implement set_ functions.
|
||||
caller->isa('Bugzilla::Object')
|
||||
|| ThrowCodeError('protection_violation',
|
||||
{ caller => caller,
|
||||
superclass => __PACKAGE__,
|
||||
function => 'Bugzilla::Object->set' });
|
||||
|
||||
my %validators = (%{$self->VALIDATORS}, %{$self->UPDATE_VALIDATORS});
|
||||
if (exists $validators{$field}) {
|
||||
my $validator = $validators{$field};
|
||||
$value = $self->$validator($value, $field);
|
||||
trick_taint($value) if (defined $value && !ref($value));
|
||||
|
||||
if ($self->can('_set_global_validator')) {
|
||||
$self->_set_global_validator($value, $field);
|
||||
}
|
||||
}
|
||||
|
||||
$self->{$field} = $value;
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $table = $self->DB_TABLE;
|
||||
my $id_field = $self->ID_FIELD;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
my $old_self = $self->new($self->id);
|
||||
|
||||
my %numeric = map { $_ => 1 } $self->NUMERIC_COLUMNS;
|
||||
my %date = map { $_ => 1 } $self->DATE_COLUMNS;
|
||||
my (@update_columns, @values, %changes);
|
||||
foreach my $column ($self->UPDATE_COLUMNS) {
|
||||
my ($old, $new) = ($old_self->{$column}, $self->{$column});
|
||||
# This has to be written this way in order to allow us to set a field
|
||||
# from undef or to undef, and avoid warnings about comparing an undef
|
||||
# with the "eq" operator.
|
||||
if (!defined $new || !defined $old) {
|
||||
next if !defined $new && !defined $old;
|
||||
}
|
||||
elsif ( ($numeric{$column} && $old == $new)
|
||||
|| ($date{$column} && str2time($old) == str2time($new))
|
||||
|| $old eq $new ) {
|
||||
next;
|
||||
}
|
||||
|
||||
trick_taint($new) if defined $new;
|
||||
push(@values, $new);
|
||||
push(@update_columns, $column);
|
||||
# We don't use $new because we don't want to detaint this for
|
||||
# the caller.
|
||||
$changes{$column} = [$old, $self->{$column}];
|
||||
}
|
||||
|
||||
my $columns = join(', ', map {"$_ = ?"} @update_columns);
|
||||
|
||||
$dbh->do("UPDATE $table SET $columns WHERE $id_field = ?", undef,
|
||||
@values, $self->id) if @values;
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
return \%changes;
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Subroutines ######
|
||||
###############################
|
||||
|
||||
sub create {
|
||||
my ($class, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
$class->check_required_create_fields($params);
|
||||
my $field_values = $class->run_create_validators($params);
|
||||
my $object = $class->insert_create_data($field_values);
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
sub check_required_create_fields {
|
||||
my ($class, $params) = @_;
|
||||
|
||||
foreach my $field ($class->REQUIRED_CREATE_FIELDS) {
|
||||
ThrowCodeError('param_required',
|
||||
{ function => "${class}->create", param => $field })
|
||||
if !exists $params->{$field};
|
||||
}
|
||||
}
|
||||
|
||||
sub run_create_validators {
|
||||
my ($class, $params) = @_;
|
||||
|
||||
my $validators = $class->VALIDATORS;
|
||||
|
||||
my %field_values;
|
||||
# We do the sort just to make sure that validation always
|
||||
# happens in a consistent order.
|
||||
foreach my $field (sort keys %$params) {
|
||||
my $value;
|
||||
if (exists $validators->{$field}) {
|
||||
my $validator = $validators->{$field};
|
||||
$value = $class->$validator($params->{$field}, $field);
|
||||
}
|
||||
else {
|
||||
$value = $params->{$field};
|
||||
}
|
||||
# We want people to be able to explicitly set fields to NULL,
|
||||
# and that means they can be set to undef.
|
||||
trick_taint($value) if defined $value && !ref($value);
|
||||
$field_values{$field} = $value;
|
||||
}
|
||||
|
||||
return \%field_values;
|
||||
}
|
||||
|
||||
sub insert_create_data {
|
||||
my ($class, $field_values) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my (@field_names, @values);
|
||||
while (my ($field, $value) = each %$field_values) {
|
||||
push(@field_names, $field);
|
||||
push(@values, $value);
|
||||
}
|
||||
|
||||
my $qmarks = '?,' x @field_names;
|
||||
chop($qmarks);
|
||||
my $table = $class->DB_TABLE;
|
||||
$dbh->do("INSERT INTO $table (" . join(', ', @field_names)
|
||||
. ") VALUES ($qmarks)", undef, @values);
|
||||
my $id = $dbh->bz_last_key($table, $class->ID_FIELD);
|
||||
return $class->new($id);
|
||||
}
|
||||
|
||||
sub get_all {
|
||||
my $class = shift;
|
||||
return @{$class->_do_list_select()};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Validators ######
|
||||
###############################
|
||||
|
||||
sub check_boolean { return $_[1] ? 1 : 0 }
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Object - A base class for objects in Bugzilla.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $object = new Bugzilla::Object(1);
|
||||
my $object = new Bugzilla::Object({name => 'TestProduct'});
|
||||
|
||||
my $id = $object->id;
|
||||
my $name = $object->name;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Object is a base class for Bugzilla objects. You never actually
|
||||
create a Bugzilla::Object directly, you only make subclasses of it.
|
||||
|
||||
Basically, Bugzilla::Object exists to allow developers to create objects
|
||||
more easily. All you have to do is define C<DB_TABLE>, C<DB_COLUMNS>,
|
||||
and sometimes C<LIST_ORDER> and you have a whole new object.
|
||||
|
||||
You should also define accessors for any columns other than C<name>
|
||||
or C<id>.
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
Frequently, these will be the only things you have to define in your
|
||||
subclass in order to have a fully-functioning object. C<DB_TABLE>
|
||||
and C<DB_COLUMNS> are required.
|
||||
|
||||
=over
|
||||
|
||||
=item C<DB_TABLE>
|
||||
|
||||
The name of the table that these objects are stored in. For example,
|
||||
for C<Bugzilla::Keyword> this would be C<keyworddefs>.
|
||||
|
||||
=item C<DB_COLUMNS>
|
||||
|
||||
The names of the columns that you want to read out of the database
|
||||
and into this object. This should be an array.
|
||||
|
||||
=item C<NAME_FIELD>
|
||||
|
||||
The name of the column that should be considered to be the unique
|
||||
"name" of this object. The 'name' is a B<string> that uniquely identifies
|
||||
this Object in the database. Defaults to 'name'. When you specify
|
||||
C<{name => $name}> to C<new()>, this is the column that will be
|
||||
matched against in the DB.
|
||||
|
||||
=item C<ID_FIELD>
|
||||
|
||||
The name of the column that represents the unique B<integer> ID
|
||||
of this object in the database. Defaults to 'id'.
|
||||
|
||||
=item C<LIST_ORDER>
|
||||
|
||||
The order that C<new_from_list> and C<get_all> should return objects
|
||||
in. This should be the name of a database column. Defaults to
|
||||
L</NAME_FIELD>.
|
||||
|
||||
=item C<REQUIRED_CREATE_FIELDS>
|
||||
|
||||
The list of fields that B<must> be specified when the user calls
|
||||
C<create()>. This should be an array.
|
||||
|
||||
=item C<VALIDATORS>
|
||||
|
||||
A hashref that points to a function that will validate each param to
|
||||
L</create>.
|
||||
|
||||
Validators are called both by L</create> and L</set>. When
|
||||
they are called by L</create>, the first argument will be the name
|
||||
of the class (what we normally call C<$class>).
|
||||
|
||||
When they are called by L</set>, the first argument will be
|
||||
a reference to the current object (what we normally call C<$self>).
|
||||
|
||||
The second argument will be the value passed to L</create> or
|
||||
L</set>for that field.
|
||||
|
||||
The third argument will be the name of the field being validated.
|
||||
This may be required by validators which validate several distinct fields.
|
||||
|
||||
These functions should call L<Bugzilla::Error/ThrowUserError> if they fail.
|
||||
|
||||
The validator must return the validated value.
|
||||
|
||||
=item C<UPDATE_VALIDATORS>
|
||||
|
||||
This is just like L</VALIDATORS>, but these validators are called only
|
||||
when updating an object, not when creating it. Any validator that appears
|
||||
here must not appear in L</VALIDATORS>.
|
||||
|
||||
L<Bugzilla::Bug> has good examples in its code of when to use this.
|
||||
|
||||
=item C<UPDATE_COLUMNS>
|
||||
|
||||
A list of columns to update when L</update> is called.
|
||||
If a field can't be changed, it shouldn't be listed here. (For example,
|
||||
the L</ID_FIELD> usually can't be updated.)
|
||||
|
||||
=item C<NUMERIC_COLUMNS>
|
||||
|
||||
When L</update> is called, it compares each column in the object to its
|
||||
current value in the database. It only updates columns that have changed.
|
||||
|
||||
Any column listed in NUMERIC_COLUMNS is treated as a number, not as a string,
|
||||
during these comparisons.
|
||||
|
||||
=item C<DATE_COLUMNS>
|
||||
|
||||
This is much like L</NUMERIC_COLUMNS>, except that it treats strings as
|
||||
dates when being compared. So, for example, C<2007-01-01> would be
|
||||
equal to C<2007-01-01 00:00:00>.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 Constructors
|
||||
|
||||
=over
|
||||
|
||||
=item C<new>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
The constructor is used to load an existing object from the database,
|
||||
by id or by name.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
If you pass an integer, the integer is the id of the object,
|
||||
from the database, that we want to read in. (id is defined
|
||||
as the value in the L</ID_FIELD> column).
|
||||
|
||||
If you pass in a hashref, you can pass a C<name> key. The
|
||||
value of the C<name> key is the case-insensitive name of the object
|
||||
(from L</NAME_FIELD>) in the DB.
|
||||
|
||||
B<Additional Parameters Available for Subclasses>
|
||||
|
||||
If you are a subclass of C<Bugzilla::Object>, you can pass
|
||||
C<condition> and C<values> as hash keys, instead of the above.
|
||||
|
||||
C<condition> is a set of SQL conditions for the WHERE clause, which contain
|
||||
placeholders.
|
||||
|
||||
C<values> is a reference to an array. The array contains the values
|
||||
for each placeholder in C<condition>, in order.
|
||||
|
||||
This is to allow subclasses to have complex parameters, and then to
|
||||
translate those parameters into C<condition> and C<values> when they
|
||||
call C<$self->SUPER::new> (which is this function, usually).
|
||||
|
||||
If you try to call C<new> outside of a subclass with the C<condition>
|
||||
and C<values> parameters, Bugzilla will throw an error. These parameters
|
||||
are intended B<only> for use by subclasses.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A fully-initialized object, or C<undef> if there is no object in the
|
||||
database matching the parameters you passed in.
|
||||
|
||||
=back
|
||||
|
||||
=item C<check>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Checks if there is an object in the database with the specified name, and
|
||||
throws an error if you specified an empty name, or if there is no object
|
||||
in the database with that name.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
The parameters are the same as for L</new>, except that if you don't pass
|
||||
a hashref, the single argument is the I<name> of the object, not the id.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A fully initialized object, guaranteed.
|
||||
|
||||
=item B<Notes For Implementors>
|
||||
|
||||
If you implement this in your subclass, make sure that you also update
|
||||
the C<object_name> block at the bottom of the F<global/user-error.html.tmpl>
|
||||
template.
|
||||
|
||||
=back
|
||||
|
||||
=item C<new_from_list(\@id_list)>
|
||||
|
||||
Description: Creates an array of objects, given an array of ids.
|
||||
|
||||
Params: \@id_list - A reference to an array of numbers, database ids.
|
||||
If any of these are not numeric, the function
|
||||
will throw an error. If any of these are not
|
||||
valid ids in the database, they will simply
|
||||
be skipped.
|
||||
|
||||
Returns: A reference to an array of objects.
|
||||
|
||||
=item C<match>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Gets a list of objects from the database based on certain criteria.
|
||||
|
||||
Basically, a simple way of doing a sort of "SELECT" statement (like SQL)
|
||||
to get objects.
|
||||
|
||||
All criteria are joined by C<AND>, so adding more criteria will give you
|
||||
a smaller set of results, not a larger set.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
A hashref, where the keys are column names of the table, pointing to the
|
||||
value that you want to match against for that column.
|
||||
|
||||
There are two special values, the constants C<NULL> and C<NOT_NULL>,
|
||||
which means "give me objects where this field is NULL or NOT NULL,
|
||||
respectively."
|
||||
|
||||
If you don't specify any criteria, calling this function is the same
|
||||
as doing C<[$class-E<gt>get_all]>.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
An arrayref of objects, or an empty arrayref if there are no matches.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Database Manipulation
|
||||
|
||||
=over
|
||||
|
||||
=item C<create>
|
||||
|
||||
Description: Creates a new item in the database.
|
||||
Throws a User Error if any of the passed-in params
|
||||
are invalid.
|
||||
|
||||
Params: C<$params> - hashref - A value to put in each database
|
||||
field for this object. Certain values must be set (the
|
||||
ones specified in L</REQUIRED_CREATE_FIELDS>), and
|
||||
the function will throw a Code Error if you don't set
|
||||
them.
|
||||
|
||||
Returns: The Object just created in the database.
|
||||
|
||||
Notes: In order for this function to work in your subclass,
|
||||
your subclass's L</ID_FIELD> must be of C<SERIAL>
|
||||
type in the database. Your subclass also must
|
||||
define L</REQUIRED_CREATE_FIELDS> and L</VALIDATORS>.
|
||||
|
||||
Subclass Implementors: This function basically just
|
||||
calls L</check_required_create_fields>, then
|
||||
L</run_create_validators>, and then finally
|
||||
L</insert_create_data>. So if you have a complex system that
|
||||
you need to implement, you can do it by calling these
|
||||
three functions instead of C<SUPER::create>.
|
||||
|
||||
=item C<check_required_create_fields>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Part of L</create>. Throws an error if any of the L</REQUIRED_CREATE_FIELDS>
|
||||
have not been specified in C<$params>
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$params> - The same as C<$params> from L</create>.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=back
|
||||
|
||||
=item C<run_create_validators>
|
||||
|
||||
Description: Runs the validation of input parameters for L</create>.
|
||||
This subroutine exists so that it can be overridden
|
||||
by subclasses who need to do special validations
|
||||
of their input parameters. This method is B<only> called
|
||||
by L</create>.
|
||||
|
||||
Params: The same as L</create>.
|
||||
|
||||
Returns: A hash, in a similar format as C<$params>, except that
|
||||
these are the values to be inserted into the database,
|
||||
not the values that were input to L</create>.
|
||||
|
||||
=item C<insert_create_data>
|
||||
|
||||
Part of L</create>.
|
||||
|
||||
Takes the return value from L</run_create_validators> and inserts the
|
||||
data into the database. Returns a newly created object.
|
||||
|
||||
=item C<update>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Saves the values currently in this object to the database.
|
||||
Only the fields specified in L</UPDATE_COLUMNS> will be
|
||||
updated, and they will only be updated if their values have changed.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hashref showing what changed during the update. The keys are the column
|
||||
names from L</UPDATE_COLUMNS>. If a field was not changed, it will not be
|
||||
in the hash at all. If the field was changed, the key will point to an arrayref.
|
||||
The first item of the arrayref will be the old value, and the second item
|
||||
will be the new value.
|
||||
|
||||
If there were no changes, we return a reference to an empty hash.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Subclass Helpers
|
||||
|
||||
These functions are intended only for use by subclasses. If
|
||||
you call them from anywhere else, they will throw a C<CodeError>.
|
||||
|
||||
=over
|
||||
|
||||
=item C<set>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Sets a certain hash member of this class to a certain value.
|
||||
Used for updating fields. Calls the validator for this field,
|
||||
if it exists. Subclasses should use this function
|
||||
to implement the various C<set_> mutators for their different
|
||||
fields.
|
||||
|
||||
If your class defines a method called C<_set_global_validator>,
|
||||
C<set> will call it with C<($value, $field)> as arguments, after running
|
||||
the validator for this particular field. C<_set_global_validator> does not
|
||||
return anything.
|
||||
|
||||
|
||||
See L</VALIDATORS> for more information.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$field> - The name of the hash member to update. This should
|
||||
be the same as the name of the field in L</VALIDATORS>, if it exists there.
|
||||
|
||||
=item C<$value> - The value that you're setting the field to.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Simple Validators
|
||||
|
||||
You can use these in your subclass L</VALIDATORS> or L</UPDATE_VALIDATORS>.
|
||||
Note that you have to reference them like C<\&Bugzilla::Object::check_boolean>,
|
||||
you can't just write C<\&check_boolean>.
|
||||
|
||||
=over
|
||||
|
||||
=item C<check_boolean>
|
||||
|
||||
Returns C<1> if the passed-in value is true, C<0> otherwise.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_all>
|
||||
|
||||
Description: Returns all objects in this table from the database.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of objects, or an empty list if there are none.
|
||||
|
||||
Notes: Note that you must call this as C<$class->get_all>. For
|
||||
example, C<Bugzilla::Keyword->get_all>.
|
||||
C<Bugzilla::Keyword::get_all> will not work.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,469 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Product;
|
||||
|
||||
use Bugzilla::Version;
|
||||
use Bugzilla::Milestone;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Group;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use Bugzilla::Install::Requirements;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use constant DEFAULT_CLASSIFICATION_ID => 1;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_TABLE => 'products';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
products.id
|
||||
products.name
|
||||
products.classification_id
|
||||
products.description
|
||||
products.milestoneurl
|
||||
products.disallownew
|
||||
products.votesperuser
|
||||
products.maxvotesperbug
|
||||
products.votestoconfirm
|
||||
products.defaultmilestone
|
||||
);
|
||||
|
||||
###############################
|
||||
#### Constructors #####
|
||||
###############################
|
||||
|
||||
# This is considerably faster than calling new_from_list three times
|
||||
# for each product in the list, particularly with hundreds or thousands
|
||||
# of products.
|
||||
sub preload {
|
||||
my ($products) = @_;
|
||||
my %prods = map { $_->id => $_ } @$products;
|
||||
my @prod_ids = keys %prods;
|
||||
return unless @prod_ids;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
foreach my $field (qw(component version milestone)) {
|
||||
my $classname = "Bugzilla::" . ucfirst($field);
|
||||
my $objects = $classname->match({ product_id => \@prod_ids });
|
||||
|
||||
# Now populate the products with this set of objects.
|
||||
foreach my $obj (@$objects) {
|
||||
my $product_id = $obj->product_id;
|
||||
$prods{$product_id}->{"${field}s"} ||= [];
|
||||
push(@{$prods{$product_id}->{"${field}s"}}, $obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub components {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{components}) {
|
||||
my $ids = $dbh->selectcol_arrayref(q{
|
||||
SELECT id FROM components
|
||||
WHERE product_id = ?
|
||||
ORDER BY name}, undef, $self->id);
|
||||
|
||||
require Bugzilla::Component;
|
||||
$self->{components} = Bugzilla::Component->new_from_list($ids);
|
||||
}
|
||||
return $self->{components};
|
||||
}
|
||||
|
||||
sub group_controls {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{group_controls}) {
|
||||
my $query = qq{SELECT
|
||||
groups.id,
|
||||
group_control_map.entry,
|
||||
group_control_map.membercontrol,
|
||||
group_control_map.othercontrol,
|
||||
group_control_map.canedit,
|
||||
group_control_map.editcomponents,
|
||||
group_control_map.editbugs,
|
||||
group_control_map.canconfirm
|
||||
FROM groups
|
||||
LEFT JOIN group_control_map
|
||||
ON groups.id = group_control_map.group_id
|
||||
WHERE group_control_map.product_id = ?
|
||||
AND groups.isbuggroup != 0
|
||||
ORDER BY groups.name};
|
||||
$self->{group_controls} =
|
||||
$dbh->selectall_hashref($query, 'id', undef, $self->id);
|
||||
|
||||
# For each group ID listed above, create and store its group object.
|
||||
my @gids = keys %{$self->{group_controls}};
|
||||
my $groups = Bugzilla::Group->new_from_list(\@gids);
|
||||
$self->{group_controls}->{$_->id}->{group} = $_ foreach @$groups;
|
||||
}
|
||||
return $self->{group_controls};
|
||||
}
|
||||
|
||||
sub groups_mandatory_for {
|
||||
my ($self, $user) = @_;
|
||||
my $groups = $user->groups_as_string;
|
||||
my $mandatory = CONTROLMAPMANDATORY;
|
||||
# For membercontrol we don't check group_id IN, because if membercontrol
|
||||
# is Mandatory, the group is Mandatory for everybody, regardless of their
|
||||
# group membership.
|
||||
my $ids = Bugzilla->dbh->selectcol_arrayref(
|
||||
"SELECT group_id FROM group_control_map
|
||||
WHERE product_id = ?
|
||||
AND (membercontrol = $mandatory
|
||||
OR (othercontrol = $mandatory
|
||||
AND group_id NOT IN ($groups)))",
|
||||
undef, $self->id);
|
||||
return Bugzilla::Group->new_from_list($ids);
|
||||
}
|
||||
|
||||
sub groups_valid {
|
||||
my ($self) = @_;
|
||||
return $self->{groups_valid} if defined $self->{groups_valid};
|
||||
|
||||
# Note that we don't check OtherControl below, because there is no
|
||||
# valid NA/* combination.
|
||||
my $ids = Bugzilla->dbh->selectcol_arrayref(
|
||||
"SELECT DISTINCT group_id
|
||||
FROM group_control_map AS gcm
|
||||
INNER JOIN groups ON gcm.group_id = groups.id
|
||||
WHERE product_id = ? AND isbuggroup = 1
|
||||
AND membercontrol != " . CONTROLMAPNA, undef, $self->id);
|
||||
$self->{groups_valid} = Bugzilla::Group->new_from_list($ids);
|
||||
return $self->{groups_valid};
|
||||
}
|
||||
|
||||
sub versions {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{versions}) {
|
||||
my $ids = $dbh->selectcol_arrayref(q{
|
||||
SELECT id FROM versions
|
||||
WHERE product_id = ?}, undef, $self->id);
|
||||
|
||||
$self->{versions} = Bugzilla::Version->new_from_list($ids);
|
||||
}
|
||||
return $self->{versions};
|
||||
}
|
||||
|
||||
sub milestones {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{milestones}) {
|
||||
my $ids = $dbh->selectcol_arrayref(q{
|
||||
SELECT id FROM milestones
|
||||
WHERE product_id = ?}, undef, $self->id);
|
||||
|
||||
$self->{milestones} = Bugzilla::Milestone->new_from_list($ids);
|
||||
}
|
||||
return $self->{milestones};
|
||||
}
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_count'}) {
|
||||
$self->{'bug_count'} = $dbh->selectrow_array(qq{
|
||||
SELECT COUNT(bug_id) FROM bugs
|
||||
WHERE product_id = ?}, undef, $self->id);
|
||||
|
||||
}
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
sub bug_ids {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_ids'}) {
|
||||
$self->{'bug_ids'} =
|
||||
$dbh->selectcol_arrayref(q{SELECT bug_id FROM bugs
|
||||
WHERE product_id = ?},
|
||||
undef, $self->id);
|
||||
}
|
||||
return $self->{'bug_ids'};
|
||||
}
|
||||
|
||||
sub user_has_access {
|
||||
my ($self, $user) = @_;
|
||||
|
||||
return Bugzilla->dbh->selectrow_array(
|
||||
'SELECT CASE WHEN group_id IS NULL THEN 1 ELSE 0 END
|
||||
FROM products LEFT JOIN group_control_map
|
||||
ON group_control_map.product_id = products.id
|
||||
AND group_control_map.entry != 0
|
||||
AND group_id NOT IN (' . $user->groups_as_string . ')
|
||||
WHERE products.id = ? ' . Bugzilla->dbh->sql_limit(1),
|
||||
undef, $self->id);
|
||||
}
|
||||
|
||||
sub flag_types {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'flag_types'}) {
|
||||
$self->{'flag_types'} = {};
|
||||
foreach my $type ('bug', 'attachment') {
|
||||
my %flagtypes;
|
||||
foreach my $component (@{$self->components}) {
|
||||
foreach my $flagtype (@{$component->flag_types->{$type}}) {
|
||||
$flagtypes{$flagtype->{'id'}} ||= $flagtype;
|
||||
}
|
||||
}
|
||||
$self->{'flag_types'}->{$type} = [sort { $a->{'sortkey'} <=> $b->{'sortkey'}
|
||||
|| $a->{'name'} cmp $b->{'name'} } values %flagtypes];
|
||||
}
|
||||
}
|
||||
return $self->{'flag_types'};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub milestone_url { return $_[0]->{'milestoneurl'}; }
|
||||
sub disallow_new { return $_[0]->{'disallownew'}; }
|
||||
sub votes_per_user { return $_[0]->{'votesperuser'}; }
|
||||
sub max_votes_per_bug { return $_[0]->{'maxvotesperbug'}; }
|
||||
sub votes_to_confirm { return $_[0]->{'votestoconfirm'}; }
|
||||
sub default_milestone { return $_[0]->{'defaultmilestone'}; }
|
||||
sub classification_id { return $_[0]->{'classification_id'}; }
|
||||
|
||||
###############################
|
||||
#### Subroutines ######
|
||||
###############################
|
||||
|
||||
sub check_product {
|
||||
my ($product_name) = @_;
|
||||
|
||||
unless ($product_name) {
|
||||
ThrowUserError('product_not_specified');
|
||||
}
|
||||
my $product = new Bugzilla::Product({name => $product_name});
|
||||
unless ($product) {
|
||||
ThrowUserError('product_doesnt_exist',
|
||||
{'product' => $product_name});
|
||||
}
|
||||
return $product;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Product - Bugzilla product class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Product;
|
||||
|
||||
my $product = new Bugzilla::Product(1);
|
||||
my $product = new Bugzilla::Product({ name => 'AcmeProduct' });
|
||||
|
||||
my @components = $product->components();
|
||||
my $groups_controls = $product->group_controls();
|
||||
my @milestones = $product->milestones();
|
||||
my @versions = $product->versions();
|
||||
my $bugcount = $product->bug_count();
|
||||
my $bug_ids = $product->bug_ids();
|
||||
my $has_access = $product->user_has_access($user);
|
||||
my $flag_types = $product->flag_types();
|
||||
|
||||
my $id = $product->id;
|
||||
my $name = $product->name;
|
||||
my $description = $product->description;
|
||||
my $milestoneurl = $product->milestone_url;
|
||||
my disallownew = $product->disallow_new;
|
||||
my votesperuser = $product->votes_per_user;
|
||||
my maxvotesperbug = $product->max_votes_per_bug;
|
||||
my votestoconfirm = $product->votes_to_confirm;
|
||||
my $defaultmilestone = $product->default_milestone;
|
||||
my $classificationid = $product->classification_id;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Product.pm represents a product object. It is an implementation
|
||||
of L<Bugzilla::Object>, and thus provides all methods that
|
||||
L<Bugzilla::Object> provides.
|
||||
|
||||
The methods that are specific to C<Bugzilla::Product> are listed
|
||||
below.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<components>
|
||||
|
||||
Description: Returns an array of component objects belonging to
|
||||
the product.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: An array of Bugzilla::Component object.
|
||||
|
||||
=item C<group_controls()>
|
||||
|
||||
Description: Returns a hash (group id as key) with all product
|
||||
group controls.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A hash with group id as key and hash containing
|
||||
a Bugzilla::Group object and the properties of group
|
||||
relative to the product.
|
||||
|
||||
=item C<groups_mandatory_for>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Tells you what groups are mandatory for bugs in this product.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
C<$user> - The user who you want to check.
|
||||
|
||||
=item B<Returns> An arrayref of C<Bugzilla::Group> objects.
|
||||
|
||||
=back
|
||||
|
||||
=item C<groups_valid>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns an arrayref of L<Bugzilla::Group> objects, representing groups
|
||||
that bugs could validly be restricted to within this product. Used mostly
|
||||
by L<Bugzilla::Bug> to assure that you're adding valid groups to a bug.
|
||||
|
||||
B<Note>: This doesn't check whether or not the current user can add/remove
|
||||
bugs to/from these groups. It just tells you that bugs I<could be in> these
|
||||
groups, in this product.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns> An arrayref of L<Bugzilla::Group> objects.
|
||||
|
||||
=back
|
||||
|
||||
=item C<versions>
|
||||
|
||||
Description: Returns all valid versions for that product.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: An array of Bugzilla::Version objects.
|
||||
|
||||
=item C<milestones>
|
||||
|
||||
Description: Returns all valid milestones for that product.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: An array of Bugzilla::Milestone objects.
|
||||
|
||||
=item C<bug_count()>
|
||||
|
||||
Description: Returns the total of bugs that belong to the product.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer with the number of bugs.
|
||||
|
||||
=item C<bug_ids()>
|
||||
|
||||
Description: Returns the IDs of bugs that belong to the product.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: An array of integer.
|
||||
|
||||
=item C<user_has_access()>
|
||||
|
||||
Description: Tells you whether or not the user is allowed to enter
|
||||
bugs into this product, based on the C<entry> group
|
||||
control. To see whether or not a user can actually
|
||||
enter a bug into a product, use C<$user->can_enter_product>.
|
||||
|
||||
Params: C<$user> - A Bugzilla::User object.
|
||||
|
||||
Returns C<1> If this user's groups allow him C<entry> access to
|
||||
this Product, C<0> otherwise.
|
||||
|
||||
=item C<flag_types()>
|
||||
|
||||
Description: Returns flag types available for at least one of
|
||||
its components.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Two references to an array of flagtype objects.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<preload>
|
||||
|
||||
When passed an arrayref of C<Bugzilla::Product> objects, preloads their
|
||||
L</milestones>, L</components>, and L</versions>, which is much faster
|
||||
than calling those accessors on every item in the array individually.
|
||||
|
||||
This function is not exported, so must be called like
|
||||
C<Bugzilla::Product::preload($products)>.
|
||||
|
||||
=item C<check_product($product_name)>
|
||||
|
||||
Description: Checks if the product name was passed in and if is a valid
|
||||
product.
|
||||
|
||||
Params: $product_name - String with a product name.
|
||||
|
||||
Returns: Bugzilla::Product object.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla::Object>
|
||||
|
||||
=cut
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,530 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): C. Begle
|
||||
# Jesse Ruderman
|
||||
# Andreas Franke <afranke@mathweb.org>
|
||||
# Stephen Lee <slee@uk.bnsmc.com>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Search::Quicksearch;
|
||||
|
||||
# Make it harder for us to do dangerous things in Perl.
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Keyword;
|
||||
use Bugzilla::Status;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Search::Quicksearch::EXPORT = qw(quicksearch);
|
||||
|
||||
# Word renamings
|
||||
use constant MAPPINGS => {
|
||||
# Status, Resolution, Platform, OS, Priority, Severity
|
||||
"status" => "bug_status",
|
||||
"resolution" => "resolution", # no change
|
||||
"platform" => "rep_platform",
|
||||
"os" => "op_sys",
|
||||
"opsys" => "op_sys",
|
||||
"priority" => "priority", # no change
|
||||
"pri" => "priority",
|
||||
"severity" => "bug_severity",
|
||||
"sev" => "bug_severity",
|
||||
# People: AssignedTo, Reporter, QA Contact, CC, Added comment (?)
|
||||
"owner" => "assigned_to", # deprecated since bug 76507
|
||||
"assignee" => "assigned_to",
|
||||
"assignedto" => "assigned_to",
|
||||
"reporter" => "reporter", # no change
|
||||
"rep" => "reporter",
|
||||
"qa" => "qa_contact",
|
||||
"qacontact" => "qa_contact",
|
||||
"cc" => "cc", # no change
|
||||
# Product, Version, Component, Target Milestone
|
||||
"product" => "product", # no change
|
||||
"prod" => "product",
|
||||
"version" => "version", # no change
|
||||
"ver" => "version",
|
||||
"component" => "component", # no change
|
||||
"comp" => "component",
|
||||
"milestone" => "target_milestone",
|
||||
"target" => "target_milestone",
|
||||
"targetmilestone" => "target_milestone",
|
||||
# Summary, Description, URL, Status whiteboard, Keywords
|
||||
"summary" => "short_desc",
|
||||
"shortdesc" => "short_desc",
|
||||
"desc" => "longdesc",
|
||||
"description" => "longdesc",
|
||||
#"comment" => "longdesc", # ???
|
||||
# reserve "comment" for "added comment" email search?
|
||||
"longdesc" => "longdesc",
|
||||
"url" => "bug_file_loc",
|
||||
"whiteboard" => "status_whiteboard",
|
||||
"statuswhiteboard" => "status_whiteboard",
|
||||
"sw" => "status_whiteboard",
|
||||
"keywords" => "keywords", # no change
|
||||
"kw" => "keywords",
|
||||
"group" => "bug_group",
|
||||
"flag" => "flagtypes.name",
|
||||
"requestee" => "requestees.login_name",
|
||||
"req" => "requestees.login_name",
|
||||
"setter" => "setters.login_name",
|
||||
"set" => "setters.login_name",
|
||||
# Attachments
|
||||
"attachment" => "attachments.description",
|
||||
"attachmentdesc" => "attachments.description",
|
||||
"attachdesc" => "attachments.description",
|
||||
"attachmentdata" => "attach_data.thedata",
|
||||
"attachdata" => "attach_data.thedata",
|
||||
"attachmentmimetype" => "attachments.mimetype",
|
||||
"attachmimetype" => "attachments.mimetype"
|
||||
};
|
||||
|
||||
# We might want to put this into localconfig or somewhere
|
||||
use constant PLATFORMS => ('pc', 'sun', 'macintosh', 'mac');
|
||||
use constant OPSYSTEMS => ('windows', 'win', 'linux');
|
||||
use constant PRODUCT_EXCEPTIONS => (
|
||||
'row', # [Browser]
|
||||
# ^^^
|
||||
'new', # [MailNews]
|
||||
# ^^^
|
||||
);
|
||||
use constant COMPONENT_EXCEPTIONS => (
|
||||
'hang' # [Bugzilla: Component/Keyword Changes]
|
||||
# ^^^^
|
||||
);
|
||||
|
||||
# Quicksearch-wide globals for boolean charts.
|
||||
our ($chart, $and, $or);
|
||||
|
||||
sub quicksearch {
|
||||
my ($searchstring) = (@_);
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $urlbase = correct_urlbase();
|
||||
|
||||
$chart = 0;
|
||||
$and = 0;
|
||||
$or = 0;
|
||||
|
||||
# Remove leading and trailing commas and whitespace.
|
||||
$searchstring =~ s/(^[\s,]+|[\s,]+$)//g;
|
||||
ThrowUserError('buglist_parameters_required') unless ($searchstring);
|
||||
|
||||
if ($searchstring =~ m/^[0-9,\s]*$/) {
|
||||
# Bug number(s) only.
|
||||
|
||||
# Allow separation by comma or whitespace.
|
||||
$searchstring =~ s/[,\s]+/,/g;
|
||||
|
||||
if (index($searchstring, ',') < $[) {
|
||||
# Single bug number; shortcut to show_bug.cgi.
|
||||
print $cgi->redirect(-uri => "${urlbase}show_bug.cgi?id=$searchstring");
|
||||
exit;
|
||||
}
|
||||
else {
|
||||
# List of bug numbers.
|
||||
$cgi->param('bug_id', $searchstring);
|
||||
$cgi->param('order', 'bugs.bug_id');
|
||||
$cgi->param('bugidtype', 'include');
|
||||
}
|
||||
}
|
||||
else {
|
||||
# It's not just a bug number or a list of bug numbers.
|
||||
# Maybe it's an alias?
|
||||
if ($searchstring =~ /^([^,\s]+)$/) {
|
||||
if (Bugzilla->dbh->selectrow_array(q{SELECT COUNT(*)
|
||||
FROM bugs
|
||||
WHERE alias = ?},
|
||||
undef,
|
||||
$1)) {
|
||||
print $cgi->redirect(-uri => "${urlbase}show_bug.cgi?id=$1");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
# It's no alias either, so it's a more complex query.
|
||||
my $legal_statuses = get_legal_field_values('bug_status');
|
||||
my $legal_resolutions = get_legal_field_values('resolution');
|
||||
|
||||
# Globally translate " AND ", " OR ", " NOT " to space, pipe, dash.
|
||||
$searchstring =~ s/\s+AND\s+/ /g;
|
||||
$searchstring =~ s/\s+OR\s+/|/g;
|
||||
$searchstring =~ s/\s+NOT\s+/ -/g;
|
||||
|
||||
my @words = splitString($searchstring);
|
||||
my $searchComments =
|
||||
$#words < Bugzilla->params->{'quicksearch_comment_cutoff'};
|
||||
my @openStates = BUG_STATE_OPEN;
|
||||
my @closedStates;
|
||||
my @unknownFields;
|
||||
my (%states, %resolutions);
|
||||
|
||||
foreach (@$legal_statuses) {
|
||||
push(@closedStates, $_) unless is_open_state($_);
|
||||
}
|
||||
foreach (@openStates) { $states{$_} = 1 }
|
||||
if ($words[0] eq 'ALL') {
|
||||
foreach (@$legal_statuses) { $states{$_} = 1 }
|
||||
shift @words;
|
||||
}
|
||||
elsif ($words[0] eq 'OPEN') {
|
||||
shift @words;
|
||||
}
|
||||
elsif ($words[0] =~ /^\+[A-Z]+(,[A-Z]+)*$/) {
|
||||
# e.g. +DUP,FIX
|
||||
if (matchPrefixes(\%states,
|
||||
\%resolutions,
|
||||
[split(/,/, substr($words[0], 1))],
|
||||
\@closedStates,
|
||||
$legal_resolutions)) {
|
||||
shift @words;
|
||||
# Allowing additional resolutions means we need to keep
|
||||
# the "no resolution" resolution.
|
||||
$resolutions{'---'} = 1;
|
||||
}
|
||||
else {
|
||||
# Carry on if no match found.
|
||||
}
|
||||
}
|
||||
elsif ($words[0] =~ /^[A-Z]+(,[A-Z]+)*$/) {
|
||||
# e.g. NEW,ASSI,REOP,FIX
|
||||
undef %states;
|
||||
if (matchPrefixes(\%states,
|
||||
\%resolutions,
|
||||
[split(/,/, $words[0])],
|
||||
$legal_statuses,
|
||||
$legal_resolutions)) {
|
||||
shift @words;
|
||||
}
|
||||
else {
|
||||
# Carry on if no match found
|
||||
foreach (@openStates) { $states{$_} = 1 }
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Default: search for unresolved bugs only.
|
||||
# Put custom code here if you would like to change this behaviour.
|
||||
}
|
||||
|
||||
# If we have wanted resolutions, allow closed states
|
||||
if (keys(%resolutions)) {
|
||||
foreach (@closedStates) { $states{$_} = 1 }
|
||||
}
|
||||
|
||||
$cgi->param('bug_status', keys(%states));
|
||||
$cgi->param('resolution', keys(%resolutions));
|
||||
|
||||
# Loop over all main-level QuickSearch words.
|
||||
foreach my $qsword (@words) {
|
||||
my $negate = substr($qsword, 0, 1) eq '-';
|
||||
if ($negate) {
|
||||
$qsword = substr($qsword, 1);
|
||||
}
|
||||
|
||||
my $firstChar = substr($qsword, 0, 1);
|
||||
my $baseWord = substr($qsword, 1);
|
||||
my @subWords = split(/[\|,]/, $baseWord);
|
||||
if ($firstChar eq '+') {
|
||||
foreach (@subWords) {
|
||||
addChart('short_desc', 'substring', $qsword, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq '#') {
|
||||
addChart('short_desc', 'anywords', $baseWord, $negate);
|
||||
if ($searchComments) {
|
||||
addChart('longdesc', 'anywords', $baseWord, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq ':') {
|
||||
foreach (@subWords) {
|
||||
addChart('product', 'substring', $_, $negate);
|
||||
addChart('component', 'substring', $_, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq '@') {
|
||||
foreach (@subWords) {
|
||||
addChart('assigned_to', 'substring', $_, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq '[') {
|
||||
addChart('short_desc', 'substring', $baseWord, $negate);
|
||||
addChart('status_whiteboard', 'substring', $baseWord, $negate);
|
||||
}
|
||||
elsif ($firstChar eq '!') {
|
||||
addChart('keywords', 'anywords', $baseWord, $negate);
|
||||
|
||||
}
|
||||
else { # No special first char
|
||||
|
||||
# Split by '|' to get all operands for a boolean OR.
|
||||
foreach my $or_operand (split(/\|/, $qsword)) {
|
||||
if ($or_operand =~ /^votes:([0-9]+)$/) {
|
||||
# votes:xx ("at least xx votes")
|
||||
addChart('votes', 'greaterthan', $1 - 1, $negate);
|
||||
}
|
||||
elsif ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
|
||||
# Flag and requestee shortcut
|
||||
addChart('flagtypes.name', 'substring', $1, $negate);
|
||||
$chart++; $and = $or = 0; # Next chart for boolean AND
|
||||
addChart('requestees.login_name', 'substring', $2, $negate);
|
||||
}
|
||||
elsif ($or_operand =~ /^([^:]+):([^:]+)$/) {
|
||||
# generic field1,field2,field3:value1,value2 notation
|
||||
my @fields = split(/,/, $1);
|
||||
my @values = split(/,/, $2);
|
||||
foreach my $field (@fields) {
|
||||
# Skip and record any unknown fields
|
||||
if (!defined(MAPPINGS->{$field})) {
|
||||
push(@unknownFields, $field);
|
||||
next;
|
||||
}
|
||||
$field = MAPPINGS->{$field};
|
||||
foreach (@values) {
|
||||
addChart($field, 'substring', $_, $negate);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
# Having ruled out the special cases, we may now split
|
||||
# by comma, which is another legal boolean OR indicator.
|
||||
foreach my $word (split(/,/, $or_operand)) {
|
||||
# Platform and operating system
|
||||
if (grep({lc($word) eq $_} PLATFORMS)
|
||||
|| grep({lc($word) eq $_} OPSYSTEMS)) {
|
||||
addChart('rep_platform', 'substring',
|
||||
$word, $negate);
|
||||
addChart('op_sys', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
# Priority
|
||||
elsif ($word =~ m/^[pP]([1-5](-[1-5])?)$/) {
|
||||
addChart('priority', 'regexp',
|
||||
"[$1]", $negate);
|
||||
}
|
||||
# Severity
|
||||
elsif (grep({lc($word) eq substr($_, 0, 3)}
|
||||
@{get_legal_field_values('bug_severity')})) {
|
||||
addChart('bug_severity', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
# Votes (votes>xx)
|
||||
elsif ($word =~ m/^votes>([0-9]+)$/) {
|
||||
addChart('votes', 'greaterthan',
|
||||
$1, $negate);
|
||||
}
|
||||
# Votes (votes>=xx, votes=>xx)
|
||||
elsif ($word =~ m/^votes(>=|=>)([0-9]+)$/) {
|
||||
addChart('votes', 'greaterthan',
|
||||
$2-1, $negate);
|
||||
|
||||
}
|
||||
else { # Default QuickSearch word
|
||||
|
||||
if (!grep({lc($word) eq $_}
|
||||
PRODUCT_EXCEPTIONS) &&
|
||||
length($word)>2
|
||||
) {
|
||||
addChart('product', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
if (!grep({lc($word) eq $_}
|
||||
COMPONENT_EXCEPTIONS) &&
|
||||
length($word)>2
|
||||
) {
|
||||
addChart('component', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
if (grep({lc($word) eq $_}
|
||||
map($_->name, Bugzilla::Keyword->get_all))) {
|
||||
addChart('keywords', 'substring',
|
||||
$word, $negate);
|
||||
if (length($word)>2) {
|
||||
addChart('short_desc', 'substring',
|
||||
$word, $negate);
|
||||
addChart('status_whiteboard',
|
||||
'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
addChart('short_desc', 'substring',
|
||||
$word, $negate);
|
||||
addChart('status_whiteboard', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
if ($searchComments) {
|
||||
addChart('longdesc', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
}
|
||||
# URL field (for IP addrs, host.names,
|
||||
# scheme://urls)
|
||||
if ($word =~ m/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/
|
||||
|| $word =~ /^[A-Za-z]+(\.[A-Za-z]+)+/
|
||||
|| $word =~ /:[\\\/][\\\/]/
|
||||
|| $word =~ /localhost/
|
||||
|| $word =~ /mailto[:]?/
|
||||
# || $word =~ /[A-Za-z]+[:][0-9]+/ #host:port
|
||||
) {
|
||||
addChart('bug_file_loc', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
} # foreach my $word (split(/,/, $qsword))
|
||||
} # votes and generic field detection
|
||||
} # foreach (split(/\|/, $_))
|
||||
} # "switch" $firstChar
|
||||
$chart++;
|
||||
$and = 0;
|
||||
$or = 0;
|
||||
} # foreach (@words)
|
||||
|
||||
# Inform user about any unknown fields
|
||||
if (scalar(@unknownFields)) {
|
||||
ThrowUserError("quicksearch_unknown_field",
|
||||
{ fields => \@unknownFields });
|
||||
}
|
||||
|
||||
# Make sure we have some query terms left
|
||||
scalar($cgi->param())>0 || ThrowUserError("buglist_parameters_required");
|
||||
}
|
||||
|
||||
# List of quicksearch-specific CGI parameters to get rid of.
|
||||
my @params_to_strip = ('quicksearch', 'load', 'run');
|
||||
my $modified_query_string = $cgi->canonicalise_query(@params_to_strip);
|
||||
|
||||
if ($cgi->param('load')) {
|
||||
# Param 'load' asks us to display the query in the advanced search form.
|
||||
print $cgi->redirect(-uri => "${urlbase}query.cgi?format=advanced&"
|
||||
. $modified_query_string);
|
||||
}
|
||||
|
||||
# Otherwise, pass the modified query string to the caller.
|
||||
# We modified $cgi->params, so the caller can choose to look at that, too,
|
||||
# and disregard the return value.
|
||||
$cgi->delete(@params_to_strip);
|
||||
return $modified_query_string;
|
||||
}
|
||||
|
||||
###########################################################################
|
||||
# Helpers
|
||||
###########################################################################
|
||||
|
||||
# Split string on whitespace, retaining quoted strings as one
|
||||
sub splitString {
|
||||
my $string = shift;
|
||||
my @quoteparts;
|
||||
my @parts;
|
||||
my $i = 0;
|
||||
|
||||
# Now split on quote sign; be tolerant about unclosed quotes
|
||||
@quoteparts = split(/"/, $string);
|
||||
foreach my $part (@quoteparts) {
|
||||
# After every odd quote, quote special chars
|
||||
$part = url_quote($part) if $i++ % 2;
|
||||
}
|
||||
# Join again
|
||||
$string = join('"', @quoteparts);
|
||||
|
||||
# Now split on unescaped whitespace
|
||||
@parts = split(/\s+/, $string);
|
||||
foreach (@parts) {
|
||||
# Protect plus signs from becoming a blank.
|
||||
# If "+" appears as the first character, leave it alone
|
||||
# as it has a special meaning. Strings which start with
|
||||
# "+" must be quoted.
|
||||
s/(?<!^)\+/%2B/g;
|
||||
# Remove quotes
|
||||
s/"//g;
|
||||
}
|
||||
return @parts;
|
||||
}
|
||||
|
||||
# Expand found prefixes to states or resolutions
|
||||
sub matchPrefixes {
|
||||
my $hr_states = shift;
|
||||
my $hr_resolutions = shift;
|
||||
my $ar_prefixes = shift;
|
||||
my $ar_check_states = shift;
|
||||
my $ar_check_resolutions = shift;
|
||||
my $foundMatch = 0;
|
||||
|
||||
foreach my $prefix (@$ar_prefixes) {
|
||||
foreach (@$ar_check_states) {
|
||||
if (/^$prefix/) {
|
||||
$$hr_states{$_} = 1;
|
||||
$foundMatch = 1;
|
||||
}
|
||||
}
|
||||
foreach (@$ar_check_resolutions) {
|
||||
if (/^$prefix/) {
|
||||
$$hr_resolutions{$_} = 1;
|
||||
$foundMatch = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $foundMatch;
|
||||
}
|
||||
|
||||
# Negate comparison type
|
||||
sub negateComparisonType {
|
||||
my $comparisonType = shift;
|
||||
|
||||
if ($comparisonType eq 'substring') {
|
||||
return 'notsubstring';
|
||||
}
|
||||
elsif ($comparisonType eq 'anywords') {
|
||||
return 'nowords';
|
||||
}
|
||||
elsif ($comparisonType eq 'regexp') {
|
||||
return 'notregexp';
|
||||
}
|
||||
else {
|
||||
# Don't know how to negate that
|
||||
ThrowCodeError('unknown_comparison_type');
|
||||
}
|
||||
}
|
||||
|
||||
# Add a boolean chart
|
||||
sub addChart {
|
||||
my ($field, $comparisonType, $value, $negate) = @_;
|
||||
|
||||
$negate && ($comparisonType = negateComparisonType($comparisonType));
|
||||
makeChart("$chart-$and-$or", $field, $comparisonType, $value);
|
||||
if ($negate) {
|
||||
$and++;
|
||||
$or = 0;
|
||||
}
|
||||
else {
|
||||
$or++;
|
||||
}
|
||||
}
|
||||
|
||||
# Create the CGI parameters for a boolean chart
|
||||
sub makeChart {
|
||||
my ($expr, $field, $type, $value) = @_;
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$cgi->param("field$expr", $field);
|
||||
$cgi->param("type$expr", $type);
|
||||
$cgi->param("value$expr", url_decode($value));
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,314 +0,0 @@
|
||||
# -*- 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 Everything Solved.
|
||||
# Portions created by Everything Solved are Copyright (C) 2006
|
||||
# Everything Solved. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Search::Saved;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::CGI;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Group;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Search qw(IsValidQueryType);
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
|
||||
#############
|
||||
# Constants #
|
||||
#############
|
||||
|
||||
use constant DB_TABLE => 'namedqueries';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
userid
|
||||
name
|
||||
query
|
||||
query_type
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(name query);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
query => \&_check_query,
|
||||
query_type => \&_check_query_type,
|
||||
link_in_footer => \&_check_link_in_footer,
|
||||
};
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(name query query_type);
|
||||
|
||||
##############
|
||||
# Validators #
|
||||
##############
|
||||
|
||||
sub _check_link_in_footer { return $_[1] ? 1 : 0; }
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name) = @_;
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError("query_name_missing");
|
||||
$name !~ /[<>&]/ || ThrowUserError("illegal_query_name");
|
||||
if (length($name) > MAX_LEN_QUERY_NAME) {
|
||||
ThrowUserError("query_name_too_long");
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_query {
|
||||
my ($invocant, $query) = @_;
|
||||
$query || ThrowUserError("buglist_parameters_required");
|
||||
my $cgi = new Bugzilla::CGI($query);
|
||||
$cgi->clean_search_url;
|
||||
# Don't store the query name as a parameter.
|
||||
$cgi->delete('known_name');
|
||||
return $cgi->query_string;
|
||||
}
|
||||
|
||||
sub _check_query_type {
|
||||
my ($invocant, $type) = @_;
|
||||
# Right now the only query type is LIST_OF_BUGS.
|
||||
return $type ? LIST_OF_BUGS : QUERY_LIST;
|
||||
}
|
||||
|
||||
#########################
|
||||
# Database Manipulation #
|
||||
#########################
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
Bugzilla->login(LOGIN_REQUIRED);
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$class->check_required_create_fields(@_);
|
||||
$dbh->bz_start_transaction();
|
||||
my $params = $class->run_create_validators(@_);
|
||||
|
||||
# Right now you can only create a Saved Search for the current user.
|
||||
$params->{userid} = Bugzilla->user->id;
|
||||
|
||||
my $lif = delete $params->{link_in_footer};
|
||||
my $obj = $class->insert_create_data($params);
|
||||
if ($lif) {
|
||||
$dbh->do('INSERT INTO namedqueries_link_in_footer
|
||||
(user_id, namedquery_id) VALUES (?,?)',
|
||||
undef, $params->{userid}, $obj->id);
|
||||
}
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
sub preload {
|
||||
my ($searches) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return unless scalar @$searches;
|
||||
|
||||
my @query_ids = map { $_->id } @$searches;
|
||||
my $queries_in_footer = $dbh->selectcol_arrayref(
|
||||
'SELECT namedquery_id
|
||||
FROM namedqueries_link_in_footer
|
||||
WHERE ' . $dbh->sql_in('namedquery_id', \@query_ids) . ' AND user_id = ?',
|
||||
undef, Bugzilla->user->id);
|
||||
|
||||
my %links_in_footer = map { $_ => 1 } @$queries_in_footer;
|
||||
foreach my $query (@$searches) {
|
||||
$query->{link_in_footer} = ($links_in_footer{$query->id}) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
#####################
|
||||
# Complex Accessors #
|
||||
#####################
|
||||
|
||||
sub edit_link {
|
||||
my ($self) = @_;
|
||||
return $self->{edit_link} if defined $self->{edit_link};
|
||||
my $cgi = new Bugzilla::CGI($self->url);
|
||||
if (!$cgi->param('query_type')
|
||||
|| !IsValidQueryType($cgi->param('query_type')))
|
||||
{
|
||||
$cgi->param('query_type', 'advanced');
|
||||
}
|
||||
$self->{edit_link} = $cgi->canonicalise_query;
|
||||
return $self->{edit_link};
|
||||
}
|
||||
|
||||
sub used_in_whine {
|
||||
my ($self) = @_;
|
||||
return $self->{used_in_whine} if exists $self->{used_in_whine};
|
||||
($self->{used_in_whine}) = Bugzilla->dbh->selectrow_array(
|
||||
'SELECT 1 FROM whine_events INNER JOIN whine_queries
|
||||
ON whine_events.id = whine_queries.eventid
|
||||
WHERE whine_events.owner_userid = ? AND query_name = ?', undef,
|
||||
$self->{userid}, $self->name) || 0;
|
||||
return $self->{used_in_whine};
|
||||
}
|
||||
|
||||
sub link_in_footer {
|
||||
my ($self, $user) = @_;
|
||||
# We only cache link_in_footer for the current Bugzilla->user.
|
||||
return $self->{link_in_footer} if exists $self->{link_in_footer} && !$user;
|
||||
my $user_id = $user ? $user->id : Bugzilla->user->id;
|
||||
my $link_in_footer = Bugzilla->dbh->selectrow_array(
|
||||
'SELECT 1 FROM namedqueries_link_in_footer
|
||||
WHERE namedquery_id = ? AND user_id = ?',
|
||||
undef, $self->id, $user_id) || 0;
|
||||
$self->{link_in_footer} = $link_in_footer if !$user;
|
||||
return $link_in_footer;
|
||||
}
|
||||
|
||||
sub shared_with_group {
|
||||
my ($self) = @_;
|
||||
return $self->{shared_with_group} if exists $self->{shared_with_group};
|
||||
# Bugzilla only currently supports sharing with one group, even
|
||||
# though the database backend allows for an infinite number.
|
||||
my ($group_id) = Bugzilla->dbh->selectrow_array(
|
||||
'SELECT group_id FROM namedquery_group_map WHERE namedquery_id = ?',
|
||||
undef, $self->id);
|
||||
$self->{shared_with_group} = $group_id ? new Bugzilla::Group($group_id)
|
||||
: undef;
|
||||
return $self->{shared_with_group};
|
||||
}
|
||||
|
||||
sub shared_with_users {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!exists $self->{shared_with_users}) {
|
||||
$self->{shared_with_users} =
|
||||
$dbh->selectrow_array('SELECT COUNT(*)
|
||||
FROM namedqueries_link_in_footer
|
||||
INNER JOIN namedqueries
|
||||
ON namedquery_id = id
|
||||
WHERE namedquery_id = ?
|
||||
AND user_id != userid',
|
||||
undef, $self->id);
|
||||
}
|
||||
return $self->{shared_with_users};
|
||||
}
|
||||
|
||||
####################
|
||||
# Simple Accessors #
|
||||
####################
|
||||
|
||||
sub bug_ids_only { return ($_[0]->{'query_type'} == LIST_OF_BUGS) ? 1 : 0; }
|
||||
sub url { return $_[0]->{'query'}; }
|
||||
|
||||
sub user {
|
||||
my ($self) = @_;
|
||||
return $self->{user} if defined $self->{user};
|
||||
$self->{user} = new Bugzilla::User($self->{userid});
|
||||
return $self->{user};
|
||||
}
|
||||
|
||||
############
|
||||
# Mutators #
|
||||
############
|
||||
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_url { $_[0]->set('query', $_[1]); }
|
||||
sub set_query_type { $_[0]->set('query_type', $_[1]); }
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Search::Saved - A saved search
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Search::Saved;
|
||||
|
||||
my $query = new Bugzilla::Search::Saved($query_id);
|
||||
|
||||
my $edit_link = $query->edit_link;
|
||||
my $search_url = $query->url;
|
||||
my $owner = $query->user;
|
||||
my $num_subscribers = $query->shared_with_users;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module exists to represent a L<Bugzilla::Search> that has been
|
||||
saved to the database.
|
||||
|
||||
This is an implementation of L<Bugzilla::Object>, and so has all the
|
||||
same methods available as L<Bugzilla::Object>, in addition to what is
|
||||
documented below.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 Constructors and Database Manipulation
|
||||
|
||||
=over
|
||||
|
||||
=item C<new>
|
||||
|
||||
Does not accept a bare C<name> argument. Instead, accepts only an id.
|
||||
|
||||
See also: L<Bugzilla::Object/new>.
|
||||
|
||||
=item C<preload>
|
||||
|
||||
Sets C<link_in_footer> for all given saved searches at once, for the
|
||||
currently logged in user. This is much faster than calling this method
|
||||
for each saved search individually.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Accessors
|
||||
|
||||
These return data about the object, without modifying the object.
|
||||
|
||||
=over
|
||||
|
||||
=item C<edit_link>
|
||||
|
||||
A url with which you can edit the search.
|
||||
|
||||
=item C<url>
|
||||
|
||||
The CGI parameters for the search, as a string.
|
||||
|
||||
=item C<link_in_footer>
|
||||
|
||||
Whether or not this search should be displayed in the footer for the
|
||||
I<current user> (not the owner of the search, but the person actually
|
||||
using Bugzilla right now).
|
||||
|
||||
=item C<bug_ids_only>
|
||||
|
||||
True if the search contains only a list of Bug IDs.
|
||||
|
||||
=item C<shared_with_group>
|
||||
|
||||
The L<Bugzilla::Group> that this search is shared with. C<undef> if
|
||||
this search isn't shared.
|
||||
|
||||
=item C<shared_with_users>
|
||||
|
||||
Returns how many users (besides the author of the saved search) are
|
||||
using the saved search, i.e. have it displayed in their footer.
|
||||
|
||||
=back
|
||||
@@ -1,261 +0,0 @@
|
||||
# -*- 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): Gervase Markham <gerv@gerv.net>
|
||||
# Lance Larsh <lance.larsh@oracle.com>
|
||||
|
||||
use strict;
|
||||
|
||||
# This module implements a series - a set of data to be plotted on a chart.
|
||||
#
|
||||
# This Series is in the database if and only if self->{'series_id'} is defined.
|
||||
# Note that the series being in the database does not mean that the fields of
|
||||
# this object are the same as the DB entries, as the object may have been
|
||||
# altered.
|
||||
|
||||
package Bugzilla::Series;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
|
||||
# Create a ref to an empty hash and bless it
|
||||
my $self = {};
|
||||
bless($self, $class);
|
||||
|
||||
my $arg_count = scalar(@_);
|
||||
|
||||
# new() can return undef if you pass in a series_id and the user doesn't
|
||||
# have sufficient permissions. If you create a new series in this way,
|
||||
# you need to check for an undef return, and act appropriately.
|
||||
my $retval = $self;
|
||||
|
||||
# There are three ways of creating Series objects. Two (CGI and Parameters)
|
||||
# are for use when creating a new series. One (Database) is for retrieving
|
||||
# information on existing series.
|
||||
if ($arg_count == 1) {
|
||||
if (ref($_[0])) {
|
||||
# We've been given a CGI object to create a new Series from.
|
||||
# This series may already exist - external code needs to check
|
||||
# before it calls writeToDatabase().
|
||||
$self->initFromCGI($_[0]);
|
||||
}
|
||||
else {
|
||||
# We've been given a series_id, which should represent an existing
|
||||
# Series.
|
||||
$retval = $self->initFromDatabase($_[0]);
|
||||
}
|
||||
}
|
||||
elsif ($arg_count >= 6 && $arg_count <= 8) {
|
||||
# We've been given a load of parameters to create a new Series from.
|
||||
# Currently, undef is always passed as the first parameter; this allows
|
||||
# you to call writeToDatabase() unconditionally.
|
||||
$self->initFromParameters(@_);
|
||||
}
|
||||
else {
|
||||
die("Bad parameters passed in - invalid number of args: $arg_count");
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
sub initFromDatabase {
|
||||
my $self = shift;
|
||||
my $series_id = shift;
|
||||
|
||||
detaint_natural($series_id)
|
||||
|| ThrowCodeError("invalid_series_id", { 'series_id' => $series_id });
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my @series = $dbh->selectrow_array("SELECT series.series_id, cc1.name, " .
|
||||
"cc2.name, series.name, series.creator, series.frequency, " .
|
||||
"series.query, series.is_public " .
|
||||
"FROM series " .
|
||||
"LEFT JOIN series_categories AS cc1 " .
|
||||
" ON series.category = cc1.id " .
|
||||
"LEFT JOIN series_categories AS cc2 " .
|
||||
" ON series.subcategory = cc2.id " .
|
||||
"LEFT JOIN category_group_map AS cgm " .
|
||||
" ON series.category = cgm.category_id " .
|
||||
"LEFT JOIN user_group_map AS ugm " .
|
||||
" ON cgm.group_id = ugm.group_id " .
|
||||
" AND ugm.user_id = " . Bugzilla->user->id .
|
||||
" AND isbless = 0 " .
|
||||
"WHERE series.series_id = $series_id AND " .
|
||||
"(is_public = 1 OR creator = " . Bugzilla->user->id . " OR " .
|
||||
"(ugm.group_id IS NOT NULL)) " .
|
||||
$dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
|
||||
'series.name, series.creator, series.frequency, ' .
|
||||
'series.query, series.is_public'));
|
||||
|
||||
if (@series) {
|
||||
$self->initFromParameters(@series);
|
||||
return $self;
|
||||
}
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub initFromParameters {
|
||||
# Pass undef as the first parameter if you are creating a new series.
|
||||
my $self = shift;
|
||||
|
||||
($self->{'series_id'}, $self->{'category'}, $self->{'subcategory'},
|
||||
$self->{'name'}, $self->{'creator'}, $self->{'frequency'},
|
||||
$self->{'query'}, $self->{'public'}) = @_;
|
||||
|
||||
# If the first parameter is undefined, check if this series already
|
||||
# exists and update it series_id accordingly
|
||||
$self->{'series_id'} ||= $self->existsInDatabase();
|
||||
}
|
||||
|
||||
sub initFromCGI {
|
||||
my $self = shift;
|
||||
my $cgi = shift;
|
||||
|
||||
$self->{'series_id'} = $cgi->param('series_id') || undef;
|
||||
if (defined($self->{'series_id'})) {
|
||||
detaint_natural($self->{'series_id'})
|
||||
|| ThrowCodeError("invalid_series_id",
|
||||
{ 'series_id' => $self->{'series_id'} });
|
||||
}
|
||||
|
||||
$self->{'category'} = $cgi->param('category')
|
||||
|| $cgi->param('newcategory')
|
||||
|| ThrowUserError("missing_category");
|
||||
|
||||
$self->{'subcategory'} = $cgi->param('subcategory')
|
||||
|| $cgi->param('newsubcategory')
|
||||
|| ThrowUserError("missing_subcategory");
|
||||
|
||||
$self->{'name'} = $cgi->param('name')
|
||||
|| ThrowUserError("missing_name");
|
||||
|
||||
$self->{'creator'} = Bugzilla->user->id;
|
||||
|
||||
$self->{'frequency'} = $cgi->param('frequency');
|
||||
detaint_natural($self->{'frequency'})
|
||||
|| ThrowUserError("missing_frequency");
|
||||
|
||||
$self->{'query'} = $cgi->canonicalise_query("format", "ctype", "action",
|
||||
"category", "subcategory", "name",
|
||||
"frequency", "public", "query_format");
|
||||
trick_taint($self->{'query'});
|
||||
|
||||
$self->{'public'} = $cgi->param('public') ? 1 : 0;
|
||||
|
||||
# Change 'admin' here and in series.html.tmpl, or remove the check
|
||||
# completely, if you want to change who can make series public.
|
||||
$self->{'public'} = 0 unless Bugzilla->user->in_group('admin');
|
||||
}
|
||||
|
||||
sub writeToDatabase {
|
||||
my $self = shift;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
my $category_id = getCategoryID($self->{'category'});
|
||||
my $subcategory_id = getCategoryID($self->{'subcategory'});
|
||||
|
||||
my $exists;
|
||||
if ($self->{'series_id'}) {
|
||||
$exists =
|
||||
$dbh->selectrow_array("SELECT series_id FROM series
|
||||
WHERE series_id = $self->{'series_id'}");
|
||||
}
|
||||
|
||||
# Is this already in the database?
|
||||
if ($exists) {
|
||||
# Update existing series
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->do("UPDATE series SET " .
|
||||
"category = ?, subcategory = ?," .
|
||||
"name = ?, frequency = ?, is_public = ? " .
|
||||
"WHERE series_id = ?", undef,
|
||||
$category_id, $subcategory_id, $self->{'name'},
|
||||
$self->{'frequency'}, $self->{'public'},
|
||||
$self->{'series_id'});
|
||||
}
|
||||
else {
|
||||
# Insert the new series into the series table
|
||||
$dbh->do("INSERT INTO series (creator, category, subcategory, " .
|
||||
"name, frequency, query, is_public) VALUES " .
|
||||
"(?, ?, ?, ?, ?, ?, ?)", undef,
|
||||
$self->{'creator'}, $category_id, $subcategory_id, $self->{'name'},
|
||||
$self->{'frequency'}, $self->{'query'}, $self->{'public'});
|
||||
|
||||
# Retrieve series_id
|
||||
$self->{'series_id'} = $dbh->selectrow_array("SELECT MAX(series_id) " .
|
||||
"FROM series");
|
||||
$self->{'series_id'}
|
||||
|| ThrowCodeError("missing_series_id", { 'series' => $self });
|
||||
}
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
}
|
||||
|
||||
# Check whether a series with this name, category and subcategory exists in
|
||||
# the DB and, if so, returns its series_id.
|
||||
sub existsInDatabase {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $category_id = getCategoryID($self->{'category'});
|
||||
my $subcategory_id = getCategoryID($self->{'subcategory'});
|
||||
|
||||
trick_taint($self->{'name'});
|
||||
my $series_id = $dbh->selectrow_array("SELECT series_id " .
|
||||
"FROM series WHERE category = $category_id " .
|
||||
"AND subcategory = $subcategory_id AND name = " .
|
||||
$dbh->quote($self->{'name'}));
|
||||
|
||||
return($series_id);
|
||||
}
|
||||
|
||||
# Get a category or subcategory IDs, creating the category if it doesn't exist.
|
||||
sub getCategoryID {
|
||||
my ($category) = @_;
|
||||
my $category_id;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# This seems for the best idiom for "Do A. Then maybe do B and A again."
|
||||
while (1) {
|
||||
# We are quoting this to put it in the DB, so we can remove taint
|
||||
trick_taint($category);
|
||||
|
||||
$category_id = $dbh->selectrow_array("SELECT id " .
|
||||
"from series_categories " .
|
||||
"WHERE name =" . $dbh->quote($category));
|
||||
|
||||
last if defined($category_id);
|
||||
|
||||
$dbh->do("INSERT INTO series_categories (name) " .
|
||||
"VALUES (" . $dbh->quote($category) . ")");
|
||||
}
|
||||
|
||||
return $category_id;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,278 +0,0 @@
|
||||
# -*- 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 Frédéric Buclin.
|
||||
# Portions created by Frédéric Buclin are Copyright (C) 2007
|
||||
# Frédéric Buclin. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Status;
|
||||
|
||||
use base qw(Bugzilla::Object Exporter);
|
||||
@Bugzilla::Status::EXPORT = qw(BUG_STATE_OPEN is_open_state closed_bug_statuses);
|
||||
|
||||
################################
|
||||
##### Initialization #####
|
||||
################################
|
||||
|
||||
use constant DB_TABLE => 'bug_status';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
value
|
||||
sortkey
|
||||
isactive
|
||||
is_open
|
||||
);
|
||||
|
||||
use constant NAME_FIELD => 'value';
|
||||
use constant LIST_ORDER => 'sortkey, value';
|
||||
|
||||
###############################
|
||||
##### Accessors ####
|
||||
###############################
|
||||
|
||||
sub name { return $_[0]->{'value'}; }
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
sub is_active { return $_[0]->{'isactive'}; }
|
||||
sub is_open { return $_[0]->{'is_open'}; }
|
||||
|
||||
###############################
|
||||
##### Methods ####
|
||||
###############################
|
||||
|
||||
sub BUG_STATE_OPEN {
|
||||
# XXX - We should cache this list.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
return @{$dbh->selectcol_arrayref('SELECT value FROM bug_status WHERE is_open = 1')};
|
||||
}
|
||||
|
||||
# Tells you whether or not the argument is a valid "open" state.
|
||||
sub is_open_state {
|
||||
my ($state) = @_;
|
||||
return (grep($_ eq $state, BUG_STATE_OPEN) ? 1 : 0);
|
||||
}
|
||||
|
||||
sub closed_bug_statuses {
|
||||
my @bug_statuses = Bugzilla::Status->get_all;
|
||||
@bug_statuses = grep { !$_->is_open } @bug_statuses;
|
||||
return @bug_statuses;
|
||||
}
|
||||
|
||||
sub can_change_to {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!ref($self) || !defined $self->{'can_change_to'}) {
|
||||
my ($cond, @args, $self_exists);
|
||||
if (ref($self)) {
|
||||
$cond = '= ?';
|
||||
push(@args, $self->id);
|
||||
$self_exists = 1;
|
||||
}
|
||||
else {
|
||||
$cond = 'IS NULL';
|
||||
# Let's do it so that the code below works in all cases.
|
||||
$self = {};
|
||||
}
|
||||
|
||||
my $new_status_ids = $dbh->selectcol_arrayref("SELECT new_status
|
||||
FROM status_workflow
|
||||
INNER JOIN bug_status
|
||||
ON id = new_status
|
||||
WHERE isactive = 1
|
||||
AND old_status $cond
|
||||
ORDER BY sortkey",
|
||||
undef, @args);
|
||||
|
||||
# Allow the bug status to remain unchanged.
|
||||
push(@$new_status_ids, $self->id) if $self_exists;
|
||||
$self->{'can_change_to'} = Bugzilla::Status->new_from_list($new_status_ids);
|
||||
}
|
||||
|
||||
return $self->{'can_change_to'};
|
||||
}
|
||||
|
||||
sub can_change_from {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'can_change_from'}) {
|
||||
my $old_status_ids = $dbh->selectcol_arrayref('SELECT old_status
|
||||
FROM status_workflow
|
||||
INNER JOIN bug_status
|
||||
ON id = old_status
|
||||
WHERE isactive = 1
|
||||
AND new_status = ?
|
||||
AND old_status IS NOT NULL',
|
||||
undef, $self->id);
|
||||
|
||||
# Allow the bug status to remain unchanged.
|
||||
push(@$old_status_ids, $self->id);
|
||||
$self->{'can_change_from'} = Bugzilla::Status->new_from_list($old_status_ids);
|
||||
}
|
||||
|
||||
return $self->{'can_change_from'};
|
||||
}
|
||||
|
||||
sub comment_required_on_change_from {
|
||||
my ($self, $old_status) = @_;
|
||||
my ($cond, $values) = $self->_status_condition($old_status);
|
||||
|
||||
my ($require_comment) = Bugzilla->dbh->selectrow_array(
|
||||
"SELECT require_comment FROM status_workflow
|
||||
WHERE $cond", undef, @$values);
|
||||
return $require_comment;
|
||||
}
|
||||
|
||||
# Used as a helper for various functions that have to deal with old_status
|
||||
# sometimes being NULL and sometimes having a value.
|
||||
sub _status_condition {
|
||||
my ($self, $old_status) = @_;
|
||||
my @values;
|
||||
my $cond = 'old_status IS NULL';
|
||||
# For newly-filed bugs
|
||||
if ($old_status) {
|
||||
$cond = 'old_status = ?';
|
||||
push(@values, $old_status->id);
|
||||
}
|
||||
$cond .= " AND new_status = ?";
|
||||
push(@values, $self->id);
|
||||
return ($cond, \@values);
|
||||
}
|
||||
|
||||
sub add_missing_bug_status_transitions {
|
||||
my $bug_status = shift || Bugzilla->params->{'duplicate_or_move_bug_status'};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $new_status = new Bugzilla::Status({name => $bug_status});
|
||||
# Silently discard invalid bug statuses.
|
||||
$new_status || return;
|
||||
|
||||
my $missing_statuses = $dbh->selectcol_arrayref('SELECT id
|
||||
FROM bug_status
|
||||
LEFT JOIN status_workflow
|
||||
ON old_status = id
|
||||
AND new_status = ?
|
||||
WHERE old_status IS NULL',
|
||||
undef, $new_status->id);
|
||||
|
||||
my $sth = $dbh->prepare('INSERT INTO status_workflow
|
||||
(old_status, new_status) VALUES (?, ?)');
|
||||
|
||||
foreach my $old_status_id (@$missing_statuses) {
|
||||
next if ($old_status_id == $new_status->id);
|
||||
$sth->execute($old_status_id, $new_status->id);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Status - Bug status class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Status;
|
||||
|
||||
my $bug_status = new Bugzilla::Status({name => 'ASSIGNED'});
|
||||
my $bug_status = new Bugzilla::Status(4);
|
||||
|
||||
my @closed_bug_statuses = closed_bug_statuses();
|
||||
|
||||
Bugzilla::Status::add_missing_bug_status_transitions($bug_status);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Status.pm represents a bug status object. It is an implementation
|
||||
of L<Bugzilla::Object>, and thus provides all methods that
|
||||
L<Bugzilla::Object> provides.
|
||||
|
||||
The methods that are specific to C<Bugzilla::Status> are listed
|
||||
below.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<closed_bug_statuses>
|
||||
|
||||
Description: Returns a list of C<Bugzilla::Status> objects which can have
|
||||
a resolution associated with them ("closed" bug statuses).
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of Bugzilla::Status objects.
|
||||
|
||||
=item C<can_change_to>
|
||||
|
||||
Description: Returns the list of active statuses a bug can be changed to
|
||||
given the current bug status. If this method is called as a
|
||||
class method, then it returns all bug statuses available on
|
||||
bug creation.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of Bugzilla::Status objects.
|
||||
|
||||
=item C<can_change_from>
|
||||
|
||||
Description: Returns the list of active statuses a bug can be changed from
|
||||
given the new bug status. If the bug status is available on
|
||||
bug creation, this method doesn't return this information.
|
||||
You have to call C<can_change_to> instead.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of Bugzilla::Status objects.
|
||||
|
||||
=item C<comment_required_on_change_from>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Checks if a comment is required to change to this status from another
|
||||
status, according to the current settings in the workflow.
|
||||
|
||||
Note that this doesn't implement the checks enforced by the various
|
||||
C<commenton> parameters--those are checked by internal checks in
|
||||
L<Bugzilla::Bug>.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
C<$old_status> - The status you're changing from.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
C<1> if a comment is required on this change, C<0> if not.
|
||||
|
||||
=back
|
||||
|
||||
=item C<add_missing_bug_status_transitions>
|
||||
|
||||
Description: Insert all missing transitions to a given bug status.
|
||||
|
||||
Params: $bug_status - The value (name) of a bug status.
|
||||
|
||||
Returns: nothing.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,913 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Tobias Burnus <burnus@net-b.de>
|
||||
# Myk Melez <myk@mozilla.org>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Greg Hendricks <ghendricks@novell.com>
|
||||
# David D. Kilzer <ddkilzer@kilzer.net>
|
||||
|
||||
|
||||
package Bugzilla::Template;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Requirements;
|
||||
use Bugzilla::Install::Util qw(install_string template_include_path include_languages);
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Status;
|
||||
use Bugzilla::Template::Parser;
|
||||
|
||||
use Cwd qw(abs_path);
|
||||
use MIME::Base64;
|
||||
# for time2str - replace by TT Date plugin??
|
||||
use Date::Format ();
|
||||
use File::Basename qw(dirname);
|
||||
use File::Find;
|
||||
use File::Path qw(rmtree mkpath);
|
||||
use File::Spec;
|
||||
use IO::Dir;
|
||||
|
||||
use base qw(Template);
|
||||
|
||||
# As per the Template::Base documentation, the _init() method is being called
|
||||
# by the new() constructor. We take advantage of this in order to plug our
|
||||
# UTF-8-aware Parser object in neatly after the original _init() method has
|
||||
# happened, in particular, after having set up the constants namespace.
|
||||
# See bug 413121 for details.
|
||||
sub _init {
|
||||
my $self = shift;
|
||||
my $config = $_[0];
|
||||
|
||||
$self->SUPER::_init(@_) || return undef;
|
||||
|
||||
$self->{PARSER} = $config->{PARSER}
|
||||
= new Bugzilla::Template::Parser($config);
|
||||
|
||||
# Now we need to re-create the default Service object, making it aware
|
||||
# of our Parser object.
|
||||
$self->{SERVICE} = $config->{SERVICE}
|
||||
= Template::Config->service($config);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# Convert the constants in the Bugzilla::Constants module into a hash we can
|
||||
# pass to the template object for reflection into its "constants" namespace
|
||||
# (which is like its "variables" namespace, but for constants). To do so, we
|
||||
# traverse the arrays of exported and exportable symbols and ignoring the rest
|
||||
# (which, if Constants.pm exports only constants, as it should, will be nothing else).
|
||||
sub _load_constants {
|
||||
my %constants;
|
||||
foreach my $constant (@Bugzilla::Constants::EXPORT,
|
||||
@Bugzilla::Constants::EXPORT_OK)
|
||||
{
|
||||
if (ref Bugzilla::Constants->$constant) {
|
||||
$constants{$constant} = Bugzilla::Constants->$constant;
|
||||
}
|
||||
else {
|
||||
my @list = (Bugzilla::Constants->$constant);
|
||||
$constants{$constant} = (scalar(@list) == 1) ? $list[0] : \@list;
|
||||
}
|
||||
}
|
||||
return \%constants;
|
||||
}
|
||||
|
||||
# Returns the path to the templates based on the Accept-Language
|
||||
# settings of the user and of the available languages
|
||||
# If no Accept-Language is present it uses the defined default
|
||||
# Templates may also be found in the extensions/ tree
|
||||
sub getTemplateIncludePath {
|
||||
my $cache = Bugzilla->request_cache;
|
||||
my $lang = $cache->{'language'} || '';
|
||||
$cache->{"template_include_path_$lang"} ||= template_include_path({
|
||||
use_languages => Bugzilla->languages,
|
||||
only_language => $lang });
|
||||
return $cache->{"template_include_path_$lang"};
|
||||
}
|
||||
|
||||
sub get_format {
|
||||
my $self = shift;
|
||||
my ($template, $format, $ctype) = @_;
|
||||
|
||||
$ctype ||= 'html';
|
||||
$format ||= '';
|
||||
|
||||
# Security - allow letters and a hyphen only
|
||||
$ctype =~ s/[^a-zA-Z\-]//g;
|
||||
$format =~ s/[^a-zA-Z\-]//g;
|
||||
trick_taint($ctype);
|
||||
trick_taint($format);
|
||||
|
||||
$template .= ($format ? "-$format" : "");
|
||||
$template .= ".$ctype.tmpl";
|
||||
|
||||
# Now check that the template actually exists. We only want to check
|
||||
# if the template exists; any other errors (eg parse errors) will
|
||||
# end up being detected later.
|
||||
eval {
|
||||
$self->context->template($template);
|
||||
};
|
||||
# This parsing may seem fragile, but it's OK:
|
||||
# http://lists.template-toolkit.org/pipermail/templates/2003-March/004370.html
|
||||
# Even if it is wrong, any sort of error is going to cause a failure
|
||||
# eventually, so the only issue would be an incorrect error message
|
||||
if ($@ && $@->info =~ /: not found$/) {
|
||||
ThrowUserError('format_not_found', {'format' => $format,
|
||||
'ctype' => $ctype});
|
||||
}
|
||||
|
||||
# Else, just return the info
|
||||
return
|
||||
{
|
||||
'template' => $template,
|
||||
'extension' => $ctype,
|
||||
'ctype' => Bugzilla::Constants::contenttypes->{$ctype}
|
||||
};
|
||||
}
|
||||
|
||||
# This routine quoteUrls contains inspirations from the HTML::FromText CPAN
|
||||
# module by Gareth Rees <garethr@cre.canon.co.uk>. It has been heavily hacked,
|
||||
# all that is really recognizable from the original is bits of the regular
|
||||
# expressions.
|
||||
# This has been rewritten to be faster, mainly by substituting 'as we go'.
|
||||
# If you want to modify this routine, read the comments carefully
|
||||
|
||||
sub quoteUrls {
|
||||
my ($text, $curr_bugid) = (@_);
|
||||
return $text unless $text;
|
||||
|
||||
# We use /g for speed, but uris can have other things inside them
|
||||
# (http://foo/bug#3 for example). Filtering that out filters valid
|
||||
# bug refs out, so we have to do replacements.
|
||||
# mailto can't contain space or #, so we don't have to bother for that
|
||||
# Do this by escaping \0 to \1\0, and replacing matches with \0\0$count\0\0
|
||||
# \0 is used because it's unlikely to occur in the text, so the cost of
|
||||
# doing this should be very small
|
||||
|
||||
# escape the 2nd escape char we're using
|
||||
my $chr1 = chr(1);
|
||||
$text =~ s/\0/$chr1\0/g;
|
||||
|
||||
# However, note that adding the title (for buglinks) can affect things
|
||||
# In particular, attachment matches go before bug titles, so that titles
|
||||
# with 'attachment 1' don't double match.
|
||||
# Dupe checks go afterwards, because that uses ^ and \Z, which won't occur
|
||||
# if it was substituted as a bug title (since that always involve leading
|
||||
# and trailing text)
|
||||
|
||||
# Because of entities, it's easier (and quicker) to do this before escaping
|
||||
|
||||
my @things;
|
||||
my $count = 0;
|
||||
my $tmp;
|
||||
|
||||
# Provide tooltips for full bug links (Bug 74355)
|
||||
my $urlbase_re = '(' . join('|',
|
||||
map { qr/$_/ } grep($_, Bugzilla->params->{'urlbase'},
|
||||
Bugzilla->params->{'sslbase'})) . ')';
|
||||
$text =~ s~\b(${urlbase_re}\Qshow_bug.cgi?id=\E([0-9]+)(\#c([0-9]+))?)\b
|
||||
~($things[$count++] = get_bug_link($3, $1, $5)) &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egox;
|
||||
|
||||
# non-mailto protocols
|
||||
my $safe_protocols = join('|', SAFE_PROTOCOLS);
|
||||
my $protocol_re = qr/($safe_protocols)/i;
|
||||
|
||||
$text =~ s~\b(${protocol_re}: # The protocol:
|
||||
[^\s<>\"]+ # Any non-whitespace
|
||||
[\w\/]) # so that we end in \w or /
|
||||
~($tmp = html_quote($1)) &&
|
||||
($things[$count++] = "<a href=\"$tmp\">$tmp</a>") &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egox;
|
||||
|
||||
# We have to quote now, otherwise the html itself is escaped
|
||||
# THIS MEANS THAT A LITERAL ", <, >, ' MUST BE ESCAPED FOR A MATCH
|
||||
|
||||
$text = html_quote($text);
|
||||
|
||||
# Color quoted text
|
||||
$text =~ s~^(>.+)$~<span class="quote">$1</span >~mg;
|
||||
$text =~ s~</span >\n<span class="quote">~\n~g;
|
||||
|
||||
# mailto:
|
||||
# Use |<nothing> so that $1 is defined regardless
|
||||
$text =~ s~\b(mailto:|)?([\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+)\b
|
||||
~<a href=\"mailto:$2\">$1$2</a>~igx;
|
||||
|
||||
# attachment links - handle both cases separately for simplicity
|
||||
$text =~ s~((?:^Created\ an\ |\b)attachment\s*\(id=(\d+)\)(\s\[edit\])?)
|
||||
~($things[$count++] = get_attachment_link($2, $1)) &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egmx;
|
||||
|
||||
$text =~ s~\b(attachment\s*\#?\s*(\d+))
|
||||
~($things[$count++] = get_attachment_link($2, $1)) &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egmxi;
|
||||
|
||||
# Current bug ID this comment belongs to
|
||||
my $current_bugurl = $curr_bugid ? "show_bug.cgi?id=$curr_bugid" : "";
|
||||
|
||||
# This handles bug a, comment b type stuff. Because we're using /g
|
||||
# we have to do this in one pattern, and so this is semi-messy.
|
||||
# Also, we can't use $bug_re?$comment_re? because that will match the
|
||||
# empty string
|
||||
my $bug_word = get_text('term', { term => 'bug' });
|
||||
my $bug_re = qr/\Q$bug_word\E\s*\#?\s*(\d+)/i;
|
||||
my $comment_re = qr/comment\s*\#?\s*(\d+)/i;
|
||||
$text =~ s~\b($bug_re(?:\s*,?\s*$comment_re)?|$comment_re)
|
||||
~ # We have several choices. $1 here is the link, and $2-4 are set
|
||||
# depending on which part matched
|
||||
(defined($2) ? get_bug_link($2,$1,$3) :
|
||||
"<a href=\"$current_bugurl#c$4\">$1</a>")
|
||||
~egox;
|
||||
|
||||
# Old duplicate markers. These don't use $bug_word because they are old
|
||||
# and were never customizable.
|
||||
$text =~ s~(?<=^\*\*\*\ This\ bug\ has\ been\ marked\ as\ a\ duplicate\ of\ )
|
||||
(\d+)
|
||||
(?=\ \*\*\*\Z)
|
||||
~get_bug_link($1, $1)
|
||||
~egmx;
|
||||
|
||||
# Now remove the encoding hacks
|
||||
$text =~ s/\0\0(\d+)\0\0/$things[$1]/eg;
|
||||
$text =~ s/$chr1\0/\0/g;
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
# Creates a link to an attachment, including its title.
|
||||
sub get_attachment_link {
|
||||
my ($attachid, $link_text) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
detaint_natural($attachid)
|
||||
|| die "get_attachment_link() called with non-integer attachment number";
|
||||
|
||||
my ($bugid, $isobsolete, $desc) =
|
||||
$dbh->selectrow_array('SELECT bug_id, isobsolete, description
|
||||
FROM attachments WHERE attach_id = ?',
|
||||
undef, $attachid);
|
||||
|
||||
if ($bugid) {
|
||||
my $title = "";
|
||||
my $className = "";
|
||||
if (Bugzilla->user->can_see_bug($bugid)) {
|
||||
$title = $desc;
|
||||
}
|
||||
if ($isobsolete) {
|
||||
$className = "bz_obsolete";
|
||||
}
|
||||
# Prevent code injection in the title.
|
||||
$title = html_quote(clean_text($title));
|
||||
|
||||
$link_text =~ s/ \[details\]$//;
|
||||
my $linkval = "attachment.cgi?id=$attachid";
|
||||
# Whitespace matters here because these links are in <pre> tags.
|
||||
return qq|<span class="$className">|
|
||||
. qq|<a href="${linkval}" name="attach_${attachid}" title="$title">$link_text</a>|
|
||||
. qq| <a href="${linkval}&action=edit" title="$title">[details]</a>|
|
||||
. qq|</span>|;
|
||||
}
|
||||
else {
|
||||
return qq{$link_text};
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a link to a bug, including its title.
|
||||
# It takes either two or three parameters:
|
||||
# - The bug number
|
||||
# - The link text, to place between the <a>..</a>
|
||||
# - An optional comment number, for linking to a particular
|
||||
# comment in the bug
|
||||
|
||||
sub get_bug_link {
|
||||
my ($bug_num, $link_text, $comment_num) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined($bug_num) || ($bug_num eq "")) {
|
||||
return "<missing bug number>";
|
||||
}
|
||||
my $quote_bug_num = html_quote($bug_num);
|
||||
detaint_natural($bug_num) || return "<invalid bug number: $quote_bug_num>";
|
||||
|
||||
my ($bug_state, $bug_res, $bug_desc) =
|
||||
$dbh->selectrow_array('SELECT bugs.bug_status, resolution, short_desc
|
||||
FROM bugs WHERE bugs.bug_id = ?',
|
||||
undef, $bug_num);
|
||||
|
||||
if ($bug_state) {
|
||||
# Initialize these variables to be "" so that we don't get warnings
|
||||
# if we don't change them below (which is highly likely).
|
||||
my ($pre, $title, $post) = ("", "", "");
|
||||
|
||||
$title = get_text('get_status', {status => $bug_state});
|
||||
if ($bug_state eq 'UNCONFIRMED') {
|
||||
$pre = "<i>";
|
||||
$post = "</i>";
|
||||
}
|
||||
elsif (!is_open_state($bug_state)) {
|
||||
$pre = '<span class="bz_closed">';
|
||||
$title .= ' ' . get_text('get_resolution', {resolution => $bug_res});
|
||||
$post = '</span>';
|
||||
}
|
||||
if (Bugzilla->user->can_see_bug($bug_num)) {
|
||||
$title .= " - $bug_desc";
|
||||
}
|
||||
# Prevent code injection in the title.
|
||||
$title = html_quote(clean_text($title));
|
||||
|
||||
my $linkval = "show_bug.cgi?id=$bug_num";
|
||||
if (defined $comment_num) {
|
||||
$linkval .= "#c$comment_num";
|
||||
}
|
||||
return qq{$pre<a href="$linkval" title="$title">$link_text</a>$post};
|
||||
}
|
||||
else {
|
||||
return qq{$link_text};
|
||||
}
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Templatization Code
|
||||
|
||||
# The Template Toolkit throws an error if a loop iterates >1000 times.
|
||||
# We want to raise that limit.
|
||||
# NOTE: If you change this number, you MUST RE-RUN checksetup.pl!!!
|
||||
# If you do not re-run checksetup.pl, the change you make will not apply
|
||||
$Template::Directive::WHILE_MAX = 1000000;
|
||||
|
||||
# Use the Toolkit Template's Stash module to add utility pseudo-methods
|
||||
# to template variables.
|
||||
use Template::Stash;
|
||||
|
||||
# Add "contains***" methods to list variables that search for one or more
|
||||
# items in a list and return boolean values representing whether or not
|
||||
# one/all/any item(s) were found.
|
||||
$Template::Stash::LIST_OPS->{ contains } =
|
||||
sub {
|
||||
my ($list, $item) = @_;
|
||||
return grep($_ eq $item, @$list);
|
||||
};
|
||||
|
||||
$Template::Stash::LIST_OPS->{ containsany } =
|
||||
sub {
|
||||
my ($list, $items) = @_;
|
||||
foreach my $item (@$items) {
|
||||
return 1 if grep($_ eq $item, @$list);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
# Allow us to still get the scalar if we use the list operation ".0" on it,
|
||||
# as we often do for defaults in query.cgi and other places.
|
||||
$Template::Stash::SCALAR_OPS->{ 0 } =
|
||||
sub {
|
||||
return $_[0];
|
||||
};
|
||||
|
||||
# Add a "substr" method to the Template Toolkit's "scalar" object
|
||||
# that returns a substring of a string.
|
||||
$Template::Stash::SCALAR_OPS->{ substr } =
|
||||
sub {
|
||||
my ($scalar, $offset, $length) = @_;
|
||||
return substr($scalar, $offset, $length);
|
||||
};
|
||||
|
||||
# Add a "truncate" method to the Template Toolkit's "scalar" object
|
||||
# that truncates a string to a certain length.
|
||||
$Template::Stash::SCALAR_OPS->{ truncate } =
|
||||
sub {
|
||||
my ($string, $length, $ellipsis) = @_;
|
||||
$ellipsis ||= "";
|
||||
|
||||
return $string if !$length || length($string) <= $length;
|
||||
|
||||
my $strlen = $length - length($ellipsis);
|
||||
my $newstr = substr($string, 0, $strlen) . $ellipsis;
|
||||
return $newstr;
|
||||
};
|
||||
|
||||
# Create the template object that processes templates and specify
|
||||
# configuration parameters that apply to all templates.
|
||||
|
||||
###############################################################################
|
||||
|
||||
# Construct the Template object
|
||||
|
||||
# Note that all of the failure cases here can't use templateable errors,
|
||||
# since we won't have a template to use...
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my %opts = @_;
|
||||
|
||||
# checksetup.pl will call us once for any template/lang directory.
|
||||
# We need a possibility to reset the cache, so that no files from
|
||||
# the previous language pollute the action.
|
||||
if ($opts{'clean_cache'}) {
|
||||
delete Bugzilla->request_cache->{template_include_path_};
|
||||
}
|
||||
|
||||
# IMPORTANT - If you make any configuration changes here, make sure to
|
||||
# make them in t/004.template.t and checksetup.pl.
|
||||
|
||||
return $class->new({
|
||||
# Colon-separated list of directories containing templates.
|
||||
INCLUDE_PATH => [\&getTemplateIncludePath],
|
||||
|
||||
# Remove white-space before template directives (PRE_CHOMP) and at the
|
||||
# beginning and end of templates and template blocks (TRIM) for better
|
||||
# looking, more compact content. Use the plus sign at the beginning
|
||||
# of directives to maintain white space (i.e. [%+ DIRECTIVE %]).
|
||||
PRE_CHOMP => 1,
|
||||
TRIM => 1,
|
||||
|
||||
COMPILE_DIR => bz_locations()->{'datadir'} . "/template",
|
||||
|
||||
# Initialize templates (f.e. by loading plugins like Hook).
|
||||
PRE_PROCESS => "global/initialize.none.tmpl",
|
||||
|
||||
# Functions for processing text within templates in various ways.
|
||||
# IMPORTANT! When adding a filter here that does not override a
|
||||
# built-in filter, please also add a stub filter to t/004template.t.
|
||||
FILTERS => {
|
||||
|
||||
# Render text in required style.
|
||||
|
||||
inactive => [
|
||||
sub {
|
||||
my($context, $isinactive) = @_;
|
||||
return sub {
|
||||
return $isinactive ? '<span class="bz_inactive">'.$_[0].'</span>' : $_[0];
|
||||
}
|
||||
}, 1
|
||||
],
|
||||
|
||||
closed => [
|
||||
sub {
|
||||
my($context, $isclosed) = @_;
|
||||
return sub {
|
||||
return $isclosed ? '<span class="bz_closed">'.$_[0].'</span>' : $_[0];
|
||||
}
|
||||
}, 1
|
||||
],
|
||||
|
||||
obsolete => [
|
||||
sub {
|
||||
my($context, $isobsolete) = @_;
|
||||
return sub {
|
||||
return $isobsolete ? '<span class="bz_obsolete">'.$_[0].'</span>' : $_[0];
|
||||
}
|
||||
}, 1
|
||||
],
|
||||
|
||||
# Returns the text with backslashes, single/double quotes,
|
||||
# and newlines/carriage returns escaped for use in JS strings.
|
||||
js => sub {
|
||||
my ($var) = @_;
|
||||
$var =~ s/([\\\'\"\/])/\\$1/g;
|
||||
$var =~ s/\n/\\n/g;
|
||||
$var =~ s/\r/\\r/g;
|
||||
$var =~ s/\@/\\x40/g; # anti-spam for email addresses
|
||||
return $var;
|
||||
},
|
||||
|
||||
# Converts data to base64
|
||||
base64 => sub {
|
||||
my ($data) = @_;
|
||||
return encode_base64($data);
|
||||
},
|
||||
|
||||
# HTML collapses newlines in element attributes to a single space,
|
||||
# so form elements which may have whitespace (ie comments) need
|
||||
# to be encoded using 
|
||||
# See bugs 4928, 22983 and 32000 for more details
|
||||
html_linebreak => sub {
|
||||
my ($var) = @_;
|
||||
$var =~ s/\r\n/\
/g;
|
||||
$var =~ s/\n\r/\
/g;
|
||||
$var =~ s/\r/\
/g;
|
||||
$var =~ s/\n/\
/g;
|
||||
return $var;
|
||||
},
|
||||
|
||||
# Prevents line break on hyphens and whitespaces.
|
||||
no_break => sub {
|
||||
my ($var) = @_;
|
||||
$var =~ s/ /\ /g;
|
||||
$var =~ s/-/\‑/g;
|
||||
return $var;
|
||||
},
|
||||
|
||||
xml => \&Bugzilla::Util::xml_quote ,
|
||||
|
||||
# This filter escapes characters in a variable or value string for
|
||||
# use in a query string. It escapes all characters NOT in the
|
||||
# regex set: [a-zA-Z0-9_\-.]. The 'uri' filter should be used for
|
||||
# a full URL that may have characters that need encoding.
|
||||
url_quote => \&Bugzilla::Util::url_quote ,
|
||||
|
||||
# This filter is similar to url_quote but used a \ instead of a %
|
||||
# as prefix. In addition it replaces a ' ' by a '_'.
|
||||
css_class_quote => \&Bugzilla::Util::css_class_quote ,
|
||||
|
||||
quoteUrls => [ sub {
|
||||
my ($context, $bug) = @_;
|
||||
return sub {
|
||||
my $text = shift;
|
||||
return quoteUrls($text, $bug);
|
||||
};
|
||||
},
|
||||
1
|
||||
],
|
||||
|
||||
bug_link => [ sub {
|
||||
my ($context, $bug) = @_;
|
||||
return sub {
|
||||
my $text = shift;
|
||||
return get_bug_link($bug, $text);
|
||||
};
|
||||
},
|
||||
1
|
||||
],
|
||||
|
||||
bug_list_link => sub
|
||||
{
|
||||
my $buglist = shift;
|
||||
return join(", ", map(get_bug_link($_, $_), split(/ *, */, $buglist)));
|
||||
},
|
||||
|
||||
# In CSV, quotes are doubled, and any value containing a quote or a
|
||||
# comma is enclosed in quotes.
|
||||
csv => sub
|
||||
{
|
||||
my ($var) = @_;
|
||||
$var =~ s/\"/\"\"/g;
|
||||
if ($var !~ /^-?(\d+\.)?\d*$/) {
|
||||
$var = "\"$var\"";
|
||||
}
|
||||
return $var;
|
||||
} ,
|
||||
|
||||
# Format a filesize in bytes to a human readable value
|
||||
unitconvert => sub
|
||||
{
|
||||
my ($data) = @_;
|
||||
my $retval = "";
|
||||
my %units = (
|
||||
'KB' => 1024,
|
||||
'MB' => 1024 * 1024,
|
||||
'GB' => 1024 * 1024 * 1024,
|
||||
);
|
||||
|
||||
if ($data < 1024) {
|
||||
return "$data bytes";
|
||||
}
|
||||
else {
|
||||
my $u;
|
||||
foreach $u ('GB', 'MB', 'KB') {
|
||||
if ($data >= $units{$u}) {
|
||||
return sprintf("%.2f %s", $data/$units{$u}, $u);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
# Format a time for display (more info in Bugzilla::Util)
|
||||
time => \&Bugzilla::Util::format_time,
|
||||
|
||||
# Bug 120030: Override html filter to obscure the '@' in user
|
||||
# visible strings.
|
||||
# Bug 319331: Handle BiDi disruptions.
|
||||
html => sub {
|
||||
my ($var) = Template::Filters::html_filter(@_);
|
||||
# Obscure '@'.
|
||||
$var =~ s/\@/\@/g;
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
# Remove the following characters because they're
|
||||
# influencing BiDi:
|
||||
# --------------------------------------------------------
|
||||
# |Code |Name |UTF-8 representation|
|
||||
# |------|--------------------------|--------------------|
|
||||
# |U+202a|Left-To-Right Embedding |0xe2 0x80 0xaa |
|
||||
# |U+202b|Right-To-Left Embedding |0xe2 0x80 0xab |
|
||||
# |U+202c|Pop Directional Formatting|0xe2 0x80 0xac |
|
||||
# |U+202d|Left-To-Right Override |0xe2 0x80 0xad |
|
||||
# |U+202e|Right-To-Left Override |0xe2 0x80 0xae |
|
||||
# --------------------------------------------------------
|
||||
#
|
||||
# The following are characters influencing BiDi, too, but
|
||||
# they can be spared from filtering because they don't
|
||||
# influence more than one character right or left:
|
||||
# --------------------------------------------------------
|
||||
# |Code |Name |UTF-8 representation|
|
||||
# |------|--------------------------|--------------------|
|
||||
# |U+200e|Left-To-Right Mark |0xe2 0x80 0x8e |
|
||||
# |U+200f|Right-To-Left Mark |0xe2 0x80 0x8f |
|
||||
# --------------------------------------------------------
|
||||
$var =~ s/[\x{202a}-\x{202e}]//g;
|
||||
}
|
||||
return $var;
|
||||
},
|
||||
|
||||
html_light => \&Bugzilla::Util::html_light_quote,
|
||||
|
||||
# iCalendar contentline filter
|
||||
ics => [ sub {
|
||||
my ($context, @args) = @_;
|
||||
return sub {
|
||||
my ($var) = shift;
|
||||
my ($par) = shift @args;
|
||||
my ($output) = "";
|
||||
|
||||
$var =~ s/[\r\n]/ /g;
|
||||
$var =~ s/([;\\\",])/\\$1/g;
|
||||
|
||||
if ($par) {
|
||||
$output = sprintf("%s:%s", $par, $var);
|
||||
} else {
|
||||
$output = $var;
|
||||
}
|
||||
|
||||
$output =~ s/(.{75,75})/$1\n /g;
|
||||
|
||||
return $output;
|
||||
};
|
||||
},
|
||||
1
|
||||
],
|
||||
|
||||
# Note that using this filter is even more dangerous than
|
||||
# using "none," and you should only use it when you're SURE
|
||||
# the output won't be displayed directly to a web browser.
|
||||
txt => sub {
|
||||
my ($var) = @_;
|
||||
# Trivial HTML tag remover
|
||||
$var =~ s/<[^>]*>//g;
|
||||
# And this basically reverses the html filter.
|
||||
$var =~ s/\@/@/g;
|
||||
$var =~ s/\</</g;
|
||||
$var =~ s/\>/>/g;
|
||||
$var =~ s/\"/\"/g;
|
||||
$var =~ s/\&/\&/g;
|
||||
return $var;
|
||||
},
|
||||
|
||||
# Wrap a displayed comment to the appropriate length
|
||||
wrap_comment => [
|
||||
sub {
|
||||
my ($context, $cols) = @_;
|
||||
return sub { wrap_comment($_[0], $cols) }
|
||||
}, 1],
|
||||
|
||||
# We force filtering of every variable in key security-critical
|
||||
# places; we have a none filter for people to use when they
|
||||
# really, really don't want a variable to be changed.
|
||||
none => sub { return $_[0]; } ,
|
||||
},
|
||||
|
||||
PLUGIN_BASE => 'Bugzilla::Template::Plugin',
|
||||
|
||||
CONSTANTS => _load_constants(),
|
||||
|
||||
# Default variables for all templates
|
||||
VARIABLES => {
|
||||
# Function for retrieving global parameters.
|
||||
'Param' => sub { return Bugzilla->params->{$_[0]}; },
|
||||
|
||||
# Function to create date strings
|
||||
'time2str' => \&Date::Format::time2str,
|
||||
|
||||
# Generic linear search function
|
||||
'lsearch' => \&Bugzilla::Util::lsearch,
|
||||
|
||||
# Currently logged in user, if any
|
||||
# If an sudo session is in progress, this is the user we're faking
|
||||
'user' => sub { return Bugzilla->user; },
|
||||
|
||||
# If an sudo session is in progress, this is the user who
|
||||
# started the session.
|
||||
'sudoer' => sub { return Bugzilla->sudoer; },
|
||||
|
||||
# SendBugMail - sends mail about a bug, using Bugzilla::BugMail.pm
|
||||
'SendBugMail' => sub {
|
||||
my ($id, $mailrecipients) = (@_);
|
||||
require Bugzilla::BugMail;
|
||||
Bugzilla::BugMail::Send($id, $mailrecipients);
|
||||
},
|
||||
|
||||
# Allow templates to access the "corect" URLBase value
|
||||
'urlbase' => sub { return Bugzilla::Util::correct_urlbase(); },
|
||||
|
||||
# Allow templates to access docs url with users' preferred language
|
||||
'docs_urlbase' => sub {
|
||||
my ($language) = include_languages();
|
||||
my $docs_urlbase = Bugzilla->params->{'docs_urlbase'};
|
||||
$docs_urlbase =~ s/\%lang\%/$language/;
|
||||
return $docs_urlbase;
|
||||
},
|
||||
|
||||
# These don't work as normal constants.
|
||||
DB_MODULE => \&Bugzilla::Constants::DB_MODULE,
|
||||
REQUIRED_MODULES =>
|
||||
\&Bugzilla::Install::Requirements::REQUIRED_MODULES,
|
||||
OPTIONAL_MODULES => sub {
|
||||
my @optional = @{OPTIONAL_MODULES()};
|
||||
@optional = sort {$a->{feature} cmp $b->{feature}}
|
||||
@optional;
|
||||
return \@optional;
|
||||
},
|
||||
},
|
||||
|
||||
}) || die("Template creation failed: " . $class->error());
|
||||
}
|
||||
|
||||
# Used as part of the two subroutines below.
|
||||
our (%_templates_to_precompile, $_current_path);
|
||||
|
||||
sub precompile_templates {
|
||||
my ($output) = @_;
|
||||
|
||||
# Remove the compiled templates.
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
if (-e "$datadir/template") {
|
||||
print install_string('template_removing_dir') . "\n" if $output;
|
||||
|
||||
# XXX This frequently fails if the webserver made the files, because
|
||||
# then the webserver owns the directories. We could fix that by
|
||||
# doing a chmod/chown on all the directories here.
|
||||
rmtree("$datadir/template");
|
||||
|
||||
# Check that the directory was really removed
|
||||
if(-e "$datadir/template") {
|
||||
print "\n\n";
|
||||
print "The directory '$datadir/template' could not be removed.\n";
|
||||
print "Please remove it manually and rerun checksetup.pl.\n\n";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
print install_string('template_precompile') if $output;
|
||||
|
||||
my $templatedir = bz_locations()->{'templatedir'};
|
||||
# Don't hang on templates which use the CGI library
|
||||
eval("use CGI qw(-no_debug)");
|
||||
|
||||
my $dir_reader = new IO::Dir($templatedir) || die "$templatedir: $!";
|
||||
my @language_dirs = grep { /^[a-z-]+$/i } $dir_reader->read;
|
||||
$dir_reader->close;
|
||||
|
||||
foreach my $dir (@language_dirs) {
|
||||
next if ($dir eq 'CVS');
|
||||
-d "$templatedir/$dir/default" || -d "$templatedir/$dir/custom"
|
||||
|| next;
|
||||
local $ENV{'HTTP_ACCEPT_LANGUAGE'} = $dir;
|
||||
my $template = Bugzilla::Template->create(clean_cache => 1);
|
||||
|
||||
# Precompile all the templates found in all the directories.
|
||||
%_templates_to_precompile = ();
|
||||
foreach my $subdir (qw(custom extension default), bz_locations()->{'project'}) {
|
||||
next unless $subdir; # If 'project' is empty.
|
||||
$_current_path = File::Spec->catdir($templatedir, $dir, $subdir);
|
||||
next unless -d $_current_path;
|
||||
# Traverse the template hierarchy.
|
||||
find({ wanted => \&_precompile_push, no_chdir => 1 }, $_current_path);
|
||||
}
|
||||
# The sort isn't totally necessary, but it makes debugging easier
|
||||
# by making the templates always be compiled in the same order.
|
||||
foreach my $file (sort keys %_templates_to_precompile) {
|
||||
# Compile the template but throw away the result. This has the side-
|
||||
# effect of writing the compiled version to disk.
|
||||
$template->context->template($file);
|
||||
}
|
||||
}
|
||||
|
||||
# Under mod_perl, we look for templates using the absolute path of the
|
||||
# template directory, which causes Template Toolkit to look for their
|
||||
# *compiled* versions using the full absolute path under the data/template
|
||||
# directory. (Like data/template/var/www/html/mod_perl/.) To avoid
|
||||
# re-compiling templates under mod_perl, we symlink to the
|
||||
# already-compiled templates. This doesn't work on Windows.
|
||||
if (!ON_WINDOWS) {
|
||||
my $abs_root = dirname(abs_path($templatedir));
|
||||
my $todir = "$datadir/template$abs_root";
|
||||
mkpath($todir);
|
||||
# We use abs2rel so that the symlink will look like
|
||||
# "../../../../template" which works, while just
|
||||
# "data/template/template/" doesn't work.
|
||||
my $fromdir = File::Spec->abs2rel("$datadir/template/template", $todir);
|
||||
# We eval for systems that can't symlink at all, where "symlink"
|
||||
# throws a fatal error.
|
||||
eval { symlink($fromdir, "$todir/template")
|
||||
or warn "Failed to symlink from $fromdir to $todir: $!" };
|
||||
}
|
||||
|
||||
# If anything created a Template object before now, clear it out.
|
||||
delete Bugzilla->request_cache->{template};
|
||||
# This is the single variable used to precompile templates,
|
||||
# which needs to be cleared as well.
|
||||
delete Bugzilla->request_cache->{template_include_path_};
|
||||
|
||||
print install_string('done') . "\n" if $output;
|
||||
}
|
||||
|
||||
# Helper for precompile_templates
|
||||
sub _precompile_push {
|
||||
my $name = $File::Find::name;
|
||||
return if (-d $name);
|
||||
return if ($name =~ /\/CVS\//);
|
||||
return if ($name !~ /\.tmpl$/);
|
||||
|
||||
$name =~ s/\Q$_current_path\E\///;
|
||||
$_templates_to_precompile{$name} = 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template - Wrapper around the Template Toolkit C<Template> object
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $template = Bugzilla::Template->create;
|
||||
my $format = $template->get_format("foo/bar",
|
||||
scalar($cgi->param('format')),
|
||||
scalar($cgi->param('ctype')));
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is basically a wrapper so that the correct arguments get passed into
|
||||
the C<Template> constructor.
|
||||
|
||||
It should not be used directly by scripts or modules - instead, use
|
||||
C<Bugzilla-E<gt>instance-E<gt>template> to get an already created module.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<precompile_templates($output)>
|
||||
|
||||
Description: Compiles all of Bugzilla's templates in every language.
|
||||
Used mostly by F<checksetup.pl>.
|
||||
|
||||
Params: C<$output> - C<true> if you want the function to print
|
||||
out information about what it's doing.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_format($file, $format, $ctype)>
|
||||
|
||||
Description: Construct a format object from URL parameters.
|
||||
|
||||
Params: $file - Name of the template to display.
|
||||
$format - When the template exists under several formats
|
||||
(e.g. table or graph), specify the one to choose.
|
||||
$ctype - Content type, see Bugzilla::Constants::contenttypes.
|
||||
|
||||
Returns: A format object.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla>, L<Template>
|
||||
@@ -1,66 +0,0 @@
|
||||
# -*- 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 Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (C) 2008 Marc Schumann.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
|
||||
package Bugzilla::Template::Parser;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Template::Parser);
|
||||
|
||||
sub parse {
|
||||
my ($self, $text, @params) = @_;
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
utf8::is_utf8($text) || utf8::decode($text);
|
||||
}
|
||||
return $self->SUPER::parse($text, @params);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Parser - Wrapper around the Template Toolkit
|
||||
C<Template::Parser> object
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This wrapper makes the Template Toolkit aware of UTF-8 templates.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<parse($options)>
|
||||
|
||||
Description: Parses template text using Template::Parser::parse(),
|
||||
converting the text to UTF-8 encoding before, if necessary.
|
||||
|
||||
Params: C<$text> - Text to pass to Template::Parser::parse().
|
||||
|
||||
Returns: Parsed text as returned by Template::Parser::parse().
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Template>
|
||||
@@ -1,64 +0,0 @@
|
||||
# -*- 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): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
#
|
||||
|
||||
package Bugzilla::Template::Plugin::Bugzilla;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Template::Plugin);
|
||||
|
||||
use Bugzilla;
|
||||
|
||||
sub new {
|
||||
my ($class, $context) = @_;
|
||||
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
sub AUTOLOAD {
|
||||
my $class = shift;
|
||||
our $AUTOLOAD;
|
||||
|
||||
$AUTOLOAD =~ s/^.*:://;
|
||||
|
||||
return if $AUTOLOAD eq 'DESTROY';
|
||||
|
||||
return Bugzilla->$AUTOLOAD(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Plugin::Bugzilla
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Template Toolkit plugin to allow access to the persistent C<Bugzilla>
|
||||
object.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla>, L<Template::Plugin>
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
# -*- 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): Myk Melez <myk@mozilla.org>
|
||||
# Zach Lipton <zach@zachlipton.com>
|
||||
# Elliotte Martin <everythingsolved.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Template::Plugin::Hook;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Util qw(include_languages);
|
||||
use Bugzilla::Template;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use File::Spec;
|
||||
|
||||
use base qw(Template::Plugin);
|
||||
|
||||
sub load {
|
||||
my ($class, $context) = @_;
|
||||
return $class;
|
||||
}
|
||||
|
||||
sub new {
|
||||
my ($class, $context) = @_;
|
||||
return bless { _CONTEXT => $context }, $class;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my ($self, $hook_name, $template) = @_;
|
||||
$template ||= $self->{_CONTEXT}->stash->{component}->{name};
|
||||
|
||||
my @hooks;
|
||||
|
||||
# sanity check:
|
||||
if (!$template =~ /[\w\.\/\-_\\]+/) {
|
||||
ThrowCodeError('template_invalid', { name => $template});
|
||||
}
|
||||
|
||||
# also get extension hook files that live in extensions/:
|
||||
# parse out the parts of the template name
|
||||
my ($vol, $subpath, $filename) = File::Spec->splitpath($template);
|
||||
$subpath = $subpath || '';
|
||||
$filename =~ m/(.*)\.(.*)\.tmpl/;
|
||||
my $templatename = $1;
|
||||
my $type = $2;
|
||||
# munge the filename to create the extension hook filename:
|
||||
my $extensiontemplate = $subpath.'/'.$templatename.'-'.$hook_name.'.'.$type.'.tmpl';
|
||||
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
|
||||
my @usedlanguages = include_languages({use_languages => Bugzilla->languages});
|
||||
foreach my $extension (@extensions) {
|
||||
next if -e "$extension/disabled";
|
||||
foreach my $language (@usedlanguages) {
|
||||
my $file = $extension.'/template/'.$language.'/'.$extensiontemplate;
|
||||
if (-e $file) {
|
||||
# tt is stubborn and won't take a template file not in its
|
||||
# include path, so we open a filehandle and give it to process()
|
||||
# so the hook gets invoked:
|
||||
open (my $fh, $file);
|
||||
push(@hooks, $fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $paths = $self->{_CONTEXT}->{LOAD_TEMPLATES}->[0]->paths;
|
||||
|
||||
# we keep this too since you can still put hook templates in
|
||||
# template/en/custom/hook
|
||||
foreach my $path (@$paths) {
|
||||
my @files = glob("$path/hook/$template/$hook_name/*.tmpl");
|
||||
|
||||
# Have to remove the templates path (INCLUDE_PATH) from the
|
||||
# file path since the template processor auto-adds it back.
|
||||
@files = map($_ =~ /^$path\/(.*)$/ ? $1 : {}, @files);
|
||||
|
||||
# Add found files to the list of hooks, but removing duplicates,
|
||||
# which can happen when there are identical hooks or duplicate
|
||||
# directories in the INCLUDE_PATH (the latter probably being a TT bug).
|
||||
foreach my $file (@files) {
|
||||
push(@hooks, $file) unless grep($file eq $_, @hooks);
|
||||
}
|
||||
}
|
||||
|
||||
my $output;
|
||||
foreach my $hook (@hooks) {
|
||||
$output .= $self->{_CONTEXT}->process($hook);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Plugin::Hook
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Template Toolkit plugin to process hooks added into templates by extensions.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item B<process>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Processes hooks added into templates by extensions.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<hook_name>
|
||||
|
||||
The unique name of the template hook.
|
||||
|
||||
=item C<template> (optional)
|
||||
|
||||
The path of the calling template.
|
||||
This is used as a work around to a bug which causes the path to the hook
|
||||
to be incorrect when the hook is called from inside a block.
|
||||
|
||||
Example: If the hook C<lastrow> is added to the template
|
||||
F<show-multiple.html.tmpl> and it is desired to force the correct template
|
||||
path, the template hook would be:
|
||||
|
||||
[% Hook.process("lastrow", "bug/show-multiple.html.tmpl") %]
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
Output from processing template extension.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Template::Plugin>
|
||||
|
||||
L<http://www.bugzilla.org/docs/tip/html/customization.html>
|
||||
|
||||
L<http://bugzilla.mozilla.org/show_bug.cgi?id=229658>
|
||||
|
||||
L<http://bugzilla.mozilla.org/show_bug.cgi?id=298341>
|
||||
@@ -1,65 +0,0 @@
|
||||
# -*- 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): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joel Peshkin <bugreport@peshkin.net>
|
||||
#
|
||||
|
||||
package Bugzilla::Template::Plugin::User;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Template::Plugin);
|
||||
|
||||
use Bugzilla::User;
|
||||
|
||||
sub new {
|
||||
my ($class, $context) = @_;
|
||||
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
sub AUTOLOAD {
|
||||
my $class = shift;
|
||||
our $AUTOLOAD;
|
||||
|
||||
$AUTOLOAD =~ s/^.*:://;
|
||||
|
||||
return if $AUTOLOAD eq 'DESTROY';
|
||||
|
||||
return Bugzilla::User->$AUTOLOAD(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Plugin::User
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Template Toolkit plugin to allow access to the C<User>
|
||||
object.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla::User>, L<Template::Plugin>
|
||||
|
||||
@@ -1,563 +0,0 @@
|
||||
# -*- 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): Myk Melez <myk@mozilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
################################################################################
|
||||
# Module Initialization
|
||||
################################################################################
|
||||
|
||||
# Make it harder for us to do dangerous things in Perl.
|
||||
use strict;
|
||||
|
||||
# Bundle the functions in this file together into the "Bugzilla::Token" package.
|
||||
package Bugzilla::Token;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Mailer;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
|
||||
use Date::Format;
|
||||
use Date::Parse;
|
||||
use File::Basename;
|
||||
|
||||
use base qw(Exporter);
|
||||
|
||||
@Bugzilla::Token::EXPORT = qw(issue_session_token check_token_data delete_token);
|
||||
|
||||
################################################################################
|
||||
# Public Functions
|
||||
################################################################################
|
||||
|
||||
# Creates and sends a token to create a new user account.
|
||||
# It assumes that the login has the correct format and is not already in use.
|
||||
sub issue_new_user_account_token {
|
||||
my $login_name = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $template = Bugzilla->template;
|
||||
my $vars = {};
|
||||
|
||||
# Is there already a pending request for this login name? If yes, do not throw
|
||||
# an error because the user may have lost his email with the token inside.
|
||||
# But to prevent using this way to mailbomb an email address, make sure
|
||||
# the last request is at least 10 minutes old before sending a new email.
|
||||
|
||||
my $pending_requests =
|
||||
$dbh->selectrow_array('SELECT COUNT(*)
|
||||
FROM tokens
|
||||
WHERE tokentype = ?
|
||||
AND ' . $dbh->sql_istrcmp('eventdata', '?') . '
|
||||
AND issuedate > NOW() - ' . $dbh->sql_interval(10, 'MINUTE'),
|
||||
undef, ('account', $login_name));
|
||||
|
||||
ThrowUserError('too_soon_for_new_token', {'type' => 'account'}) if $pending_requests;
|
||||
|
||||
my ($token, $token_ts) = _create_token(undef, 'account', $login_name);
|
||||
|
||||
$vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
|
||||
$vars->{'token_ts'} = $token_ts;
|
||||
$vars->{'token'} = $token;
|
||||
|
||||
my $message;
|
||||
$template->process('account/email/request-new.txt.tmpl', $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
# In 99% of cases, the user getting the confirmation email is the same one
|
||||
# who made the request, and so it is reasonable to send the email in the same
|
||||
# language used to view the "Create a New Account" page (we cannot use his
|
||||
# user prefs as the user has no account yet!).
|
||||
MessageToMTA($message);
|
||||
}
|
||||
|
||||
sub IssueEmailChangeToken {
|
||||
my ($user, $old_email, $new_email) = @_;
|
||||
my $email_suffix = Bugzilla->params->{'emailsuffix'};
|
||||
|
||||
my ($token, $token_ts) = _create_token($user->id, 'emailold', $old_email . ":" . $new_email);
|
||||
|
||||
my $newtoken = _create_token($user->id, 'emailnew', $old_email . ":" . $new_email);
|
||||
|
||||
# Mail the user the token along with instructions for using it.
|
||||
|
||||
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
|
||||
my $vars = {};
|
||||
|
||||
$vars->{'oldemailaddress'} = $old_email . $email_suffix;
|
||||
$vars->{'newemailaddress'} = $new_email . $email_suffix;
|
||||
|
||||
$vars->{'max_token_age'} = MAX_TOKEN_AGE;
|
||||
$vars->{'token_ts'} = $token_ts;
|
||||
|
||||
$vars->{'token'} = $token;
|
||||
$vars->{'emailaddress'} = $old_email . $email_suffix;
|
||||
|
||||
my $message;
|
||||
$template->process("account/email/change-old.txt.tmpl", $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
MessageToMTA($message);
|
||||
|
||||
$vars->{'token'} = $newtoken;
|
||||
$vars->{'emailaddress'} = $new_email . $email_suffix;
|
||||
|
||||
$message = "";
|
||||
$template->process("account/email/change-new.txt.tmpl", $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
Bugzilla->template_inner("");
|
||||
MessageToMTA($message);
|
||||
}
|
||||
|
||||
# Generates a random token, adds it to the tokens table, and sends it
|
||||
# to the user with instructions for using it to change their password.
|
||||
sub IssuePasswordToken {
|
||||
my $user = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $too_soon =
|
||||
$dbh->selectrow_array('SELECT 1 FROM tokens
|
||||
WHERE userid = ?
|
||||
AND tokentype = ?
|
||||
AND issuedate > NOW() - ' .
|
||||
$dbh->sql_interval(10, 'MINUTE'),
|
||||
undef, ($user->id, 'password'));
|
||||
|
||||
ThrowUserError('too_soon_for_new_token', {'type' => 'password'}) if $too_soon;
|
||||
|
||||
my ($token, $token_ts) = _create_token($user->id, 'password', $::ENV{'REMOTE_ADDR'});
|
||||
|
||||
# Mail the user the token along with instructions for using it.
|
||||
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
|
||||
my $vars = {};
|
||||
|
||||
$vars->{'token'} = $token;
|
||||
$vars->{'emailaddress'} = $user->email;
|
||||
$vars->{'max_token_age'} = MAX_TOKEN_AGE;
|
||||
$vars->{'token_ts'} = $token_ts;
|
||||
|
||||
my $message = "";
|
||||
$template->process("account/password/forgotten-password.txt.tmpl",
|
||||
$vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
Bugzilla->template_inner("");
|
||||
MessageToMTA($message);
|
||||
}
|
||||
|
||||
sub issue_session_token {
|
||||
# Generates a random token, adds it to the tokens table, and returns
|
||||
# the token to the caller.
|
||||
|
||||
my $data = shift;
|
||||
return _create_token(Bugzilla->user->id, 'session', $data);
|
||||
}
|
||||
|
||||
sub CleanTokenTable {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->do('DELETE FROM tokens
|
||||
WHERE ' . $dbh->sql_to_days('NOW()') . ' - ' .
|
||||
$dbh->sql_to_days('issuedate') . ' >= ?',
|
||||
undef, MAX_TOKEN_AGE);
|
||||
}
|
||||
|
||||
sub GenerateUniqueToken {
|
||||
# Generates a unique random token. Uses generate_random_password
|
||||
# for the tokens themselves and checks uniqueness by searching for
|
||||
# the token in the "tokens" table. Gives up if it can't come up
|
||||
# with a token after about one hundred tries.
|
||||
my ($table, $column) = @_;
|
||||
|
||||
my $token;
|
||||
my $duplicate = 1;
|
||||
my $tries = 0;
|
||||
$table ||= "tokens";
|
||||
$column ||= "token";
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->prepare("SELECT userid FROM $table WHERE $column = ?");
|
||||
|
||||
while ($duplicate) {
|
||||
++$tries;
|
||||
if ($tries > 100) {
|
||||
ThrowCodeError("token_generation_error");
|
||||
}
|
||||
$token = generate_random_password();
|
||||
$sth->execute($token);
|
||||
$duplicate = $sth->fetchrow_array;
|
||||
}
|
||||
return $token;
|
||||
}
|
||||
|
||||
# Cancels a previously issued token and notifies the user.
|
||||
# This should only happen when the user accidentally makes a token request
|
||||
# or when a malicious hacker makes a token request on behalf of a user.
|
||||
sub Cancel {
|
||||
my ($token, $cancelaction, $vars) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$vars ||= {};
|
||||
|
||||
# Get information about the token being canceled.
|
||||
trick_taint($token);
|
||||
my ($issuedate, $tokentype, $eventdata, $userid) =
|
||||
$dbh->selectrow_array('SELECT ' . $dbh->sql_date_format('issuedate') . ',
|
||||
tokentype, eventdata, userid
|
||||
FROM tokens
|
||||
WHERE token = ?',
|
||||
undef, $token);
|
||||
|
||||
# If we are canceling the creation of a new user account, then there
|
||||
# is no entry in the 'profiles' table.
|
||||
my $user = new Bugzilla::User($userid);
|
||||
|
||||
$vars->{'emailaddress'} = $userid ? $user->email : $eventdata;
|
||||
$vars->{'remoteaddress'} = $::ENV{'REMOTE_ADDR'};
|
||||
$vars->{'token'} = $token;
|
||||
$vars->{'tokentype'} = $tokentype;
|
||||
$vars->{'issuedate'} = $issuedate;
|
||||
$vars->{'eventdata'} = $eventdata;
|
||||
$vars->{'cancelaction'} = $cancelaction;
|
||||
|
||||
# Notify the user via email about the cancellation.
|
||||
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
|
||||
|
||||
my $message;
|
||||
$template->process("account/cancel-token.txt.tmpl", $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
Bugzilla->template_inner("");
|
||||
MessageToMTA($message);
|
||||
|
||||
# Delete the token from the database.
|
||||
delete_token($token);
|
||||
}
|
||||
|
||||
sub DeletePasswordTokens {
|
||||
my ($userid, $reason) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
detaint_natural($userid);
|
||||
my $tokens = $dbh->selectcol_arrayref('SELECT token FROM tokens
|
||||
WHERE userid = ? AND tokentype = ?',
|
||||
undef, ($userid, 'password'));
|
||||
|
||||
foreach my $token (@$tokens) {
|
||||
Bugzilla::Token::Cancel($token, $reason);
|
||||
}
|
||||
}
|
||||
|
||||
# Returns an email change token if the user has one.
|
||||
sub HasEmailChangeToken {
|
||||
my $userid = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $token = $dbh->selectrow_array('SELECT token FROM tokens
|
||||
WHERE userid = ?
|
||||
AND (tokentype = ? OR tokentype = ?) ' .
|
||||
$dbh->sql_limit(1),
|
||||
undef, ($userid, 'emailnew', 'emailold'));
|
||||
return $token;
|
||||
}
|
||||
|
||||
# Returns the userid, issuedate and eventdata for the specified token
|
||||
sub GetTokenData {
|
||||
my ($token) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return unless defined $token;
|
||||
$token = clean_text($token);
|
||||
trick_taint($token);
|
||||
|
||||
return $dbh->selectrow_array(
|
||||
"SELECT userid, " . $dbh->sql_date_format('issuedate') . ", eventdata
|
||||
FROM tokens
|
||||
WHERE token = ?", undef, $token);
|
||||
}
|
||||
|
||||
# Deletes specified token
|
||||
sub delete_token {
|
||||
my ($token) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return unless defined $token;
|
||||
trick_taint($token);
|
||||
|
||||
$dbh->do("DELETE FROM tokens WHERE token = ?", undef, $token);
|
||||
}
|
||||
|
||||
# Given a token, makes sure it comes from the currently logged in user
|
||||
# and match the expected event. Returns 1 on success, else displays a warning.
|
||||
# Note: this routine must not be called while tables are locked as it will try
|
||||
# to lock some tables itself, see CleanTokenTable().
|
||||
sub check_token_data {
|
||||
my ($token, $expected_action) = @_;
|
||||
my $user = Bugzilla->user;
|
||||
my $template = Bugzilla->template;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
my ($creator_id, $date, $token_action) = GetTokenData($token);
|
||||
unless ($creator_id
|
||||
&& $creator_id == $user->id
|
||||
&& $token_action eq $expected_action)
|
||||
{
|
||||
# Something is going wrong. Ask confirmation before processing.
|
||||
# It is possible that someone tried to trick an administrator.
|
||||
# In this case, we want to know his name!
|
||||
require Bugzilla::User;
|
||||
|
||||
my $vars = {};
|
||||
$vars->{'abuser'} = Bugzilla::User->new($creator_id)->identity;
|
||||
$vars->{'token_action'} = $token_action;
|
||||
$vars->{'expected_action'} = $expected_action;
|
||||
$vars->{'script_name'} = basename($0);
|
||||
|
||||
# Now is a good time to remove old tokens from the DB.
|
||||
CleanTokenTable();
|
||||
|
||||
# If no token was found, create a valid token for the given action.
|
||||
unless ($creator_id) {
|
||||
$token = issue_session_token($expected_action);
|
||||
$cgi->param('token', $token);
|
||||
}
|
||||
|
||||
print $cgi->header();
|
||||
|
||||
$template->process('admin/confirm-action.html.tmpl', $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
exit;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Internal Functions
|
||||
################################################################################
|
||||
|
||||
# Generates a unique token and inserts it into the database
|
||||
# Returns the token and the token timestamp
|
||||
sub _create_token {
|
||||
my ($userid, $tokentype, $eventdata) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
detaint_natural($userid) if defined $userid;
|
||||
trick_taint($tokentype);
|
||||
trick_taint($eventdata);
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
my $token = GenerateUniqueToken();
|
||||
|
||||
$dbh->do("INSERT INTO tokens (userid, issuedate, token, tokentype, eventdata)
|
||||
VALUES (?, NOW(), ?, ?, ?)", undef, ($userid, $token, $tokentype, $eventdata));
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
if (wantarray) {
|
||||
my (undef, $token_ts, undef) = GetTokenData($token);
|
||||
$token_ts = str2time($token_ts);
|
||||
return ($token, $token_ts);
|
||||
} else {
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Token - Provides different routines to manage tokens.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Token;
|
||||
|
||||
Bugzilla::Token::issue_new_user_account_token($login_name);
|
||||
Bugzilla::Token::IssueEmailChangeToken($user, $old_email, $new_email);
|
||||
Bugzilla::Token::IssuePasswordToken($user);
|
||||
Bugzilla::Token::DeletePasswordTokens($user_id, $reason);
|
||||
Bugzilla::Token::Cancel($token, $cancelaction, $vars);
|
||||
|
||||
Bugzilla::Token::CleanTokenTable();
|
||||
|
||||
my $token = issue_session_token($event);
|
||||
check_token_data($token, $event)
|
||||
delete_token($token);
|
||||
|
||||
my $token = Bugzilla::Token::GenerateUniqueToken($table, $column);
|
||||
my $token = Bugzilla::Token::HasEmailChangeToken($user_id);
|
||||
my ($token, $date, $data) = Bugzilla::Token::GetTokenData($token);
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<issue_new_user_account_token($login_name)>
|
||||
|
||||
Description: Creates and sends a token per email to the email address
|
||||
requesting a new user account. It doesn't check whether
|
||||
the user account already exists. The user will have to
|
||||
use this token to confirm the creation of his user account.
|
||||
|
||||
Params: $login_name - The new login name requested by the user.
|
||||
|
||||
Returns: Nothing. It throws an error if the same user made the same
|
||||
request in the last few minutes.
|
||||
|
||||
=item C<sub IssueEmailChangeToken($user, $old_email, $new_email)>
|
||||
|
||||
Description: Sends two distinct tokens per email to the old and new email
|
||||
addresses to confirm the email address change for the given
|
||||
user. These tokens remain valid for the next MAX_TOKEN_AGE days.
|
||||
|
||||
Params: $user - User object of the user requesting a new
|
||||
email address.
|
||||
$old_email - The current (old) email address of the user.
|
||||
$new_email - The new email address of the user.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<IssuePasswordToken($user)>
|
||||
|
||||
Description: Sends a token per email to the given user. This token
|
||||
can be used to change the password (e.g. in case the user
|
||||
cannot remember his password and wishes to enter a new one).
|
||||
|
||||
Params: $user - User object of the user requesting a new password.
|
||||
|
||||
Returns: Nothing. It throws an error if the same user made the same
|
||||
request in the last few minutes.
|
||||
|
||||
=item C<CleanTokenTable()>
|
||||
|
||||
Description: Removes all tokens older than MAX_TOKEN_AGE days from the DB.
|
||||
This means that these tokens will now be considered as invalid.
|
||||
|
||||
Params: None.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<GenerateUniqueToken($table, $column)>
|
||||
|
||||
Description: Generates and returns a unique token. This token is unique
|
||||
in the $column of the $table. This token is NOT stored in the DB.
|
||||
|
||||
Params: $table (optional): The table to look at (default: tokens).
|
||||
$column (optional): The column to look at for uniqueness (default: token).
|
||||
|
||||
Returns: A token which is unique in $column.
|
||||
|
||||
=item C<Cancel($token, $cancelaction, $vars)>
|
||||
|
||||
Description: Invalidates an existing token, generally when the token is used
|
||||
for an action which is not the one expected. An email is sent
|
||||
to the user who originally requested this token to inform him
|
||||
that this token has been invalidated (e.g. because an hacker
|
||||
tried to use this token for some malicious action).
|
||||
|
||||
Params: $token: The token to invalidate.
|
||||
$cancelaction: The reason why this token is invalidated.
|
||||
$vars: Some additional information about this action.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<DeletePasswordTokens($user_id, $reason)>
|
||||
|
||||
Description: Cancels all password tokens for the given user. Emails are sent
|
||||
to the user to inform him about this action.
|
||||
|
||||
Params: $user_id: The user ID of the user account whose password tokens
|
||||
are canceled.
|
||||
$reason: The reason why these tokens are canceled.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<HasEmailChangeToken($user_id)>
|
||||
|
||||
Description: Returns any existing token currently used for an email change
|
||||
for the given user.
|
||||
|
||||
Params: $user_id - A user ID.
|
||||
|
||||
Returns: A token if it exists, else undef.
|
||||
|
||||
=item C<GetTokenData($token)>
|
||||
|
||||
Description: Returns all stored data for the given token.
|
||||
|
||||
Params: $token - A valid token.
|
||||
|
||||
Returns: The user ID, the date and time when the token was created and
|
||||
the (event)data stored with that token.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Security related routines
|
||||
|
||||
The following routines have been written to be used together as described below,
|
||||
although they can be used separately.
|
||||
|
||||
=over
|
||||
|
||||
=item C<issue_session_token($event)>
|
||||
|
||||
Description: Creates and returns a token used internally.
|
||||
|
||||
Params: $event - The event which needs to be stored in the DB for future
|
||||
reference/checks.
|
||||
|
||||
Returns: A unique token.
|
||||
|
||||
=item C<check_token_data($token, $event)>
|
||||
|
||||
Description: Makes sure the $token has been created by the currently logged in
|
||||
user and to be used for the given $event. If this token is used for
|
||||
an unexpected action (i.e. $event doesn't match the information stored
|
||||
with the token), a warning is displayed asking whether the user really
|
||||
wants to continue. On success, it returns 1.
|
||||
This is the routine to use for security checks, combined with
|
||||
issue_session_token() and delete_token() as follows:
|
||||
|
||||
1. First, create a token for some coming action.
|
||||
my $token = issue_session_token($action);
|
||||
2. Some time later, it's time to make sure that the expected action
|
||||
is going to be executed, and by the expected user.
|
||||
check_token_data($token, $action);
|
||||
3. The check has been done and we no longer need this token.
|
||||
delete_token($token);
|
||||
|
||||
Params: $token - The token used for security checks.
|
||||
$event - The expected event to be run.
|
||||
|
||||
Returns: 1 on success, else a warning is thrown.
|
||||
|
||||
=item C<delete_token($token)>
|
||||
|
||||
Description: Deletes the specified token. No notification is sent.
|
||||
|
||||
Params: $token - The token to delete.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,221 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
package Bugzilla::Update;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
|
||||
use constant LOCAL_FILE => "/bugzilla-update.xml"; # Relative to datadir.
|
||||
use constant TIME_INTERVAL => 86400; # Default is one day, in seconds.
|
||||
use constant TIMEOUT => 5; # Number of seconds before timeout.
|
||||
|
||||
# Look for new releases and notify logged in administrators about them.
|
||||
sub get_notifications {
|
||||
return if (Bugzilla->params->{'upgrade_notification'} eq 'disabled');
|
||||
|
||||
# If the XML::Twig module is missing, we won't be able to parse
|
||||
# the XML file. So there is no need to go further.
|
||||
eval("require XML::Twig");
|
||||
return if $@;
|
||||
|
||||
my $local_file = bz_locations()->{'datadir'} . LOCAL_FILE;
|
||||
# Update the local XML file if this one doesn't exist or if
|
||||
# the last modification time (stat[9]) is older than TIME_INTERVAL.
|
||||
if (!-e $local_file || (time() - (stat($local_file))[9] > TIME_INTERVAL)) {
|
||||
# Are we sure we didn't try to refresh this file already
|
||||
# but we failed because we cannot modify its timestamp?
|
||||
my $can_alter = (-e $local_file) ? utime(undef, undef, $local_file) : 1;
|
||||
if ($can_alter) {
|
||||
unlink $local_file; # Make sure the old copy is away.
|
||||
my $error = _synchronize_data();
|
||||
# If an error is returned, leave now.
|
||||
return $error if $error;
|
||||
}
|
||||
else {
|
||||
return {'error' => 'no_update', 'xml_file' => $local_file};
|
||||
}
|
||||
}
|
||||
|
||||
# If we cannot access the local XML file, ignore it.
|
||||
return {'error' => 'no_access', 'xml_file' => $local_file} unless (-r $local_file);
|
||||
|
||||
my $twig = XML::Twig->new();
|
||||
$twig->safe_parsefile($local_file);
|
||||
# If the XML file is invalid, return.
|
||||
return {'error' => 'corrupted', 'xml_file' => $local_file} if $@;
|
||||
my $root = $twig->root;
|
||||
|
||||
my @releases;
|
||||
foreach my $branch ($root->children('branch')) {
|
||||
my $release = {
|
||||
'branch_ver' => $branch->{'att'}->{'id'},
|
||||
'latest_ver' => $branch->{'att'}->{'vid'},
|
||||
'status' => $branch->{'att'}->{'status'},
|
||||
'url' => $branch->{'att'}->{'url'},
|
||||
'date' => $branch->{'att'}->{'date'}
|
||||
};
|
||||
push(@releases, $release);
|
||||
}
|
||||
|
||||
# On which branch is the current installation running?
|
||||
my @current_version =
|
||||
(BUGZILLA_VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
|
||||
|
||||
my @release;
|
||||
if (Bugzilla->params->{'upgrade_notification'} eq 'development_snapshot') {
|
||||
@release = grep {$_->{'status'} eq 'development'} @releases;
|
||||
# If there is no development snapshot available, then we are in the
|
||||
# process of releasing a release candidate. That's the release we want.
|
||||
unless (scalar(@release)) {
|
||||
@release = grep {$_->{'status'} eq 'release-candidate'} @releases;
|
||||
}
|
||||
}
|
||||
elsif (Bugzilla->params->{'upgrade_notification'} eq 'latest_stable_release') {
|
||||
@release = grep {$_->{'status'} eq 'stable'} @releases;
|
||||
}
|
||||
elsif (Bugzilla->params->{'upgrade_notification'} eq 'stable_branch_release') {
|
||||
# We want the latest stable version for the current branch.
|
||||
# If we are running a development snapshot, we won't match anything.
|
||||
my $branch_version = $current_version[0] . '.' . $current_version[1];
|
||||
|
||||
# We do a string comparison instead of a numerical one, because
|
||||
# e.g. 2.2 == 2.20, but 2.2 ne 2.20 (and 2.2 is indeed much older).
|
||||
@release = grep {$_->{'branch_ver'} eq $branch_version} @releases;
|
||||
|
||||
# If the branch is now closed, we should strongly suggest
|
||||
# to upgrade to the latest stable release available.
|
||||
if (scalar(@release) && $release[0]->{'status'} eq 'closed') {
|
||||
@release = grep {$_->{'status'} eq 'stable'} @releases;
|
||||
return {'data' => $release[0], 'deprecated' => $branch_version};
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Unknown parameter.
|
||||
return {'error' => 'unknown_parameter'};
|
||||
}
|
||||
|
||||
# Return if no new release is available.
|
||||
return unless scalar(@release);
|
||||
|
||||
# Only notify the administrator if the latest version available
|
||||
# is newer than the current one.
|
||||
my @new_version =
|
||||
($release[0]->{'latest_ver'} =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
|
||||
|
||||
# We convert release candidates 'rc' to integers (rc ? 0 : 1) in order
|
||||
# to compare versions easily.
|
||||
$current_version[2] = ($current_version[2] && $current_version[2] eq 'rc') ? 0 : 1;
|
||||
$new_version[2] = ($new_version[2] && $new_version[2] eq 'rc') ? 0 : 1;
|
||||
|
||||
my $is_newer = _compare_versions(\@current_version, \@new_version);
|
||||
return ($is_newer == 1) ? {'data' => $release[0]} : undef;
|
||||
}
|
||||
|
||||
sub _synchronize_data {
|
||||
eval("require LWP::UserAgent");
|
||||
return {'error' => 'missing_package', 'package' => 'LWP::UserAgent'} if $@;
|
||||
|
||||
my $local_file = bz_locations()->{'datadir'} . LOCAL_FILE;
|
||||
|
||||
my $ua = LWP::UserAgent->new();
|
||||
$ua->timeout(TIMEOUT);
|
||||
$ua->protocols_allowed(['http', 'https']);
|
||||
# If the URL of the proxy is given, use it, else get this information
|
||||
# from the environment variable.
|
||||
my $proxy_url = Bugzilla->params->{'proxy_url'};
|
||||
if ($proxy_url) {
|
||||
$ua->proxy(['http', 'https'], $proxy_url);
|
||||
}
|
||||
else {
|
||||
$ua->env_proxy;
|
||||
}
|
||||
$ua->mirror(REMOTE_FILE, $local_file);
|
||||
|
||||
# $ua->mirror() forces the modification time of the local XML file
|
||||
# to match the modification time of the remote one.
|
||||
# So we have to update it manually to reflect that a newer version
|
||||
# of the file has effectively been requested. This will avoid
|
||||
# any new download for the next TIME_INTERVAL.
|
||||
if (-e $local_file) {
|
||||
# Try to alter its last modification time.
|
||||
my $can_alter = utime(undef, undef, $local_file);
|
||||
# This error should never happen.
|
||||
$can_alter || return {'error' => 'no_update', 'xml_file' => $local_file};
|
||||
}
|
||||
else {
|
||||
# We have been unable to download the file.
|
||||
return {'error' => 'cannot_download', 'xml_file' => $local_file};
|
||||
}
|
||||
|
||||
# Everything went well.
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub _compare_versions {
|
||||
my ($old_ver, $new_ver) = @_;
|
||||
while (scalar(@$old_ver) && scalar(@$new_ver)) {
|
||||
my $old = shift(@$old_ver) || 0;
|
||||
my $new = shift(@$new_ver) || 0;
|
||||
return $new <=> $old if ($new <=> $old);
|
||||
}
|
||||
return scalar(@$new_ver) <=> scalar(@$old_ver);
|
||||
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Update - Update routines for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Update;
|
||||
|
||||
# Get information about new releases
|
||||
my $new_release = Bugzilla::Update::get_notifications();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains all required routines to notify you
|
||||
about new releases. It downloads an XML file from bugzilla.org
|
||||
and parses it, in order to display information based on your
|
||||
preferences. Absolutely no information about the Bugzilla version
|
||||
you are running is sent to bugzilla.org.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_notifications()>
|
||||
|
||||
Description: This function informs you about new releases, if any.
|
||||
|
||||
Params: None.
|
||||
|
||||
Returns: On success, a reference to a hash with data about
|
||||
new releases, if any.
|
||||
On failure, a reference to a hash with the reason
|
||||
of the failure and the name of the unusable XML file.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,433 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
|
||||
package Bugzilla::User::Setting;
|
||||
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
|
||||
# Module stuff
|
||||
@Bugzilla::User::Setting::EXPORT = qw(get_all_settings get_defaults
|
||||
add_setting);
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util qw(trick_taint get_text);
|
||||
|
||||
###############################
|
||||
### Module Initialization ###
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $setting_name = shift;
|
||||
my $user_id = shift;
|
||||
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my $subclass = '';
|
||||
|
||||
# Create a ref to an empty hash and bless it
|
||||
my $self = {};
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Confirm that the $setting_name is properly formed;
|
||||
# if not, throw a code error.
|
||||
#
|
||||
# NOTE: due to the way that setting names are used in templates,
|
||||
# they must conform to to the limitations set for HTML NAMEs and IDs.
|
||||
#
|
||||
if ( !($setting_name =~ /^[a-zA-Z][-.:\w]*$/) ) {
|
||||
ThrowCodeError("setting_name_invalid", { name => $setting_name });
|
||||
}
|
||||
|
||||
# If there were only two parameters passed in, then we need
|
||||
# to retrieve the information for this setting ourselves.
|
||||
if (scalar @_ == 0) {
|
||||
|
||||
my ($default, $is_enabled, $value);
|
||||
($default, $is_enabled, $value, $subclass) =
|
||||
$dbh->selectrow_array(
|
||||
q{SELECT default_value, is_enabled, setting_value, subclass
|
||||
FROM setting
|
||||
LEFT JOIN profile_setting
|
||||
ON setting.name = profile_setting.setting_name
|
||||
WHERE name = ?
|
||||
AND profile_setting.user_id = ?},
|
||||
undef,
|
||||
$setting_name, $user_id);
|
||||
|
||||
# if not defined, then grab the default value
|
||||
if (! defined $value) {
|
||||
($default, $is_enabled, $subclass) =
|
||||
$dbh->selectrow_array(
|
||||
q{SELECT default_value, is_enabled, subclass
|
||||
FROM setting
|
||||
WHERE name = ?},
|
||||
undef,
|
||||
$setting_name);
|
||||
}
|
||||
|
||||
$self->{'is_enabled'} = $is_enabled;
|
||||
$self->{'default_value'} = $default;
|
||||
|
||||
# IF the setting is enabled, AND the user has chosen a setting
|
||||
# THEN return that value
|
||||
# ELSE return the site default, and note that it is the default.
|
||||
if ( ($is_enabled) && (defined $value) ) {
|
||||
$self->{'value'} = $value;
|
||||
} else {
|
||||
$self->{'value'} = $default;
|
||||
$self->{'isdefault'} = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
# If the values were passed in, simply assign them and return.
|
||||
$self->{'is_enabled'} = shift;
|
||||
$self->{'default_value'} = shift;
|
||||
$self->{'value'} = shift;
|
||||
$self->{'is_default'} = shift;
|
||||
$subclass = shift;
|
||||
}
|
||||
if ($subclass) {
|
||||
eval('require ' . $class . '::' . $subclass);
|
||||
$@ && ThrowCodeError('setting_subclass_invalid',
|
||||
{'subclass' => $subclass});
|
||||
$class = $class . '::' . $subclass;
|
||||
}
|
||||
bless($self, $class);
|
||||
|
||||
$self->{'_setting_name'} = $setting_name;
|
||||
$self->{'_user_id'} = $user_id;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
###############################
|
||||
### Subroutine Definitions ###
|
||||
###############################
|
||||
|
||||
sub add_setting {
|
||||
my ($name, $values, $default_value, $subclass, $force_check) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $exists = _setting_exists($name);
|
||||
return if ($exists && !$force_check);
|
||||
|
||||
($name && $default_value)
|
||||
|| ThrowCodeError("setting_info_invalid");
|
||||
|
||||
if ($exists) {
|
||||
# If this setting exists, we delete it and regenerate it.
|
||||
$dbh->do('DELETE FROM setting_value WHERE name = ?', undef, $name);
|
||||
$dbh->do('DELETE FROM setting WHERE name = ?', undef, $name);
|
||||
# Remove obsolete user preferences for this setting.
|
||||
if (defined $values && scalar(@$values)) {
|
||||
my $list = join(', ', map {$dbh->quote($_)} @$values);
|
||||
$dbh->do("DELETE FROM profile_setting
|
||||
WHERE setting_name = ? AND setting_value NOT IN ($list)",
|
||||
undef, $name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
print get_text('install_setting_new', { name => $name }) . "\n";
|
||||
}
|
||||
$dbh->do(q{INSERT INTO setting (name, default_value, is_enabled, subclass)
|
||||
VALUES (?, ?, 1, ?)},
|
||||
undef, ($name, $default_value, $subclass));
|
||||
|
||||
my $sth = $dbh->prepare(q{INSERT INTO setting_value (name, value, sortindex)
|
||||
VALUES (?, ?, ?)});
|
||||
|
||||
my $sortindex = 5;
|
||||
foreach my $key (@$values){
|
||||
$sth->execute($name, $key, $sortindex);
|
||||
$sortindex += 5;
|
||||
}
|
||||
}
|
||||
|
||||
sub get_all_settings {
|
||||
my ($user_id) = @_;
|
||||
my $settings = get_defaults($user_id); # first get the defaults
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $sth = $dbh->prepare(
|
||||
q{SELECT name, default_value, is_enabled, setting_value, subclass
|
||||
FROM setting
|
||||
LEFT JOIN profile_setting
|
||||
ON setting.name = profile_setting.setting_name
|
||||
WHERE profile_setting.user_id = ?
|
||||
ORDER BY name});
|
||||
|
||||
$sth->execute($user_id);
|
||||
while (my ($name, $default_value, $is_enabled, $value, $subclass)
|
||||
= $sth->fetchrow_array())
|
||||
{
|
||||
|
||||
my $is_default;
|
||||
|
||||
if ( ($is_enabled) && (defined $value) ) {
|
||||
$is_default = 0;
|
||||
} else {
|
||||
$value = $default_value;
|
||||
$is_default = 1;
|
||||
}
|
||||
|
||||
$settings->{$name} = new Bugzilla::User::Setting(
|
||||
$name, $user_id, $is_enabled,
|
||||
$default_value, $value, $is_default, $subclass);
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
sub get_defaults {
|
||||
my ($user_id) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $default_settings = {};
|
||||
|
||||
$user_id ||= 0;
|
||||
|
||||
my $sth = $dbh->prepare(q{SELECT name, default_value, is_enabled, subclass
|
||||
FROM setting
|
||||
ORDER BY name});
|
||||
$sth->execute();
|
||||
while (my ($name, $default_value, $is_enabled, $subclass)
|
||||
= $sth->fetchrow_array())
|
||||
{
|
||||
|
||||
$default_settings->{$name} = new Bugzilla::User::Setting(
|
||||
$name, $user_id, $is_enabled, $default_value, $default_value, 1,
|
||||
$subclass);
|
||||
}
|
||||
|
||||
return $default_settings;
|
||||
}
|
||||
|
||||
sub set_default {
|
||||
my ($setting_name, $default_value, $is_enabled) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $sth = $dbh->prepare(q{UPDATE setting
|
||||
SET default_value = ?, is_enabled = ?
|
||||
WHERE name = ?});
|
||||
$sth->execute($default_value, $is_enabled, $setting_name);
|
||||
}
|
||||
|
||||
sub _setting_exists {
|
||||
my ($setting_name) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
return $dbh->selectrow_arrayref(
|
||||
"SELECT 1 FROM setting WHERE name = ?", undef, $setting_name) || 0;
|
||||
}
|
||||
|
||||
|
||||
sub legal_values {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{'legal_values'} if defined $self->{'legal_values'};
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$self->{'legal_values'} = $dbh->selectcol_arrayref(
|
||||
q{SELECT value
|
||||
FROM setting_value
|
||||
WHERE name = ?
|
||||
ORDER BY sortindex},
|
||||
undef, $self->{'_setting_name'});
|
||||
|
||||
return $self->{'legal_values'};
|
||||
}
|
||||
|
||||
sub validate_value {
|
||||
my $self = shift;
|
||||
|
||||
if (grep(/^$_[0]$/, @{$self->legal_values()})) {
|
||||
trick_taint($_[0]);
|
||||
}
|
||||
else {
|
||||
ThrowCodeError('setting_value_invalid',
|
||||
{'name' => $self->{'_setting_name'},
|
||||
'value' => $_[0]});
|
||||
}
|
||||
}
|
||||
|
||||
sub reset_to_default {
|
||||
my ($self) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->do(q{ DELETE
|
||||
FROM profile_setting
|
||||
WHERE setting_name = ?
|
||||
AND user_id = ?},
|
||||
undef, $self->{'_setting_name'}, $self->{'_user_id'});
|
||||
$self->{'value'} = $self->{'default_value'};
|
||||
$self->{'is_default'} = 1;
|
||||
}
|
||||
|
||||
sub set {
|
||||
my ($self, $value) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $query;
|
||||
|
||||
if ($self->{'is_default'}) {
|
||||
$query = q{INSERT INTO profile_setting
|
||||
(setting_value, setting_name, user_id)
|
||||
VALUES (?,?,?)};
|
||||
} else {
|
||||
$query = q{UPDATE profile_setting
|
||||
SET setting_value = ?
|
||||
WHERE setting_name = ?
|
||||
AND user_id = ?};
|
||||
}
|
||||
$dbh->do($query, undef, $value, $self->{'_setting_name'}, $self->{'_user_id'});
|
||||
|
||||
$self->{'value'} = $value;
|
||||
$self->{'is_default'} = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::User::Setting - Object for a user preference setting
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Setting.pm creates a setting object, which is a hash containing the user
|
||||
preference information for a single preference for a single user. These
|
||||
are usually accessed through the "settings" object of a user, and not
|
||||
directly.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
use Bugzilla::User::Setting;
|
||||
my $settings;
|
||||
|
||||
$settings->{$setting_name} = new Bugzilla::User::Setting(
|
||||
$setting_name, $user_id);
|
||||
|
||||
OR
|
||||
|
||||
$settings->{$setting_name} = new Bugzilla::User::Setting(
|
||||
$setting_name, $user_id, $is_enabled,
|
||||
$default_value, $value, $is_default);
|
||||
|
||||
=head1 CLASS FUNCTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<add_setting($name, \@values, $default_value, $subclass, $force_check)>
|
||||
|
||||
Description: Checks for the existence of a setting, and adds it
|
||||
to the database if it does not yet exist.
|
||||
|
||||
Params: C<$name> - string - the name of the new setting
|
||||
C<$values> - arrayref - contains the new choices
|
||||
for the new Setting.
|
||||
C<$default_value> - string - the site default
|
||||
C<$subclass> - string - name of the module returning
|
||||
the list of valid values. This means legal values are
|
||||
not stored in the DB.
|
||||
C<$force_check> - boolean - when true, the existing setting
|
||||
and all its values are deleted and replaced by new data.
|
||||
|
||||
Returns: a pointer to a hash of settings
|
||||
|
||||
|
||||
=item C<get_all_settings($user_id)>
|
||||
|
||||
Description: Provides the user's choices for each setting in the
|
||||
system; if the user has made no choice, uses the site
|
||||
default instead.
|
||||
Params: C<$user_id> - integer - the user id.
|
||||
Returns: a pointer to a hash of settings
|
||||
|
||||
=item C<get_defaults($user_id)>
|
||||
|
||||
Description: When a user is not logged in, they must use the site
|
||||
defaults for every settings; this subroutine provides them.
|
||||
Params: C<$user_id> (optional) - integer - the user id. Note that
|
||||
this optional parameter is mainly for internal use only.
|
||||
Returns: A pointer to a hash of settings. If $user_id was passed, set
|
||||
the user_id value for each setting.
|
||||
|
||||
=item C<set_default($setting_name, $default_value, $is_enabled)>
|
||||
|
||||
Description: Sets the global default for a given setting. Also sets
|
||||
whether users are allowed to choose their own value for
|
||||
this setting, or if they must use the global default.
|
||||
Params: C<$setting_name> - string - the name of the setting
|
||||
C<$default_value> - string - the new default value for this setting
|
||||
C<$is_enabled> - boolean - if false, all users must use the global default
|
||||
Returns: nothing
|
||||
|
||||
=begin private
|
||||
|
||||
=item C<_setting_exists>
|
||||
|
||||
Description: Determines if a given setting exists in the database.
|
||||
Params: C<$setting_name> - string - the setting name
|
||||
Returns: boolean - true if the setting already exists in the DB.
|
||||
|
||||
=back
|
||||
|
||||
=end private
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<legal_values($setting_name)>
|
||||
|
||||
Description: Returns all legal values for this setting
|
||||
Params: none
|
||||
Returns: A reference to an array containing all legal values
|
||||
|
||||
=item C<validate_value>
|
||||
|
||||
Description: Determines whether a value is valid for the setting
|
||||
by checking against the list of legal values.
|
||||
Untaints the parameter if the value is indeed valid,
|
||||
and throws a setting_value_invalid code error if not.
|
||||
Params: An lvalue containing a candidate for a setting value
|
||||
Returns: nothing
|
||||
|
||||
=item C<reset_to_default>
|
||||
|
||||
Description: If a user chooses to use the global default for a given
|
||||
setting, their saved entry is removed from the database via
|
||||
this subroutine.
|
||||
Params: none
|
||||
Returns: nothing
|
||||
|
||||
=item C<set($value)>
|
||||
|
||||
Description: If a user chooses to use their own value rather than the
|
||||
global value for a given setting, OR changes their value for
|
||||
a given setting, this subroutine is called to insert or
|
||||
update the database as appropriate.
|
||||
Params: C<$value> - string - the new value for this setting for this user.
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- 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 Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::User::Setting::Lang;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Bugzilla::User::Setting);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
sub legal_values {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{'legal_values'} if defined $self->{'legal_values'};
|
||||
|
||||
return $self->{'legal_values'} = Bugzilla->languages;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::User::Setting::Lang - Object for a user preference setting for preferred language
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Lang.pm extends Bugzilla::User::Setting and implements a class specialized for
|
||||
setting the preferred language.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<legal_values()>
|
||||
|
||||
Description: Returns all legal languages
|
||||
Params: none
|
||||
Returns: A reference to an array containing the names of all legal languages
|
||||
|
||||
=back
|
||||
@@ -1,79 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
#
|
||||
|
||||
|
||||
package Bugzilla::User::Setting::Skin;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Bugzilla::User::Setting);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use File::Spec::Functions;
|
||||
use File::Basename;
|
||||
|
||||
use constant BUILTIN_SKIN_NAMES => ['standard'];
|
||||
|
||||
sub legal_values {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{'legal_values'} if defined $self->{'legal_values'};
|
||||
|
||||
my $dirbase = bz_locations()->{'skinsdir'} . '/contrib';
|
||||
# Avoid modification of the list BUILTIN_SKIN_NAMES points to by copying the
|
||||
# list over instead of simply writing $legal_values = BUILTIN_SKIN_NAMES.
|
||||
my @legal_values = @{(BUILTIN_SKIN_NAMES)};
|
||||
|
||||
foreach my $direntry (glob(catdir($dirbase, '*'))) {
|
||||
if (-d $direntry) {
|
||||
# Stylesheet set
|
||||
next if basename($direntry) =~ /^cvs$/i;
|
||||
push(@legal_values, basename($direntry));
|
||||
}
|
||||
elsif ($direntry =~ /\.css$/) {
|
||||
# Single-file stylesheet
|
||||
push(@legal_values, basename($direntry));
|
||||
}
|
||||
}
|
||||
|
||||
return $self->{'legal_values'} = \@legal_values;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::User::Setting::Skin - Object for a user preference setting for skins
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Skin.pm extends Bugzilla::User::Setting and implements a class specialized for
|
||||
skins settings.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<legal_values()>
|
||||
|
||||
Description: Returns all legal skins
|
||||
Params: none
|
||||
Returns: A reference to an array containing the names of all legal skins
|
||||
|
||||
=back
|
||||
@@ -1,953 +0,0 @@
|
||||
# -*- 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 <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Util;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Util::EXPORT = qw(is_tainted trick_taint detaint_natural
|
||||
detaint_signed
|
||||
html_quote url_quote xml_quote
|
||||
css_class_quote html_light_quote url_decode
|
||||
i_am_cgi get_netaddr correct_urlbase
|
||||
lsearch ssl_require_redirect
|
||||
diff_arrays diff_strings
|
||||
trim wrap_hard wrap_comment find_wrap_point
|
||||
format_time format_time_decimal validate_date
|
||||
validate_time
|
||||
file_mod_time is_7bit_clean
|
||||
bz_crypt generate_random_password
|
||||
validate_email_syntax clean_text
|
||||
get_text disable_utf8);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
use Date::Parse;
|
||||
use Date::Format;
|
||||
use Text::Wrap;
|
||||
|
||||
# This is from the perlsec page, slightly modified to remove a warning
|
||||
# From that page:
|
||||
# This function makes use of the fact that the presence of
|
||||
# tainted data anywhere within an expression renders the
|
||||
# entire expression tainted.
|
||||
# Don't ask me how it works...
|
||||
sub is_tainted {
|
||||
return not eval { my $foo = join('',@_), kill 0; 1; };
|
||||
}
|
||||
|
||||
sub trick_taint {
|
||||
require Carp;
|
||||
Carp::confess("Undef to trick_taint") unless defined $_[0];
|
||||
my $match = $_[0] =~ /^(.*)$/s;
|
||||
$_[0] = $match ? $1 : undef;
|
||||
return (defined($_[0]));
|
||||
}
|
||||
|
||||
sub detaint_natural {
|
||||
my $match = $_[0] =~ /^(\d+)$/;
|
||||
$_[0] = $match ? $1 : undef;
|
||||
return (defined($_[0]));
|
||||
}
|
||||
|
||||
sub detaint_signed {
|
||||
my $match = $_[0] =~ /^([-+]?\d+)$/;
|
||||
$_[0] = $match ? $1 : undef;
|
||||
# Remove any leading plus sign.
|
||||
if (defined($_[0]) && $_[0] =~ /^\+(\d+)$/) {
|
||||
$_[0] = $1;
|
||||
}
|
||||
return (defined($_[0]));
|
||||
}
|
||||
|
||||
sub html_quote {
|
||||
my ($var) = (@_);
|
||||
$var =~ s/\&/\&/g;
|
||||
$var =~ s/</\</g;
|
||||
$var =~ s/>/\>/g;
|
||||
$var =~ s/\"/\"/g;
|
||||
return $var;
|
||||
}
|
||||
|
||||
sub html_light_quote {
|
||||
my ($text) = @_;
|
||||
|
||||
# List of allowed HTML elements having no attributes.
|
||||
my @allow = qw(b strong em i u p br abbr acronym ins del cite code var
|
||||
dfn samp kbd big small sub sup tt dd dt dl ul li ol
|
||||
fieldset legend);
|
||||
|
||||
# Are HTML::Scrubber and HTML::Parser installed?
|
||||
eval { require HTML::Scrubber;
|
||||
require HTML::Parser;
|
||||
};
|
||||
|
||||
if ($@) { # Package(s) not installed.
|
||||
my $safe = join('|', @allow);
|
||||
my $chr = chr(1);
|
||||
|
||||
# First, escape safe elements.
|
||||
$text =~ s#<($safe)>#$chr$1$chr#go;
|
||||
$text =~ s#</($safe)>#$chr/$1$chr#go;
|
||||
# Now filter < and >.
|
||||
$text =~ s#<#<#g;
|
||||
$text =~ s#>#>#g;
|
||||
# Restore safe elements.
|
||||
$text =~ s#$chr/($safe)$chr#</$1>#go;
|
||||
$text =~ s#$chr($safe)$chr#<$1>#go;
|
||||
return $text;
|
||||
}
|
||||
else { # Packages installed.
|
||||
# We can be less restrictive. We can accept elements with attributes.
|
||||
push(@allow, qw(a blockquote q span));
|
||||
|
||||
# Allowed protocols.
|
||||
my $safe_protocols = join('|', SAFE_PROTOCOLS);
|
||||
my $protocol_regexp = qr{(^(?:$safe_protocols):|^[^:]+$)}i;
|
||||
|
||||
# Deny all elements and attributes unless explicitly authorized.
|
||||
my @default = (0 => {
|
||||
id => 1,
|
||||
name => 1,
|
||||
class => 1,
|
||||
'*' => 0, # Reject all other attributes.
|
||||
}
|
||||
);
|
||||
|
||||
# Specific rules for allowed elements. If no specific rule is set
|
||||
# for a given element, then the default is used.
|
||||
my @rules = (a => {
|
||||
href => $protocol_regexp,
|
||||
title => 1,
|
||||
id => 1,
|
||||
name => 1,
|
||||
class => 1,
|
||||
'*' => 0, # Reject all other attributes.
|
||||
},
|
||||
blockquote => {
|
||||
cite => $protocol_regexp,
|
||||
id => 1,
|
||||
name => 1,
|
||||
class => 1,
|
||||
'*' => 0, # Reject all other attributes.
|
||||
},
|
||||
'q' => {
|
||||
cite => $protocol_regexp,
|
||||
id => 1,
|
||||
name => 1,
|
||||
class => 1,
|
||||
'*' => 0, # Reject all other attributes.
|
||||
},
|
||||
);
|
||||
|
||||
my $scrubber = HTML::Scrubber->new(default => \@default,
|
||||
allow => \@allow,
|
||||
rules => \@rules,
|
||||
comment => 0,
|
||||
process => 0);
|
||||
|
||||
return $scrubber->scrub($text);
|
||||
}
|
||||
}
|
||||
|
||||
# This originally came from CGI.pm, by Lincoln D. Stein
|
||||
sub url_quote {
|
||||
my ($toencode) = (@_);
|
||||
utf8::encode($toencode) # The below regex works only on bytes
|
||||
if Bugzilla->params->{'utf8'} && utf8::is_utf8($toencode);
|
||||
$toencode =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg;
|
||||
return $toencode;
|
||||
}
|
||||
|
||||
sub css_class_quote {
|
||||
my ($toencode) = (@_);
|
||||
$toencode =~ s/ /_/g;
|
||||
$toencode =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("&#x%x;",ord($1))/eg;
|
||||
return $toencode;
|
||||
}
|
||||
|
||||
sub xml_quote {
|
||||
my ($var) = (@_);
|
||||
$var =~ s/\&/\&/g;
|
||||
$var =~ s/</\</g;
|
||||
$var =~ s/>/\>/g;
|
||||
$var =~ s/\"/\"/g;
|
||||
$var =~ s/\'/\'/g;
|
||||
return $var;
|
||||
}
|
||||
|
||||
# This function must not be relied upon to return a valid string to pass to
|
||||
# the DB or the user in UTF-8 situations. The only thing you can rely upon
|
||||
# it for is that if you url_decode a string, it will url_encode back to the
|
||||
# exact same thing.
|
||||
sub url_decode {
|
||||
my ($todecode) = (@_);
|
||||
$todecode =~ tr/+/ /; # pluses become spaces
|
||||
$todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
|
||||
return $todecode;
|
||||
}
|
||||
|
||||
sub i_am_cgi {
|
||||
# I use SERVER_SOFTWARE because it's required to be
|
||||
# defined for all requests in the CGI spec.
|
||||
return exists $ENV{'SERVER_SOFTWARE'} ? 1 : 0;
|
||||
}
|
||||
|
||||
sub ssl_require_redirect {
|
||||
my $method = shift;
|
||||
|
||||
# If currently not in a protected SSL
|
||||
# connection, determine if a redirection is
|
||||
# needed based on value in Bugzilla->params->{ssl}.
|
||||
# If we are already in a protected connection or
|
||||
# sslbase is not set then no action is required.
|
||||
if (uc($ENV{'HTTPS'}) ne 'ON'
|
||||
&& $ENV{'SERVER_PORT'} != 443
|
||||
&& Bugzilla->params->{'sslbase'} ne '')
|
||||
{
|
||||
# System is configured to never require SSL
|
||||
# so no redirection is needed.
|
||||
return 0
|
||||
if Bugzilla->params->{'ssl'} eq 'never';
|
||||
|
||||
# System is configured to always require a SSL
|
||||
# connection so we need to redirect.
|
||||
return 1
|
||||
if Bugzilla->params->{'ssl'} eq 'always';
|
||||
|
||||
# System is configured such that if we are inside
|
||||
# of an authenticated session, then we need to make
|
||||
# sure that all of the connections are over SSL. Non
|
||||
# authenticated sessions SSL is not mandatory.
|
||||
# For XMLRPC requests, if the method is User.login
|
||||
# then we always want the connection to be over SSL
|
||||
# if the system is configured for authenticated
|
||||
# sessions since the user's username and password
|
||||
# will be passed before the user is logged in.
|
||||
return 1
|
||||
if Bugzilla->params->{'ssl'} eq 'authenticated sessions'
|
||||
&& (Bugzilla->user->id
|
||||
|| (defined $method && $method eq 'User.login'));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub correct_urlbase {
|
||||
my $ssl = Bugzilla->params->{'ssl'};
|
||||
return Bugzilla->params->{'urlbase'} if $ssl eq 'never';
|
||||
|
||||
my $sslbase = Bugzilla->params->{'sslbase'};
|
||||
if ($sslbase) {
|
||||
return $sslbase if $ssl eq 'always';
|
||||
# Authenticated Sessions
|
||||
return $sslbase if Bugzilla->user->id;
|
||||
}
|
||||
|
||||
# Set to "authenticated sessions" but nobody's logged in, or
|
||||
# sslbase isn't set.
|
||||
return Bugzilla->params->{'urlbase'};
|
||||
}
|
||||
|
||||
sub lsearch {
|
||||
my ($list,$item) = (@_);
|
||||
my $count = 0;
|
||||
foreach my $i (@$list) {
|
||||
if ($i eq $item) {
|
||||
return $count;
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
sub diff_arrays {
|
||||
my ($old_ref, $new_ref) = @_;
|
||||
|
||||
my @old = @$old_ref;
|
||||
my @new = @$new_ref;
|
||||
|
||||
# For each pair of (old, new) entries:
|
||||
# If they're equal, set them to empty. When done, @old contains entries
|
||||
# that were removed; @new contains ones that got added.
|
||||
foreach my $oldv (@old) {
|
||||
foreach my $newv (@new) {
|
||||
next if ($newv eq '');
|
||||
if ($oldv eq $newv) {
|
||||
$newv = $oldv = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @removed = grep { $_ ne '' } @old;
|
||||
my @added = grep { $_ ne '' } @new;
|
||||
return (\@removed, \@added);
|
||||
}
|
||||
|
||||
sub trim {
|
||||
my ($str) = @_;
|
||||
if ($str) {
|
||||
$str =~ s/^\s+//g;
|
||||
$str =~ s/\s+$//g;
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
sub diff_strings {
|
||||
my ($oldstr, $newstr) = @_;
|
||||
|
||||
# Split the old and new strings into arrays containing their values.
|
||||
$oldstr =~ s/[\s,]+/ /g;
|
||||
$newstr =~ s/[\s,]+/ /g;
|
||||
my @old = split(" ", $oldstr);
|
||||
my @new = split(" ", $newstr);
|
||||
|
||||
my ($rem, $add) = diff_arrays(\@old, \@new);
|
||||
|
||||
my $removed = join (", ", @$rem);
|
||||
my $added = join (", ", @$add);
|
||||
|
||||
return ($removed, $added);
|
||||
}
|
||||
|
||||
sub wrap_comment {
|
||||
my ($comment, $cols) = @_;
|
||||
my $wrappedcomment = "";
|
||||
|
||||
# Use 'local', as recommended by Text::Wrap's perldoc.
|
||||
local $Text::Wrap::columns = $cols || COMMENT_COLS;
|
||||
# Make words that are longer than COMMENT_COLS not wrap.
|
||||
local $Text::Wrap::huge = 'overflow';
|
||||
# Don't mess with tabs.
|
||||
local $Text::Wrap::unexpand = 0;
|
||||
|
||||
# If the line starts with ">", don't wrap it. Otherwise, wrap.
|
||||
foreach my $line (split(/\r\n|\r|\n/, $comment)) {
|
||||
if ($line =~ qr/^>/) {
|
||||
$wrappedcomment .= ($line . "\n");
|
||||
}
|
||||
else {
|
||||
# Due to a segfault in Text::Tabs::expand() when processing tabs with
|
||||
# Unicode (see http://rt.perl.org/rt3/Public/Bug/Display.html?id=52104),
|
||||
# we have to remove tabs before processing the comment. This restriction
|
||||
# can go away when we require Perl 5.8.9 or newer.
|
||||
$line =~ s/\t/ /g;
|
||||
$wrappedcomment .= (wrap('', '', $line) . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
chomp($wrappedcomment); # Text::Wrap adds an extra newline at the end.
|
||||
return $wrappedcomment;
|
||||
}
|
||||
|
||||
sub find_wrap_point {
|
||||
my ($string, $maxpos) = @_;
|
||||
if (!$string) { return 0 }
|
||||
if (length($string) < $maxpos) { return length($string) }
|
||||
my $wrappoint = rindex($string, ",", $maxpos); # look for comma
|
||||
if ($wrappoint < 0) { # can't find comma
|
||||
$wrappoint = rindex($string, " ", $maxpos); # look for space
|
||||
if ($wrappoint < 0) { # can't find space
|
||||
$wrappoint = rindex($string, "-", $maxpos); # look for hyphen
|
||||
if ($wrappoint < 0) { # can't find hyphen
|
||||
$wrappoint = $maxpos; # just truncate it
|
||||
} else {
|
||||
$wrappoint++; # leave hyphen on the left side
|
||||
}
|
||||
}
|
||||
}
|
||||
return $wrappoint;
|
||||
}
|
||||
|
||||
sub wrap_hard {
|
||||
my ($string, $columns) = @_;
|
||||
local $Text::Wrap::columns = $columns;
|
||||
local $Text::Wrap::unexpand = 0;
|
||||
local $Text::Wrap::huge = 'wrap';
|
||||
|
||||
my $wrapped = wrap('', '', $string);
|
||||
chomp($wrapped);
|
||||
return $wrapped;
|
||||
}
|
||||
|
||||
sub format_time {
|
||||
my ($date, $format) = @_;
|
||||
|
||||
# If $format is undefined, try to guess the correct date format.
|
||||
my $show_timezone;
|
||||
if (!defined($format)) {
|
||||
if ($date =~ m/^(\d{4})[-\.](\d{2})[-\.](\d{2}) (\d{2}):(\d{2})(:(\d{2}))?$/) {
|
||||
my $sec = $7;
|
||||
if (defined $sec) {
|
||||
$format = "%Y-%m-%d %T";
|
||||
} else {
|
||||
$format = "%Y-%m-%d %R";
|
||||
}
|
||||
} else {
|
||||
# Default date format. See Date::Format for other formats available.
|
||||
$format = "%Y-%m-%d %R";
|
||||
}
|
||||
# By default, we want the timezone to be displayed.
|
||||
$show_timezone = 1;
|
||||
}
|
||||
else {
|
||||
# Search for %Z or %z, meaning we want the timezone to be displayed.
|
||||
# Till bug 182238 gets fixed, we assume Bugzilla->params->{'timezone'}
|
||||
# is used.
|
||||
$show_timezone = ($format =~ s/\s?%Z$//i);
|
||||
}
|
||||
|
||||
# str2time($date) is undefined if $date has an invalid date format.
|
||||
my $time = str2time($date);
|
||||
|
||||
if (defined $time) {
|
||||
$date = time2str($format, $time);
|
||||
$date .= " " . Bugzilla->params->{'timezone'} if $show_timezone;
|
||||
}
|
||||
else {
|
||||
# Don't let invalid (time) strings to be passed to templates!
|
||||
$date = '';
|
||||
}
|
||||
return trim($date);
|
||||
}
|
||||
|
||||
sub format_time_decimal {
|
||||
my ($time) = (@_);
|
||||
|
||||
my $newtime = sprintf("%.2f", $time);
|
||||
|
||||
if ($newtime =~ /0\Z/) {
|
||||
$newtime = sprintf("%.1f", $time);
|
||||
}
|
||||
|
||||
return $newtime;
|
||||
}
|
||||
|
||||
sub file_mod_time {
|
||||
my ($filename) = (@_);
|
||||
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
|
||||
$atime,$mtime,$ctime,$blksize,$blocks)
|
||||
= stat($filename);
|
||||
return $mtime;
|
||||
}
|
||||
|
||||
sub bz_crypt {
|
||||
my ($password) = @_;
|
||||
|
||||
# The list of characters that can appear in a salt. Salts and hashes
|
||||
# are both encoded as a sequence of characters from a set containing
|
||||
# 64 characters, each one of which represents 6 bits of the salt/hash.
|
||||
# The encoding is similar to BASE64, the difference being that the
|
||||
# BASE64 plus sign (+) is replaced with a forward slash (/).
|
||||
my @saltchars = (0..9, 'A'..'Z', 'a'..'z', '.', '/');
|
||||
|
||||
# Generate the salt. We use an 8 character (48 bit) salt for maximum
|
||||
# security on systems whose crypt uses MD5. Systems with older
|
||||
# versions of crypt will just use the first two characters of the salt.
|
||||
my $salt = '';
|
||||
for ( my $i=0 ; $i < 8 ; ++$i ) {
|
||||
$salt .= $saltchars[rand(64)];
|
||||
}
|
||||
|
||||
# Wide characters cause crypt to die
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
utf8::encode($password) if utf8::is_utf8($password);
|
||||
}
|
||||
|
||||
# Crypt the password.
|
||||
my $cryptedpassword = crypt($password, $salt);
|
||||
|
||||
# Return the crypted password.
|
||||
return $cryptedpassword;
|
||||
}
|
||||
|
||||
sub generate_random_password {
|
||||
my $size = shift || 10; # default to 10 chars if nothing specified
|
||||
return join("", map{ ('0'..'9','a'..'z','A'..'Z')[rand 62] } (1..$size));
|
||||
}
|
||||
|
||||
sub validate_email_syntax {
|
||||
my ($addr) = @_;
|
||||
my $match = Bugzilla->params->{'emailregexp'};
|
||||
my $ret = ($addr =~ /$match/ && $addr !~ /[\\\(\)<>&,;:"\[\] \t\r\n]/);
|
||||
if ($ret) {
|
||||
# We assume these checks to suffice to consider the address untainted.
|
||||
trick_taint($_[0]);
|
||||
}
|
||||
return $ret ? 1 : 0;
|
||||
}
|
||||
|
||||
sub validate_date {
|
||||
my ($date) = @_;
|
||||
my $date2;
|
||||
|
||||
# $ts is undefined if the parser fails.
|
||||
my $ts = str2time($date);
|
||||
if ($ts) {
|
||||
$date2 = time2str("%Y-%m-%d", $ts);
|
||||
|
||||
$date =~ s/(\d+)-0*(\d+?)-0*(\d+?)/$1-$2-$3/;
|
||||
$date2 =~ s/(\d+)-0*(\d+?)-0*(\d+?)/$1-$2-$3/;
|
||||
}
|
||||
my $ret = ($ts && $date eq $date2);
|
||||
return $ret ? 1 : 0;
|
||||
}
|
||||
|
||||
sub validate_time {
|
||||
my ($time) = @_;
|
||||
my $time2;
|
||||
|
||||
# $ts is undefined if the parser fails.
|
||||
my $ts = str2time($time);
|
||||
if ($ts) {
|
||||
$time2 = time2str("%H:%M:%S", $ts);
|
||||
if ($time =~ /^(\d{1,2}):(\d\d)(?::(\d\d))?$/) {
|
||||
$time = sprintf("%02d:%02d:%02d", $1, $2, $3 || 0);
|
||||
}
|
||||
}
|
||||
my $ret = ($ts && $time eq $time2);
|
||||
return $ret ? 1 : 0;
|
||||
}
|
||||
|
||||
sub is_7bit_clean {
|
||||
return $_[0] !~ /[^\x20-\x7E\x0A\x0D]/;
|
||||
}
|
||||
|
||||
sub clean_text {
|
||||
my ($dtext) = shift;
|
||||
$dtext =~ s/[\x00-\x1F\x7F]+/ /g; # change control characters into a space
|
||||
return trim($dtext);
|
||||
}
|
||||
|
||||
sub get_text {
|
||||
my ($name, $vars) = @_;
|
||||
my $template = Bugzilla->template_inner;
|
||||
$vars ||= {};
|
||||
$vars->{'message'} = $name;
|
||||
my $message;
|
||||
$template->process('global/message.txt.tmpl', $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
# Remove the indenting that exists in messages.html.tmpl.
|
||||
$message =~ s/^ //gm;
|
||||
return $message;
|
||||
}
|
||||
|
||||
|
||||
sub get_netaddr {
|
||||
my $ipaddr = shift;
|
||||
|
||||
# Check for a valid IPv4 addr which we know how to parse
|
||||
if (!$ipaddr || $ipaddr !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
|
||||
return undef;
|
||||
}
|
||||
|
||||
my $addr = unpack("N", pack("CCCC", split(/\./, $ipaddr)));
|
||||
|
||||
my $maskbits = Bugzilla->params->{'loginnetmask'};
|
||||
|
||||
# Make Bugzilla ignore the IP address if loginnetmask is set to 0
|
||||
return "0.0.0.0" if ($maskbits == 0);
|
||||
|
||||
$addr >>= (32-$maskbits);
|
||||
|
||||
$addr <<= (32-$maskbits);
|
||||
return join(".", unpack("CCCC", pack("N", $addr)));
|
||||
}
|
||||
|
||||
sub disable_utf8 {
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
binmode STDOUT, ':raw'; # Turn off UTF8 encoding.
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Util - Generic utility functions for bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Util;
|
||||
|
||||
# Functions for dealing with variable tainting
|
||||
$rv = is_tainted($var);
|
||||
trick_taint($var);
|
||||
detaint_natural($var);
|
||||
detaint_signed($var);
|
||||
|
||||
# Functions for quoting
|
||||
html_quote($var);
|
||||
url_quote($var);
|
||||
xml_quote($var);
|
||||
|
||||
# Functions for decoding
|
||||
$rv = url_decode($var);
|
||||
|
||||
# Functions that tell you about your environment
|
||||
my $is_cgi = i_am_cgi();
|
||||
my $net_addr = get_netaddr($ip_addr);
|
||||
my $urlbase = correct_urlbase();
|
||||
|
||||
# Functions for searching
|
||||
$loc = lsearch(\@arr, $val);
|
||||
|
||||
# Data manipulation
|
||||
($removed, $added) = diff_arrays(\@old, \@new);
|
||||
|
||||
# Functions for manipulating strings
|
||||
$val = trim(" abc ");
|
||||
($removed, $added) = diff_strings($old, $new);
|
||||
$wrapped = wrap_comment($comment);
|
||||
|
||||
# Functions for formatting time
|
||||
format_time($time);
|
||||
|
||||
# Functions for dealing with files
|
||||
$time = file_mod_time($filename);
|
||||
|
||||
# Cryptographic Functions
|
||||
$crypted_password = bz_crypt($password);
|
||||
$new_password = generate_random_password($password_length);
|
||||
|
||||
# Validation Functions
|
||||
validate_email_syntax($email);
|
||||
validate_date($date);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This package contains various utility functions which do not belong anywhere
|
||||
else.
|
||||
|
||||
B<It is not intended as a general dumping group for something which
|
||||
people feel might be useful somewhere, someday>. Do not add methods to this
|
||||
package unless it is intended to be used for a significant number of files,
|
||||
and it does not belong anywhere else.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
This package provides several types of routines:
|
||||
|
||||
=head2 Tainting
|
||||
|
||||
Several functions are available to deal with tainted variables. B<Use these
|
||||
with care> to avoid security holes.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<is_tainted>
|
||||
|
||||
Determines whether a particular variable is tainted
|
||||
|
||||
=item C<trick_taint($val)>
|
||||
|
||||
Tricks perl into untainting a particular variable.
|
||||
|
||||
Use trick_taint() when you know that there is no way that the data
|
||||
in a scalar can be tainted, but taint mode still bails on it.
|
||||
|
||||
B<WARNING!! Using this routine on data that really could be tainted defeats
|
||||
the purpose of taint mode. It should only be used on variables that have been
|
||||
sanity checked in some way and have been determined to be OK.>
|
||||
|
||||
=item C<detaint_natural($num)>
|
||||
|
||||
This routine detaints a natural number. It returns a true value if the
|
||||
value passed in was a valid natural number, else it returns false. You
|
||||
B<MUST> check the result of this routine to avoid security holes.
|
||||
|
||||
=item C<detaint_signed($num)>
|
||||
|
||||
This routine detaints a signed integer. It returns a true value if the
|
||||
value passed in was a valid signed integer, else it returns false. You
|
||||
B<MUST> check the result of this routine to avoid security holes.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Quoting
|
||||
|
||||
Some values may need to be quoted from perl. However, this should in general
|
||||
be done in the template where possible.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<html_quote($val)>
|
||||
|
||||
Returns a value quoted for use in HTML, with &, E<lt>, E<gt>, and E<34> being
|
||||
replaced with their appropriate HTML entities.
|
||||
|
||||
=item C<html_light_quote($val)>
|
||||
|
||||
Returns a string where only explicitly allowed HTML elements and attributes
|
||||
are kept. All HTML elements and attributes not being in the whitelist are either
|
||||
escaped (if HTML::Scrubber is not installed) or removed.
|
||||
|
||||
=item C<url_quote($val)>
|
||||
|
||||
Quotes characters so that they may be included as part of a url.
|
||||
|
||||
=item C<css_class_quote($val)>
|
||||
|
||||
Quotes characters so that they may be used as CSS class names. Spaces
|
||||
are replaced by underscores.
|
||||
|
||||
=item C<xml_quote($val)>
|
||||
|
||||
This is similar to C<html_quote>, except that ' is escaped to '. This
|
||||
is kept separate from html_quote partly for compatibility with previous code
|
||||
(for ') and partly for future handling of non-ASCII characters.
|
||||
|
||||
=item C<url_decode($val)>
|
||||
|
||||
Converts the %xx encoding from the given URL back to its original form.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Environment and Location
|
||||
|
||||
Functions returning information about your environment or location.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<i_am_cgi()>
|
||||
|
||||
Tells you whether or not you are being run as a CGI script in a web
|
||||
server. For example, it would return false if the caller is running
|
||||
in a command-line script.
|
||||
|
||||
=item C<get_netaddr($ipaddr)>
|
||||
|
||||
Given an IP address, this returns the associated network address, using
|
||||
C<Bugzilla->params->{'loginnetmask'}> as the netmask. This can be used
|
||||
to obtain data in order to restrict weak authentication methods (such as
|
||||
cookies) to only some addresses.
|
||||
|
||||
=item C<correct_urlbase()>
|
||||
|
||||
Returns either the C<sslbase> or C<urlbase> parameter, depending on the
|
||||
current setting for the C<ssl> parameter.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Searching
|
||||
|
||||
Functions for searching within a set of values.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<lsearch($list, $item)>
|
||||
|
||||
Returns the position of C<$item> in C<$list>. C<$list> must be a list
|
||||
reference.
|
||||
|
||||
If the item is not in the list, returns -1.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Data Manipulation
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<diff_arrays(\@old, \@new)>
|
||||
|
||||
Description: Takes two arrayrefs, and will tell you what it takes to
|
||||
get from @old to @new.
|
||||
Params: @old = array that you are changing from
|
||||
@new = array that you are changing to
|
||||
Returns: A list of two arrayrefs. The first is a reference to an
|
||||
array containing items that were removed from @old. The
|
||||
second is a reference to an array containing items
|
||||
that were added to @old. If both returned arrays are
|
||||
empty, @old and @new contain the same values.
|
||||
|
||||
=back
|
||||
|
||||
=head2 String Manipulation
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<trim($str)>
|
||||
|
||||
Removes any leading or trailing whitespace from a string. This routine does not
|
||||
modify the existing string.
|
||||
|
||||
=item C<diff_strings($oldstr, $newstr)>
|
||||
|
||||
Takes two strings containing a list of comma- or space-separated items
|
||||
and returns what items were removed from or added to the new one,
|
||||
compared to the old one. Returns a list, where the first entry is a scalar
|
||||
containing removed items, and the second entry is a scalar containing added
|
||||
items.
|
||||
|
||||
=item C<wrap_hard($string, $size)>
|
||||
|
||||
Wraps a string, so that a line is I<never> longer than C<$size>.
|
||||
Returns the string, wrapped.
|
||||
|
||||
=item C<wrap_comment($comment)>
|
||||
|
||||
Takes a bug comment, and wraps it to the appropriate length. The length is
|
||||
currently specified in C<Bugzilla::Constants::COMMENT_COLS>. Lines beginning
|
||||
with ">" are assumed to be quotes, and they will not be wrapped.
|
||||
|
||||
The intended use of this function is to wrap comments that are about to be
|
||||
displayed or emailed. Generally, wrapped text should not be stored in the
|
||||
database.
|
||||
|
||||
=item C<find_wrap_point($string, $maxpos)>
|
||||
|
||||
Search for a comma, a whitespace or a hyphen to split $string, within the first
|
||||
$maxpos characters. If none of them is found, just split $string at $maxpos.
|
||||
The search starts at $maxpos and goes back to the beginning of the string.
|
||||
|
||||
=item C<is_7bit_clean($str)>
|
||||
|
||||
Returns true is the string contains only 7-bit characters (ASCII 32 through 126,
|
||||
ASCII 10 (LineFeed) and ASCII 13 (Carrage Return).
|
||||
|
||||
=item C<disable_utf8()>
|
||||
|
||||
Disable utf8 on STDOUT (and display raw data instead).
|
||||
|
||||
=item C<clean_text($str)>
|
||||
Returns the parameter "cleaned" by exchanging non-printable characters with spaces.
|
||||
Specifically characters (ASCII 0 through 31) and (ASCII 127) will become ASCII 32 (Space).
|
||||
|
||||
=item C<get_text>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This is a method of getting localized strings within Bugzilla code.
|
||||
Use this when you don't want to display a whole template, you just
|
||||
want a particular string.
|
||||
|
||||
It uses the F<global/message.txt.tmpl> template to return a string.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$message> - The identifier for the message.
|
||||
|
||||
=item C<$vars> - A hashref. Any variables you want to pass to the template.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A string.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Formatting Time
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<format_time($time)>
|
||||
|
||||
Takes a time, converts it to the desired format and appends the timezone
|
||||
as defined in editparams.cgi, if desired. This routine will be expanded
|
||||
in the future to adjust for user preferences regarding what timezone to
|
||||
display times in.
|
||||
|
||||
This routine is mainly called from templates to filter dates, see
|
||||
"FILTER time" in Templates.pm. In this case, $format is undefined and
|
||||
the routine has to "guess" the date format that was passed to $dbh->sql_date_format().
|
||||
|
||||
|
||||
=item C<format_time_decimal($time)>
|
||||
|
||||
Returns a number with 2 digit precision, unless the last digit is a 0. Then it
|
||||
returns only 1 digit precision.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Files
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<file_mod_time($filename)>
|
||||
|
||||
Takes a filename and returns the modification time. It returns it in the format
|
||||
of the "mtime" parameter of the perl "stat" function.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Cryptography
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<bz_crypt($password)>
|
||||
|
||||
Takes a string and returns a C<crypt>ed value for it, using a random salt.
|
||||
|
||||
Please always use this function instead of the built-in perl "crypt"
|
||||
when initially encrypting a password.
|
||||
|
||||
=begin undocumented
|
||||
|
||||
Random salts are generated because the alternative is usually
|
||||
to use the first two characters of the password itself, and since
|
||||
the salt appears in plaintext at the beginning of the encrypted
|
||||
password string this has the effect of revealing the first two
|
||||
characters of the password to anyone who views the encrypted version.
|
||||
|
||||
=end undocumented
|
||||
|
||||
=item C<generate_random_password($password_length)>
|
||||
|
||||
Returns an alphanumeric string with the specified length
|
||||
(10 characters by default). Use this function to generate passwords
|
||||
and tokens.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Validation
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<validate_email_syntax($email)>
|
||||
|
||||
Do a syntax checking for a legal email address and returns 1 if
|
||||
the check is successful, else returns 0.
|
||||
Untaints C<$email> if successful.
|
||||
|
||||
=item C<validate_date($date)>
|
||||
|
||||
Make sure the date has the correct format and returns 1 if
|
||||
the check is successful, else returns 0.
|
||||
|
||||
=back
|
||||
@@ -1,263 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Version;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Install::Util qw(vers_cmp);
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
################################
|
||||
##### Initialization #####
|
||||
################################
|
||||
|
||||
use constant DEFAULT_VERSION => 'unspecified';
|
||||
|
||||
use constant DB_TABLE => 'versions';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
value
|
||||
product_id
|
||||
);
|
||||
|
||||
use constant NAME_FIELD => 'value';
|
||||
# This is "id" because it has to be filled in and id is probably the fastest.
|
||||
# We do a custom sort in new_from_list below.
|
||||
use constant LIST_ORDER => 'id';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $param = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $product;
|
||||
if (ref $param) {
|
||||
$product = $param->{product};
|
||||
my $name = $param->{name};
|
||||
if (!defined $product) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'product',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
if (!defined $name) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'name',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
|
||||
my $condition = 'product_id = ? AND value = ?';
|
||||
my @values = ($product->id, $name);
|
||||
$param = { condition => $condition, values => \@values };
|
||||
}
|
||||
|
||||
unshift @_, $param;
|
||||
return $class->SUPER::new(@_);
|
||||
}
|
||||
|
||||
sub new_from_list {
|
||||
my $self = shift;
|
||||
my $list = $self->SUPER::new_from_list(@_);
|
||||
return [sort { vers_cmp(lc($a->name), lc($b->name)) } @$list];
|
||||
}
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_count'}) {
|
||||
$self->{'bug_count'} = $dbh->selectrow_array(qq{
|
||||
SELECT COUNT(*) FROM bugs
|
||||
WHERE product_id = ? AND version = ?}, undef,
|
||||
($self->product_id, $self->name)) || 0;
|
||||
}
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# The version cannot be removed if there are bugs
|
||||
# associated with it.
|
||||
if ($self->bug_count) {
|
||||
ThrowUserError("version_has_bugs", { nb => $self->bug_count });
|
||||
}
|
||||
|
||||
$dbh->do(q{DELETE FROM versions WHERE product_id = ? AND value = ?},
|
||||
undef, ($self->product_id, $self->name));
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my ($name, $product) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$name || ThrowUserError('version_not_specified');
|
||||
|
||||
# Remove unprintable characters
|
||||
$name = clean_text($name);
|
||||
|
||||
return 0 if ($name eq $self->name);
|
||||
my $version = new Bugzilla::Version({ product => $product, name => $name });
|
||||
|
||||
if ($version) {
|
||||
ThrowUserError('version_already_exists',
|
||||
{'name' => $version->name,
|
||||
'product' => $product->name});
|
||||
}
|
||||
|
||||
trick_taint($name);
|
||||
$dbh->do("UPDATE bugs SET version = ?
|
||||
WHERE version = ? AND product_id = ?", undef,
|
||||
($name, $self->name, $self->product_id));
|
||||
|
||||
$dbh->do("UPDATE versions SET value = ?
|
||||
WHERE product_id = ? AND value = ?", undef,
|
||||
($name, $self->product_id, $self->name));
|
||||
|
||||
$self->{'value'} = $name;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
###############################
|
||||
##### Accessors ####
|
||||
###############################
|
||||
|
||||
sub name { return $_[0]->{'value'}; }
|
||||
sub product_id { return $_[0]->{'product_id'}; }
|
||||
|
||||
###############################
|
||||
##### Subroutines ###
|
||||
###############################
|
||||
|
||||
sub create {
|
||||
my ($name, $product) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Cleanups and validity checks
|
||||
$name || ThrowUserError('version_blank_name');
|
||||
|
||||
# Remove unprintable characters
|
||||
$name = clean_text($name);
|
||||
|
||||
my $version = new Bugzilla::Version({ product => $product, name => $name });
|
||||
if ($version) {
|
||||
ThrowUserError('version_already_exists',
|
||||
{'name' => $version->name,
|
||||
'product' => $product->name});
|
||||
}
|
||||
|
||||
# Add the new version
|
||||
trick_taint($name);
|
||||
$dbh->do(q{INSERT INTO versions (value, product_id)
|
||||
VALUES (?, ?)}, undef, ($name, $product->id));
|
||||
|
||||
return new Bugzilla::Version($dbh->bz_last_key('versions', 'id'));
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Version - Bugzilla product version class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Version;
|
||||
|
||||
my $version = new Bugzilla::Version(1, 'version_value');
|
||||
|
||||
my $product_id = $version->product_id;
|
||||
my $value = $version->value;
|
||||
|
||||
$version->remove_from_db;
|
||||
|
||||
my $updated = $version->update($version_name, $product);
|
||||
|
||||
my $version = $hash_ref->{'version_value'};
|
||||
|
||||
my $version = Bugzilla::Version::create($version_name, $product);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Version.pm represents a Product Version object.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<new($product_id, $value)>
|
||||
|
||||
Description: The constructor is used to load an existing version
|
||||
by passing a product id and a version value.
|
||||
|
||||
Params: $product_id - Integer with a product id.
|
||||
$value - String with a version value.
|
||||
|
||||
Returns: A Bugzilla::Version object.
|
||||
|
||||
=item C<bug_count()>
|
||||
|
||||
Description: Returns the total of bugs that belong to the version.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer with the number of bugs.
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: Removes the version from the database.
|
||||
|
||||
Params: none.
|
||||
|
||||
Retruns: none.
|
||||
|
||||
=item C<update($name, $product)>
|
||||
|
||||
Description: Update the value of the version.
|
||||
|
||||
Params: $name - String with the new version value.
|
||||
$product - Bugzilla::Product object the version belongs to.
|
||||
|
||||
Returns: An integer - 1 if the version has been updated, else 0.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<create($version_name, $product)>
|
||||
|
||||
Description: Create a new version for the given product.
|
||||
|
||||
Params: $version_name - String with a version value.
|
||||
$product - A Bugzilla::Product object.
|
||||
|
||||
Returns: A Bugzilla::Version object.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,287 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::WebService;
|
||||
|
||||
use strict;
|
||||
use Bugzilla::WebService::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Date::Parse;
|
||||
|
||||
sub fail_unimplemented {
|
||||
my $this = shift;
|
||||
|
||||
die SOAP::Fault
|
||||
->faultcode(ERROR_UNIMPLEMENTED)
|
||||
->faultstring('Service Unimplemented');
|
||||
}
|
||||
|
||||
sub datetime_format {
|
||||
my ($self, $date_string) = @_;
|
||||
|
||||
my $time = str2time($date_string);
|
||||
my ($sec, $min, $hour, $mday, $mon, $year) = localtime $time;
|
||||
# This format string was stolen from SOAP::Utils->format_datetime,
|
||||
# which doesn't work but which has almost the right format string.
|
||||
my $iso_datetime = sprintf('%d%02d%02dT%02d:%02d:%02d',
|
||||
$year + 1900, $mon + 1, $mday, $hour, $min, $sec);
|
||||
return $iso_datetime;
|
||||
}
|
||||
|
||||
sub handle_login {
|
||||
my ($classes, $action, $uri, $method) = @_;
|
||||
|
||||
my $class = $classes->{$uri};
|
||||
eval "require $class";
|
||||
|
||||
return if $class->login_exempt($method);
|
||||
Bugzilla->login();
|
||||
|
||||
# Even though we check for the need to redirect in
|
||||
# Bugzilla->login() we check here again since Bugzilla->login()
|
||||
# does not know what the current XMLRPC method is. Therefore
|
||||
# ssl_require_redirect in Bugzilla->login() will have returned
|
||||
# false if system was configured to redirect for authenticated
|
||||
# sessions and the user was not yet logged in.
|
||||
# So here we pass in the method name to ssl_require_redirect so
|
||||
# it can then check for the extra case where the method equals
|
||||
# User.login, which we would then need to redirect if not
|
||||
# over a secure connection.
|
||||
my $full_method = $uri . "." . $method;
|
||||
Bugzilla->cgi->require_https(Bugzilla->params->{'sslbase'})
|
||||
if ssl_require_redirect($full_method);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# For some methods, we shouldn't call Bugzilla->login before we call them
|
||||
use constant LOGIN_EXEMPT => { };
|
||||
|
||||
sub login_exempt {
|
||||
my ($class, $method) = @_;
|
||||
|
||||
return $class->LOGIN_EXEMPT->{$method};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
package Bugzilla::WebService::XMLRPC::Transport::HTTP::CGI;
|
||||
use strict;
|
||||
eval { require XMLRPC::Transport::HTTP; };
|
||||
our @ISA = qw(XMLRPC::Transport::HTTP::CGI);
|
||||
|
||||
sub initialize {
|
||||
my $self = shift;
|
||||
my %retval = $self->SUPER::initialize(@_);
|
||||
$retval{'serializer'} = Bugzilla::WebService::XMLRPC::Serializer->new;
|
||||
return %retval;
|
||||
}
|
||||
|
||||
sub make_response {
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::make_response(@_);
|
||||
|
||||
# XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
|
||||
# its cookies in Bugzilla::CGI, so we need to copy them over.
|
||||
foreach (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
|
||||
$self->response->headers->push_header('Set-Cookie', $_);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
# This package exists to fix a UTF-8 bug in SOAP::Lite.
|
||||
# See http://rt.cpan.org/Public/Bug/Display.html?id=32952.
|
||||
package Bugzilla::WebService::XMLRPC::Serializer;
|
||||
use strict;
|
||||
# We can't use "use base" because XMLRPC::Serializer doesn't return
|
||||
# a true value.
|
||||
eval { require XMLRPC::Lite; };
|
||||
our @ISA = qw(XMLRPC::Serializer);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
# This fixes UTF-8.
|
||||
$self->{'_typelookup'}->{'base64'} =
|
||||
[10, sub { !utf8::is_utf8($_[0]) && $_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/},
|
||||
'as_base64'];
|
||||
# This makes arrays work right even though we're a subclass.
|
||||
# (See http://rt.cpan.org//Ticket/Display.html?id=34514)
|
||||
$self->{'_encodingStyle'} = '';
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
my ($value) = @_;
|
||||
# Something weird happens with XML::Parser when we have upper-ASCII
|
||||
# characters encoded as UTF-8, and this fixes it.
|
||||
utf8::encode($value) if utf8::is_utf8($value)
|
||||
&& $value =~ /^[\x00-\xff]+$/;
|
||||
return $self->SUPER::as_string($value);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::WebService - The Web Service interface to Bugzilla
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is the standard API for external programs that want to interact
|
||||
with Bugzilla. It provides various methods in various modules.
|
||||
|
||||
Currently the only method of accessing the API is via XML-RPC. The XML-RPC
|
||||
standard is described here: L<http://www.xmlrpc.com/spec>
|
||||
|
||||
The endpoint for Bugzilla WebServices is the C<xmlrpc.cgi> script in
|
||||
your Bugzilla installation. For example, if your Bugzilla is at
|
||||
C<bugzilla.yourdomain.com>, then your XML-RPC client would access the
|
||||
API via: C<http://bugzilla.yourdomain.com/xmlrpc.cgi>
|
||||
|
||||
=head1 CALLING METHODS
|
||||
|
||||
Methods are called in the normal XML-RPC fashion. Bugzilla does not currently
|
||||
implement any extensions to the standard method of XML-RPC method calling.
|
||||
|
||||
Methods are grouped into "packages", like C<Bug> for
|
||||
L<Bugzilla::WebService::Bug>. So, for example,
|
||||
L<Bugzilla::WebService::Bug/get>, is called as C<Bug.get> in XML-RPC.
|
||||
|
||||
=head1 PARAMETERS
|
||||
|
||||
In addition to the standard parameter types like C<int>, C<string>, etc.,
|
||||
XML-RPC has two data structures, a C<< <struct> >> and an C<< <array> >>.
|
||||
|
||||
=head2 Structs
|
||||
|
||||
In Perl, we call a C<< <struct> >> a "hash" or a "hashref". You may see
|
||||
us refer to it that way in the API documentation.
|
||||
|
||||
In example code, you will see the characters C<{> and C<}> used to represent
|
||||
the beginning and end of structs.
|
||||
|
||||
For example, here's a struct in XML-RPC:
|
||||
|
||||
<struct>
|
||||
<member>
|
||||
<name>fruit</name>
|
||||
<value><string>oranges</string></value>
|
||||
</member>
|
||||
<member>
|
||||
<name>vegetable</name>
|
||||
<value><string>lettuce</string></value>
|
||||
</member>
|
||||
</struct>
|
||||
|
||||
In our example code in these API docs, that would look like:
|
||||
|
||||
{ fruit => 'oranges', vegetable => 'lettuce' }
|
||||
|
||||
=head2 Arrays
|
||||
|
||||
In example code, you will see the characters C<[> and C<]> used to
|
||||
represent the beginning and end of arrays.
|
||||
|
||||
For example, here's an array in XML-RPC:
|
||||
|
||||
<array>
|
||||
<data>
|
||||
<value><i4>1</i4></value>
|
||||
<value><i4>2</i4></value>
|
||||
<value><i4>3</i4></value>
|
||||
</data>
|
||||
</array>
|
||||
|
||||
In our example code in these API docs, that would look like:
|
||||
|
||||
[1, 2, 3]
|
||||
|
||||
=head2 How Bugzilla WebService Methods Take Parameters
|
||||
|
||||
B<All> Bugzilla WebServices functions take their parameters in
|
||||
a C<< <struct> >>. Another way of saying this would be: All functions
|
||||
take a single argument, a C<< <struct> >> that contains all parameters.
|
||||
The names of the parameters listed in the API docs for each function are
|
||||
the C<name> element for the struct C<member>s.
|
||||
|
||||
=head1 LOGGING IN
|
||||
|
||||
You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla
|
||||
user. This issues standard HTTP cookies that you must then use in future
|
||||
calls, so your XML-RPC client must be capable of receiving and transmitting
|
||||
cookies.
|
||||
|
||||
=head1 STABLE, EXPERIMENTAL, and UNSTABLE
|
||||
|
||||
Methods are marked B<STABLE> if you can expect their parameters and
|
||||
return values not to change between versions of Bugzilla. You are
|
||||
best off always using methods marked B<STABLE>. We may add parameters
|
||||
and additional items to the return values, but your old code will
|
||||
always continue to work with any new changes we make. If we ever break
|
||||
a B<STABLE> interface, we'll post a big notice in the Release Notes,
|
||||
and it will only happen during a major new release.
|
||||
|
||||
Methods (or parts of methods) are marked B<EXPERIMENTAL> if
|
||||
we I<believe> they will be stable, but there's a slight chance that
|
||||
small parts will change in the future.
|
||||
|
||||
Certain parts of a method's description may be marked as B<UNSTABLE>,
|
||||
in which case those parts are not guaranteed to stay the same between
|
||||
Bugzilla versions.
|
||||
|
||||
=head1 ERRORS
|
||||
|
||||
If a particular webservice call fails, it will throw a standard XML-RPC
|
||||
error. There will be a numeric error code, and then the description
|
||||
field will contain descriptive text of the error. Each error that Bugzilla
|
||||
can throw has a specific code that will not change between versions of
|
||||
Bugzilla.
|
||||
|
||||
The various errors that functions can throw are specified by the
|
||||
documentation of those functions.
|
||||
|
||||
If your code needs to know what error Bugzilla threw, use the numeric
|
||||
code. Don't try to parse the description, because that may change
|
||||
from version to version of Bugzilla.
|
||||
|
||||
Note that if you display the error to the user in an HTML program, make
|
||||
sure that you properly escape the error, as it will not be HTML-escaped.
|
||||
|
||||
=head2 Transient vs. Fatal Errors
|
||||
|
||||
If the error code is a number greater than 0, the error is considered
|
||||
"transient," which means that it was an error made by the user, not
|
||||
some problem with Bugzilla itself.
|
||||
|
||||
If the error code is a number less than 0, the error is "fatal," which
|
||||
means that it's some error in Bugzilla itself that probably requires
|
||||
administrative attention.
|
||||
|
||||
Negative numbers and positive numbers don't overlap. That is, if there's
|
||||
an error 302, there won't be an error -302.
|
||||
|
||||
=head2 Unknown Errors
|
||||
|
||||
Sometimes a function will throw an error that doesn't have a specific
|
||||
error code. In this case, the code will be C<-32000> if it's a "fatal"
|
||||
error, and C<32000> if it's a "transient" error.
|
||||
@@ -1,583 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Mads Bondo Dydensborg <mbd@dbc.dk>
|
||||
# Tsahi Asher <tsahi_75@yahoo.com>
|
||||
|
||||
package Bugzilla::WebService::Bug;
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::WebService);
|
||||
import SOAP::Data qw(type);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::WebService::Constants;
|
||||
use Bugzilla::Bug;
|
||||
use Bugzilla::BugMail;
|
||||
use Bugzilla::Util qw(trim);
|
||||
|
||||
#############
|
||||
# Constants #
|
||||
#############
|
||||
|
||||
# This maps the names of internal Bugzilla bug fields to things that would
|
||||
# make sense to somebody who's not intimately familiar with the inner workings
|
||||
# of Bugzilla. (These are the field names that the WebService uses.)
|
||||
use constant FIELD_MAP => {
|
||||
status => 'bug_status',
|
||||
severity => 'bug_severity',
|
||||
description => 'comment',
|
||||
summary => 'short_desc',
|
||||
platform => 'rep_platform',
|
||||
};
|
||||
|
||||
use constant GLOBAL_SELECT_FIELDS => qw(
|
||||
bug_severity
|
||||
bug_status
|
||||
op_sys
|
||||
priority
|
||||
rep_platform
|
||||
resolution
|
||||
);
|
||||
|
||||
use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component);
|
||||
|
||||
######################################################
|
||||
# Add aliases here for old method name compatibility #
|
||||
######################################################
|
||||
|
||||
BEGIN { *get_bugs = \&get }
|
||||
|
||||
###########
|
||||
# Methods #
|
||||
###########
|
||||
|
||||
sub get {
|
||||
my ($self, $params) = @_;
|
||||
my $ids = $params->{ids};
|
||||
defined $ids || ThrowCodeError('param_required', { param => 'ids' });
|
||||
|
||||
my @return;
|
||||
foreach my $bug_id (@$ids) {
|
||||
ValidateBugID($bug_id);
|
||||
my $bug = new Bugzilla::Bug($bug_id);
|
||||
|
||||
# Timetracking fields are deleted if the user doesn't belong to
|
||||
# the corresponding group.
|
||||
unless (Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
|
||||
delete $bug->{'estimated_time'};
|
||||
delete $bug->{'remaining_time'};
|
||||
delete $bug->{'deadline'};
|
||||
}
|
||||
# This is done in this fashion in order to produce a stable API.
|
||||
# The internals of Bugzilla::Bug are not stable enough to just
|
||||
# return them directly.
|
||||
my $creation_ts = $self->datetime_format($bug->creation_ts);
|
||||
my $delta_ts = $self->datetime_format($bug->delta_ts);
|
||||
my %item;
|
||||
$item{'creation_time'} = type('dateTime')->value($creation_ts);
|
||||
$item{'last_change_time'} = type('dateTime')->value($delta_ts);
|
||||
$item{'internals'} = $bug;
|
||||
$item{'id'} = type('int')->value($bug->bug_id);
|
||||
$item{'summary'} = type('string')->value($bug->short_desc);
|
||||
|
||||
if (Bugzilla->params->{'usebugaliases'}) {
|
||||
$item{'alias'} = type('string')->value($bug->alias);
|
||||
}
|
||||
else {
|
||||
# For API reasons, we always want the value to appear, we just
|
||||
# don't want it to have a value if aliases are turned off.
|
||||
$item{'alias'} = undef;
|
||||
}
|
||||
|
||||
push(@return, \%item);
|
||||
}
|
||||
|
||||
return { bugs => \@return };
|
||||
}
|
||||
|
||||
|
||||
sub create {
|
||||
my ($self, $params) = @_;
|
||||
|
||||
Bugzilla->login(LOGIN_REQUIRED);
|
||||
|
||||
my %field_values;
|
||||
foreach my $field (keys %$params) {
|
||||
my $field_name = FIELD_MAP->{$field} || $field;
|
||||
$field_values{$field_name} = $params->{$field};
|
||||
}
|
||||
|
||||
# WebService users can't set the creation date of a bug.
|
||||
delete $field_values{'creation_ts'};
|
||||
|
||||
my $bug = Bugzilla::Bug->create(\%field_values);
|
||||
|
||||
Bugzilla::BugMail::Send($bug->bug_id, { changer => $bug->reporter->login });
|
||||
|
||||
return { id => type('int')->value($bug->bug_id) };
|
||||
}
|
||||
|
||||
sub legal_values {
|
||||
my ($self, $params) = @_;
|
||||
my $field = FIELD_MAP->{$params->{field}} || $params->{field};
|
||||
|
||||
my @custom_select = Bugzilla->get_fields(
|
||||
{custom => 1, type => [FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT]});
|
||||
# We only want field names.
|
||||
@custom_select = map {$_->name} @custom_select;
|
||||
|
||||
my $values;
|
||||
if (grep($_ eq $field, GLOBAL_SELECT_FIELDS, @custom_select)) {
|
||||
$values = get_legal_field_values($field);
|
||||
}
|
||||
elsif (grep($_ eq $field, PRODUCT_SPECIFIC_FIELDS)) {
|
||||
my $id = $params->{product_id};
|
||||
defined $id || ThrowCodeError('param_required',
|
||||
{ function => 'Bug.legal_values', param => 'product_id' });
|
||||
grep($_->id eq $id, @{Bugzilla->user->get_accessible_products})
|
||||
|| ThrowUserError('product_access_denied', { product => $id });
|
||||
|
||||
my $product = new Bugzilla::Product($id);
|
||||
my @objects;
|
||||
if ($field eq 'version') {
|
||||
@objects = @{$product->versions};
|
||||
}
|
||||
elsif ($field eq 'target_milestone') {
|
||||
@objects = @{$product->milestones};
|
||||
}
|
||||
elsif ($field eq 'component') {
|
||||
@objects = @{$product->components};
|
||||
}
|
||||
|
||||
$values = [map { $_->name } @objects];
|
||||
}
|
||||
else {
|
||||
ThrowCodeError('invalid_field_name', { field => $params->{field} });
|
||||
}
|
||||
|
||||
my @result;
|
||||
foreach my $val (@$values) {
|
||||
push(@result, type('string')->value($val));
|
||||
}
|
||||
|
||||
return { values => \@result };
|
||||
}
|
||||
|
||||
sub add_comment {
|
||||
my ($self, $params) = @_;
|
||||
|
||||
#The user must login in order add a comment
|
||||
Bugzilla->login(LOGIN_REQUIRED);
|
||||
|
||||
# Check parameters
|
||||
defined $params->{id}
|
||||
|| ThrowCodeError('param_required', { param => 'id' });
|
||||
ValidateBugID($params->{id});
|
||||
|
||||
my $comment = $params->{comment};
|
||||
(defined $comment && trim($comment) ne '')
|
||||
|| ThrowCodeError('param_required', { param => 'comment' });
|
||||
|
||||
my $bug = new Bugzilla::Bug($params->{id});
|
||||
|
||||
Bugzilla->user->can_edit_product($bug->product_id)
|
||||
|| ThrowUserError("product_edit_denied", {product => $bug->product});
|
||||
|
||||
# Append comment
|
||||
$bug->add_comment($comment, { isprivate => $params->{private},
|
||||
work_time => $params->{work_time} });
|
||||
$bug->update();
|
||||
|
||||
# Send mail.
|
||||
Bugzilla::BugMail::Send($bug->bug_id, { changer => Bugzilla->user->login });
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Webservice::Bug - The API for creating, changing, and getting the
|
||||
details of bugs.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This part of the Bugzilla API allows you to file a new bug in Bugzilla,
|
||||
or get information about bugs that have already been filed.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=head2 Utility Functions
|
||||
|
||||
=over
|
||||
|
||||
=item C<legal_values>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Tells you what values are allowed for a particular field.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<field> - The name of the field you want information about.
|
||||
This should be the same as the name you would use in L</create>, below.
|
||||
|
||||
=item C<product_id> - If you're picking a product-specific field, you have
|
||||
to specify the id of the product you want the values for.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
C<values> - An array of strings: the legal values for this field.
|
||||
The values will be sorted as they normally would be in Bugzilla.
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 106 (Invalid Product)
|
||||
|
||||
You were required to specify a product, and either you didn't, or you
|
||||
specified an invalid product (or a product that you can't access).
|
||||
|
||||
=item 108 (Invalid Field Name)
|
||||
|
||||
You specified a field that doesn't exist or isn't a drop-down field.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=back
|
||||
|
||||
=head2 Bug Information
|
||||
|
||||
=over
|
||||
|
||||
=item C<get>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Gets information about particular bugs in the database.
|
||||
|
||||
Note: Can also be called as "get_bugs" for compatibilty with Bugzilla 3.0 API.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<ids>
|
||||
|
||||
An array of numbers and strings.
|
||||
|
||||
If an element in the array is entirely numeric, it represents a bug_id
|
||||
from the Bugzilla database to fetch. If it contains any non-numeric
|
||||
characters, it is considered to be a bug alias instead, and the bug with
|
||||
that alias will be loaded.
|
||||
|
||||
Note that it's possible for aliases to be disabled in Bugzilla, in which
|
||||
case you will be told that you have specified an invalid bug_id if you
|
||||
try to specify an alias. (It will be error 100.)
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing a single element, C<bugs>. This is an array of hashes.
|
||||
Each hash contains the following items:
|
||||
|
||||
=over
|
||||
|
||||
=item id
|
||||
|
||||
C<int> The numeric bug_id of this bug.
|
||||
|
||||
=item alias
|
||||
|
||||
C<string> The alias of this bug. If there is no alias or aliases are
|
||||
disabled in this Bugzilla, this will be an empty string.
|
||||
|
||||
=item summary
|
||||
|
||||
C<string> The summary of this bug.
|
||||
|
||||
=item creation_time
|
||||
|
||||
C<dateTime> When the bug was created.
|
||||
|
||||
=item last_change_time
|
||||
|
||||
C<dateTime> When the bug was last changed.
|
||||
|
||||
=item internals B<UNSTABLE>
|
||||
|
||||
A hash. The internals of a L<Bugzilla::Bug> object. This is extremely
|
||||
unstable, and you should only rely on this if you absolutely have to. The
|
||||
structure of the hash may even change between point releases of Bugzilla.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 100 (Invalid Bug Alias)
|
||||
|
||||
If you specified an alias and either: (a) the Bugzilla you're querying
|
||||
doesn't support aliases or (b) there is no bug with that alias.
|
||||
|
||||
=item 101 (Invalid Bug ID)
|
||||
|
||||
The bug_id you specified doesn't exist in the database.
|
||||
|
||||
=item 102 (Access Denied)
|
||||
|
||||
You do not have access to the bug_id you specified.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Bug Creation and Modification
|
||||
|
||||
=over
|
||||
|
||||
=item C<create> B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This allows you to create a new bug in Bugzilla. If you specify any
|
||||
invalid fields, they will be ignored. If you specify any fields you
|
||||
are not allowed to set, they will just be set to their defaults or ignored.
|
||||
|
||||
You cannot currently set all the items here that you can set on enter_bug.cgi.
|
||||
|
||||
The WebService interface may allow you to set things other than those listed
|
||||
here, but realize that anything undocumented is B<UNSTABLE> and will very
|
||||
likely change in the future.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
Some params must be set, or an error will be thrown. These params are
|
||||
marked B<Required>.
|
||||
|
||||
Some parameters can have defaults set in Bugzilla, by the administrator.
|
||||
If these parameters have defaults set, you can omit them. These parameters
|
||||
are marked B<Defaulted>.
|
||||
|
||||
Clients that want to be able to interact uniformly with multiple
|
||||
Bugzillas should always set both the params marked B<Required> and those
|
||||
marked B<Defaulted>, because some Bugzillas may not have defaults set
|
||||
for B<Defaulted> parameters, and then this method will throw an error
|
||||
if you don't specify them.
|
||||
|
||||
The descriptions of the parameters below are what they mean when Bugzilla is
|
||||
being used to track software bugs. They may have other meanings in some
|
||||
installations.
|
||||
|
||||
=over
|
||||
|
||||
=item C<product> (string) B<Required> - The name of the product the bug
|
||||
is being filed against.
|
||||
|
||||
=item C<component> (string) B<Required> - The name of a component in the
|
||||
product above.
|
||||
|
||||
=item C<summary> (string) B<Required> - A brief description of the bug being
|
||||
filed.
|
||||
|
||||
=item C<version> (string) B<Required> - A version of the product above;
|
||||
the version the bug was found in.
|
||||
|
||||
=item C<description> (string) B<Defaulted> - The initial description for
|
||||
this bug. Some Bugzilla installations require this to not be blank.
|
||||
|
||||
=item C<op_sys> (string) B<Defaulted> - The operating system the bug was
|
||||
discovered on.
|
||||
|
||||
=item C<platform> (string) B<Defaulted> - What type of hardware the bug was
|
||||
experienced on.
|
||||
|
||||
=item C<priority> (string) B<Defaulted> - What order the bug will be fixed
|
||||
in by the developer, compared to the developer's other bugs.
|
||||
|
||||
=item C<severity> (string) B<Defaulted> - How severe the bug is.
|
||||
|
||||
=item C<alias> (string) - A brief alias for the bug that can be used
|
||||
instead of a bug number when accessing this bug. Must be unique in
|
||||
all of this Bugzilla.
|
||||
|
||||
=item C<assigned_to> (username) - A user to assign this bug to, if you
|
||||
don't want it to be assigned to the component owner.
|
||||
|
||||
=item C<cc> (array) - An array of usernames to CC on this bug.
|
||||
|
||||
=item C<qa_contact> (username) - If this installation has QA Contacts
|
||||
enabled, you can set the QA Contact here if you don't want to use
|
||||
the component's default QA Contact.
|
||||
|
||||
=item C<status> (string) - The status that this bug should start out as.
|
||||
Note that only certain statuses can be set on bug creation.
|
||||
|
||||
=item C<target_milestone> (string) - A valid target milestone for this
|
||||
product.
|
||||
|
||||
=back
|
||||
|
||||
In addition to the above parameters, if your installation has any custom
|
||||
fields, you can set them just by passing in the name of the field and
|
||||
its value as a string.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with one element, C<id>. This is the id of the newly-filed bug.
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 51 (Invalid Object)
|
||||
|
||||
The component you specified is not valid for this Product.
|
||||
|
||||
=item 103 (Invalid Alias)
|
||||
|
||||
The alias you specified is invalid for some reason. See the error message
|
||||
for more details.
|
||||
|
||||
=item 104 (Invalid Field)
|
||||
|
||||
One of the drop-down fields has an invalid value, or a value entered in a
|
||||
text field is too long. The error message will have more detail.
|
||||
|
||||
=item 105 (Invalid Component)
|
||||
|
||||
You didn't specify a component.
|
||||
|
||||
=item 106 (Invalid Product)
|
||||
|
||||
Either you didn't specify a product, this product doesn't exist, or
|
||||
you don't have permission to enter bugs in this product.
|
||||
|
||||
=item 107 (Invalid Summary)
|
||||
|
||||
You didn't specify a summary for the bug.
|
||||
|
||||
=item 504 (Invalid User)
|
||||
|
||||
Either the QA Contact, Assignee, or CC lists have some invalid user
|
||||
in them. The error message will have more details.
|
||||
|
||||
=back
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Before B<3.0.4>, parameters marked as B<Defaulted> were actually
|
||||
B<Required>, due to a bug in Bugzilla.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<add_comment>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This allows you to add a comment to a bug in Bugzilla.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<id> (int) B<Required> - The id or alias of the bug to append a
|
||||
comment to.
|
||||
|
||||
=item C<comment> (string) B<Required> - The comment to append to the bug.
|
||||
If this is empty or all whitespace, an error will be thrown saying that
|
||||
you did not set the C<comment> parameter.
|
||||
|
||||
=item C<private> (boolean) - If set to true, the comment is private, otherwise
|
||||
it is assumed to be public.
|
||||
|
||||
=item C<work_time> (double) - Adds this many hours to the "Hours Worked"
|
||||
on the bug. If you are not in the time tracking group, this value will
|
||||
be ignored.
|
||||
|
||||
|
||||
=back
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 100 (Invalid Bug Alias)
|
||||
|
||||
If you specified an alias and either: (a) the Bugzilla you're querying
|
||||
doesn't support aliases or (b) there is no bug with that alias.
|
||||
|
||||
=item 101 (Invalid Bug ID)
|
||||
|
||||
The id you specified doesn't exist in the database.
|
||||
|
||||
=item 108 (Bug Edit Denied)
|
||||
|
||||
You did not have the necessary rights to edit the bug.
|
||||
|
||||
=back
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Added in Bugzilla B<3.2>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=back
|
||||
@@ -1,149 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Mads Bondo Dydensborg <mbd@dbc.dk>
|
||||
|
||||
package Bugzilla::WebService::Bugzilla;
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::WebService);
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Hook;
|
||||
import SOAP::Data qw(type);
|
||||
|
||||
use Time::Zone;
|
||||
|
||||
# Basic info that is needed before logins
|
||||
use constant LOGIN_EXEMPT => {
|
||||
timezone => 1,
|
||||
version => 1,
|
||||
};
|
||||
|
||||
sub version {
|
||||
return { version => type('string')->value(BUGZILLA_VERSION) };
|
||||
}
|
||||
|
||||
sub extensions {
|
||||
my $extensions = Bugzilla::Hook::enabled_plugins();
|
||||
foreach my $name (keys %$extensions) {
|
||||
my $info = $extensions->{$name};
|
||||
foreach my $data (keys %$info)
|
||||
{
|
||||
$extensions->{$name}->{$data} = type('string')->value($info->{$data});
|
||||
}
|
||||
}
|
||||
return { extensions => $extensions };
|
||||
}
|
||||
|
||||
sub timezone {
|
||||
my $offset = tz_offset();
|
||||
$offset = (($offset / 60) / 60) * 100;
|
||||
$offset = sprintf('%+05d', $offset);
|
||||
return { timezone => type('string')->value($offset) };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::WebService::Bugzilla - Global functions for the webservice interface.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This provides functions that tell you about Bugzilla in general.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=over
|
||||
|
||||
=item C<version>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns the current version of Bugzilla.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with a single item, C<version>, that is the version as a
|
||||
string.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<extensions>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Gets information about the extensions that are currently installed and enabled
|
||||
in this Bugzilla.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with a single item, C<extesions>. This points to a hash. I<That> hash
|
||||
contains the names of extensions as keys, and information about the extension
|
||||
as values. One of the values that must be returned is the 'version' of the
|
||||
extension
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Added in Bugzilla B<3.2>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<timezone>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns the timezone of the server Bugzilla is running on. This is
|
||||
important because all dates/times that the webservice interface
|
||||
returns will be in this timezone.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with a single item, C<timezone>, that is the timezone offset as a
|
||||
string in (+/-)XXXX (RFC 2822) format.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
@@ -1,110 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::WebService::Constants;
|
||||
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
@Bugzilla::WebService::Constants::EXPORT = qw(
|
||||
WS_ERROR_CODE
|
||||
ERROR_UNKNOWN_FATAL
|
||||
ERROR_UNKNOWN_TRANSIENT
|
||||
|
||||
ERROR_AUTH_NODATA
|
||||
ERROR_UNIMPLEMENTED
|
||||
);
|
||||
|
||||
# This maps the error names in global/*-error.html.tmpl to numbers.
|
||||
# Generally, transient errors should have a number above 0, and
|
||||
# fatal errors should have a number below 0.
|
||||
#
|
||||
# This hash should generally contain any error that could be thrown
|
||||
# by the WebService interface. If it's extremely unlikely that the
|
||||
# error could be thrown (like some CodeErrors), it doesn't have to
|
||||
# be listed here.
|
||||
#
|
||||
# "Transient" means "If you resubmit that request with different data,
|
||||
# it may work."
|
||||
#
|
||||
# "Fatal" means, "There's something wrong with Bugzilla, probably
|
||||
# something an administrator would have to fix."
|
||||
#
|
||||
# NOTE: Numbers must never be recycled. If you remove a number, leave a
|
||||
# comment that it was retired. Also, if an error changes its name, you'll
|
||||
# have to fix it here.
|
||||
use constant WS_ERROR_CODE => {
|
||||
# Generic Bugzilla::Object errors are 50-99.
|
||||
object_name_not_specified => 50,
|
||||
param_required => 50,
|
||||
object_does_not_exist => 51,
|
||||
# Bug errors usually occupy the 100-200 range.
|
||||
improper_bug_id_field_value => 100,
|
||||
bug_id_does_not_exist => 101,
|
||||
bug_access_denied => 102,
|
||||
bug_access_query => 102,
|
||||
# These all mean "invalid alias"
|
||||
alias_too_long => 103,
|
||||
alias_in_use => 103,
|
||||
alias_is_numeric => 103,
|
||||
alias_has_comma_or_space => 103,
|
||||
# Misc. bug field errors
|
||||
illegal_field => 104,
|
||||
freetext_too_long => 104,
|
||||
# Component errors
|
||||
require_component => 105,
|
||||
component_name_too_long => 105,
|
||||
# Invalid Product
|
||||
no_products => 106,
|
||||
entry_access_denied => 106,
|
||||
product_access_denied => 106,
|
||||
product_disabled => 106,
|
||||
# Invalid Summary
|
||||
require_summary => 107,
|
||||
# Invalid field name
|
||||
invalid_field_name => 108,
|
||||
# Not authorized to edit the bug
|
||||
product_edit_denied => 109,
|
||||
|
||||
# Authentication errors are usually 300-400.
|
||||
invalid_username_or_password => 300,
|
||||
account_disabled => 301,
|
||||
auth_invalid_email => 302,
|
||||
extern_id_conflict => -303,
|
||||
|
||||
# User errors are 500-600.
|
||||
account_exists => 500,
|
||||
illegal_email_address => 501,
|
||||
account_creation_disabled => 501,
|
||||
account_creation_restricted => 501,
|
||||
password_too_short => 502,
|
||||
password_too_long => 503,
|
||||
invalid_username => 504,
|
||||
# This is from strict_isolation, but it also basically means
|
||||
# "invalid user."
|
||||
invalid_user_group => 504,
|
||||
};
|
||||
|
||||
# These are the fallback defaults for errors not in ERROR_CODE.
|
||||
use constant ERROR_UNKNOWN_FATAL => -32000;
|
||||
use constant ERROR_UNKNOWN_TRANSIENT => 32000;
|
||||
|
||||
use constant ERROR_AUTH_NODATA => 410;
|
||||
use constant ERROR_UNIMPLEMENTED => 910;
|
||||
use constant ERROR_GENERAL => 999;
|
||||
|
||||
1;
|
||||
@@ -1,198 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Mads Bondo Dydensborg <mbd@dbc.dk>
|
||||
|
||||
package Bugzilla::WebService::Product;
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::WebService);
|
||||
use Bugzilla::Product;
|
||||
use Bugzilla::User;
|
||||
import SOAP::Data qw(type);
|
||||
|
||||
##################################################
|
||||
# Add aliases here for method name compatibility #
|
||||
##################################################
|
||||
|
||||
BEGIN { *get_products = \&get }
|
||||
|
||||
# Get the ids of the products the user can search
|
||||
sub get_selectable_products {
|
||||
return {ids => [map {$_->id} @{Bugzilla->user->get_selectable_products}]};
|
||||
}
|
||||
|
||||
# Get the ids of the products the user can enter bugs against
|
||||
sub get_enterable_products {
|
||||
return {ids => [map {$_->id} @{Bugzilla->user->get_enterable_products}]};
|
||||
}
|
||||
|
||||
# Get the union of the products the user can search and enter bugs against.
|
||||
sub get_accessible_products {
|
||||
return {ids => [map {$_->id} @{Bugzilla->user->get_accessible_products}]};
|
||||
}
|
||||
|
||||
# Get a list of actual products, based on list of ids
|
||||
sub get {
|
||||
my ($self, $params) = @_;
|
||||
|
||||
# Only products that are in the users accessible products,
|
||||
# can be allowed to be returned
|
||||
my $accessible_products = Bugzilla->user->get_accessible_products;
|
||||
|
||||
# Create a hash with the ids the user wants
|
||||
my %ids = map { $_ => 1 } @{$params->{ids}};
|
||||
|
||||
# Return the intersection of this, by grepping the ids from
|
||||
# accessible products.
|
||||
my @requested_accessible = grep { $ids{$_->id} } @$accessible_products;
|
||||
|
||||
# Now create a result entry for each.
|
||||
my @products =
|
||||
map {{
|
||||
internals => $_,
|
||||
id => type('int')->value($_->id),
|
||||
name => type('string')->value($_->name),
|
||||
description => type('string')->value($_->description),
|
||||
}
|
||||
} @requested_accessible;
|
||||
|
||||
return { products => \@products };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Webservice::Product - The Product API
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This part of the Bugzilla API allows you to list the available Products and
|
||||
get information about them.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=head2 List Products
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_selectable_products>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of the ids of the products the user can search on.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<ids>, that contains an array of product
|
||||
ids.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<get_enterable_products>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of the ids of the products the user can enter bugs
|
||||
against.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<ids>, that contains an array of product
|
||||
ids.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<get_accessible_products>
|
||||
|
||||
B<UNSTABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of the ids of the products the user can search or enter
|
||||
bugs against.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<ids>, that contains an array of product
|
||||
ids.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<get>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of information about the products passed to it.
|
||||
|
||||
Note: Can also be called as "get_products" for compatibilty with Bugzilla 3.0 API.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
A hash containing one item, C<ids>, that is an array of product ids.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<products>, that is an array of
|
||||
hashes. Each hash describes a product, and has the following items:
|
||||
C<id>, C<name>, C<description>, and C<internals>. The C<id> item is
|
||||
the id of the product. The C<name> item is the name of the
|
||||
product. The C<description> is the description of the
|
||||
product. Finally, the C<internals> is an internal representation of
|
||||
the product.
|
||||
|
||||
Note, that if the user tries to access a product that is not in the
|
||||
list of accessible products for the user, or a product that does not
|
||||
exist, that is silently ignored, and no information about that product
|
||||
is returned.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Mads Bondo Dydensborg <mbd@dbc.dk>
|
||||
|
||||
package Bugzilla::WebService::User;
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::WebService);
|
||||
|
||||
import SOAP::Data qw(type);
|
||||
|
||||
use Bugzilla;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util qw(trim);
|
||||
use Bugzilla::Token;
|
||||
|
||||
# Don't need auth to login
|
||||
use constant LOGIN_EXEMPT => {
|
||||
login => 1,
|
||||
offer_account_by_email => 1,
|
||||
};
|
||||
|
||||
##############
|
||||
# User Login #
|
||||
##############
|
||||
|
||||
sub login {
|
||||
my ($self, $params) = @_;
|
||||
my $remember = $params->{remember};
|
||||
|
||||
# Username and password params are required
|
||||
foreach my $param ("login", "password") {
|
||||
defined $params->{$param}
|
||||
|| ThrowCodeError('param_required', { param => $param });
|
||||
}
|
||||
|
||||
# Convert $remember from a boolean 0/1 value to a CGI-compatible one.
|
||||
if (defined($remember)) {
|
||||
$remember = $remember? 'on': '';
|
||||
}
|
||||
else {
|
||||
# Use Bugzilla's default if $remember is not supplied.
|
||||
$remember =
|
||||
Bugzilla->params->{'rememberlogin'} eq 'defaulton'? 'on': '';
|
||||
}
|
||||
|
||||
# Make sure the CGI user info class works if necessary.
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$cgi->param('Bugzilla_login', $params->{login});
|
||||
$cgi->param('Bugzilla_password', $params->{password});
|
||||
$cgi->param('Bugzilla_remember', $remember);
|
||||
|
||||
Bugzilla->login;
|
||||
return { id => type('int')->value(Bugzilla->user->id) };
|
||||
}
|
||||
|
||||
sub logout {
|
||||
my $self = shift;
|
||||
Bugzilla->logout;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#################
|
||||
# User Creation #
|
||||
#################
|
||||
|
||||
sub offer_account_by_email {
|
||||
my $self = shift;
|
||||
my ($params) = @_;
|
||||
my $email = trim($params->{email})
|
||||
|| ThrowCodeError('param_required', { param => 'email' });
|
||||
|
||||
my $createexp = Bugzilla->params->{'createemailregexp'};
|
||||
if (!$createexp) {
|
||||
ThrowUserError("account_creation_disabled");
|
||||
}
|
||||
elsif ($email !~ /$createexp/) {
|
||||
ThrowUserError("account_creation_restricted");
|
||||
}
|
||||
|
||||
$email = Bugzilla::User->check_login_name_for_creation($email);
|
||||
|
||||
# Create and send a token for this new account.
|
||||
Bugzilla::Token::issue_new_user_account_token($email);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub create {
|
||||
my $self = shift;
|
||||
my ($params) = @_;
|
||||
|
||||
Bugzilla->user->in_group('editusers')
|
||||
|| ThrowUserError("auth_failure", { group => "editusers",
|
||||
action => "add",
|
||||
object => "users"});
|
||||
|
||||
my $email = trim($params->{email})
|
||||
|| ThrowCodeError('param_required', { param => 'email' });
|
||||
my $realname = trim($params->{full_name});
|
||||
my $password = trim($params->{password}) || '*';
|
||||
|
||||
my $user = Bugzilla::User->create({
|
||||
login_name => $email,
|
||||
realname => $realname,
|
||||
cryptpassword => $password
|
||||
});
|
||||
|
||||
return { id => type('int')->value($user->id) };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Webservice::User - The User Account and Login API
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This part of the Bugzilla API allows you to create User Accounts and
|
||||
log in/out using an existing account.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=head2 Logging In and Out
|
||||
|
||||
=over
|
||||
|
||||
=item C<login>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Logging in, with a username and password, is required for many
|
||||
Bugzilla installations, in order to search for bugs, post new bugs,
|
||||
etc. This method logs in an user.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<login> (string) - The user's login name.
|
||||
|
||||
=item C<password> (string) - The user's password.
|
||||
|
||||
=item C<remember> (bool) B<Optional> - if the cookies returned by the
|
||||
call to login should expire with the session or not. In order for
|
||||
this option to have effect the Bugzilla server must be configured to
|
||||
allow the user to set this option - the Bugzilla parameter
|
||||
I<rememberlogin> must be set to "defaulton" or
|
||||
"defaultoff". Addionally, the client application must implement
|
||||
management of cookies across sessions.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
On success, a hash containing one item, C<id>, the numeric id of the
|
||||
user that was logged in. A set of http cookies is also sent with the
|
||||
response. These cookies must be sent along with any future requests
|
||||
to the webservice, for the duration of the session.
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 300 (Invalid Username or Password)
|
||||
|
||||
The username does not exist, or the password is wrong.
|
||||
|
||||
=item 301 (Account Disabled)
|
||||
|
||||
The account has been disabled. A reason may be specified with the
|
||||
error.
|
||||
|
||||
=item 50 (Param Required)
|
||||
|
||||
A login or password parameter was not provided.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<logout>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Log out the user. Does nothing if there is no user logged in.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Account Creation
|
||||
|
||||
=over
|
||||
|
||||
=item C<offer_account_by_email>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Sends an email to the user, offering to create an account. The user
|
||||
will have to click on a URL in the email, and choose their password
|
||||
and real name.
|
||||
|
||||
This is the recommended way to create a Bugzilla account.
|
||||
|
||||
=item B<Param>
|
||||
|
||||
=over
|
||||
|
||||
=item C<email> (string) - the email to send the offer to.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 500 (Illegal Email Address)
|
||||
|
||||
This Bugzilla does not allow you to create accounts with the format of
|
||||
email address you specified. Account creation may be entirely disabled.
|
||||
|
||||
=item 501 (Account Already Exists)
|
||||
|
||||
An account with that email address already exists in Bugzilla.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<create>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Creates a user account directly in Bugzilla, password and all.
|
||||
Instead of this, you should use L</offer_account_by_email> when
|
||||
possible, because that makes sure that the email address specified can
|
||||
actually receive an email. This function does not check that.
|
||||
|
||||
You must be logged in and have the C<editusers> privilege in order to
|
||||
call this function.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<email> (string) - The email address for the new user.
|
||||
|
||||
=item C<full_name> (string) B<Optional> - The user's full name. Will
|
||||
be set to empty if not specified.
|
||||
|
||||
=item C<password> (string) B<Optional> - The password for the new user
|
||||
account, in plain text. It will be stripped of leading and trailing
|
||||
whitespace. If blank or not specified, the newly created account will
|
||||
exist in Bugzilla, but will not be allowed to log in using DB
|
||||
authentication until a password is set either by the user (through
|
||||
resetting their password) or by the administrator.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<id>, the numeric id of the user that was
|
||||
created.
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
The same as L</offer_account_by_email>. If a password is specified,
|
||||
the function may also throw:
|
||||
|
||||
=over
|
||||
|
||||
=item 502 (Password Too Short)
|
||||
|
||||
The password specified is too short. (Usually, this means the
|
||||
password is under three characters.)
|
||||
|
||||
=item 503 (Password Too Long)
|
||||
|
||||
The password specified is too long. (Usually, this means the
|
||||
password is over ten characters.)
|
||||
|
||||
=back
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Added in Bugzilla B<3.4>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
@@ -1,84 +0,0 @@
|
||||
Bugzilla Quick Start Guide
|
||||
==========================
|
||||
(or, how to get Bugzilla up and running in 10 steps)
|
||||
Christian Reis <kiko@async.com.br>
|
||||
|
||||
This express installation guide is for "normal" Bugzilla installations,
|
||||
which means a Linux or Unix system on which Apache, Perl, MySQL or PostgreSQL
|
||||
and a Sendmail compatible MTA are available. For other configurations, please
|
||||
see Section 4 of the Bugzilla Guide in the docs/ directory.
|
||||
|
||||
1. Decide from which URL and directory under your webserver root you
|
||||
will be serving the Bugzilla webpages.
|
||||
|
||||
2. Unpack the distribution into the chosen directory (there is no copying or
|
||||
installation involved).
|
||||
|
||||
3. Run ./checksetup.pl, look for unsolved requirements, and install them.
|
||||
You can run checksetup as many times as necessary to check if
|
||||
everything required has been installed.
|
||||
|
||||
These will usually include assorted Perl modules, MySQL or PostgreSQL,
|
||||
and a MTA.
|
||||
|
||||
After a successful dependency check, checksetup should complain that
|
||||
localconfig needs to be edited.
|
||||
|
||||
4. Edit the localconfig file, in particular the $webservergroup and
|
||||
$db_* variables. In particular, $db_name and $db_user will define
|
||||
your database setup in step 5.
|
||||
|
||||
5. Using the name you provided as $db_name above, create a MySQL database
|
||||
for Bugzilla. You should also create a user permission for the name
|
||||
supplied as $db_user with read/write access to that database.
|
||||
|
||||
If you are not familiar with MySQL permissions, it's a good idea to
|
||||
use the mysql_setpermission script that is installed with the MySQL
|
||||
distribution, and be sure to read Bugzilla Security - MySQL section
|
||||
in the Bugzilla Guide or PostgreSQL documentation.
|
||||
|
||||
6. Run checksetup.pl once more; if all goes well, it should set up the
|
||||
Bugzilla database for you. If not, return to step 5.
|
||||
|
||||
checksetup.pl should ask you, this time, for the administrator's
|
||||
email address and password. These will be used for the initial
|
||||
Bugzilla administrator account.
|
||||
|
||||
7. Configure Apache (or install and configure, if you don't have it up
|
||||
yet) to point to the Bugzilla directory. You should enable and
|
||||
activate mod_cgi, and add the configuration entries
|
||||
|
||||
Options +ExecCGI
|
||||
AllowOverride Limit
|
||||
DirectoryIndex index.cgi
|
||||
|
||||
to your Bugzilla <Directory> block. You may also need
|
||||
|
||||
AddHandler cgi-script .cgi
|
||||
|
||||
if you don't have that in your Apache configuration file yet.
|
||||
|
||||
8. Visit the URL you chose for Bugzilla. Your browser should display the
|
||||
default Bugzilla home page. You should then log in as the
|
||||
administrator by following the "Log in" link and supplying the
|
||||
account information you provided in step 6.
|
||||
|
||||
9. Scroll to the bottom of the page after logging in, and select
|
||||
"Parameters". Set up the relevant parameters for your local setup.
|
||||
|
||||
See section 4.2 of the Bugzilla Guide for a in-depth description of
|
||||
some of the configuration parameters available.
|
||||
|
||||
10. That's it. If anything unexpected comes up:
|
||||
|
||||
- read the error message carefully,
|
||||
- backtrack through the steps above,
|
||||
- check the official installation guide, which is section 4 in the
|
||||
Bugzilla Guide, included in the docs/ directory in various
|
||||
formats.
|
||||
|
||||
Support and installation questions should be directed to the
|
||||
mozilla-webtools@mozilla.org mailing list -- don't write to the
|
||||
developer mailing list: your post *will* be ignored if you do.
|
||||
|
||||
Further support information is at http://www.bugzilla.org/support/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user