aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin H. Johnson <robbat2@gentoo.org>2020-03-07 11:19:34 -0800
committerRobin H. Johnson <robbat2@gentoo.org>2020-03-07 11:20:45 -0800
commit1383dfc4ff1dfe5bfa5b327b4ae19b2af6a2a28e (patch)
treeef46b4d84362cabf31dd2c098d0fae38fcd0e018
parentGentoo: alpha is now ~arch-only (diff)
parentBugzilla/Util: disable BiDi tr safety (diff)
downloadbugzilla-1383dfc4ff1dfe5bfa5b327b4ae19b2af6a2a28e.tar.gz
bugzilla-1383dfc4ff1dfe5bfa5b327b4ae19b2af6a2a28e.tar.bz2
bugzilla-1383dfc4ff1dfe5bfa5b327b4ae19b2af6a2a28e.zip
Bugzilla 5.0.6! Merge branch 'bugstest'
This update Gentoo production Bugzilla to 5.0.6. Please note that upstream reformatted all code, so the commit series has some extra hops to help reflect that change. Signed-off-by: Robin H. Johnson <robbat2@gentoo.org>
-rw-r--r--.perltidyrc17
-rw-r--r--Bugzilla.pm900
-rw-r--r--Bugzilla/Attachment.pm997
-rw-r--r--Bugzilla/Attachment/PatchReader.pm499
-rw-r--r--Bugzilla/Auth.pm349
-rw-r--r--Bugzilla/Auth/Login.pm18
-rw-r--r--Bugzilla/Auth/Login/APIKey.pm35
-rw-r--r--Bugzilla/Auth/Login/CGI.pm118
-rw-r--r--Bugzilla/Auth/Login/Cookie.pm185
-rw-r--r--Bugzilla/Auth/Login/Env.pm27
-rw-r--r--Bugzilla/Auth/Login/Stack.pm126
-rw-r--r--Bugzilla/Auth/Persist/Cookie.pm267
-rw-r--r--Bugzilla/Auth/Verify.pm198
-rw-r--r--Bugzilla/Auth/Verify/DB.pm146
-rw-r--r--Bugzilla/Auth/Verify/LDAP.pm253
-rw-r--r--Bugzilla/Auth/Verify/RADIUS.pm58
-rw-r--r--Bugzilla/Auth/Verify/Stack.pm107
-rw-r--r--Bugzilla/Bug.pm7336
-rw-r--r--Bugzilla/BugMail.pm996
-rw-r--r--Bugzilla/BugUrl.pm230
-rw-r--r--Bugzilla/BugUrl/Bugzilla.pm52
-rw-r--r--Bugzilla/BugUrl/Bugzilla/Local.pm110
-rw-r--r--Bugzilla/BugUrl/Debian.pm42
-rw-r--r--Bugzilla/BugUrl/Flyspray.pm20
-rw-r--r--Bugzilla/BugUrl/GitHub.pm26
-rw-r--r--Bugzilla/BugUrl/Google.pm30
-rw-r--r--Bugzilla/BugUrl/JIRA.pm25
-rw-r--r--Bugzilla/BugUrl/Launchpad.pm31
-rw-r--r--Bugzilla/BugUrl/MantisBT.pm18
-rw-r--r--Bugzilla/BugUrl/SourceForge.pm30
-rw-r--r--Bugzilla/BugUrl/Trac.pm25
-rw-r--r--Bugzilla/BugUserLastVisit.pm22
-rw-r--r--Bugzilla/CGI.pm1083
-rw-r--r--Bugzilla/Chart.pm697
-rw-r--r--Bugzilla/Classification.pm198
-rw-r--r--Bugzilla/Comment.pm648
-rw-r--r--Bugzilla/Comment/TagWeights.pm10
-rw-r--r--Bugzilla/Component.pm540
-rw-r--r--Bugzilla/Config.pm527
-rw-r--r--Bugzilla/Config/Admin.pm39
-rw-r--r--Bugzilla/Config/Advanced.pm29
-rw-r--r--Bugzilla/Config/Attachment.pm75
-rw-r--r--Bugzilla/Config/Auth.pm182
-rw-r--r--Bugzilla/Config/BugChange.pm70
-rw-r--r--Bugzilla/Config/BugFields.pm111
-rw-r--r--Bugzilla/Config/Common.pm610
-rw-r--r--Bugzilla/Config/Core.pm32
-rw-r--r--Bugzilla/Config/DependencyGraph.pm22
-rw-r--r--Bugzilla/Config/General.pm43
-rw-r--r--Bugzilla/Config/GroupSecurity.pm133
-rw-r--r--Bugzilla/Config/LDAP.pm45
-rw-r--r--Bugzilla/Config/MTA.pm97
-rw-r--r--Bugzilla/Config/Memcached.pm12
-rw-r--r--Bugzilla/Config/Query.pm78
-rw-r--r--Bugzilla/Config/RADIUS.pm32
-rw-r--r--Bugzilla/Config/ShadowDB.pm44
-rw-r--r--Bugzilla/Config/UserMatch.pm39
-rw-r--r--Bugzilla/Constants.pm793
-rw-r--r--Bugzilla/DB.pm1889
-rw-r--r--Bugzilla/DB/Mysql.pm1603
-rw-r--r--Bugzilla/DB/Oracle.pm1019
-rw-r--r--Bugzilla/DB/Pg.pm611
-rw-r--r--Bugzilla/DB/Schema.pm4123
-rw-r--r--Bugzilla/DB/Schema/Mysql.pm577
-rw-r--r--Bugzilla/DB/Schema/Oracle.pm782
-rw-r--r--Bugzilla/DB/Schema/Pg.pm286
-rw-r--r--Bugzilla/DB/Schema/Sqlite.pm420
-rw-r--r--Bugzilla/DB/Sqlite.pm324
-rw-r--r--Bugzilla/Error.pm303
-rw-r--r--Bugzilla/Extension.pm300
-rw-r--r--Bugzilla/Field.pm1356
-rw-r--r--Bugzilla/Field/Choice.pm309
-rw-r--r--Bugzilla/Field/ChoiceInterface.pm227
-rw-r--r--Bugzilla/Flag.pm1687
-rw-r--r--Bugzilla/FlagType.pm791
-rw-r--r--Bugzilla/Group.pm642
-rw-r--r--Bugzilla/Hook.pm43
-rw-r--r--Bugzilla/Install.pm681
-rw-r--r--Bugzilla/Install/CPAN.pm426
-rw-r--r--Bugzilla/Install/DB.pm6729
-rw-r--r--Bugzilla/Install/Filesystem.pm1347
-rw-r--r--Bugzilla/Install/Localconfig.pm398
-rw-r--r--Bugzilla/Install/Requirements.pm1178
-rw-r--r--Bugzilla/Install/Util.pm1080
-rw-r--r--Bugzilla/Job/BugMail.pm24
-rw-r--r--Bugzilla/Job/Mailer.pm34
-rw-r--r--Bugzilla/JobQueue.pm210
-rw-r--r--Bugzilla/JobQueue/Runner.pm261
-rw-r--r--Bugzilla/Keyword.pm120
-rw-r--r--Bugzilla/MIME.pm158
-rw-r--r--Bugzilla/Mailer.pm362
-rw-r--r--Bugzilla/Memcached.pm417
-rw-r--r--Bugzilla/Migrate.pm1231
-rw-r--r--Bugzilla/Migrate/Gnats.pm1034
-rw-r--r--Bugzilla/Milestone.pm296
-rw-r--r--Bugzilla/Object.pm1349
-rw-r--r--Bugzilla/Product.pm1222
-rw-r--r--Bugzilla/RNG.pm247
-rw-r--r--Bugzilla/Report.pm74
-rw-r--r--Bugzilla/Search.pm5176
-rw-r--r--Bugzilla/Search/Clause.pm170
-rw-r--r--Bugzilla/Search/ClauseGroup.pm131
-rw-r--r--Bugzilla/Search/Condition.pm70
-rw-r--r--Bugzilla/Search/Quicksearch.pm1069
-rw-r--r--Bugzilla/Search/Recent.pm116
-rw-r--r--Bugzilla/Search/Saved.pm371
-rw-r--r--Bugzilla/Sender/Transport/Sendmail.pm145
-rw-r--r--Bugzilla/Series.pm421
-rw-r--r--Bugzilla/Status.pm241
-rw-r--r--Bugzilla/Template.pm2122
-rw-r--r--Bugzilla/Template/Context.pm126
-rw-r--r--Bugzilla/Template/Plugin/Bugzilla.pm14
-rw-r--r--Bugzilla/Template/Plugin/Hook.pm111
-rw-r--r--Bugzilla/Token.pm646
-rw-r--r--Bugzilla/Update.pm274
-rw-r--r--Bugzilla/User.pm3619
-rw-r--r--Bugzilla/User/APIKey.pm68
-rw-r--r--Bugzilla/User/Setting.pm406
-rw-r--r--Bugzilla/User/Setting/Lang.pm6
-rw-r--r--Bugzilla/User/Setting/Skin.pm28
-rw-r--r--Bugzilla/User/Setting/Timezone.pm22
-rw-r--r--Bugzilla/UserAgent.pm352
-rw-r--r--Bugzilla/Util.pm1391
-rw-r--r--Bugzilla/Version.pm295
-rw-r--r--Bugzilla/WebService.pm9
-rw-r--r--Bugzilla/WebService/Bug.pm2295
-rw-r--r--Bugzilla/WebService/BugUserLastVisit.pm113
-rw-r--r--Bugzilla/WebService/Bugzilla.pm237
-rw-r--r--Bugzilla/WebService/Classification.pm89
-rw-r--r--Bugzilla/WebService/Component.pm39
-rw-r--r--Bugzilla/WebService/Constants.pm494
-rw-r--r--Bugzilla/WebService/FlagType.pm514
-rw-r--r--Bugzilla/WebService/Group.pm325
-rw-r--r--Bugzilla/WebService/Product.pm516
-rw-r--r--Bugzilla/WebService/Server.pm115
-rw-r--r--Bugzilla/WebService/Server/JSONRPC.pm696
-rw-r--r--Bugzilla/WebService/Server/REST.pm778
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Bug.pm274
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm35
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm46
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Classification.pm27
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Component.pm18
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/FlagType.pm67
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Group.pm41
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Product.pm78
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/User.pm76
-rw-r--r--Bugzilla/WebService/Server/XMLRPC.pm489
-rw-r--r--Bugzilla/WebService/User.pm595
-rw-r--r--Bugzilla/WebService/Util.pm442
-rw-r--r--Bugzilla/Whine.pm22
-rw-r--r--Bugzilla/Whine/Query.pm20
-rw-r--r--Bugzilla/Whine/Schedule.pm78
-rw-r--r--Build.PL50
-rwxr-xr-xadmin.cgi7
-rwxr-xr-xattachment.cgi1172
-rwxr-xr-xbuglist.cgi1329
-rwxr-xr-xchart.cgi410
-rwxr-xr-xchecksetup.pl40
-rwxr-xr-xclean-bug-user-last-visit.pl6
-rwxr-xr-xcolchange.cgi234
-rwxr-xr-xcollectstats.pl658
-rwxr-xr-xconfig.cgi155
-rw-r--r--contrib/Bugzilla.pm2
-rwxr-xr-xcontrib/bz_webservice_demo.pl239
-rwxr-xr-xcontrib/bzdbcopy.pl306
-rwxr-xr-xcontrib/console.pl159
-rwxr-xr-xcontrib/convert-workflow.pl178
-rwxr-xr-xcontrib/extension-convert.pl304
-rwxr-xr-xcontrib/jb2bz.py3
-rwxr-xr-xcontrib/merge-users.pl204
-rwxr-xr-xcontrib/mysqld-watcher.pl93
-rwxr-xr-xcontrib/perl-fmt24
-rwxr-xr-xcontrib/recode.pl315
-rwxr-xr-xcontrib/sendbugmail.pl35
-rwxr-xr-xcontrib/sendunsentbugmail.pl38
-rwxr-xr-xcontrib/syncLDAP.pl377
-rwxr-xr-xcreateaccount.cgi25
-rwxr-xr-xcustom_buglist.cgi86
-rwxr-xr-xcustom_disabled.cgi41
-rwxr-xr-xcustom_extraperms.cgi30
-rwxr-xr-xcustom_userhistory.cgi115
-rwxr-xr-xdescribecomponents.cgi66
-rwxr-xr-xdescribekeywords.cgi6
-rw-r--r--docs/lib/Pod/Simple/HTML/Bugzilla.pm48
-rw-r--r--docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm128
-rwxr-xr-xdocs/makedocs.pl132
-rwxr-xr-xduplicates.cgi270
-rwxr-xr-xeditclassifications.cgi212
-rwxr-xr-xeditcomponents.cgi262
-rwxr-xr-xeditfields.cgi244
-rwxr-xr-xeditflagtypes.cgi860
-rwxr-xr-xeditgroups.cgi580
-rwxr-xr-xeditkeywords.cgi142
-rwxr-xr-xeditmilestones.cgi207
-rwxr-xr-xeditparams.cgi201
-rwxr-xr-xeditproducts.cgi545
-rwxr-xr-xeditsettings.cgi51
-rwxr-xr-xeditusers.cgi1168
-rwxr-xr-xeditvalues.cgi131
-rwxr-xr-xeditversions.cgi188
-rwxr-xr-xeditwhines.cgi550
-rwxr-xr-xeditworkflow.cgi195
-rwxr-xr-xemail_in.pl766
-rwxr-xr-xenter_bug.cgi385
-rw-r--r--extensions/BmpConvert/Config.pm9
-rw-r--r--extensions/BmpConvert/Extension.pm49
-rw-r--r--extensions/Example/Config.pm21
-rw-r--r--extensions/Example/Extension.pm1505
-rw-r--r--extensions/Example/lib/Auth/Login.pm2
-rw-r--r--extensions/Example/lib/Auth/Verify.pm2
-rw-r--r--extensions/Example/lib/Config.pm13
-rw-r--r--extensions/Example/lib/WebService.pm4
-rw-r--r--extensions/Example/template/en/default/setup/strings.txt.pl4
-rw-r--r--extensions/Gentoo/Extension.pm66
-rw-r--r--extensions/InlineHistory/Extension.pm341
-rw-r--r--extensions/MoreBugUrl/Config.pm6
-rw-r--r--extensions/MoreBugUrl/Extension.pm52
-rw-r--r--extensions/MoreBugUrl/lib/BitBucket.pm20
-rw-r--r--extensions/MoreBugUrl/lib/GetSatisfaction.pm22
-rw-r--r--extensions/MoreBugUrl/lib/PHP.pm26
-rw-r--r--extensions/MoreBugUrl/lib/Phabricator.pm23
-rw-r--r--extensions/MoreBugUrl/lib/RT.pm25
-rw-r--r--extensions/MoreBugUrl/lib/Redmine.pm23
-rw-r--r--extensions/MoreBugUrl/lib/ReviewBoard.pm31
-rw-r--r--extensions/MoreBugUrl/lib/Rietveld.pm48
-rw-r--r--extensions/MoreBugUrl/lib/Savane.pm27
-rw-r--r--extensions/OldBugMove/Extension.pm248
-rw-r--r--extensions/OldBugMove/lib/Params.pm22
-rw-r--r--extensions/SecureMail/Config.pm32
-rw-r--r--extensions/SecureMail/Extension.pm992
-rw-r--r--extensions/SecureMail/disabled0
-rw-r--r--extensions/TypeSniffer/Config.pm14
-rw-r--r--extensions/TypeSniffer/Extension.pm73
-rw-r--r--extensions/Voting/Config.pm6
-rw-r--r--extensions/Voting/Extension.pm1354
-rwxr-xr-xextensions/create.pl45
-rwxr-xr-ximportxml.pl2064
-rwxr-xr-xindex.cgi58
-rwxr-xr-xinstall-module.pl72
-rwxr-xr-xjobqueue.pl7
-rwxr-xr-xjsonrpc.cgi7
-rwxr-xr-xmigrate.pl14
-rw-r--r--mod_perl.pl97
-rwxr-xr-xpage.cgi79
-rwxr-xr-xpost_bug.cgi192
-rwxr-xr-xprocess_bug.cgi518
-rwxr-xr-xquery.cgi317
-rwxr-xr-xquips.cgi199
-rwxr-xr-xrelogin.cgi329
-rwxr-xr-xreport.cgi510
-rwxr-xr-xreports.cgi313
-rwxr-xr-xrequest.cgi475
-rwxr-xr-xrest.cgi9
-rwxr-xr-xruntests.pl12
-rwxr-xr-xsanitycheck.cgi1076
-rwxr-xr-xsanitycheck.pl34
-rwxr-xr-xsearch_plugin.cgi12
-rwxr-xr-xshow_activity.cgi11
-rwxr-xr-xshow_bug.cgi124
-rwxr-xr-xshowdependencygraph.cgi421
-rwxr-xr-xshowdependencytree.cgi122
-rwxr-xr-xsummarize_time.cgi503
-rw-r--r--t/001compile.t118
-rw-r--r--t/002goodperl.t271
-rw-r--r--t/003safesys.t61
-rw-r--r--t/004template.t180
-rw-r--r--t/005whitespace.t66
-rw-r--r--t/006spellcheck.t104
-rw-r--r--t/007util.t68
-rw-r--r--t/008filter.t310
-rw-r--r--t/009bugwords.t84
-rw-r--r--t/010dependencies.t76
-rw-r--r--t/011pod.t154
-rw-r--r--t/012throwables.t280
-rw-r--r--t/013dbschema.t80
-rw-r--r--t/Support/Files.pm40
-rw-r--r--t/Support/Templates.pm91
-rw-r--r--template/en/default/filterexceptions.pl645
-rw-r--r--template/en/default/list/list.html.tmpl2
-rw-r--r--template/en/default/pages/release-notes.html.tmpl13
-rw-r--r--template/en/default/setup/strings.txt.pl217
-rwxr-xr-xtestserver.pl360
-rwxr-xr-xtoken.cgi415
-rwxr-xr-xuserprefs.cgi982
-rwxr-xr-xvotes.cgi18
-rwxr-xr-xwhine.pl901
-rwxr-xr-xwhineatnews.pl71
-rwxr-xr-xxmlrpc.cgi16
-rw-r--r--xt/lib/Bugzilla/Test/Search.pm1495
-rw-r--r--xt/lib/Bugzilla/Test/Search/AndTest.pm30
-rw-r--r--xt/lib/Bugzilla/Test/Search/Constants.pm1834
-rw-r--r--xt/lib/Bugzilla/Test/Search/CustomTest.pm81
-rw-r--r--xt/lib/Bugzilla/Test/Search/FieldTest.pm832
-rw-r--r--xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm129
-rw-r--r--xt/lib/Bugzilla/Test/Search/InjectionTest.pm80
-rw-r--r--xt/lib/Bugzilla/Test/Search/NotTest.pm35
-rw-r--r--xt/lib/Bugzilla/Test/Search/OperatorTest.pm103
-rw-r--r--xt/lib/Bugzilla/Test/Search/OrTest.pm139
-rw-r--r--xt/search.t3
299 files changed, 60652 insertions, 57954 deletions
diff --git a/.perltidyrc b/.perltidyrc
new file mode 100644
index 000000000..95c897374
--- /dev/null
+++ b/.perltidyrc
@@ -0,0 +1,17 @@
+-pbp # Start with Perl Best Practices
+-w # Show all warnings
+-iob # Ignore old breakpoints
+-l=80 # 80 characters per line
+-vmll
+-ibc
+-iscl
+-hsc
+-mbl=2 # No more than 2 blank lines
+-i=2 # Indentation is 2 columns
+-ci=2 # Continuation indentation is 2 columns
+-vt=0 # Less vertical tightness
+-pt=2 # High parenthesis tightness
+-bt=2 # High brace tightness
+-sbt=2 # High square bracket tightness
+-wn # Weld nested containers
+-isbc # Don't indent comments without leading space
diff --git a/Bugzilla.pm b/Bugzilla.pm
index e4772e08b..deffa259e 100644
--- a/Bugzilla.pm
+++ b/Bugzilla.pm
@@ -13,11 +13,11 @@ use warnings;
# 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');
- }
+ # This makes sure we're in a CGI.
+ if ($ENV{SERVER_SOFTWARE} && !$ENV{MOD_PERL}) {
+ require CGI::Carp;
+ CGI::Carp->import('fatalsToBrowser');
+ }
}
use Bugzilla::Auth;
@@ -51,15 +51,15 @@ use Safe;
# Scripts that are not stopped by shutdownhtml being in effect.
use constant SHUTDOWNHTML_EXEMPT => qw(
- editparams.cgi
- checksetup.pl
- migrate.pl
- recode.pl
+ editparams.cgi
+ checksetup.pl
+ migrate.pl
+ recode.pl
);
# Non-cgi scripts that should silently exit.
use constant SHUTDOWNHTML_EXIT_SILENTLY => qw(
- whine.pl
+ whine.pl
);
# shutdownhtml pages are sent as an HTTP 503. After how many seconds
@@ -74,119 +74,127 @@ use constant SHUTDOWNHTML_RETRY_AFTER => 3600;
# Note that this is a raw subroutine, not a method, so $class isn't available.
sub init_page {
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- init_console();
- }
- elsif (Bugzilla->params->{'utf8'}) {
- binmode STDOUT, ':utf8';
- }
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ init_console();
+ }
+ elsif (Bugzilla->params->{'utf8'}) {
+ binmode STDOUT, ':utf8';
+ }
- if (${^TAINT}) {
- my $path = '';
- if (ON_WINDOWS) {
- # On Windows, these paths are tainted, preventing
- # File::Spec::Win32->tmpdir from using them. But we need
- # a place to temporary store attachments which are uploaded.
- foreach my $temp (qw(TMPDIR TMP TEMP WINDIR)) {
- trick_taint($ENV{$temp}) if $ENV{$temp};
- }
- # Some DLLs used by Strawberry Perl are also in c\bin,
- # see https://rt.cpan.org/Public/Bug/Display.html?id=99104
- if (!ON_ACTIVESTATE) {
- my $c_path = $path = dirname($^X);
- $c_path =~ s/\bperl\b(?=\\bin)/c/;
- $path .= ";$c_path";
- trick_taint($path);
- }
- }
- # 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'} = $path;
+ if (${^TAINT}) {
+ my $path = '';
+ if (ON_WINDOWS) {
+
+ # On Windows, these paths are tainted, preventing
+ # File::Spec::Win32->tmpdir from using them. But we need
+ # a place to temporary store attachments which are uploaded.
+ foreach my $temp (qw(TMPDIR TMP TEMP WINDIR)) {
+ trick_taint($ENV{$temp}) if $ENV{$temp};
+ }
+
+ # Some DLLs used by Strawberry Perl are also in c\bin,
+ # see https://rt.cpan.org/Public/Bug/Display.html?id=99104
+ if (!ON_ACTIVESTATE) {
+ my $c_path = $path = dirname($^X);
+ $c_path =~ s/\bperl\b(?=\\bin)/c/;
+ $path .= ";$c_path";
+ trick_taint($path);
+ }
}
- # Because this function is run live from perl "use" commands of
- # other scripts, we're skipping the rest of this function if we get here
- # during a perl syntax check (perl -c, like we do during the
- # 001compile.t test).
- return if $^C;
-
- # 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();
- }
- };
- }
+ # 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'} = $path;
+ }
+
+ # Because this function is run live from perl "use" commands of
+ # other scripts, we're skipping the rest of this function if we get here
+ # during a perl syntax check (perl -c, like we do during the
+ # 001compile.t test).
+ return if $^C;
+
+ # 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();
+ }
+ };
+ }
+
+ my $script = basename($0);
- my $script = basename($0);
+ # Because of attachment_base, attachment.cgi handles this itself.
+ if ($script ne 'attachment.cgi') {
+ do_ssl_redirect_if_required();
+ }
- # Because of attachment_base, attachment.cgi handles this itself.
- if ($script ne 'attachment.cgi') {
- do_ssl_redirect_if_required();
+ # 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.
+ #
+ # This code must go here. It cannot go anywhere in Bugzilla::CGI, because
+ # it uses Template, and that causes various dependency loops.
+ if (!grep { $_ eq $script } SHUTDOWNHTML_EXEMPT
+ and Bugzilla->params->{'shutdownhtml'})
+ {
+ # 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 (!i_am_cgi() && grep { $_ eq $script } SHUTDOWNHTML_EXIT_SILENTLY) {
+ exit;
}
- # 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.
- #
- # This code must go here. It cannot go anywhere in Bugzilla::CGI, because
- # it uses Template, and that causes various dependency loops.
- if (!grep { $_ eq $script } SHUTDOWNHTML_EXEMPT
- and Bugzilla->params->{'shutdownhtml'})
+ # 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'))
{
- # 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 (!i_am_cgi()
- && grep { $_ eq $script } SHUTDOWNHTML_EXIT_SILENTLY)
- {
- 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';
- }
- if (i_am_cgi()) {
- # Set the HTTP status to 503 when Bugzilla is down to avoid pages
- # being indexed by search engines.
- print Bugzilla->cgi->header(-status => 503,
- -retry_after => SHUTDOWNHTML_RETRY_AFTER);
- }
- $template->process("global/message.$extension.tmpl", $vars)
- || ThrowTemplateError($template->error);
- exit;
+ $extension = 'html';
+ }
+ else {
+ $extension = 'txt';
+ }
+ if (i_am_cgi()) {
+
+ # Set the HTTP status to 503 when Bugzilla is down to avoid pages
+ # being indexed by search engines.
+ print Bugzilla->cgi->header(
+ -status => 503,
+ -retry_after => SHUTDOWNHTML_RETRY_AFTER
+ );
}
+ $template->process("global/message.$extension.tmpl", $vars)
+ || ThrowTemplateError($template->error);
+ exit;
+ }
}
#####################################################################
@@ -194,434 +202,446 @@ sub init_page {
#####################################################################
sub template {
- return $_[0]->request_cache->{template} ||= Bugzilla::Template->create();
+ return $_[0]->request_cache->{template} ||= Bugzilla::Template->create();
}
sub template_inner {
- my ($class, $lang) = @_;
- my $cache = $class->request_cache;
- my $current_lang = $cache->{template_current_lang}->[0];
- $lang ||= $current_lang || '';
- return $cache->{"template_inner_$lang"} ||= Bugzilla::Template->create(language => $lang);
+ my ($class, $lang) = @_;
+ my $cache = $class->request_cache;
+ my $current_lang = $cache->{template_current_lang}->[0];
+ $lang ||= $current_lang || '';
+ return $cache->{"template_inner_$lang"}
+ ||= Bugzilla::Template->create(language => $lang);
}
our $extension_packages;
+
sub extensions {
- my ($class) = @_;
- my $cache = $class->request_cache;
- if (!$cache->{extensions}) {
- # Under mod_perl, mod_perl.pl populates $extension_packages for us.
- if (!$extension_packages) {
- $extension_packages = Bugzilla::Extension->load_all();
- }
- my @extensions;
- foreach my $package (@$extension_packages) {
- my $extension = $package->new();
- if ($extension->enabled) {
- push(@extensions, $extension);
- }
- }
- $cache->{extensions} = \@extensions;
+ my ($class) = @_;
+ my $cache = $class->request_cache;
+ if (!$cache->{extensions}) {
+
+ # Under mod_perl, mod_perl.pl populates $extension_packages for us.
+ if (!$extension_packages) {
+ $extension_packages = Bugzilla::Extension->load_all();
}
- return $cache->{extensions};
+ my @extensions;
+ foreach my $package (@$extension_packages) {
+ my $extension = $package->new();
+ if ($extension->enabled) {
+ push(@extensions, $extension);
+ }
+ }
+ $cache->{extensions} = \@extensions;
+ }
+ return $cache->{extensions};
}
sub feature {
- my ($class, $feature) = @_;
- my $cache = $class->request_cache;
- return $cache->{feature}->{$feature}
- if exists $cache->{feature}->{$feature};
-
- my $feature_map = $cache->{feature_map};
- if (!$feature_map) {
- foreach my $package (@{ OPTIONAL_MODULES() }) {
- foreach my $f (@{ $package->{feature} }) {
- $feature_map->{$f} ||= [];
- push(@{ $feature_map->{$f} }, $package);
- }
- }
- $cache->{feature_map} = $feature_map;
+ my ($class, $feature) = @_;
+ my $cache = $class->request_cache;
+ return $cache->{feature}->{$feature} if exists $cache->{feature}->{$feature};
+
+ my $feature_map = $cache->{feature_map};
+ if (!$feature_map) {
+ foreach my $package (@{OPTIONAL_MODULES()}) {
+ foreach my $f (@{$package->{feature}}) {
+ $feature_map->{$f} ||= [];
+ push(@{$feature_map->{$f}}, $package);
+ }
}
+ $cache->{feature_map} = $feature_map;
+ }
- if (!$feature_map->{$feature}) {
- ThrowCodeError('invalid_feature', { feature => $feature });
- }
+ if (!$feature_map->{$feature}) {
+ ThrowCodeError('invalid_feature', {feature => $feature});
+ }
- my $success = 1;
- foreach my $package (@{ $feature_map->{$feature} }) {
- have_vers($package) or $success = 0;
- }
- $cache->{feature}->{$feature} = $success;
- return $success;
+ my $success = 1;
+ foreach my $package (@{$feature_map->{$feature}}) {
+ have_vers($package) or $success = 0;
+ }
+ $cache->{feature}->{$feature} = $success;
+ return $success;
}
sub cgi {
- return $_[0]->request_cache->{cgi} ||= new Bugzilla::CGI();
+ return $_[0]->request_cache->{cgi} ||= new Bugzilla::CGI();
}
sub input_params {
- my ($class, $params) = @_;
- my $cache = $class->request_cache;
- # This is how the WebService and other places set input_params.
- if (defined $params) {
- $cache->{input_params} = $params;
- }
- return $cache->{input_params} if defined $cache->{input_params};
+ my ($class, $params) = @_;
+ my $cache = $class->request_cache;
+
+ # This is how the WebService and other places set input_params.
+ if (defined $params) {
+ $cache->{input_params} = $params;
+ }
+ return $cache->{input_params} if defined $cache->{input_params};
- # Making this scalar makes it a tied hash to the internals of $cgi,
- # so if a variable is changed, then it actually changes the $cgi object
- # as well.
- $cache->{input_params} = $class->cgi->Vars;
- return $cache->{input_params};
+ # Making this scalar makes it a tied hash to the internals of $cgi,
+ # so if a variable is changed, then it actually changes the $cgi object
+ # as well.
+ $cache->{input_params} = $class->cgi->Vars;
+ return $cache->{input_params};
}
sub localconfig {
- return $_[0]->process_cache->{localconfig} ||= read_localconfig();
+ return $_[0]->process_cache->{localconfig} ||= read_localconfig();
}
sub params {
- return $_[0]->request_cache->{params} ||= Bugzilla::Config::read_param_file();
+ return $_[0]->request_cache->{params} ||= Bugzilla::Config::read_param_file();
}
sub user {
- return $_[0]->request_cache->{user} ||= new Bugzilla::User;
+ return $_[0]->request_cache->{user} ||= new Bugzilla::User;
}
sub set_user {
- my ($class, $user) = @_;
- $class->request_cache->{user} = $user;
+ my ($class, $user) = @_;
+ $class->request_cache->{user} = $user;
}
sub sudoer {
- return $_[0]->request_cache->{sudoer};
+ return $_[0]->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.
+ 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 page_requires_login {
- return $_[0]->request_cache->{page_requires_login};
+ return $_[0]->request_cache->{page_requires_login};
}
sub login {
- my ($class, $type) = @_;
+ my ($class, $type) = @_;
- return $class->user if $class->user->id;
+ return $class->user if $class->user->id;
- my $authorizer = new Bugzilla::Auth();
- $type = LOGIN_REQUIRED if $class->cgi->param('GoAheadAndLogIn');
+ 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;
- }
+ if (!defined $type || $type == LOGIN_NORMAL) {
+ $type = $class->params->{'requirelogin'} ? LOGIN_REQUIRED : LOGIN_NORMAL;
+ }
+
+ # Allow templates to know that we're in a page that always requires
+ # login.
+ if ($type == LOGIN_REQUIRED) {
+ $class->request_cache->{page_requires_login} = 1;
+ }
- # Allow templates to know that we're in a page that always requires
- # login.
- if ($type == LOGIN_REQUIRED) {
- $class->request_cache->{page_requires_login} = 1;
+ 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 $token = $class->cgi->cookie('sudo');
+ if (defined $authenticated_user && $token) {
+ my ($user_id, $date, $sudo_target_id) = Bugzilla::Token::GetTokenData($token);
+ if (!$user_id
+ || $user_id != $authenticated_user->id
+ || !detaint_natural($sudo_target_id)
+ || (time() - str2time($date) > MAX_SUDO_TOKEN_AGE))
+ {
+ $class->cgi->remove_cookie('sudo');
+ ThrowUserError('sudo_invalid_cookie');
}
- 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 $token = $class->cgi->cookie('sudo');
- if (defined $authenticated_user && $token) {
- my ($user_id, $date, $sudo_target_id) = Bugzilla::Token::GetTokenData($token);
- if (!$user_id
- || $user_id != $authenticated_user->id
- || !detaint_natural($sudo_target_id)
- || (time() - str2time($date) > MAX_SUDO_TOKEN_AGE))
- {
- $class->cgi->remove_cookie('sudo');
- ThrowUserError('sudo_invalid_cookie');
- }
-
- my $sudo_target = new Bugzilla::User($sudo_target_id);
- if ($authenticated_user->in_group('bz_sudoers')
- && 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 {
- delete_token($token);
- $class->cgi->remove_cookie('sudo');
- ThrowUserError('sudo_illegal_action', { sudoer => $authenticated_user,
- target_user => $sudo_target });
- }
+ my $sudo_target = new Bugzilla::User($sudo_target_id);
+ if ( $authenticated_user->in_group('bz_sudoers')
+ && 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);
+ delete_token($token);
+ $class->cgi->remove_cookie('sudo');
+ ThrowUserError('sudo_illegal_action',
+ {sudoer => $authenticated_user, target_user => $sudo_target});
}
+ }
+ else {
+ $class->set_user($authenticated_user);
+ }
- if ($class->sudoer) {
- $class->sudoer->update_last_seen_date();
- } else {
- $class->user->update_last_seen_date();
- }
+ if ($class->sudoer) {
+ $class->sudoer->update_last_seen_date();
+ }
+ else {
+ $class->user->update_last_seen_date();
+ }
- return $class->user;
+ return $class->user;
}
sub logout {
- my ($class, $option) = @_;
+ my ($class, $option) = @_;
- # If we're not logged in, go away
- return unless $class->user->id;
+ # 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;
+ $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});
+ 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);
+ 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!
+ 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 job_queue {
- require Bugzilla::JobQueue;
- return $_[0]->request_cache->{job_queue} ||= Bugzilla::JobQueue->new();
+ require Bugzilla::JobQueue;
+ return $_[0]->request_cache->{job_queue} ||= Bugzilla::JobQueue->new();
}
sub dbh {
- # If we're not connected, then we must want the main db
- return $_[0]->request_cache->{dbh} ||= $_[0]->dbh_main;
+
+ # If we're not connected, then we must want the main db
+ return $_[0]->request_cache->{dbh} ||= $_[0]->dbh_main;
}
sub dbh_main {
- return $_[0]->request_cache->{dbh_main} ||= Bugzilla::DB::connect_main();
+ return $_[0]->request_cache->{dbh_main} ||= Bugzilla::DB::connect_main();
}
sub languages {
- return Bugzilla::Install::Util::supported_languages();
+ return Bugzilla::Install::Util::supported_languages();
}
sub current_language {
- return $_[0]->request_cache->{current_language} ||= (include_languages())[0];
+ return $_[0]->request_cache->{current_language} ||= (include_languages())[0];
}
sub error_mode {
- my ($class, $newval) = @_;
- if (defined $newval) {
- $class->request_cache->{error_mode} = $newval;
- }
+ my ($class, $newval) = @_;
+ if (defined $newval) {
+ $class->request_cache->{error_mode} = $newval;
+ }
- # XXX - Once we require Perl 5.10.1, this test can be replaced by //.
- if (exists $class->request_cache->{error_mode}) {
- return $class->request_cache->{error_mode};
- }
- else {
- return (i_am_cgi() ? ERROR_MODE_WEBPAGE : ERROR_MODE_DIE);
- }
+ # XXX - Once we require Perl 5.10.1, this test can be replaced by //.
+ if (exists $class->request_cache->{error_mode}) {
+ return $class->request_cache->{error_mode};
+ }
+ else {
+ return (i_am_cgi() ? ERROR_MODE_WEBPAGE : ERROR_MODE_DIE);
+ }
}
# This is used only by Bugzilla::Error to throw errors.
sub _json_server {
- my ($class, $newval) = @_;
- if (defined $newval) {
- $class->request_cache->{_json_server} = $newval;
- }
- return $class->request_cache->{_json_server};
+ my ($class, $newval) = @_;
+ if (defined $newval) {
+ $class->request_cache->{_json_server} = $newval;
+ }
+ return $class->request_cache->{_json_server};
}
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_XMLRPC) {
- $class->error_mode(ERROR_MODE_DIE_SOAP_FAULT);
- }
- elsif ($newval == USAGE_MODE_JSON) {
- $class->error_mode(ERROR_MODE_JSON_RPC);
- }
- elsif ($newval == USAGE_MODE_EMAIL) {
- $class->error_mode(ERROR_MODE_DIE);
- }
- elsif ($newval == USAGE_MODE_TEST) {
- $class->error_mode(ERROR_MODE_TEST);
- }
- elsif ($newval == USAGE_MODE_REST) {
- $class->error_mode(ERROR_MODE_REST);
- }
- else {
- ThrowCodeError('usage_mode_invalid',
- {'invalid_usage_mode', $newval});
- }
- $class->request_cache->{usage_mode} = $newval;
+ my ($class, $newval) = @_;
+ if (defined $newval) {
+ if ($newval == USAGE_MODE_BROWSER) {
+ $class->error_mode(ERROR_MODE_WEBPAGE);
}
-
- # XXX - Once we require Perl 5.10.1, this test can be replaced by //.
- if (exists $class->request_cache->{usage_mode}) {
- return $class->request_cache->{usage_mode};
+ elsif ($newval == USAGE_MODE_CMDLINE) {
+ $class->error_mode(ERROR_MODE_DIE);
+ }
+ elsif ($newval == USAGE_MODE_XMLRPC) {
+ $class->error_mode(ERROR_MODE_DIE_SOAP_FAULT);
+ }
+ elsif ($newval == USAGE_MODE_JSON) {
+ $class->error_mode(ERROR_MODE_JSON_RPC);
+ }
+ elsif ($newval == USAGE_MODE_EMAIL) {
+ $class->error_mode(ERROR_MODE_DIE);
+ }
+ elsif ($newval == USAGE_MODE_TEST) {
+ $class->error_mode(ERROR_MODE_TEST);
+ }
+ elsif ($newval == USAGE_MODE_REST) {
+ $class->error_mode(ERROR_MODE_REST);
}
else {
- return (i_am_cgi()? USAGE_MODE_BROWSER : USAGE_MODE_CMDLINE);
+ ThrowCodeError('usage_mode_invalid', {'invalid_usage_mode', $newval});
}
+ $class->request_cache->{usage_mode} = $newval;
+ }
+
+ # XXX - Once we require Perl 5.10.1, this test can be replaced by //.
+ if (exists $class->request_cache->{usage_mode}) {
+ return $class->request_cache->{usage_mode};
+ }
+ else {
+ return (i_am_cgi() ? USAGE_MODE_BROWSER : USAGE_MODE_CMDLINE);
+ }
}
sub installation_mode {
- my ($class, $newval) = @_;
- ($class->request_cache->{installation_mode} = $newval) if defined $newval;
- return $class->request_cache->{installation_mode}
- || INSTALLATION_MODE_INTERACTIVE;
+ 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);
+ my ($class, $filename) = @_;
+ if ($filename) {
+ my $s = new Safe;
+ $s->rdo($filename);
- die "Error reading $filename: $!" if $!;
- die "Error evaluating $filename: $@" if $@;
+ 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} || {};
+ # 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} = $class->dbh_main;
- }
+ 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} = $class->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;
+ $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;
+ my $class = shift;
- $class->request_cache->{dbh} = $class->dbh_main;
- return $class->dbh_main;
+ $class->request_cache->{dbh} = $class->dbh_main;
+ return $class->dbh_main;
}
sub is_shadow_db {
- my $class = shift;
- return $class->request_cache->{dbh} != $class->dbh_main;
+ my $class = shift;
+ return $class->request_cache->{dbh} != $class->dbh_main;
}
sub fields {
- my ($class, $criteria) = @_;
- $criteria ||= {};
- my $cache = $class->request_cache;
-
- # We create an advanced cache for fields by type, so that we
- # can avoid going back to the database for every fields() call.
- # (And most of our fields() calls are for getting fields by type.)
- #
- # We also cache fields by name, because calling $field->name a few
- # million times can be slow in calling code, but if we just do it
- # once here, that makes things a lot faster for callers.
- if (!defined $cache->{fields}) {
- my @all_fields = Bugzilla::Field->get_all;
- my (%by_name, %by_type);
- foreach my $field (@all_fields) {
- my $name = $field->name;
- $by_type{$field->type}->{$name} = $field;
- $by_name{$name} = $field;
- }
- $cache->{fields} = { by_type => \%by_type, by_name => \%by_name };
+ my ($class, $criteria) = @_;
+ $criteria ||= {};
+ my $cache = $class->request_cache;
+
+ # We create an advanced cache for fields by type, so that we
+ # can avoid going back to the database for every fields() call.
+ # (And most of our fields() calls are for getting fields by type.)
+ #
+ # We also cache fields by name, because calling $field->name a few
+ # million times can be slow in calling code, but if we just do it
+ # once here, that makes things a lot faster for callers.
+ if (!defined $cache->{fields}) {
+ my @all_fields = Bugzilla::Field->get_all;
+ my (%by_name, %by_type);
+ foreach my $field (@all_fields) {
+ my $name = $field->name;
+ $by_type{$field->type}->{$name} = $field;
+ $by_name{$name} = $field;
}
+ $cache->{fields} = {by_type => \%by_type, by_name => \%by_name};
+ }
- my $fields = $cache->{fields};
- my %requested;
- if (my $types = delete $criteria->{type}) {
- $types = ref($types) ? $types : [$types];
- %requested = map { %{ $fields->{by_type}->{$_} || {} } } @$types;
- }
- else {
- %requested = %{ $fields->{by_name} };
- }
+ my $fields = $cache->{fields};
+ my %requested;
+ if (my $types = delete $criteria->{type}) {
+ $types = ref($types) ? $types : [$types];
+ %requested = map { %{$fields->{by_type}->{$_} || {}} } @$types;
+ }
+ else {
+ %requested = %{$fields->{by_name}};
+ }
- my $do_by_name = delete $criteria->{by_name};
+ my $do_by_name = delete $criteria->{by_name};
- # Filtering before returning the fields based on
- # the criterias.
- foreach my $filter (keys %$criteria) {
- foreach my $field (keys %requested) {
- if ($requested{$field}->$filter != $criteria->{$filter}) {
- delete $requested{$field};
- }
- }
+ # Filtering before returning the fields based on
+ # the criterias.
+ foreach my $filter (keys %$criteria) {
+ foreach my $field (keys %requested) {
+ if ($requested{$field}->$filter != $criteria->{$filter}) {
+ delete $requested{$field};
+ }
}
+ }
- return $do_by_name ? \%requested
- : [sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name } values %requested];
+ return $do_by_name
+ ? \%requested
+ : [sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name }
+ values %requested];
}
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}};
+ 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;
+ my $class = shift;
- if (!defined $class->request_cache->{has_flags}) {
- $class->request_cache->{has_flags} = Bugzilla::Flag->any_exist;
- }
- return $class->request_cache->{has_flags};
+ if (!defined $class->request_cache->{has_flags}) {
+ $class->request_cache->{has_flags} = Bugzilla::Flag->any_exist;
+ }
+ return $class->request_cache->{has_flags};
}
sub local_timezone {
- return $_[0]->process_cache->{local_timezone}
- ||= DateTime::TimeZone->new(name => 'local');
+ return $_[0]->process_cache->{local_timezone}
+ ||= DateTime::TimeZone->new(name => 'local');
}
# This creates the request cache for non-mod_perl installations.
@@ -631,27 +651,28 @@ sub local_timezone {
our $_request_cache = $Bugzilla::Install::Util::_cache;
sub request_cache {
- if ($ENV{MOD_PERL}) {
- require Apache2::RequestUtil;
- # Sometimes (for example, during mod_perl.pl), the request
- # object isn't available, and we should use $_request_cache instead.
- my $request = eval { Apache2::RequestUtil->request };
- return $_request_cache if !$request;
- return $request->pnotes();
- }
- return $_request_cache;
+ if ($ENV{MOD_PERL}) {
+ require Apache2::RequestUtil;
+
+ # Sometimes (for example, during mod_perl.pl), the request
+ # object isn't available, and we should use $_request_cache instead.
+ my $request = eval { Apache2::RequestUtil->request };
+ return $_request_cache if !$request;
+ return $request->pnotes();
+ }
+ return $_request_cache;
}
sub clear_request_cache {
- $_request_cache = {};
- if ($ENV{MOD_PERL}) {
- require Apache2::RequestUtil;
- my $request = eval { Apache2::RequestUtil->request };
- if ($request) {
- my $pnotes = $request->pnotes;
- delete @$pnotes{(keys %$pnotes)};
- }
+ $_request_cache = {};
+ if ($ENV{MOD_PERL}) {
+ require Apache2::RequestUtil;
+ my $request = eval { Apache2::RequestUtil->request };
+ if ($request) {
+ my $pnotes = $request->pnotes;
+ delete @$pnotes{(keys %$pnotes)};
}
+ }
}
# This is a per-process cache. Under mod_cgi it's identical to the
@@ -660,13 +681,13 @@ sub clear_request_cache {
our $_process_cache = {};
sub process_cache {
- return $_process_cache;
+ return $_process_cache;
}
# This is a memcached wrapper, which provides cross-process and cross-system
# caching.
sub memcached {
- return $_[0]->process_cache->{memcached} ||= Bugzilla::Memcached->_new();
+ return $_[0]->process_cache->{memcached} ||= Bugzilla::Memcached->_new();
}
# Private methods
@@ -674,28 +695,29 @@ sub memcached {
# Per-process cleanup. Note that this is a plain subroutine, not a method,
# so we don't have $class available.
sub _cleanup {
- my $cache = Bugzilla->request_cache;
- my $main = $cache->{dbh_main};
- my $shadow = $cache->{dbh_shadow};
- foreach my $dbh ($main, $shadow) {
- next if !$dbh;
- $dbh->bz_rollback_transaction() if $dbh->bz_in_transaction;
- $dbh->disconnect;
- }
- my $smtp = $cache->{smtp};
- $smtp->disconnect if $smtp;
- clear_request_cache();
-
- # These are both set by CGI.pm but need to be undone so that
- # Apache can actually shut down its children if it needs to.
- foreach my $signal (qw(TERM PIPE)) {
- $SIG{$signal} = 'DEFAULT' if $SIG{$signal} && $SIG{$signal} eq 'IGNORE';
- }
+ my $cache = Bugzilla->request_cache;
+ my $main = $cache->{dbh_main};
+ my $shadow = $cache->{dbh_shadow};
+ foreach my $dbh ($main, $shadow) {
+ next if !$dbh;
+ $dbh->bz_rollback_transaction() if $dbh->bz_in_transaction;
+ $dbh->disconnect;
+ }
+ my $smtp = $cache->{smtp};
+ $smtp->disconnect if $smtp;
+ clear_request_cache();
+
+ # These are both set by CGI.pm but need to be undone so that
+ # Apache can actually shut down its children if it needs to.
+ foreach my $signal (qw(TERM PIPE)) {
+ $SIG{$signal} = 'DEFAULT' if $SIG{$signal} && $SIG{$signal} eq 'IGNORE';
+ }
}
sub END {
- # Bugzilla.pm cannot compile in mod_perl.pl if this runs.
- _cleanup() unless $ENV{MOD_PERL};
+
+ # Bugzilla.pm cannot compile in mod_perl.pl if this runs.
+ _cleanup() unless $ENV{MOD_PERL};
}
init_page() if !$ENV{MOD_PERL};
diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm
index 33183797b..26f768c2f 100644
--- a/Bugzilla/Attachment.pm
+++ b/Bugzilla/Attachment.pm
@@ -57,55 +57,51 @@ use parent qw(Bugzilla::Object);
use constant DB_TABLE => 'attachments';
use constant ID_FIELD => 'attach_id';
use constant LIST_ORDER => ID_FIELD;
+
# Attachments are tracked in bugs_activity.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant DB_COLUMNS => qw(
- attach_id
- bug_id
- creation_ts
- description
- filename
- isobsolete
- ispatch
- isprivate
- mimetype
- modification_time
- submitter_id
+ attach_id
+ bug_id
+ creation_ts
+ description
+ filename
+ isobsolete
+ ispatch
+ isprivate
+ mimetype
+ modification_time
+ submitter_id
);
-use constant REQUIRED_FIELD_MAP => {
- bug_id => 'bug',
-};
+use constant REQUIRED_FIELD_MAP => {bug_id => 'bug',};
use constant EXTRA_REQUIRED_FIELDS => qw(data);
use constant UPDATE_COLUMNS => qw(
- description
- filename
- isobsolete
- ispatch
- isprivate
- mimetype
+ description
+ filename
+ isobsolete
+ ispatch
+ isprivate
+ mimetype
);
use constant VALIDATORS => {
- bug => \&_check_bug,
- description => \&_check_description,
- filename => \&_check_filename,
- ispatch => \&Bugzilla::Object::check_boolean,
- isprivate => \&_check_is_private,
- mimetype => \&_check_content_type,
+ bug => \&_check_bug,
+ description => \&_check_description,
+ filename => \&_check_filename,
+ ispatch => \&Bugzilla::Object::check_boolean,
+ isprivate => \&_check_is_private,
+ mimetype => \&_check_content_type,
};
-use constant VALIDATOR_DEPENDENCIES => {
- content_type => ['ispatch'],
- mimetype => ['ispatch'],
-};
+use constant VALIDATOR_DEPENDENCIES =>
+ {content_type => ['ispatch'], mimetype => ['ispatch'],};
-use constant UPDATE_VALIDATORS => {
- isobsolete => \&Bugzilla::Object::check_boolean,
-};
+use constant UPDATE_VALIDATORS =>
+ {isobsolete => \&Bugzilla::Object::check_boolean,};
###############################
#### Accessors ######
@@ -126,7 +122,7 @@ the ID of the bug to which the attachment is attached
=cut
sub bug_id {
- return $_[0]->{bug_id};
+ return $_[0]->{bug_id};
}
=over
@@ -140,8 +136,8 @@ the bug object to which the attachment is attached
=cut
sub bug {
- require Bugzilla::Bug;
- return $_[0]->{bug} //= Bugzilla::Bug->new({ id => $_[0]->bug_id, cache => 1 });
+ require Bugzilla::Bug;
+ return $_[0]->{bug} //= Bugzilla::Bug->new({id => $_[0]->bug_id, cache => 1});
}
=over
@@ -155,7 +151,7 @@ user-provided text describing the attachment
=cut
sub description {
- return $_[0]->{description};
+ return $_[0]->{description};
}
=over
@@ -169,7 +165,7 @@ the attachment's MIME media type
=cut
sub contenttype {
- return $_[0]->{mimetype};
+ return $_[0]->{mimetype};
}
=over
@@ -183,8 +179,8 @@ the user who attached the attachment
=cut
sub attacher {
- return $_[0]->{attacher}
- //= new Bugzilla::User({ id => $_[0]->{submitter_id}, cache => 1 });
+ return $_[0]->{attacher}
+ //= new Bugzilla::User({id => $_[0]->{submitter_id}, cache => 1});
}
=over
@@ -198,7 +194,7 @@ the date and time on which the attacher attached the attachment
=cut
sub attached {
- return $_[0]->{creation_ts};
+ return $_[0]->{creation_ts};
}
=over
@@ -212,7 +208,7 @@ the date and time on which the attachment was last modified.
=cut
sub modification_time {
- return $_[0]->{modification_time};
+ return $_[0]->{modification_time};
}
=over
@@ -226,7 +222,7 @@ the name of the file the attacher attached
=cut
sub filename {
- return $_[0]->{filename};
+ return $_[0]->{filename};
}
=over
@@ -240,7 +236,7 @@ whether or not the attachment is a patch
=cut
sub ispatch {
- return $_[0]->{ispatch};
+ return $_[0]->{ispatch};
}
=over
@@ -254,7 +250,7 @@ whether or not the attachment is obsolete
=cut
sub isobsolete {
- return $_[0]->{isobsolete};
+ return $_[0]->{isobsolete};
}
=over
@@ -268,7 +264,7 @@ whether or not the attachment is private
=cut
sub isprivate {
- return $_[0]->{isprivate};
+ return $_[0]->{isprivate};
}
=over
@@ -285,23 +281,24 @@ matches, because this will return a value even if it's matched by the generic
=cut
sub is_viewable {
- my $contenttype = $_[0]->contenttype;
- my $cgi = Bugzilla->cgi;
+ my $contenttype = $_[0]->contenttype;
+ my $cgi = Bugzilla->cgi;
- # We assume we can view all text and image types.
- return 1 if ($contenttype =~ /^(text|image)\//);
+ # 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\//));
+ # 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(,.*)?$/);
+ # 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;
+ return 0;
}
=over
@@ -315,28 +312,29 @@ the content of the attachment
=cut
sub data {
- my $self = shift;
- return $self->{data} if exists $self->{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
+ # 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);
- }
+ 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};
+ return $self->{data};
}
=over
@@ -358,37 +356,37 @@ the length (in bytes) of the attachment content
# LENGTH() function or stat()ing the file instead. I've left it in for now.
sub datasize {
- my $self = shift;
- return $self->{datasize} if defined $self->{datasize};
+ my $self = shift;
+ return $self->{datasize} if defined $self->{datasize};
- # If we have already retrieved the data, return its size.
- return length($self->{data}) if exists $self->{data};
+ # 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)
+ $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);
- }
+ 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};
+ return $self->{datasize};
}
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;
+ my $self = shift;
+ my $hash = ($self->id % 100) + 100;
+ $hash =~ s/.*(\d\d)$/group.$1/;
+ return bz_locations()->{'attachdir'} . "/$hash/attachment." . $self->id;
}
=over
@@ -402,8 +400,9 @@ flags that have been set on the attachment
=cut
sub flags {
- # Don't cache it as it must be in sync with ->flag_types.
- return $_[0]->{flags} = [map { @{$_->{flags}} } @{$_[0]->flag_types}];
+
+ # Don't cache it as it must be in sync with ->flag_types.
+ return $_[0]->{flags} = [map { @{$_->{flags}} } @{$_[0]->flag_types}];
}
=over
@@ -418,202 +417,216 @@ already set, grouped by flag type.
=cut
sub flag_types {
- my $self = shift;
- return $self->{flag_types} if exists $self->{flag_types};
+ my $self = shift;
+ return $self->{flag_types} if exists $self->{flag_types};
- my $vars = { target_type => 'attachment',
- product_id => $self->bug->product_id,
- component_id => $self->bug->component_id,
- attach_id => $self->id };
+ my $vars = {
+ target_type => 'attachment',
+ product_id => $self->bug->product_id,
+ component_id => $self->bug->component_id,
+ attach_id => $self->id
+ };
- return $self->{flag_types} = Bugzilla::Flag->_flag_types($vars);
+ return $self->{flag_types} = Bugzilla::Flag->_flag_types($vars);
}
###############################
#### Validators ######
###############################
-sub set_content_type { $_[0]->set('mimetype', $_[1]); }
+sub set_content_type { $_[0]->set('mimetype', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); }
-sub set_filename { $_[0]->set('filename', $_[1]); }
-sub set_is_patch { $_[0]->set('ispatch', $_[1]); }
-sub set_is_private { $_[0]->set('isprivate', $_[1]); }
-
-sub set_is_obsolete {
- my ($self, $obsolete) = @_;
-
- my $old = $self->isobsolete;
- $self->set('isobsolete', $obsolete);
- my $new = $self->isobsolete;
-
- # If the attachment is being marked as obsolete, cancel pending requests.
- if ($new && $old != $new) {
- my @requests = grep { $_->status eq '?' } @{$self->flags};
- return unless scalar @requests;
-
- my %flag_ids = map { $_->id => 1 } @requests;
- foreach my $flagtype (@{$self->flag_types}) {
- @{$flagtype->{flags}} = grep { !$flag_ids{$_->id} } @{$flagtype->{flags}};
- }
+sub set_filename { $_[0]->set('filename', $_[1]); }
+sub set_is_patch { $_[0]->set('ispatch', $_[1]); }
+sub set_is_private { $_[0]->set('isprivate', $_[1]); }
+
+sub set_is_obsolete {
+ my ($self, $obsolete) = @_;
+
+ my $old = $self->isobsolete;
+ $self->set('isobsolete', $obsolete);
+ my $new = $self->isobsolete;
+
+ # If the attachment is being marked as obsolete, cancel pending requests.
+ if ($new && $old != $new) {
+ my @requests = grep { $_->status eq '?' } @{$self->flags};
+ return unless scalar @requests;
+
+ my %flag_ids = map { $_->id => 1 } @requests;
+ foreach my $flagtype (@{$self->flag_types}) {
+ @{$flagtype->{flags}} = grep { !$flag_ids{$_->id} } @{$flagtype->{flags}};
}
+ }
}
sub set_flags {
- my ($self, $flags, $new_flags) = @_;
+ my ($self, $flags, $new_flags) = @_;
- Bugzilla::Flag->set_flag($self, $_) foreach (@$flags, @$new_flags);
+ Bugzilla::Flag->set_flag($self, $_) foreach (@$flags, @$new_flags);
}
sub _check_bug {
- my ($invocant, $bug) = @_;
- my $user = Bugzilla->user;
+ my ($invocant, $bug) = @_;
+ my $user = Bugzilla->user;
- $bug = ref $invocant ? $invocant->bug : $bug;
+ $bug = ref $invocant ? $invocant->bug : $bug;
- $bug || ThrowCodeError('param_required',
- { function => "$invocant->create", param => 'bug' });
+ $bug
+ || ThrowCodeError('param_required',
+ {function => "$invocant->create", param => 'bug'});
- ($user->can_see_bug($bug->id) && $user->can_edit_product($bug->product_id))
- || ThrowUserError("illegal_attachment_edit_bug", { bug_id => $bug->id });
+ ($user->can_see_bug($bug->id) && $user->can_edit_product($bug->product_id))
+ || ThrowUserError("illegal_attachment_edit_bug", {bug_id => $bug->id});
- return $bug;
+ return $bug;
}
sub _check_content_type {
- my ($invocant, $content_type, undef, $params) = @_;
-
- my $is_patch = ref($invocant) ? $invocant->ispatch : $params->{ispatch};
- $content_type = 'text/plain' if $is_patch;
- $content_type = clean_text($content_type);
- # The subsets below cover all existing MIME types and charsets registered by IANA.
- # (MIME type: RFC 2045 section 5.1; charset: RFC 2278 section 3.3)
- my $legal_types = join('|', LEGAL_CONTENT_TYPES);
- if (!$content_type
- || $content_type !~ /^($legal_types)\/[a-z0-9_\-\+\.]+(;\s*charset=[a-z0-9_\-\+]+)?$/i)
- {
- ThrowUserError("invalid_content_type", { contenttype => $content_type });
+ my ($invocant, $content_type, undef, $params) = @_;
+
+ my $is_patch = ref($invocant) ? $invocant->ispatch : $params->{ispatch};
+ $content_type = 'text/plain' if $is_patch;
+ $content_type = clean_text($content_type);
+
+# The subsets below cover all existing MIME types and charsets registered by IANA.
+# (MIME type: RFC 2045 section 5.1; charset: RFC 2278 section 3.3)
+ my $legal_types = join('|', LEGAL_CONTENT_TYPES);
+ if (!$content_type
+ || $content_type
+ !~ /^($legal_types)\/[a-z0-9_\-\+\.]+(;\s*charset=[a-z0-9_\-\+]+)?$/i)
+ {
+ ThrowUserError("invalid_content_type", {contenttype => $content_type});
+ }
+ trick_taint($content_type);
+
+ # $ENV{HOME} must be defined when using File::MimeInfo::Magic,
+ # see https://rt.cpan.org/Public/Bug/Display.html?id=41744.
+ local $ENV{HOME} = $ENV{HOME} || File::Spec->rootdir();
+
+ # If we have autodetected application/octet-stream from the Content-Type
+ # header, let's have a better go using a sniffer if available.
+ if ( defined Bugzilla->input_params->{contenttypemethod}
+ && Bugzilla->input_params->{contenttypemethod} eq 'autodetect'
+ && $content_type eq 'application/octet-stream'
+ && Bugzilla->feature('typesniffer'))
+ {
+ import File::MimeInfo::Magic qw(mimetype);
+ require IO::Scalar;
+
+ # data is either a filehandle, or the data itself.
+ my $fh = $params->{data};
+ if (!ref($fh)) {
+ $fh = new IO::Scalar \$fh;
}
- trick_taint($content_type);
-
- # $ENV{HOME} must be defined when using File::MimeInfo::Magic,
- # see https://rt.cpan.org/Public/Bug/Display.html?id=41744.
- local $ENV{HOME} = $ENV{HOME} || File::Spec->rootdir();
-
- # If we have autodetected application/octet-stream from the Content-Type
- # header, let's have a better go using a sniffer if available.
- if (defined Bugzilla->input_params->{contenttypemethod}
- && Bugzilla->input_params->{contenttypemethod} eq 'autodetect'
- && $content_type eq 'application/octet-stream'
- && Bugzilla->feature('typesniffer'))
- {
- import File::MimeInfo::Magic qw(mimetype);
- require IO::Scalar;
-
- # data is either a filehandle, or the data itself.
- my $fh = $params->{data};
- if (!ref($fh)) {
- $fh = new IO::Scalar \$fh;
- }
- elsif (!$fh->isa('IO::Handle')) {
- # CGI.pm sends us an Fh that isn't actually an IO::Handle, but
- # has a method for getting an actual handle out of it.
- $fh = $fh->handle;
- # ->handle returns an literal IO::Handle, even though the
- # underlying object is a file. So we rebless it to be a proper
- # IO::File object so that we can call ->seek on it and so on.
- # Just in case CGI.pm fixes this some day, we check ->isa first.
- if (!$fh->isa('IO::File')) {
- bless $fh, 'IO::File';
- }
- }
-
- my $mimetype = mimetype($fh);
- $fh->seek(0, 0);
- $content_type = $mimetype if $mimetype;
+ elsif (!$fh->isa('IO::Handle')) {
+
+ # CGI.pm sends us an Fh that isn't actually an IO::Handle, but
+ # has a method for getting an actual handle out of it.
+ $fh = $fh->handle;
+
+ # ->handle returns an literal IO::Handle, even though the
+ # underlying object is a file. So we rebless it to be a proper
+ # IO::File object so that we can call ->seek on it and so on.
+ # Just in case CGI.pm fixes this some day, we check ->isa first.
+ if (!$fh->isa('IO::File')) {
+ bless $fh, 'IO::File';
+ }
}
- # Make sure patches are viewable in the browser
- if (!ref($invocant)
- && defined Bugzilla->input_params->{contenttypemethod}
- && Bugzilla->input_params->{contenttypemethod} eq 'autodetect'
- && $content_type =~ m{text/x-(?:diff|patch)})
- {
- $params->{ispatch} = 1;
- $content_type = 'text/plain';
- }
-
- return $content_type;
+ my $mimetype = mimetype($fh);
+ $fh->seek(0, 0);
+ $content_type = $mimetype if $mimetype;
+ }
+
+ # Make sure patches are viewable in the browser
+ if (!ref($invocant)
+ && defined Bugzilla->input_params->{contenttypemethod}
+ && Bugzilla->input_params->{contenttypemethod} eq 'autodetect'
+ && $content_type =~ m{text/x-(?:diff|patch)})
+ {
+ $params->{ispatch} = 1;
+ $content_type = 'text/plain';
+ }
+
+ return $content_type;
}
sub _check_data {
- my ($invocant, $params) = @_;
+ my ($invocant, $params) = @_;
- my $data = $params->{data};
- $params->{filesize} = ref $data ? -s $data : length($data);
+ my $data = $params->{data};
+ $params->{filesize} = ref $data ? -s $data : length($data);
- Bugzilla::Hook::process('attachment_process_data', { data => \$data,
- attributes => $params });
+ Bugzilla::Hook::process('attachment_process_data',
+ {data => \$data, attributes => $params});
- $params->{filesize} || ThrowUserError('zero_length_file');
- # Make sure the attachment does not exceed the maximum permitted size.
- my $max_size = max(Bugzilla->params->{'maxlocalattachment'} * 1048576,
- Bugzilla->params->{'maxattachmentsize'} * 1024);
+ $params->{filesize} || ThrowUserError('zero_length_file');
- if ($params->{filesize} > $max_size) {
- my $vars = { filesize => sprintf("%.0f", $params->{filesize}/1024) };
- ThrowUserError('file_too_large', $vars);
- }
- return $data;
+ # Make sure the attachment does not exceed the maximum permitted size.
+ my $max_size = max(
+ Bugzilla->params->{'maxlocalattachment'} * 1048576,
+ Bugzilla->params->{'maxattachmentsize'} * 1024
+ );
+
+ if ($params->{filesize} > $max_size) {
+ my $vars = {filesize => sprintf("%.0f", $params->{filesize} / 1024)};
+ ThrowUserError('file_too_large', $vars);
+ }
+ return $data;
}
sub _check_description {
- my ($invocant, $description) = @_;
+ my ($invocant, $description) = @_;
- $description = trim($description);
- $description || ThrowUserError('missing_attachment_description');
- return $description;
+ $description = trim($description);
+ $description || ThrowUserError('missing_attachment_description');
+ return $description;
}
sub _check_filename {
- my ($invocant, $filename) = @_;
-
- $filename = clean_text($filename);
- if (!$filename) {
- if (ref $invocant) {
- ThrowUserError('filename_not_specified');
- }
- else {
- ThrowUserError('file_not_specified');
- }
- }
+ my ($invocant, $filename) = @_;
- # 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 MAX_ATTACH_FILENAME_LENGTH characters, counting
- # from the end of the string to make sure we keep the filename extension.
- $filename = substr($filename,
- -&MAX_ATTACH_FILENAME_LENGTH,
- MAX_ATTACH_FILENAME_LENGTH);
- trick_taint($filename);
-
- return $filename;
+ $filename = clean_text($filename);
+ if (!$filename) {
+ if (ref $invocant) {
+ ThrowUserError('filename_not_specified');
+ }
+ else {
+ ThrowUserError('file_not_specified');
+ }
+ }
+
+ # 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 MAX_ATTACH_FILENAME_LENGTH characters, counting
+ # from the end of the string to make sure we keep the filename extension.
+ $filename
+ = substr($filename, -&MAX_ATTACH_FILENAME_LENGTH, MAX_ATTACH_FILENAME_LENGTH);
+ trick_taint($filename);
+
+ return $filename;
}
sub _check_is_private {
- my ($invocant, $is_private) = @_;
-
- $is_private = $is_private ? 1 : 0;
- if (((!ref $invocant && $is_private)
- || (ref $invocant && $invocant->isprivate != $is_private))
- && !Bugzilla->user->is_insider) {
- ThrowUserError('user_not_insider');
- }
- return $is_private;
+ my ($invocant, $is_private) = @_;
+
+ $is_private = $is_private ? 1 : 0;
+ if (
+ (
+ (!ref $invocant && $is_private)
+ || (ref $invocant && $invocant->isprivate != $is_private)
+ )
+ && !Bugzilla->user->is_insider
+ )
+ {
+ ThrowUserError('user_not_insider');
+ }
+ return $is_private;
}
=pod
@@ -635,69 +648,74 @@ Returns: a reference to an array of attachment objects.
=cut
sub get_attachments_by_bug {
- my ($class, $bug, $vars) = @_;
- 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 ($class, $bug, $vars) = @_;
+ 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->new_from_list($attach_ids);
+ $_->{bug} = $bug foreach @$attachments;
+
+ # To avoid $attachment->flags and $attachment->flag_types running SQL queries
+ # themselves for each attachment listed here, we collect all the data at once and
+ # populate $attachment->{flag_types} ourselves. We also load all attachers and
+ # datasizes at once for the same reason.
+ if ($vars->{preload}) {
+
+ # Preload flag types and flags
+ my $vars = {
+ target_type => 'attachment',
+ product_id => $bug->product_id,
+ component_id => $bug->component_id,
+ attach_id => $attach_ids
+ };
+ my $flag_types = Bugzilla::Flag->_flag_types($vars);
+
+ foreach my $attachment (@$attachments) {
+ $attachment->{flag_types} = [];
+ my $new_types = dclone($flag_types);
+ foreach my $new_type (@$new_types) {
+ $new_type->{flags}
+ = [grep($_->attach_id == $attachment->id, @{$new_type->{flags}})];
+ push(@{$attachment->{flag_types}}, $new_type);
+ }
}
- my $attach_ids = $dbh->selectcol_arrayref("SELECT attach_id FROM attachments
- WHERE bug_id = ? $and_restriction",
- undef, @values);
-
- my $attachments = Bugzilla::Attachment->new_from_list($attach_ids);
- $_->{bug} = $bug foreach @$attachments;
-
- # To avoid $attachment->flags and $attachment->flag_types running SQL queries
- # themselves for each attachment listed here, we collect all the data at once and
- # populate $attachment->{flag_types} ourselves. We also load all attachers and
- # datasizes at once for the same reason.
- if ($vars->{preload}) {
- # Preload flag types and flags
- my $vars = { target_type => 'attachment',
- product_id => $bug->product_id,
- component_id => $bug->component_id,
- attach_id => $attach_ids };
- my $flag_types = Bugzilla::Flag->_flag_types($vars);
-
- foreach my $attachment (@$attachments) {
- $attachment->{flag_types} = [];
- my $new_types = dclone($flag_types);
- foreach my $new_type (@$new_types) {
- $new_type->{flags} = [ grep($_->attach_id == $attachment->id,
- @{ $new_type->{flags} }) ];
- push(@{ $attachment->{flag_types} }, $new_type);
- }
- }
-
- # Preload attachers.
- my %user_ids = map { $_->{submitter_id} => 1 } @$attachments;
- my $users = Bugzilla::User->new_from_list([keys %user_ids]);
- my %user_map = map { $_->id => $_ } @$users;
- foreach my $attachment (@$attachments) {
- $attachment->{attacher} = $user_map{$attachment->{submitter_id}};
- }
-
- # Preload datasizes.
- my $sizes =
- $dbh->selectall_hashref('SELECT attach_id, LENGTH(thedata) AS datasize
+ # Preload attachers.
+ my %user_ids = map { $_->{submitter_id} => 1 } @$attachments;
+ my $users = Bugzilla::User->new_from_list([keys %user_ids]);
+ my %user_map = map { $_->id => $_ } @$users;
+ foreach my $attachment (@$attachments) {
+ $attachment->{attacher} = $user_map{$attachment->{submitter_id}};
+ }
+
+ # Preload datasizes.
+ my $sizes = $dbh->selectall_hashref(
+ 'SELECT attach_id, LENGTH(thedata) AS datasize
FROM attachments LEFT JOIN attach_data ON attach_id = id
- WHERE bug_id = ?',
- 'attach_id', undef, $bug->id);
+ WHERE bug_id = ?', 'attach_id', undef, $bug->id
+ );
- # Force the size of attachments not in the DB to be recalculated.
- $_->{datasize} = $sizes->{$_->id}->{datasize} || undef foreach @$attachments;
- }
+ # Force the size of attachments not in the DB to be recalculated.
+ $_->{datasize} = $sizes->{$_->id}->{datasize} || undef foreach @$attachments;
+ }
- return $attachments;
+ return $attachments;
}
=pod
@@ -716,13 +734,15 @@ Returns: 1 on success, 0 otherwise.
=cut
sub validate_can_edit {
- my $attachment = shift;
- my $user = Bugzilla->user;
-
- # The submitter can edit their attachments.
- return ($attachment->attacher->id == $user->id
- || ((!$attachment->isprivate || $user->is_insider)
- && $user->in_group('editbugs', $attachment->bug->product_id))) ? 1 : 0;
+ my $attachment = shift;
+ my $user = Bugzilla->user;
+
+ # The submitter can edit their attachments.
+ return (
+ $attachment->attacher->id == $user->id
+ || ((!$attachment->isprivate || $user->is_insider)
+ && $user->in_group('editbugs', $attachment->bug->product_id))
+ ) ? 1 : 0;
}
=item C<validate_obsolete($bug, $attach_ids)>
@@ -741,37 +761,36 @@ Returns: The list of attachment objects to mark as obsolete.
=cut
sub validate_obsolete {
- my ($class, $bug, $list) = @_;
+ my ($class, $bug, $list) = @_;
- # 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 (@$list) {
- my $vars = {};
- $vars->{'attach_id'} = $attachid;
+ # 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 (@$list) {
+ my $vars = {};
+ $vars->{'attach_id'} = $attachid;
- detaint_natural($attachid)
- || ThrowUserError('invalid_attach_id', $vars);
+ detaint_natural($attachid) || ThrowUserError('invalid_attach_id', $vars);
- # Make sure the attachment exists in the database.
- my $attachment = new Bugzilla::Attachment($attachid)
- || ThrowUserError('invalid_attach_id', $vars);
+ # Make sure the attachment exists in the database.
+ my $attachment = new Bugzilla::Attachment($attachid)
+ || ThrowUserError('invalid_attach_id', $vars);
- # Check that the user can view and edit this attachment.
- $attachment->validate_can_edit
- || ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
+ # Check that the user can view and edit this attachment.
+ $attachment->validate_can_edit
+ || ThrowUserError('illegal_attachment_edit', {attach_id => $attachment->id});
- if ($attachment->bug_id != $bug->bug_id) {
- $vars->{'my_bug_id'} = $bug->bug_id;
- ThrowUserError('mismatched_bug_ids_on_obsolete', $vars);
- }
+ if ($attachment->bug_id != $bug->bug_id) {
+ $vars->{'my_bug_id'} = $bug->bug_id;
+ ThrowUserError('mismatched_bug_ids_on_obsolete', $vars);
+ }
- next if $attachment->isobsolete;
+ next if $attachment->isobsolete;
- push(@obsolete_attachments, $attachment);
- }
- return @obsolete_attachments;
+ push(@obsolete_attachments, $attachment);
+ }
+ return @obsolete_attachments;
}
###############################
@@ -806,112 +825,119 @@ Returns: The new attachment object.
=cut
sub create {
- my $class = shift;
- my $dbh = Bugzilla->dbh;
-
- $class->check_required_create_fields(@_);
- my $params = $class->run_create_validators(@_);
-
- # Extract everything which is not a valid column name.
- my $bug = delete $params->{bug};
- $params->{bug_id} = $bug->id;
- my $data = delete $params->{data};
- my $size = delete $params->{filesize};
-
- my $attachment = $class->insert_create_data($params);
- my $attachid = $attachment->id;
-
- # The file is too large to be stored in the DB, so we store it locally.
- if ($size > Bugzilla->params->{'maxattachmentsize'} * 1024) {
- my $attachdir = bz_locations()->{'attachdir'};
- my $hash = ($attachid % 100) + 100;
- $hash =~ s/.*(\d\d)$/group.$1/;
- mkdir "$attachdir/$hash", 0770;
- chmod 0770, "$attachdir/$hash";
- if (ref $data) {
- copy($data, "$attachdir/$hash/attachment.$attachid");
- close $data;
- }
- else {
- open(AH, '>', "$attachdir/$hash/attachment.$attachid");
- binmode AH;
- print AH $data;
- close AH;
- }
- $data = ''; # Will be stored in the DB.
- }
- # If we have a filehandle, we need its content to store it in the DB.
- elsif (ref $data) {
- local $/;
- # Store the content in a temp variable while we close the FH.
- my $tmp = <$data>;
- close $data;
- $data = $tmp;
- }
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare("INSERT INTO attach_data
- (id, thedata) VALUES ($attachid, ?)");
+ $class->check_required_create_fields(@_);
+ my $params = $class->run_create_validators(@_);
- trick_taint($data);
- $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
- $sth->execute();
+ # Extract everything which is not a valid column name.
+ my $bug = delete $params->{bug};
+ $params->{bug_id} = $bug->id;
+ my $data = delete $params->{data};
+ my $size = delete $params->{filesize};
- $attachment->{bug} = $bug;
+ my $attachment = $class->insert_create_data($params);
+ my $attachid = $attachment->id;
- # Return the new attachment object.
- return $attachment;
-}
+ # The file is too large to be stored in the DB, so we store it locally.
+ if ($size > Bugzilla->params->{'maxattachmentsize'} * 1024) {
+ my $attachdir = bz_locations()->{'attachdir'};
+ my $hash = ($attachid % 100) + 100;
+ $hash =~ s/.*(\d\d)$/group.$1/;
+ mkdir "$attachdir/$hash", 0770;
+ chmod 0770, "$attachdir/$hash";
+ if (ref $data) {
+ copy($data, "$attachdir/$hash/attachment.$attachid");
+ close $data;
+ }
+ else {
+ open(AH, '>', "$attachdir/$hash/attachment.$attachid");
+ binmode AH;
+ print AH $data;
+ close AH;
+ }
+ $data = ''; # Will be stored in the DB.
+ }
-sub run_create_validators {
- my ($class, $params) = @_;
+ # If we have a filehandle, we need its content to store it in the DB.
+ elsif (ref $data) {
+ local $/;
+
+ # Store the content in a temp variable while we close the FH.
+ my $tmp = <$data>;
+ close $data;
+ $data = $tmp;
+ }
- $params->{submitter_id} = Bugzilla->user->id || ThrowUserError('invalid_user');
+ my $sth = $dbh->prepare(
+ "INSERT INTO attach_data
+ (id, thedata) VALUES ($attachid, ?)"
+ );
- # Let's validate the attachment content first as it may
- # alter some other attachment attributes.
- $params->{data} = $class->_check_data($params);
- $params = $class->SUPER::run_create_validators($params);
+ trick_taint($data);
+ $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
+ $sth->execute();
- $params->{creation_ts} ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- $params->{modification_time} = $params->{creation_ts};
+ $attachment->{bug} = $bug;
- return $params;
+ # Return the new attachment object.
+ return $attachment;
}
-sub update {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
- my $timestamp = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+sub run_create_validators {
+ my ($class, $params) = @_;
- my ($changes, $old_self) = $self->SUPER::update(@_);
+ $params->{submitter_id} = Bugzilla->user->id || ThrowUserError('invalid_user');
- my ($removed, $added) = Bugzilla::Flag->update_flags($self, $old_self, $timestamp);
- if ($removed || $added) {
- $changes->{'flagtypes.name'} = [$removed, $added];
- }
+ # Let's validate the attachment content first as it may
+ # alter some other attachment attributes.
+ $params->{data} = $class->_check_data($params);
+ $params = $class->SUPER::run_create_validators($params);
- # Record changes in the activity table.
- require Bugzilla::Bug;
- foreach my $field (keys %$changes) {
- my $change = $changes->{$field};
- $field = "attachments.$field" unless $field eq "flagtypes.name";
- Bugzilla::Bug::LogActivityEntry($self->bug_id, $field, $change->[0],
- $change->[1], $user->id, $timestamp, undef, $self->id);
- }
+ $params->{creation_ts}
+ ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $params->{modification_time} = $params->{creation_ts};
- if (scalar(keys %$changes)) {
- $dbh->do('UPDATE attachments SET modification_time = ? WHERE attach_id = ?',
- undef, ($timestamp, $self->id));
- $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
- undef, ($timestamp, $self->bug_id));
- $self->{modification_time} = $timestamp;
- # because we updated the attachments table after SUPER::update(), we
- # need to ensure the cache is flushed.
- Bugzilla->memcached->clear({ table => 'attachments', id => $self->id });
- }
+ return $params;
+}
- return $changes;
+sub update {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ my $timestamp = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ my ($changes, $old_self) = $self->SUPER::update(@_);
+
+ my ($removed, $added)
+ = Bugzilla::Flag->update_flags($self, $old_self, $timestamp);
+ if ($removed || $added) {
+ $changes->{'flagtypes.name'} = [$removed, $added];
+ }
+
+ # Record changes in the activity table.
+ require Bugzilla::Bug;
+ foreach my $field (keys %$changes) {
+ my $change = $changes->{$field};
+ $field = "attachments.$field" unless $field eq "flagtypes.name";
+ Bugzilla::Bug::LogActivityEntry($self->bug_id, $field, $change->[0],
+ $change->[1], $user->id, $timestamp, undef, $self->id);
+ }
+
+ if (scalar(keys %$changes)) {
+ $dbh->do('UPDATE attachments SET modification_time = ? WHERE attach_id = ?',
+ undef, ($timestamp, $self->id));
+ $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+ undef, ($timestamp, $self->bug_id));
+ $self->{modification_time} = $timestamp;
+
+ # because we updated the attachments table after SUPER::update(), we
+ # need to ensure the cache is flushed.
+ Bugzilla->memcached->clear({table => 'attachments', id => $self->id});
+ }
+
+ return $changes;
}
=pod
@@ -929,30 +955,33 @@ Returns: nothing
=cut
sub remove_from_db {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- $dbh->bz_start_transaction();
- my $flag_ids = $dbh->selectcol_arrayref(
- 'SELECT id FROM flags WHERE attach_id = ?', undef, $self->id);
- $dbh->do('DELETE FROM flags WHERE ' . $dbh->sql_in('id', $flag_ids))
- if @$flag_ids;
- $dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $self->id);
- $dbh->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isobsolete = ?
- WHERE attach_id = ?', undef, ('text/plain', 0, 1, $self->id));
- $dbh->bz_commit_transaction();
-
- my $filename = $self->_get_local_filename;
- if (-e $filename) {
- unlink $filename or warn "Couldn't unlink $filename: $!";
- }
-
- # As we don't call SUPER->remove_from_db we need to manually clear
- # memcached here.
- Bugzilla->memcached->clear({ table => 'attachments', id => $self->id });
- foreach my $flag_id (@$flag_ids) {
- Bugzilla->memcached->clear({ table => 'flags', id => $flag_id });
- }
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+ my $flag_ids
+ = $dbh->selectcol_arrayref('SELECT id FROM flags WHERE attach_id = ?',
+ undef, $self->id);
+ $dbh->do('DELETE FROM flags WHERE ' . $dbh->sql_in('id', $flag_ids))
+ if @$flag_ids;
+ $dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $self->id);
+ $dbh->do(
+ 'UPDATE attachments SET mimetype = ?, ispatch = ?, isobsolete = ?
+ WHERE attach_id = ?', undef, ('text/plain', 0, 1, $self->id)
+ );
+ $dbh->bz_commit_transaction();
+
+ my $filename = $self->_get_local_filename;
+ if (-e $filename) {
+ unlink $filename or warn "Couldn't unlink $filename: $!";
+ }
+
+ # As we don't call SUPER->remove_from_db we need to manually clear
+ # memcached here.
+ Bugzilla->memcached->clear({table => 'attachments', id => $self->id});
+ foreach my $flag_id (@$flag_ids) {
+ Bugzilla->memcached->clear({table => 'flags', id => $flag_id});
+ }
}
###############################
@@ -961,37 +990,39 @@ sub remove_from_db {
# Extract the content type from the attachment form.
sub get_content_type {
- my $cgi = Bugzilla->cgi;
+ my $cgi = Bugzilla->cgi;
- return 'text/plain' if ($cgi->param('ispatch') || $cgi->param('attach_text'));
+ return 'text/plain' if ($cgi->param('ispatch') || $cgi->param('attach_text'));
- my $content_type;
- my $method = $cgi->param('contenttypemethod') || '';
+ my $content_type;
+ my $method = $cgi->param('contenttypemethod') || '';
- if ($method eq 'list') {
- # The user selected a content type from the list, so use their
- # selection.
- $content_type = $cgi->param('contenttypeselection');
- }
- elsif ($method eq 'manual') {
- # The user entered a content type manually, so use their entry.
- $content_type = $cgi->param('contenttypeentry');
- }
- else {
- defined $cgi->upload('data') || ThrowUserError('file_not_specified');
- # The user asked us to auto-detect the content type, so use the type
- # specified in the HTTP request headers.
- $content_type =
- $cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
- $content_type || ThrowUserError("missing_content_type");
-
- # Internet Explorer sends image/x-png for PNG images,
- # so convert that to image/png to match other browsers.
- if ($content_type eq 'image/x-png') {
- $content_type = 'image/png';
- }
+ if ($method eq 'list') {
+
+ # The user selected a content type from the list, so use their
+ # selection.
+ $content_type = $cgi->param('contenttypeselection');
+ }
+ elsif ($method eq 'manual') {
+
+ # The user entered a content type manually, so use their entry.
+ $content_type = $cgi->param('contenttypeentry');
+ }
+ else {
+ defined $cgi->upload('data') || ThrowUserError('file_not_specified');
+
+ # The user asked us to auto-detect the content type, so use the type
+ # specified in the HTTP request headers.
+ $content_type = $cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
+ $content_type || ThrowUserError("missing_content_type");
+
+ # Internet Explorer sends image/x-png for PNG images,
+ # so convert that to image/png to match other browsers.
+ if ($content_type eq 'image/x-png') {
+ $content_type = 'image/png';
}
- return $content_type;
+ }
+ return $content_type;
}
diff --git a/Bugzilla/Attachment/PatchReader.pm b/Bugzilla/Attachment/PatchReader.pm
index d0e221220..01ed51518 100644
--- a/Bugzilla/Attachment/PatchReader.pm
+++ b/Bugzilla/Attachment/PatchReader.pm
@@ -23,184 +23,199 @@ use Bugzilla::Util;
use constant PERLIO_IS_ENABLED => $Config{useperlio};
sub process_diff {
- my ($attachment, $format) = @_;
- my $dbh = Bugzilla->dbh;
- my $cgi = Bugzilla->cgi;
- my $lc = Bugzilla->localconfig;
- my $vars = {};
-
- require PatchReader::Raw;
- my $reader = new PatchReader::Raw;
-
- if ($format eq 'raw') {
- require PatchReader::DiffPrinter::raw;
- $reader->sends_data_to(new PatchReader::DiffPrinter::raw());
- # Actually print out the patch.
- print $cgi->header(-type => 'text/plain');
- 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)};
- # 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;
- }
- }
- }
+ my ($attachment, $format) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $cgi = Bugzilla->cgi;
+ my $lc = Bugzilla->localconfig;
+ my $vars = {};
- $vars->{'bugid'} = $attachment->bug_id;
- $vars->{'attachid'} = $attachment->id;
- $vars->{'description'} = $attachment->description;
- $vars->{'other_patches'} = \@other_patches;
-
- setup_template_patch_reader($reader, $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) = @_;
- my $cgi = Bugzilla->cgi;
- my $lc = Bugzilla->localconfig;
- my $vars = {};
-
- require PatchReader::Raw;
-
- # 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.
- local $ENV{'PATH'} = $lc->{diffpath};
-
- # Open the interdiff pipe, reading from both STDOUT and STDERR
- # To avoid deadlocks, we have to read the entire output from all handles
- my ($stdout, $stderr) = ('', '');
- my ($pid, $interdiff_stdout, $interdiff_stderr, $use_select);
- if ($ENV{MOD_PERL}) {
- require Apache2::RequestUtil;
- require Apache2::SubProcess;
- my $request = Apache2::RequestUtil->request;
- (undef, $interdiff_stdout, $interdiff_stderr) = $request->spawn_proc_prog(
- $lc->{interdiffbin}, [$old_filename, $new_filename]
- );
- $use_select = !PERLIO_IS_ENABLED;
- } else {
- $interdiff_stderr = gensym;
- $pid = open3(gensym, $interdiff_stdout, $interdiff_stderr,
- $lc->{interdiffbin}, $old_filename, $new_filename);
- $use_select = 1;
- }
+ require PatchReader::Raw;
+ my $reader = new PatchReader::Raw;
- if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
- binmode $interdiff_stdout, ':utf8';
- binmode $interdiff_stderr, ':utf8';
- } else {
- binmode $interdiff_stdout;
- binmode $interdiff_stderr;
- }
-
- if ($use_select) {
- my $select = IO::Select->new();
- $select->add($interdiff_stdout, $interdiff_stderr);
- while (my @handles = $select->can_read) {
- foreach my $handle (@handles) {
- my $line = <$handle>;
- if (!defined $line) {
- $select->remove($handle);
- next;
- }
- if ($handle == $interdiff_stdout) {
- $stdout .= $line;
- } else {
- $stderr .= $line;
- }
+ if ($format eq 'raw') {
+ require PatchReader::DiffPrinter::raw;
+ $reader->sends_data_to(new PatchReader::DiffPrinter::raw());
+
+ # Actually print out the patch.
+ print $cgi->header(-type => 'text/plain');
+ 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)};
+
+ # 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;
}
- waitpid($pid, 0) if $pid;
-
- } else {
- local $/ = undef;
- $stdout = <$interdiff_stdout>;
- $stdout //= '';
- $stderr = <$interdiff_stderr>;
- $stderr //= '';
+ }
}
- close($interdiff_stdout),
- close($interdiff_stderr);
+ $vars->{'bugid'} = $attachment->bug_id;
+ $vars->{'attachid'} = $attachment->id;
+ $vars->{'description'} = $attachment->description;
+ $vars->{'other_patches'} = \@other_patches;
- # Tidy up
- unlink($old_filename) or warn "Could not unlink $old_filename: $!";
- unlink($new_filename) or warn "Could not unlink $new_filename: $!";
+ setup_template_patch_reader($reader, $vars);
- # Any output on STDERR means interdiff failed to full process the patches.
- # Interdiff's error messages are generic and not useful to end users, so we
- # show a generic failure message.
- if ($stderr) {
- warn($stderr);
- $warning = 'interdiff3';
+ # 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);
+ }
+}
- my $reader = new PatchReader::Raw;
-
- if ($format eq 'raw') {
- require PatchReader::DiffPrinter::raw;
- $reader->sends_data_to(new PatchReader::DiffPrinter::raw());
- # Actually print out the patch.
- print $cgi->header(-type => 'text/plain');
- disable_utf8();
- }
- else {
- $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($reader, $vars);
+sub process_interdiff {
+ my ($old_attachment, $new_attachment, $format) = @_;
+ my $cgi = Bugzilla->cgi;
+ my $lc = Bugzilla->localconfig;
+ my $vars = {};
+
+ require PatchReader::Raw;
+
+ # 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.
+ local $ENV{'PATH'} = $lc->{diffpath};
+
+ # Open the interdiff pipe, reading from both STDOUT and STDERR
+ # To avoid deadlocks, we have to read the entire output from all handles
+ my ($stdout, $stderr) = ('', '');
+ my ($pid, $interdiff_stdout, $interdiff_stderr, $use_select);
+ if ($ENV{MOD_PERL}) {
+ require Apache2::RequestUtil;
+ require Apache2::SubProcess;
+ my $request = Apache2::RequestUtil->request;
+ (undef, $interdiff_stdout, $interdiff_stderr)
+ = $request->spawn_proc_prog($lc->{interdiffbin},
+ [$old_filename, $new_filename]);
+ $use_select = !PERLIO_IS_ENABLED;
+ }
+ else {
+ $interdiff_stderr = gensym;
+ $pid = open3(gensym, $interdiff_stdout, $interdiff_stderr, $lc->{interdiffbin},
+ $old_filename, $new_filename);
+ $use_select = 1;
+ }
+
+ if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
+ binmode $interdiff_stdout, ':utf8';
+ binmode $interdiff_stderr, ':utf8';
+ }
+ else {
+ binmode $interdiff_stdout;
+ binmode $interdiff_stderr;
+ }
+
+ if ($use_select) {
+ my $select = IO::Select->new();
+ $select->add($interdiff_stdout, $interdiff_stderr);
+ while (my @handles = $select->can_read) {
+ foreach my $handle (@handles) {
+ my $line = <$handle>;
+ if (!defined $line) {
+ $select->remove($handle);
+ next;
+ }
+ if ($handle == $interdiff_stdout) {
+ $stdout .= $line;
+ }
+ else {
+ $stderr .= $line;
+ }
+ }
}
- $reader->iterate_string('interdiff #' . $old_attachment->id .
- ' #' . $new_attachment->id, $stdout);
+ waitpid($pid, 0) if $pid;
+
+ }
+ else {
+ local $/ = undef;
+ $stdout = <$interdiff_stdout>;
+ $stdout //= '';
+ $stderr = <$interdiff_stderr>;
+ $stderr //= '';
+ }
+
+ close($interdiff_stdout), close($interdiff_stderr);
+
+ # Tidy up
+ unlink($old_filename) or warn "Could not unlink $old_filename: $!";
+ unlink($new_filename) or warn "Could not unlink $new_filename: $!";
+
+ # Any output on STDERR means interdiff failed to full process the patches.
+ # Interdiff's error messages are generic and not useful to end users, so we
+ # show a generic failure message.
+ if ($stderr) {
+ warn($stderr);
+ $warning = 'interdiff3';
+ }
+
+ my $reader = new PatchReader::Raw;
+
+ if ($format eq 'raw') {
+ require PatchReader::DiffPrinter::raw;
+ $reader->sends_data_to(new PatchReader::DiffPrinter::raw());
+
+ # Actually print out the patch.
+ print $cgi->header(-type => 'text/plain');
+ disable_utf8();
+ }
+ else {
+ $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($reader, $vars);
+ }
+ $reader->iterate_string(
+ 'interdiff #' . $old_attachment->id . ' #' . $new_attachment->id, $stdout);
}
######################
@@ -208,92 +223,92 @@ sub process_interdiff {
######################
sub get_unified_diff {
- my ($attachment, $format) = @_;
+ my ($attachment, $format) = @_;
- # Bring in the modules we need.
- require PatchReader::Raw;
- 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;
-
- # 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;
+ # Bring in the modules we need.
+ require PatchReader::Raw;
+ require PatchReader::DiffPrinter::raw;
+ require PatchReader::PatchInfoGrabber;
+ require File::Temp;
- # Iterate!
- $reader->iterate_string($attachment->id, $attachment->data);
+ $attachment->ispatch
+ || ThrowCodeError('must_be_patch', {'attach_id' => $attachment->id});
- return ($filename, $patch_info_grabber->patch_info()->{files});
-}
+ # Reads in the patch, converting to unified diff in a temp file.
+ my $reader = new PatchReader::Raw;
+ my $last_reader = $reader;
-sub warn_if_interdiff_might_fail {
- my ($old_file_list, $new_file_list) = @_;
+ # 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;
- # 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';
- }
+ # Prints out to temporary file.
+ my ($fh, $filename) = File::Temp::tempfile();
+ if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
- # Verify that the revisions in the files are the same.
- foreach my $file (keys %{$old_file_list}) {
- if (exists $old_file_list->{$file}{old_revision}
- && exists $new_file_list->{$file}{old_revision}
- && $old_file_list->{$file}{old_revision} ne
- $new_file_list->{$file}{old_revision})
- {
- return 'interdiff2';
- }
- }
- return undef;
-}
+ # 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;
-sub setup_template_patch_reader {
- my ($last_reader, $vars) = @_;
- my $cgi = Bugzilla->cgi;
- my $template = Bugzilla->template;
+ # Iterate!
+ $reader->iterate_string($attachment->id, $attachment->data);
- require PatchReader::DiffPrinter::template;
+ return ($filename, $patch_info_grabber->patch_info()->{files});
+}
- # Define the vars for templates.
- if (defined $cgi->param('headers')) {
- $vars->{'headers'} = $cgi->param('headers');
- }
- else {
- $vars->{'headers'} = 1;
+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 ( exists $old_file_list->{$file}{old_revision}
+ && exists $new_file_list->{$file}{old_revision}
+ && $old_file_list->{$file}{old_revision} ne
+ $new_file_list->{$file}{old_revision})
+ {
+ return 'interdiff2';
}
+ }
+ return undef;
+}
- $vars->{'collapsed'} = $cgi->param('collapsed');
-
- # Print everything out.
- print $cgi->header(-type => 'text/html');
-
- $last_reader->sends_data_to(new PatchReader::DiffPrinter::template($template,
- 'attachment/diff-header.html.tmpl',
- 'attachment/diff-file.html.tmpl',
- 'attachment/diff-footer.html.tmpl',
- $vars));
+sub setup_template_patch_reader {
+ my ($last_reader, $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');
+
+ # Print everything out.
+ print $cgi->header(-type => 'text/html');
+
+ $last_reader->sends_data_to(new PatchReader::DiffPrinter::template(
+ $template, 'attachment/diff-header.html.tmpl',
+ 'attachment/diff-file.html.tmpl', 'attachment/diff-footer.html.tmpl',
+ $vars
+ ));
}
1;
diff --git a/Bugzilla/Auth.pm b/Bugzilla/Auth.pm
index c830f0506..23de9b4bd 100644
--- a/Bugzilla/Auth.pm
+++ b/Bugzilla/Auth.pm
@@ -12,9 +12,9 @@ use strict;
use warnings;
use fields qw(
- _info_getter
- _verifier
- _persister
+ _info_getter
+ _verifier
+ _persister
);
use Bugzilla::Constants;
@@ -28,218 +28,223 @@ use Bugzilla::Auth::Persist::Cookie;
use Socket;
sub new {
- my ($class, $params) = @_;
- my $self = fields::new($class);
+ my ($class, $params) = @_;
+ my $self = fields::new($class);
- $params ||= {};
- $params->{Login} ||= Bugzilla->params->{'user_info_class'} . ',Cookie,APIKey';
- $params->{Verify} ||= Bugzilla->params->{'user_verify_class'};
+ $params ||= {};
+ $params->{Login} ||= Bugzilla->params->{'user_info_class'} . ',Cookie,APIKey';
+ $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();
+ $self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login});
+ $self->{_verifier} = new Bugzilla::Auth::Verify::Stack($params->{Verify});
- return $self;
+ # 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) = @_;
-
- # Get login info from the cookie, form, environment variables, etc.
- my $login_info = $self->{_info_getter}->get_login_info();
+ my ($self, $type) = @_;
- if ($login_info->{failure}) {
- return $self->_handle_login_result($login_info, $type);
- }
+ # Get login info from the cookie, form, environment variables, etc.
+ my $login_info = $self->{_info_getter}->get_login_info();
- # Now verify their 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);
+ }
+ # Now verify their 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);
+ 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->is_enabled) {
- return $self->_handle_login_result({ failure => AUTH_DISABLED,
- user => $user }, $type);
- }
- $user->set_authorizer($self);
+ # Make sure the user isn't disabled.
+ my $user = $login_info->{user};
+ if (!$user->is_enabled) {
+ return $self->_handle_login_result({failure => AUTH_DISABLED, user => $user},
+ $type);
+ }
+ $user->set_authorizer($self);
- return $self->_handle_login_result($login_info, $type);
+ 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;
+ 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;
+ 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;
+ 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 login_token {
- my ($self) = @_;
- my $getter = $self->{_info_getter}->{successful};
- if ($getter && $getter->isa('Bugzilla::Auth::Login::Cookie')) {
- return $getter->login_token;
- }
- return undef;
+ my ($self) = @_;
+ my $getter = $self->{_info_getter}->{successful};
+ if ($getter && $getter->isa('Bugzilla::Auth::Login::Cookie')) {
+ return $getter->login_token;
+ }
+ return undef;
}
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;
+ 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 extern_id_used {
- my ($self) = @_;
- return $self->{_info_getter}->extern_id_used
- || $self->{_verifier}->extern_id_used;
+ my ($self) = @_;
+ return $self->{_info_getter}->extern_id_used
+ || $self->{_verifier}->extern_id_used;
}
sub can_change_email {
- return $_[0]->user_can_create_account;
+ 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) {
- # We don't persist logins over GET requests in the WebService,
- # because the persistance information can't be re-used again.
- # (See Bugzilla::WebService::Server::JSONRPC for more info.)
- if ($self->{_info_getter}->{successful}->requires_persistence
- and !Bugzilla->request_cache->{auth_no_automatic_login})
- {
- $user->{_login_token} = $self->{_persister}->persist_login($user);
- }
- }
- elsif ($fail_code == AUTH_ERROR) {
- if ($result->{user_error}) {
- ThrowUserError($result->{user_error}, $result->{details});
- }
- else {
- ThrowCodeError($result->{error}, $result->{details});
- }
- }
- elsif ($fail_code == AUTH_NODATA) {
- $self->{_info_getter}->fail_nodata($self)
- if $login_type == LOGIN_REQUIRED;
+ my ($self, $result, $login_type) = @_;
+ my $dbh = Bugzilla->dbh;
- # If we're not LOGIN_REQUIRED, 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 or $fail_code == AUTH_NO_SUCH_USER) {
- my $remaining_attempts = MAX_LOGIN_ATTEMPTS
- - ($result->{failure_count} || 0);
- ThrowUserError("invalid_login_or_password",
- { remaining => $remaining_attempts });
- }
- # 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});
+ my $user = $result->{user};
+ my $fail_code = $result->{failure};
+
+ if (!$fail_code) {
+
+ # We don't persist logins over GET requests in the WebService,
+ # because the persistance information can't be re-used again.
+ # (See Bugzilla::WebService::Server::JSONRPC for more info.)
+ if ($self->{_info_getter}->{successful}->requires_persistence
+ and !Bugzilla->request_cache->{auth_no_automatic_login})
+ {
+ $user->{_login_token} = $self->{_persister}->persist_login($user);
}
- elsif ($fail_code == AUTH_LOCKOUT) {
- my $attempts = $user->account_ip_login_failures;
-
- # We want to know when the account will be unlocked. This is
- # determined by the 5th-from-last login failure (or more/less than
- # 5th, if MAX_LOGIN_ATTEMPTS is not 5).
- my $determiner = $attempts->[scalar(@$attempts) - MAX_LOGIN_ATTEMPTS];
- my $unlock_at = datetime_from($determiner->{login_time},
- Bugzilla->local_timezone);
- $unlock_at->add(minutes => LOGIN_LOCKOUT_INTERVAL);
-
- # If we were *just* locked out, notify the maintainer about the
- # lockout.
- if ($result->{just_locked_out}) {
- # We're sending to the maintainer, who may be not a Bugzilla
- # account, but just an email address. So we use the
- # installation's default language for sending the email.
- my $default_settings = Bugzilla::User::Setting::get_defaults();
- my $template = Bugzilla->template_inner(
- $default_settings->{lang}->{default_value});
- my $address = $attempts->[0]->{ip_addr};
- # Note: inet_aton will only resolve IPv4 addresses.
- # For IPv6 we'll need to use inet_pton which requires Perl 5.12.
- my $n = inet_aton($address);
- if ($n) {
- $address = gethostbyaddr($n, AF_INET) . " ($address)"
- }
- my $vars = {
- locked_user => $user,
- attempts => $attempts,
- unlock_at => $unlock_at,
- address => $address,
- };
- my $message;
- $template->process('email/lockout.txt.tmpl', $vars, \$message)
- || ThrowTemplateError($template->error);
- MessageToMTA($message);
- }
-
- $unlock_at->set_time_zone($user->timezone);
- ThrowUserError('account_locked',
- { ip_addr => $determiner->{ip_addr}, unlock_at => $unlock_at });
+ }
+ elsif ($fail_code == AUTH_ERROR) {
+ if ($result->{user_error}) {
+ ThrowUserError($result->{user_error}, $result->{details});
}
- # If we get here, then we've run out of options, which shouldn't happen.
else {
- ThrowCodeError("authres_unhandled", { value => $fail_code });
+ ThrowCodeError($result->{error}, $result->{details});
+ }
+ }
+ elsif ($fail_code == AUTH_NODATA) {
+ $self->{_info_getter}->fail_nodata($self) if $login_type == LOGIN_REQUIRED;
+
+ # If we're not LOGIN_REQUIRED, 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 or $fail_code == AUTH_NO_SUCH_USER) {
+ my $remaining_attempts = MAX_LOGIN_ATTEMPTS - ($result->{failure_count} || 0);
+ ThrowUserError("invalid_login_or_password", {remaining => $remaining_attempts});
+ }
+
+ # 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});
+ }
+ elsif ($fail_code == AUTH_LOCKOUT) {
+ my $attempts = $user->account_ip_login_failures;
+
+ # We want to know when the account will be unlocked. This is
+ # determined by the 5th-from-last login failure (or more/less than
+ # 5th, if MAX_LOGIN_ATTEMPTS is not 5).
+ my $determiner = $attempts->[scalar(@$attempts) - MAX_LOGIN_ATTEMPTS];
+ my $unlock_at
+ = datetime_from($determiner->{login_time}, Bugzilla->local_timezone);
+ $unlock_at->add(minutes => LOGIN_LOCKOUT_INTERVAL);
+
+ # If we were *just* locked out, notify the maintainer about the
+ # lockout.
+ if ($result->{just_locked_out}) {
+
+ # We're sending to the maintainer, who may be not a Bugzilla
+ # account, but just an email address. So we use the
+ # installation's default language for sending the email.
+ my $default_settings = Bugzilla::User::Setting::get_defaults();
+ my $template
+ = Bugzilla->template_inner($default_settings->{lang}->{default_value});
+ my $address = $attempts->[0]->{ip_addr};
+
+ # Note: inet_aton will only resolve IPv4 addresses.
+ # For IPv6 we'll need to use inet_pton which requires Perl 5.12.
+ my $n = inet_aton($address);
+ if ($n) {
+ $address = gethostbyaddr($n, AF_INET) . " ($address)";
+ }
+ my $vars = {
+ locked_user => $user,
+ attempts => $attempts,
+ unlock_at => $unlock_at,
+ address => $address,
+ };
+ my $message;
+ $template->process('email/lockout.txt.tmpl', $vars, \$message)
+ || ThrowTemplateError($template->error);
+ MessageToMTA($message);
}
- return $user;
+ $unlock_at->set_time_zone($user->timezone);
+ ThrowUserError('account_locked',
+ {ip_addr => $determiner->{ip_addr}, unlock_at => $unlock_at});
+ }
+
+ # 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;
diff --git a/Bugzilla/Auth/Login.pm b/Bugzilla/Auth/Login.pm
index a5f089777..68c7464f2 100644
--- a/Bugzilla/Auth/Login.pm
+++ b/Bugzilla/Auth/Login.pm
@@ -16,18 +16,18 @@ 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 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;
-use constant is_automatic => 0;
-use constant extern_id_used => 0;
+use constant is_automatic => 0;
+use constant extern_id_used => 0;
sub new {
- my ($class) = @_;
- my $self = fields::new($class);
- return $self;
+ my ($class) = @_;
+ my $self = fields::new($class);
+ return $self;
}
1;
diff --git a/Bugzilla/Auth/Login/APIKey.pm b/Bugzilla/Auth/Login/APIKey.pm
index 63e35578a..79c16825e 100644
--- a/Bugzilla/Auth/Login/APIKey.pm
+++ b/Bugzilla/Auth/Login/APIKey.pm
@@ -26,28 +26,29 @@ use constant can_logout => 0;
# This method is only available to web services. An API key can never
# be used to authenticate a Web request.
sub get_login_info {
- my ($self) = @_;
- my $params = Bugzilla->input_params;
- my ($user_id, $login_cookie);
+ my ($self) = @_;
+ my $params = Bugzilla->input_params;
+ my ($user_id, $login_cookie);
- my $api_key_text = trim(delete $params->{'Bugzilla_api_key'});
- if (!i_am_webservice() || !$api_key_text) {
- return { failure => AUTH_NODATA };
- }
+ my $api_key_text = trim(delete $params->{'Bugzilla_api_key'});
+ if (!i_am_webservice() || !$api_key_text) {
+ return {failure => AUTH_NODATA};
+ }
- my $api_key = Bugzilla::User::APIKey->new({ name => $api_key_text });
+ my $api_key = Bugzilla::User::APIKey->new({name => $api_key_text});
- if (!$api_key or $api_key->api_key ne $api_key_text) {
- # The second part checks the correct capitalisation. Silly MySQL
- ThrowUserError("api_key_not_valid");
- }
- elsif ($api_key->revoked) {
- ThrowUserError('api_key_revoked');
- }
+ if (!$api_key or $api_key->api_key ne $api_key_text) {
- $api_key->update_last_used();
+ # The second part checks the correct capitalisation. Silly MySQL
+ ThrowUserError("api_key_not_valid");
+ }
+ elsif ($api_key->revoked) {
+ ThrowUserError('api_key_revoked');
+ }
- return { user_id => $api_key->user_id };
+ $api_key->update_last_used();
+
+ return {user_id => $api_key->user_id};
}
1;
diff --git a/Bugzilla/Auth/Login/CGI.pm b/Bugzilla/Auth/Login/CGI.pm
index 6003d62a5..0824f1ebd 100644
--- a/Bugzilla/Auth/Login/CGI.pm
+++ b/Bugzilla/Auth/Login/CGI.pm
@@ -21,65 +21,71 @@ use Bugzilla::Error;
use Bugzilla::Token;
sub get_login_info {
- my ($self) = @_;
- my $params = Bugzilla->input_params;
- my $cgi = Bugzilla->cgi;
-
- my $login = trim(delete $params->{'Bugzilla_login'});
- my $password = delete $params->{'Bugzilla_password'};
- # The token must match the cookie to authenticate the request.
- my $login_token = delete $params->{'Bugzilla_login_token'};
- my $login_cookie = $cgi->cookie('Bugzilla_login_request_cookie');
-
- my $valid = 0;
- # If the web browser accepts cookies, use them.
- if ($login_token && $login_cookie) {
- my ($time, undef) = split(/-/, $login_token);
- # Regenerate the token based on the information we have.
- my $expected_token = issue_hash_token(['login_request', $login_cookie], $time);
- $valid = 1 if $expected_token eq $login_token;
- $cgi->remove_cookie('Bugzilla_login_request_cookie');
- }
- # WebServices and other local scripts can bypass this check.
- # This is safe because we won't store a login cookie in this case.
- elsif (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
- $valid = 1;
- }
- # Else falls back to the Referer header and accept local URLs.
- # Attachments are served from a separate host (ideally), and so
- # an evil attachment cannot abuse this check with a redirect.
- elsif (my $referer = $cgi->referer) {
- my $urlbase = correct_urlbase();
- $valid = 1 if $referer =~ /^\Q$urlbase\E/;
- }
- # If the web browser doesn't accept cookies and the Referer header
- # is missing, we have no way to make sure that the authentication
- # request comes from the user.
- elsif ($login && $password) {
- ThrowUserError('auth_untrusted_request', { login => $login });
- }
-
- if (!defined($login) || !defined($password) || !$valid) {
- return { failure => AUTH_NODATA };
- }
-
- return { username => $login, password => $password };
+ my ($self) = @_;
+ my $params = Bugzilla->input_params;
+ my $cgi = Bugzilla->cgi;
+
+ my $login = trim(delete $params->{'Bugzilla_login'});
+ my $password = delete $params->{'Bugzilla_password'};
+
+ # The token must match the cookie to authenticate the request.
+ my $login_token = delete $params->{'Bugzilla_login_token'};
+ my $login_cookie = $cgi->cookie('Bugzilla_login_request_cookie');
+
+ my $valid = 0;
+
+ # If the web browser accepts cookies, use them.
+ if ($login_token && $login_cookie) {
+ my ($time, undef) = split(/-/, $login_token);
+
+ # Regenerate the token based on the information we have.
+ my $expected_token = issue_hash_token(['login_request', $login_cookie], $time);
+ $valid = 1 if $expected_token eq $login_token;
+ $cgi->remove_cookie('Bugzilla_login_request_cookie');
+ }
+
+ # WebServices and other local scripts can bypass this check.
+ # This is safe because we won't store a login cookie in this case.
+ elsif (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
+ $valid = 1;
+ }
+
+ # Else falls back to the Referer header and accept local URLs.
+ # Attachments are served from a separate host (ideally), and so
+ # an evil attachment cannot abuse this check with a redirect.
+ elsif (my $referer = $cgi->referer) {
+ my $urlbase = correct_urlbase();
+ $valid = 1 if $referer =~ /^\Q$urlbase\E/;
+ }
+
+ # If the web browser doesn't accept cookies and the Referer header
+ # is missing, we have no way to make sure that the authentication
+ # request comes from the user.
+ elsif ($login && $password) {
+ ThrowUserError('auth_untrusted_request', {login => $login});
+ }
+
+ if (!defined($login) || !defined($password) || !$valid) {
+ return {failure => AUTH_NODATA};
+ }
+
+ return {username => $login, password => $password};
}
sub fail_nodata {
- my ($self) = @_;
- my $cgi = Bugzilla->cgi;
- my $template = Bugzilla->template;
-
- if (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
- ThrowUserError('login_required');
- }
-
- print $cgi->header();
- $template->process("account/auth/login.html.tmpl",
- { 'target' => $cgi->url(-relative=>1) })
- || ThrowTemplateError($template->error());
- exit;
+ my ($self) = @_;
+ my $cgi = Bugzilla->cgi;
+ my $template = Bugzilla->template;
+
+ if (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
+ ThrowUserError('login_required');
+ }
+
+ print $cgi->header();
+ $template->process("account/auth/login.html.tmpl",
+ {'target' => $cgi->url(-relative => 1)})
+ || ThrowTemplateError($template->error());
+ exit;
}
1;
diff --git a/Bugzilla/Auth/Login/Cookie.pm b/Bugzilla/Auth/Login/Cookie.pm
index c09f08d24..1983dbd4c 100644
--- a/Bugzilla/Auth/Login/Cookie.pm
+++ b/Bugzilla/Auth/Login/Cookie.pm
@@ -23,121 +23,124 @@ use List::Util qw(first);
use constant requires_persistence => 0;
use constant requires_verification => 0;
-use constant can_login => 0;
+use constant can_login => 0;
sub is_automatic { return $_[0]->login_token ? 0 : 1; }
# 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 ($user_id, $login_cookie);
-
- if (!Bugzilla->request_cache->{auth_no_automatic_login}) {
- $login_cookie = $cgi->cookie("Bugzilla_logincookie");
- $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 the call is for a web service, and an api token is provided, check
- # it is valid.
- if (i_am_webservice() && Bugzilla->input_params->{Bugzilla_api_token}) {
- my $api_token = Bugzilla->input_params->{Bugzilla_api_token};
- my ($token_user_id, undef, undef, $token_type)
- = Bugzilla::Token::GetTokenData($api_token);
- if (!defined $token_type
- || $token_type ne 'api_token'
- || $user_id != $token_user_id)
- {
- ThrowUserError('auth_invalid_token', { token => $api_token });
- }
- }
+ my ($self) = @_;
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+ my ($user_id, $login_cookie);
+
+ if (!Bugzilla->request_cache->{auth_no_automatic_login}) {
+ $login_cookie = $cgi->cookie("Bugzilla_logincookie");
+ $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 no cookies were provided, we also look for a login token
- # passed in the parameters of a webservice
- my $token = $self->login_token;
- if ($token && (!$login_cookie || !$user_id)) {
- ($user_id, $login_cookie) = ($token->{'user_id'}, $token->{'login_token'});
+ # If the call is for a web service, and an api token is provided, check
+ # it is valid.
+ if (i_am_webservice() && Bugzilla->input_params->{Bugzilla_api_token}) {
+ my $api_token = Bugzilla->input_params->{Bugzilla_api_token};
+ my ($token_user_id, undef, undef, $token_type)
+ = Bugzilla::Token::GetTokenData($api_token);
+ if ( !defined $token_type
+ || $token_type ne 'api_token'
+ || $user_id != $token_user_id)
+ {
+ ThrowUserError('auth_invalid_token', {token => $api_token});
+ }
}
+ }
+
+ # If no cookies were provided, we also look for a login token
+ # passed in the parameters of a webservice
+ my $token = $self->login_token;
+ if ($token && (!$login_cookie || !$user_id)) {
+ ($user_id, $login_cookie) = ($token->{'user_id'}, $token->{'login_token'});
+ }
- my $ip_addr = remote_ip();
+ my $ip_addr = remote_ip();
- 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);
+ if ($login_cookie && $user_id) {
- my $db_cookie =
- $dbh->selectrow_array('SELECT cookie
+ # 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 $db_cookie = $dbh->selectrow_array(
+ 'SELECT cookie
FROM logincookies
WHERE cookie = ?
AND userid = ?
- AND (ipaddr = ? OR ipaddr IS NULL)',
- undef, ($login_cookie, $user_id, $ip_addr));
-
- # If the cookie or token is valid, return a valid username.
- # If they were not valid and we are using a webservice, then
- # throw an error notifying the client.
- if (defined $db_cookie && $login_cookie eq $db_cookie) {
- # 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 };
- }
- elsif (i_am_webservice()) {
- ThrowUserError('invalid_cookies_or_token');
- }
+ AND (ipaddr = ? OR ipaddr IS NULL)', undef,
+ ($login_cookie, $user_id, $ip_addr)
+ );
+
+ # If the cookie or token is valid, return a valid username.
+ # If they were not valid and we are using a webservice, then
+ # throw an error notifying the client.
+ if (defined $db_cookie && $login_cookie eq $db_cookie) {
+
+ # 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 cookie or token is invalid and we are not authenticating
- # via a webservice, or we did not receive a cookie or token. 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 or token. It should just
- # look like there was no cookie or token to begin with.
- return { failure => AUTH_NODATA };
+ elsif (i_am_webservice()) {
+ ThrowUserError('invalid_cookies_or_token');
+ }
+ }
+
+ # Either the cookie or token is invalid and we are not authenticating
+ # via a webservice, or we did not receive a cookie or token. 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 or token. It should just
+ # look like there was no cookie or token to begin with.
+ return {failure => AUTH_NODATA};
}
sub login_token {
- my ($self) = @_;
- my $input = Bugzilla->input_params;
- my $usage_mode = Bugzilla->usage_mode;
+ my ($self) = @_;
+ my $input = Bugzilla->input_params;
+ my $usage_mode = Bugzilla->usage_mode;
- return $self->{'_login_token'} if exists $self->{'_login_token'};
+ return $self->{'_login_token'} if exists $self->{'_login_token'};
- if (!i_am_webservice()) {
- return $self->{'_login_token'} = undef;
- }
+ if (!i_am_webservice()) {
+ return $self->{'_login_token'} = undef;
+ }
- # Check if a token was passed in via requests for WebServices
- my $token = trim(delete $input->{'Bugzilla_token'});
- return $self->{'_login_token'} = undef if !$token;
+ # Check if a token was passed in via requests for WebServices
+ my $token = trim(delete $input->{'Bugzilla_token'});
+ return $self->{'_login_token'} = undef if !$token;
- my ($user_id, $login_token) = split('-', $token, 2);
- if (!detaint_natural($user_id) || !$login_token) {
- return $self->{'_login_token'} = undef;
- }
+ my ($user_id, $login_token) = split('-', $token, 2);
+ if (!detaint_natural($user_id) || !$login_token) {
+ return $self->{'_login_token'} = undef;
+ }
- return $self->{'_login_token'} = {
- user_id => $user_id,
- login_token => $login_token
- };
+ return $self->{'_login_token'}
+ = {user_id => $user_id, login_token => $login_token};
}
1;
diff --git a/Bugzilla/Auth/Login/Env.pm b/Bugzilla/Auth/Login/Env.pm
index 653df2bb3..5fc33921b 100644
--- a/Bugzilla/Auth/Login/Env.pm
+++ b/Bugzilla/Auth/Login/Env.pm
@@ -16,28 +16,31 @@ use parent qw(Bugzilla::Auth::Login);
use Bugzilla::Constants;
use Bugzilla::Error;
-use constant can_logout => 0;
-use constant can_login => 0;
+use constant can_logout => 0;
+use constant can_login => 0;
use constant requires_persistence => 0;
use constant requires_verification => 0;
-use constant is_automatic => 1;
-use constant extern_id_used => 1;
+use constant is_automatic => 1;
+use constant extern_id_used => 1;
sub get_login_info {
- my ($self) = @_;
+ my ($self) = @_;
- 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"}} || '';
+ 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 {failure => AUTH_NODATA} if !$env_email;
- return { username => $env_email, extern_id => $env_id,
- realname => $env_realname };
+ return {
+ username => $env_email,
+ extern_id => $env_id,
+ realname => $env_realname
+ };
}
sub fail_nodata {
- ThrowCodeError('env_no_email');
+ ThrowCodeError('env_no_email');
}
1;
diff --git a/Bugzilla/Auth/Login/Stack.pm b/Bugzilla/Auth/Login/Stack.pm
index dc35998e4..7786f26c8 100644
--- a/Bugzilla/Auth/Login/Stack.pm
+++ b/Bugzilla/Auth/Login/Stack.pm
@@ -13,8 +13,8 @@ use warnings;
use base qw(Bugzilla::Auth::Login);
use fields qw(
- _stack
- successful
+ _stack
+ successful
);
use Hash::Util qw(lock_keys);
use Bugzilla::Hook;
@@ -22,81 +22,87 @@ use Bugzilla::Constants;
use List::MoreUtils qw(any);
sub new {
- my $class = shift;
- my $self = $class->SUPER::new(@_);
- my $list = shift;
- my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list);
- lock_keys(%methods);
- Bugzilla::Hook::process('auth_login_methods', { modules => \%methods });
-
- $self->{_stack} = [];
- foreach my $login_method (split(',', $list)) {
- my $module = $methods{$login_method};
- require $module;
- $module =~ s|/|::|g;
- $module =~ s/.pm$//;
- push(@{$self->{_stack}}, $module->new(@_));
- }
- return $self;
+ my $class = shift;
+ my $self = $class->SUPER::new(@_);
+ my $list = shift;
+ my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list);
+ lock_keys(%methods);
+ Bugzilla::Hook::process('auth_login_methods', {modules => \%methods});
+
+ $self->{_stack} = [];
+ foreach my $login_method (split(',', $list)) {
+ my $module = $methods{$login_method};
+ require $module;
+ $module =~ s|/|::|g;
+ $module =~ s/.pm$//;
+ push(@{$self->{_stack}}, $module->new(@_));
+ }
+ return $self;
}
sub get_login_info {
- my $self = shift;
- my $result;
- foreach my $object (@{$self->{_stack}}) {
- # See Bugzilla::WebService::Server::JSONRPC for where and why
- # auth_no_automatic_login is used.
- if (Bugzilla->request_cache->{auth_no_automatic_login}) {
- next if $object->is_automatic;
- }
- $result = $object->get_login_info(@_);
- $self->{successful} = $object;
-
- # We only carry on down the stack if this method denied all knowledge.
- last unless ($result->{failure}
- && ($result->{failure} eq AUTH_NODATA
- || $result->{failure} eq AUTH_NO_SUCH_USER));
-
- # If none of the methods succeed, it's undef.
- $self->{successful} = undef;
+ my $self = shift;
+ my $result;
+ foreach my $object (@{$self->{_stack}}) {
+
+ # See Bugzilla::WebService::Server::JSONRPC for where and why
+ # auth_no_automatic_login is used.
+ if (Bugzilla->request_cache->{auth_no_automatic_login}) {
+ next if $object->is_automatic;
}
- return $result;
+ $result = $object->get_login_info(@_);
+ $self->{successful} = $object;
+
+ # We only carry on down the stack if this method denied all knowledge.
+ last
+ unless ($result->{failure}
+ && ( $result->{failure} eq AUTH_NODATA
+ || $result->{failure} eq AUTH_NO_SUCH_USER));
+
+ # If none of the methods 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(@_);
- }
+ 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;
+ 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;
+ 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;
}
sub extern_id_used {
- my ($self) = @_;
- return any { $_->extern_id_used } @{ $self->{_stack} };
+ my ($self) = @_;
+ return any { $_->extern_id_used } @{$self->{_stack}};
}
1;
diff --git a/Bugzilla/Auth/Persist/Cookie.pm b/Bugzilla/Auth/Persist/Cookie.pm
index 2d1291f3b..af6b0d77d 100644
--- a/Bugzilla/Auth/Persist/Cookie.pm
+++ b/Bugzilla/Auth/Persist/Cookie.pm
@@ -20,145 +20,154 @@ use Bugzilla::Token;
use List::Util qw(first);
sub new {
- my ($class) = @_;
- my $self = fields::new($class);
- return $self;
+ my ($class) = @_;
+ my $self = fields::new($class);
+ return $self;
}
sub persist_login {
- my ($self, $user) = @_;
- my $dbh = Bugzilla->dbh;
- my $cgi = Bugzilla->cgi;
- my $input_params = Bugzilla->input_params;
-
- my $ip_addr;
- if ($input_params->{'Bugzilla_restrictlogin'}) {
- $ip_addr = remote_ip();
- # The IP address is valid, at least for comparing with itself in a
- # subsequent login
- trick_taint($ip_addr);
- }
-
- $dbh->bz_start_transaction();
-
- 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);
-
- # Issuing a new cookie is a good time to clean up the old
- # cookies.
- $dbh->do("DELETE FROM logincookies WHERE lastused < "
- . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-',
- MAX_LOGINCOOKIE_AGE, 'DAY'));
-
- $dbh->bz_commit_transaction();
-
- # We do not want WebServices to generate login cookies.
- # All we need is the login token for User.login.
- return $login_cookie if i_am_webservice();
-
- # 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' &&
- $input_params->{'Bugzilla_remember'} &&
- $input_params->{'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_redirect'}) {
- # Make these cookies only be sent to us by the browser during
- # HTTPS sessions, if we're using SSL.
- $cookieargs{'-secure'} = 1;
- }
-
- $cgi->send_cookie(-name => 'Bugzilla_login',
- -value => $user->id,
- %cookieargs);
- $cgi->send_cookie(-name => 'Bugzilla_logincookie',
- -value => $login_cookie,
- %cookieargs);
+ my ($self, $user) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $cgi = Bugzilla->cgi;
+ my $input_params = Bugzilla->input_params;
+
+ my $ip_addr;
+ if ($input_params->{'Bugzilla_restrictlogin'}) {
+ $ip_addr = remote_ip();
+
+ # The IP address is valid, at least for comparing with itself in a
+ # subsequent login
+ trick_taint($ip_addr);
+ }
+
+ $dbh->bz_start_transaction();
+
+ 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
+ );
+
+ # Issuing a new cookie is a good time to clean up the old
+ # cookies.
+ $dbh->do("DELETE FROM logincookies WHERE lastused < "
+ . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', MAX_LOGINCOOKIE_AGE, 'DAY'));
+
+ $dbh->bz_commit_transaction();
+
+ # We do not want WebServices to generate login cookies.
+ # All we need is the login token for User.login.
+ return $login_cookie if i_am_webservice();
+
+ # 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'
+ && $input_params->{'Bugzilla_remember'}
+ && $input_params->{'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_redirect'}) {
+
+ # Make these cookies only be sent to us by the browser during
+ # HTTPS sessions, if we're using SSL.
+ $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;
- my $input = Bugzilla->input_params;
- $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 @login_cookies;
- my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
- @{$cgi->{'Bugzilla_cookie_list'}};
- if ($cookie) {
- push(@login_cookies, $cookie->value);
- }
- elsif ($cookie = $cgi->cookie('Bugzilla_logincookie')) {
- push(@login_cookies, $cookie);
- }
-
- # If we are a webservice using a token instead of cookie
- # then add that as well to the login cookies to delete
- if (my $login_token = $user->authorizer->login_token) {
- push(@login_cookies, $login_token->{'login_token'});
- }
-
- # Make sure that @login_cookies is not empty to not break SQL statements.
- push(@login_cookies, '') unless @login_cookies;
-
- # 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
- map { trick_taint($_) } @login_cookies;
- @login_cookies = map { $dbh->quote($_) } @login_cookies;
- if ($type == LOGOUT_KEEP_CURRENT) {
- $dbh->do("DELETE FROM logincookies WHERE " .
- $dbh->sql_in('cookie', \@login_cookies, 1) .
- " AND userid = ?",
- undef, $user->id);
- } elsif ($type == LOGOUT_CURRENT) {
- $dbh->do("DELETE FROM logincookies WHERE " .
- $dbh->sql_in('cookie', \@login_cookies) .
- " AND userid = ?",
- undef, $user->id);
- } else {
- die("Invalid type $type supplied to logout()");
- }
-
- if ($type != LOGOUT_KEEP_CURRENT) {
- clear_browser_cookies();
- }
+ my ($self, $param) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ my $cgi = Bugzilla->cgi;
+ my $input = Bugzilla->input_params;
+ $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 @login_cookies;
+ my $cookie = first { $_->name eq 'Bugzilla_logincookie' }
+ @{$cgi->{'Bugzilla_cookie_list'}};
+ if ($cookie) {
+ push(@login_cookies, $cookie->value);
+ }
+ elsif ($cookie = $cgi->cookie('Bugzilla_logincookie')) {
+ push(@login_cookies, $cookie);
+ }
+
+ # If we are a webservice using a token instead of cookie
+ # then add that as well to the login cookies to delete
+ if (my $login_token = $user->authorizer->login_token) {
+ push(@login_cookies, $login_token->{'login_token'});
+ }
+
+ # Make sure that @login_cookies is not empty to not break SQL statements.
+ push(@login_cookies, '') unless @login_cookies;
+
+ # 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
+ map { trick_taint($_) } @login_cookies;
+ @login_cookies = map { $dbh->quote($_) } @login_cookies;
+ if ($type == LOGOUT_KEEP_CURRENT) {
+ $dbh->do(
+ "DELETE FROM logincookies WHERE "
+ . $dbh->sql_in('cookie', \@login_cookies, 1)
+ . " AND userid = ?",
+ undef, $user->id
+ );
+ }
+ elsif ($type == LOGOUT_CURRENT) {
+ $dbh->do(
+ "DELETE FROM logincookies WHERE "
+ . $dbh->sql_in('cookie', \@login_cookies)
+ . " AND userid = ?",
+ undef, $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');
- $cgi->remove_cookie('sudo');
+ my $cgi = Bugzilla->cgi;
+ $cgi->remove_cookie('Bugzilla_login');
+ $cgi->remove_cookie('Bugzilla_logincookie');
+ $cgi->remove_cookie('sudo');
}
1;
diff --git a/Bugzilla/Auth/Verify.pm b/Bugzilla/Auth/Verify.pm
index 9dc83273b..2e49175d7 100644
--- a/Bugzilla/Auth/Verify.pm
+++ b/Bugzilla/Auth/Verify.pm
@@ -19,113 +19,125 @@ use Bugzilla::User;
use Bugzilla::Util;
use constant user_can_create_account => 1;
-use constant extern_id_used => 0;
+use constant extern_id_used => 0;
sub new {
- my ($class, $login_type) = @_;
- my $self = fields::new($class);
- return $self;
+ my ($class, $login_type) = @_;
+ my $self = fields::new($class);
+ return $self;
}
sub can_change_password {
- return $_[0]->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);
- Bugzilla->memcached->clear({ table => 'profiles', 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;
+ 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 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.
- my $changed = 0;
+ # 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 ($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);
- $changed = 1;
+ # 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 ($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);
- $changed = 1;
+
+ # 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);
+ Bugzilla->memcached->clear({table => 'profiles', id => $username_user_id});
}
- $user->update() if $changed;
- return { user => $user };
+ # 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.
+ my $changed = 0;
+
+ 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);
+ $changed = 1;
+ }
+ 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);
+ $changed = 1;
+ }
+ $user->update() if $changed;
+
+ return {user => $user};
}
1;
diff --git a/Bugzilla/Auth/Verify/DB.pm b/Bugzilla/Auth/Verify/DB.pm
index 28a9310c9..6ed9ba15c 100644
--- a/Bugzilla/Auth/Verify/DB.pm
+++ b/Bugzilla/Auth/Verify/DB.pm
@@ -19,95 +19,97 @@ use Bugzilla::Util;
use Bugzilla::User;
sub check_credentials {
- my ($self, $login_data) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($self, $login_data) = @_;
+ my $dbh = Bugzilla->dbh;
- my $username = $login_data->{username};
- my $user = new Bugzilla::User({ name => $username });
+ my $username = $login_data->{username};
+ my $user = new Bugzilla::User({name => $username});
- return { failure => AUTH_NO_SUCH_USER } unless $user;
+ return {failure => AUTH_NO_SUCH_USER} unless $user;
- $login_data->{user} = $user;
- $login_data->{bz_username} = $user->login;
+ $login_data->{user} = $user;
+ $login_data->{bz_username} = $user->login;
+ if ($user->account_is_locked_out) {
+ return {failure => AUTH_LOCKOUT, user => $user};
+ }
+
+ my $password = $login_data->{password};
+ my $real_password_crypted = $user->cryptpassword;
+
+ # Using the internal crypted password as the salt,
+ # crypt the password the user entered.
+ my $entered_password_crypted = bz_crypt($password, $real_password_crypted);
+
+ if ($entered_password_crypted ne $real_password_crypted) {
+
+ # Record the login failure
+ $user->note_login_failure();
+
+ # Immediately check if we are locked out
if ($user->account_is_locked_out) {
- return { failure => AUTH_LOCKOUT, user => $user };
+ return {failure => AUTH_LOCKOUT, user => $user, just_locked_out => 1};
}
- my $password = $login_data->{password};
- my $real_password_crypted = $user->cryptpassword;
-
- # Using the internal crypted password as the salt,
- # crypt the password the user entered.
- my $entered_password_crypted = bz_crypt($password, $real_password_crypted);
-
- if ($entered_password_crypted ne $real_password_crypted) {
- # Record the login failure
- $user->note_login_failure();
-
- # Immediately check if we are locked out
- if ($user->account_is_locked_out) {
- return { failure => AUTH_LOCKOUT, user => $user,
- just_locked_out => 1 };
- }
-
- return { failure => AUTH_LOGINFAILED,
- failure_count => scalar(@{ $user->account_ip_login_failures }),
- };
- }
-
- # Force the user to change their password if it does not meet the current
- # criteria. This should usually only happen if the criteria has changed.
- if (Bugzilla->usage_mode == USAGE_MODE_BROWSER &&
- Bugzilla->params->{password_check_on_login})
- {
- my $check = validate_password_check($password);
- if ($check) {
- return {
- failure => AUTH_ERROR,
- user_error => $check,
- details => { locked_user => $user }
- }
- }
+ return {
+ failure => AUTH_LOGINFAILED,
+ failure_count => scalar(@{$user->account_ip_login_failures}),
+ };
+ }
+
+ # Force the user to change their password if it does not meet the current
+ # criteria. This should usually only happen if the criteria has changed.
+ if ( Bugzilla->usage_mode == USAGE_MODE_BROWSER
+ && Bugzilla->params->{password_check_on_login})
+ {
+ my $check = validate_password_check($password);
+ if ($check) {
+ return {
+ failure => AUTH_ERROR,
+ user_error => $check,
+ details => {locked_user => $user}
+ };
}
+ }
- # The user's credentials are okay, so delete any outstanding
- # password tokens or login failures they may have generated.
- Bugzilla::Token::DeletePasswordTokens($user->id, "user_logged_in");
- $user->clear_login_failures();
+ # The user's credentials are okay, so delete any outstanding
+ # password tokens or login failures they may have generated.
+ Bugzilla::Token::DeletePasswordTokens($user->id, "user_logged_in");
+ $user->clear_login_failures();
- my $update_password = 0;
+ my $update_password = 0;
- # If their old password was using crypt() or some different hash
- # than we're using now, convert the stored password to using
- # whatever hashing system we're using now.
- my $current_algorithm = PASSWORD_DIGEST_ALGORITHM;
- $update_password = 1 if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/);
+ # If their old password was using crypt() or some different hash
+ # than we're using now, convert the stored password to using
+ # whatever hashing system we're using now.
+ my $current_algorithm = PASSWORD_DIGEST_ALGORITHM;
+ $update_password = 1 if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/);
- # If their old password was using a different length salt than what
- # we're using now, update the password to use the new salt length.
- if ($real_password_crypted =~ /^([^,]+),/) {
- $update_password = 1 if (length($1) != PASSWORD_SALT_LENGTH);
- }
+ # If their old password was using a different length salt than what
+ # we're using now, update the password to use the new salt length.
+ if ($real_password_crypted =~ /^([^,]+),/) {
+ $update_password = 1 if (length($1) != PASSWORD_SALT_LENGTH);
+ }
- # If needed, update the user's password.
- if ($update_password) {
- # We can't call $user->set_password because we don't want the password
- # complexity rules to apply here.
- $user->{cryptpassword} = bz_crypt($password);
- $user->update();
- }
+ # If needed, update the user's password.
+ if ($update_password) {
+
+ # We can't call $user->set_password because we don't want the password
+ # complexity rules to apply here.
+ $user->{cryptpassword} = bz_crypt($password);
+ $user->update();
+ }
- return $login_data;
+ 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);
- Bugzilla->memcached->clear({ table => 'profiles', id => $user->id });
+ 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);
+ Bugzilla->memcached->clear({table => 'profiles', id => $user->id});
}
1;
diff --git a/Bugzilla/Auth/Verify/LDAP.pm b/Bugzilla/Auth/Verify/LDAP.pm
index e37f55793..c92a38909 100644
--- a/Bugzilla/Auth/Verify/LDAP.pm
+++ b/Bugzilla/Auth/Verify/LDAP.pm
@@ -13,7 +13,7 @@ use warnings;
use base qw(Bugzilla::Auth::Verify);
use fields qw(
- ldap
+ ldap
);
use Bugzilla::Constants;
@@ -28,126 +28,139 @@ 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.
+ 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_for_search();
+
+ # 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.
+
+ # First try the search as the (already bound) user in question.
+ my $user_entry;
+ my $error_string;
+ my $detail_result = $self->ldap->search(_bz_search_params($username));
+ if ($detail_result->code) {
+
+ # Stash away the original error, just in case
+ $error_string = $detail_result->error;
+ }
+ else {
+ $user_entry = $detail_result->shift_entry;
+ }
+
+ # If that failed (either because the search failed, or returned no
+ # results) then try re-binding as the initial search user, but only
+ # if the LDAPbinddn parameter is set.
+ if (!$user_entry && Bugzilla->params->{"LDAPbinddn"}) {
$self->_bind_ldap_for_search();
- # 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.
-
- # First try the search as the (already bound) user in question.
- my $user_entry;
- my $error_string;
- my $detail_result = $self->ldap->search(_bz_search_params($username));
- if ($detail_result->code) {
- # Stash away the original error, just in case
- $error_string = $detail_result->error;
- } else {
- $user_entry = $detail_result->shift_entry;
+ $detail_result = $self->ldap->search(_bz_search_params($username));
+ if (!$detail_result->code) {
+ $user_entry = $detail_result->shift_entry;
}
+ }
- # If that failed (either because the search failed, or returned no
- # results) then try re-binding as the initial search user, but only
- # if the LDAPbinddn parameter is set.
- if (!$user_entry && Bugzilla->params->{"LDAPbinddn"}) {
- $self->_bind_ldap_for_search();
-
- $detail_result = $self->ldap->search(_bz_search_params($username));
- if (!$detail_result->code) {
- $user_entry = $detail_result->shift_entry;
- }
+ # If we *still* don't have anything in $user_entry then give up.
+ return {
+ failure => AUTH_ERROR,
+ error => "ldap_search_error",
+ details => {errstr => $error_string, username => $username}
}
+ if !$user_entry;
- # If we *still* don't have anything in $user_entry then give up.
- return { failure => AUTH_ERROR, error => "ldap_search_error",
- details => {errstr => $error_string, username => $username}
- } if !$user_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 $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);
- my @emails = $user_entry->get_value($mail_attr);
+ # Default to the first email address returned.
+ $params->{bz_username} = $emails[0];
- # Default to the first email address returned.
- $params->{bz_username} = $emails[0];
+ if (@emails > 1) {
- 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;
- }
- }
+ # 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");
+ }
+ else {
+ $params->{bz_username} = $username;
+ }
+
+ $params->{realname} ||= $user_entry->get_value("displayName");
+ $params->{realname} ||= $user_entry->get_value("cn");
- $params->{extern_id} = $username;
+ $params->{extern_id} = $username;
- return $params;
+ return $params;
}
sub _bz_search_params {
- my ($username) = @_;
- $username = escape_filter_value($username);
- return (base => Bugzilla->params->{"LDAPBaseDN"},
- scope => "sub",
- filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"}
- . "=$username)"
- . Bugzilla->params->{"LDAPfilter"} . ')');
+ my ($username) = @_;
+ $username = escape_filter_value($username);
+ return (
+ base => Bugzilla->params->{"LDAPBaseDN"},
+ scope => "sub",
+ filter => '(&('
+ . Bugzilla->params->{"LDAPuidattribute"}
+ . "=$username)"
+ . Bugzilla->params->{"LDAPfilter"} . ')'
+ );
}
sub _bind_ldap_for_search {
- 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;
+ 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
@@ -156,27 +169,27 @@ sub _bind_ldap_for_search {
# to fix their 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};
+ 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;
diff --git a/Bugzilla/Auth/Verify/RADIUS.pm b/Bugzilla/Auth/Verify/RADIUS.pm
index 283d9b466..2cbde0404 100644
--- a/Bugzilla/Auth/Verify/RADIUS.pm
+++ b/Bugzilla/Auth/Verify/RADIUS.pm
@@ -23,33 +23,37 @@ 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;
+ 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;
diff --git a/Bugzilla/Auth/Verify/Stack.pm b/Bugzilla/Auth/Verify/Stack.pm
index 3e5db3cec..9a9412915 100644
--- a/Bugzilla/Auth/Verify/Stack.pm
+++ b/Bugzilla/Auth/Verify/Stack.pm
@@ -13,8 +13,8 @@ use warnings;
use base qw(Bugzilla::Auth::Verify);
use fields qw(
- _stack
- successful
+ _stack
+ successful
);
use Bugzilla::Hook;
@@ -23,70 +23,75 @@ use Hash::Util qw(lock_keys);
use List::MoreUtils qw(any);
sub new {
- my $class = shift;
- my $list = shift;
- my $self = $class->SUPER::new(@_);
- my %methods = map { $_ => "Bugzilla/Auth/Verify/$_.pm" } split(',', $list);
- lock_keys(%methods);
- Bugzilla::Hook::process('auth_verify_methods', { modules => \%methods });
-
- $self->{_stack} = [];
- foreach my $verify_method (split(',', $list)) {
- my $module = $methods{$verify_method};
- require $module;
- $module =~ s|/|::|g;
- $module =~ s/.pm$//;
- push(@{$self->{_stack}}, $module->new(@_));
- }
- return $self;
+ my $class = shift;
+ my $list = shift;
+ my $self = $class->SUPER::new(@_);
+ my %methods = map { $_ => "Bugzilla/Auth/Verify/$_.pm" } split(',', $list);
+ lock_keys(%methods);
+ Bugzilla::Hook::process('auth_verify_methods', {modules => \%methods});
+
+ $self->{_stack} = [];
+ foreach my $verify_method (split(',', $list)) {
+ my $module = $methods{$verify_method};
+ require $module;
+ $module =~ s|/|::|g;
+ $module =~ s/.pm$//;
+ push(@{$self->{_stack}}, $module->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;
+ 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;
+ 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;
+ 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;
+ 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;
}
sub extern_id_used {
- my ($self) = @_;
- return any { $_->extern_id_used } @{ $self->{_stack} };
+ my ($self) = @_;
+ return any { $_->extern_id_used } @{$self->{_stack}};
}
1;
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index 8b4493f85..b6b44b262 100644
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -38,9 +38,9 @@ use Scalar::Util qw(blessed);
use parent qw(Bugzilla::Object Exporter);
@Bugzilla::Bug::EXPORT = qw(
- bug_alias_to_id
- LogActivityEntry
- editable_bug_fields
+ bug_alias_to_id
+ LogActivityEntry
+ editable_bug_fields
);
#####################################################################
@@ -51,198 +51,199 @@ use constant DB_TABLE => 'bugs';
use constant ID_FIELD => 'bug_id';
use constant NAME_FIELD => 'bug_id';
use constant LIST_ORDER => ID_FIELD;
+
# Bugs have their own auditing table, bugs_activity.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
+
# This will be enabled later
use constant USE_MEMCACHED => 0;
# This is a sub because it needs to call other subroutines.
sub DB_COLUMNS {
- my $dbh = Bugzilla->dbh;
- my @custom = grep {$_->type != FIELD_TYPE_MULTI_SELECT}
- Bugzilla->active_custom_fields;
- my @custom_names = map {$_->name} @custom;
-
- my @columns = (qw(
- assigned_to
- bug_file_loc
- bug_id
- bug_severity
- bug_status
- cclist_accessible
- component_id
- creation_ts
- delta_ts
- estimated_time
- everconfirmed
- lastdiffed
- op_sys
- priority
- product_id
- qa_contact
- remaining_time
- rep_platform
- reporter_accessible
- resolution
- short_desc
- status_whiteboard
- target_milestone
- version
- ),
- 'reporter AS reporter_id',
- $dbh->sql_date_format('deadline', '%Y-%m-%d') . ' AS deadline',
- @custom_names);
-
- Bugzilla::Hook::process("bug_columns", { columns => \@columns });
-
- return @columns;
+ my $dbh = Bugzilla->dbh;
+ my @custom
+ = grep { $_->type != FIELD_TYPE_MULTI_SELECT } Bugzilla->active_custom_fields;
+ my @custom_names = map { $_->name } @custom;
+
+ my @columns = (
+ qw(
+ assigned_to
+ bug_file_loc
+ bug_id
+ bug_severity
+ bug_status
+ cclist_accessible
+ component_id
+ creation_ts
+ delta_ts
+ estimated_time
+ everconfirmed
+ lastdiffed
+ op_sys
+ priority
+ product_id
+ qa_contact
+ remaining_time
+ rep_platform
+ reporter_accessible
+ resolution
+ short_desc
+ status_whiteboard
+ target_milestone
+ version
+ ), 'reporter AS reporter_id',
+ $dbh->sql_date_format('deadline', '%Y-%m-%d') . ' AS deadline', @custom_names
+ );
+
+ Bugzilla::Hook::process("bug_columns", {columns => \@columns});
+
+ return @columns;
}
sub VALIDATORS {
- my $validators = {
- alias => \&_check_alias,
- assigned_to => \&_check_assigned_to,
- blocked => \&_check_dependencies,
- bug_file_loc => \&_check_bug_file_loc,
- bug_severity => \&_check_select_field,
- bug_status => \&_check_bug_status,
- cc => \&_check_cc,
- comment => \&_check_comment,
- component => \&_check_component,
- creation_ts => \&_check_creation_ts,
- deadline => \&_check_deadline,
- dependson => \&_check_dependencies,
- dup_id => \&_check_dup_id,
- estimated_time => \&_check_time_field,
- everconfirmed => \&Bugzilla::Object::check_boolean,
- groups => \&_check_groups,
- keywords => \&_check_keywords,
- op_sys => \&_check_select_field,
- priority => \&_check_priority,
- product => \&_check_product,
- qa_contact => \&_check_qa_contact,
- remaining_time => \&_check_time_field,
- rep_platform => \&_check_select_field,
- resolution => \&_check_resolution,
- short_desc => \&_check_short_desc,
- status_whiteboard => \&_check_status_whiteboard,
- target_milestone => \&_check_target_milestone,
- version => \&_check_version,
-
- cclist_accessible => \&Bugzilla::Object::check_boolean,
- reporter_accessible => \&Bugzilla::Object::check_boolean,
- };
-
- # Set up validators for custom fields.
- foreach my $field (Bugzilla->active_custom_fields) {
- my $validator;
- if ($field->type == FIELD_TYPE_SINGLE_SELECT) {
- $validator = \&_check_select_field;
- }
- elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
- $validator = \&_check_multi_select_field;
- }
- elsif ($field->type == FIELD_TYPE_DATETIME) {
- $validator = \&_check_datetime_field;
- }
- elsif ($field->type == FIELD_TYPE_DATE) {
- $validator = \&_check_date_field;
- }
- elsif ($field->type == FIELD_TYPE_FREETEXT) {
- $validator = \&_check_freetext_field;
- }
- elsif ($field->type == FIELD_TYPE_BUG_ID) {
- $validator = \&_check_bugid_field;
- }
- elsif ($field->type == FIELD_TYPE_TEXTAREA) {
- $validator = \&_check_textarea_field;
- }
- elsif ($field->type == FIELD_TYPE_INTEGER) {
- $validator = \&_check_integer_field;
- }
- else {
- $validator = \&_check_default_field;
- }
- $validators->{$field->name} = $validator;
+ my $validators = {
+ alias => \&_check_alias,
+ assigned_to => \&_check_assigned_to,
+ blocked => \&_check_dependencies,
+ bug_file_loc => \&_check_bug_file_loc,
+ bug_severity => \&_check_select_field,
+ bug_status => \&_check_bug_status,
+ cc => \&_check_cc,
+ comment => \&_check_comment,
+ component => \&_check_component,
+ creation_ts => \&_check_creation_ts,
+ deadline => \&_check_deadline,
+ dependson => \&_check_dependencies,
+ dup_id => \&_check_dup_id,
+ estimated_time => \&_check_time_field,
+ everconfirmed => \&Bugzilla::Object::check_boolean,
+ groups => \&_check_groups,
+ keywords => \&_check_keywords,
+ op_sys => \&_check_select_field,
+ priority => \&_check_priority,
+ product => \&_check_product,
+ qa_contact => \&_check_qa_contact,
+ remaining_time => \&_check_time_field,
+ rep_platform => \&_check_select_field,
+ resolution => \&_check_resolution,
+ short_desc => \&_check_short_desc,
+ status_whiteboard => \&_check_status_whiteboard,
+ target_milestone => \&_check_target_milestone,
+ version => \&_check_version,
+
+ cclist_accessible => \&Bugzilla::Object::check_boolean,
+ reporter_accessible => \&Bugzilla::Object::check_boolean,
+ };
+
+ # Set up validators for custom fields.
+ foreach my $field (Bugzilla->active_custom_fields) {
+ my $validator;
+ if ($field->type == FIELD_TYPE_SINGLE_SELECT) {
+ $validator = \&_check_select_field;
+ }
+ elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ $validator = \&_check_multi_select_field;
+ }
+ elsif ($field->type == FIELD_TYPE_DATETIME) {
+ $validator = \&_check_datetime_field;
+ }
+ elsif ($field->type == FIELD_TYPE_DATE) {
+ $validator = \&_check_date_field;
+ }
+ elsif ($field->type == FIELD_TYPE_FREETEXT) {
+ $validator = \&_check_freetext_field;
+ }
+ elsif ($field->type == FIELD_TYPE_BUG_ID) {
+ $validator = \&_check_bugid_field;
+ }
+ elsif ($field->type == FIELD_TYPE_TEXTAREA) {
+ $validator = \&_check_textarea_field;
+ }
+ elsif ($field->type == FIELD_TYPE_INTEGER) {
+ $validator = \&_check_integer_field;
+ }
+ else {
+ $validator = \&_check_default_field;
}
+ $validators->{$field->name} = $validator;
+ }
- return $validators;
-};
+ return $validators;
+}
sub VALIDATOR_DEPENDENCIES {
- my $cache = Bugzilla->request_cache;
- return $cache->{bug_validator_dependencies}
- if $cache->{bug_validator_dependencies};
-
- my %deps = (
- assigned_to => ['component'],
- blocked => ['product'],
- bug_status => ['product', 'comment', 'target_milestone'],
- cc => ['component'],
- comment => ['creation_ts'],
- component => ['product'],
- dependson => ['product'],
- dup_id => ['bug_status', 'resolution'],
- groups => ['product'],
- keywords => ['product'],
- resolution => ['bug_status', 'dependson'],
- qa_contact => ['component'],
- target_milestone => ['product'],
- version => ['product'],
- );
-
- foreach my $field (@{ Bugzilla->fields }) {
- $deps{$field->name} = [ $field->visibility_field->name ]
- if $field->{visibility_field_id};
- }
-
- $cache->{bug_validator_dependencies} = \%deps;
- return \%deps;
-};
+ my $cache = Bugzilla->request_cache;
+ return $cache->{bug_validator_dependencies}
+ if $cache->{bug_validator_dependencies};
+
+ my %deps = (
+ assigned_to => ['component'],
+ blocked => ['product'],
+ bug_status => ['product', 'comment', 'target_milestone'],
+ cc => ['component'],
+ comment => ['creation_ts'],
+ component => ['product'],
+ dependson => ['product'],
+ dup_id => ['bug_status', 'resolution'],
+ groups => ['product'],
+ keywords => ['product'],
+ resolution => ['bug_status', 'dependson'],
+ qa_contact => ['component'],
+ target_milestone => ['product'],
+ version => ['product'],
+ );
+
+ foreach my $field (@{Bugzilla->fields}) {
+ $deps{$field->name} = [$field->visibility_field->name]
+ if $field->{visibility_field_id};
+ }
+
+ $cache->{bug_validator_dependencies} = \%deps;
+ return \%deps;
+}
sub UPDATE_COLUMNS {
- my @custom = grep {$_->type != FIELD_TYPE_MULTI_SELECT}
- Bugzilla->active_custom_fields;
- my @custom_names = map {$_->name} @custom;
- my @columns = qw(
- assigned_to
- bug_file_loc
- bug_severity
- bug_status
- cclist_accessible
- component_id
- deadline
- estimated_time
- everconfirmed
- op_sys
- priority
- product_id
- qa_contact
- remaining_time
- rep_platform
- reporter_accessible
- resolution
- short_desc
- status_whiteboard
- target_milestone
- version
- );
- push(@columns, @custom_names);
- return @columns;
-};
-
-use constant NUMERIC_COLUMNS => qw(
+ my @custom
+ = grep { $_->type != FIELD_TYPE_MULTI_SELECT } Bugzilla->active_custom_fields;
+ my @custom_names = map { $_->name } @custom;
+ my @columns = qw(
+ assigned_to
+ bug_file_loc
+ bug_severity
+ bug_status
+ cclist_accessible
+ component_id
+ deadline
estimated_time
+ everconfirmed
+ op_sys
+ priority
+ product_id
+ qa_contact
remaining_time
+ rep_platform
+ reporter_accessible
+ resolution
+ short_desc
+ status_whiteboard
+ target_milestone
+ version
+ );
+ push(@columns, @custom_names);
+ return @columns;
+}
+
+use constant NUMERIC_COLUMNS => qw(
+ estimated_time
+ remaining_time
);
sub DATE_COLUMNS {
- my @fields = (@{ Bugzilla->fields({ type => [FIELD_TYPE_DATETIME,
- FIELD_TYPE_DATE] })
- });
- return map { $_->name } @fields;
+ my @fields
+ = (@{Bugzilla->fields({type => [FIELD_TYPE_DATETIME, FIELD_TYPE_DATE]})});
+ return map { $_->name } @fields;
}
# Used in LogActivityEntry(). Gives the max length of lines in the
@@ -254,30 +255,28 @@ use constant MAX_LINE_LENGTH => 254;
# of Bugzilla. (These are the field names that the WebService and email_in.pl
# use.)
use constant FIELD_MAP => {
- blocks => 'blocked',
- commentprivacy => 'comment_is_private',
- creation_time => 'creation_ts',
- creator => 'reporter',
- description => 'comment',
- depends_on => 'dependson',
- dupe_of => 'dup_id',
- id => 'bug_id',
- is_confirmed => 'everconfirmed',
- is_cc_accessible => 'cclist_accessible',
- is_creator_accessible => 'reporter_accessible',
- last_change_time => 'delta_ts',
- platform => 'rep_platform',
- severity => 'bug_severity',
- status => 'bug_status',
- summary => 'short_desc',
- url => 'bug_file_loc',
- whiteboard => 'status_whiteboard',
+ blocks => 'blocked',
+ commentprivacy => 'comment_is_private',
+ creation_time => 'creation_ts',
+ creator => 'reporter',
+ description => 'comment',
+ depends_on => 'dependson',
+ dupe_of => 'dup_id',
+ id => 'bug_id',
+ is_confirmed => 'everconfirmed',
+ is_cc_accessible => 'cclist_accessible',
+ is_creator_accessible => 'reporter_accessible',
+ last_change_time => 'delta_ts',
+ platform => 'rep_platform',
+ severity => 'bug_severity',
+ status => 'bug_status',
+ summary => 'short_desc',
+ url => 'bug_file_loc',
+ whiteboard => 'status_whiteboard',
};
-use constant REQUIRED_FIELD_MAP => {
- product_id => 'product',
- component_id => 'component',
-};
+use constant REQUIRED_FIELD_MAP =>
+ {product_id => 'product', component_id => 'component',};
# Creation timestamp is here because it needs to be validated
# but it can be NULL in the database (see comments in create above)
@@ -295,360 +294,374 @@ use constant REQUIRED_FIELD_MAP => {
#
# Groups are in a separate table, but must always be validated so that
# mandatory groups get set on bugs.
-use constant EXTRA_REQUIRED_FIELDS => qw(creation_ts target_milestone cc qa_contact groups);
+use constant EXTRA_REQUIRED_FIELDS =>
+ qw(creation_ts target_milestone cc qa_contact groups);
#####################################################################
sub new {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $param = shift;
-
- # Remove leading "#" mark if we've just been passed an id.
- if (!ref $param && $param =~ /^#([0-9]+)$/) {
- $param = $1;
- }
-
- # If we get something that looks like a word (not a number),
- # make it the "name" param.
- if (!defined $param
- || (!ref($param) && $param !~ /^[0-9]+$/)
- || (ref($param) && $param->{id} !~ /^[0-9]+$/))
- {
- if ($param) {
- my $alias = ref($param) ? $param->{id} : $param;
- my $bug_id = bug_alias_to_id($alias);
- if (! $bug_id) {
- my $error_self = {};
- bless $error_self, $class;
- $error_self->{'bug_id'} = $alias;
- $error_self->{'error'} = 'InvalidBugId';
- return $error_self;
- }
- $param = { id => $bug_id,
- cache => ref($param) ? $param->{cache} : 0 };
- }
- else {
- # We got something that's not a number.
- my $error_self = {};
- bless $error_self, $class;
- $error_self->{'bug_id'} = $param;
- $error_self->{'error'} = 'InvalidBugId';
- return $error_self;
- }
- }
-
- unshift @_, $param;
- my $self = $class->SUPER::new(@_);
-
- # Bugzilla::Bug->new always returns something, but sets $self->{error}
- # if the bug wasn't found in the database.
- if (!$self) {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $param = shift;
+
+ # Remove leading "#" mark if we've just been passed an id.
+ if (!ref $param && $param =~ /^#([0-9]+)$/) {
+ $param = $1;
+ }
+
+ # If we get something that looks like a word (not a number),
+ # make it the "name" param.
+ if ( !defined $param
+ || (!ref($param) && $param !~ /^[0-9]+$/)
+ || (ref($param) && $param->{id} !~ /^[0-9]+$/))
+ {
+ if ($param) {
+ my $alias = ref($param) ? $param->{id} : $param;
+ my $bug_id = bug_alias_to_id($alias);
+ if (!$bug_id) {
my $error_self = {};
- if (ref $param) {
- $error_self->{bug_id} = $param->{name};
- $error_self->{error} = 'InvalidBugId';
- }
- else {
- $error_self->{bug_id} = $param;
- $error_self->{error} = 'NotFound';
- }
bless $error_self, $class;
+ $error_self->{'bug_id'} = $alias;
+ $error_self->{'error'} = 'InvalidBugId';
return $error_self;
+ }
+ $param = {id => $bug_id, cache => ref($param) ? $param->{cache} : 0};
+ }
+ else {
+ # We got something that's not a number.
+ my $error_self = {};
+ bless $error_self, $class;
+ $error_self->{'bug_id'} = $param;
+ $error_self->{'error'} = 'InvalidBugId';
+ return $error_self;
+ }
+ }
+
+ unshift @_, $param;
+ my $self = $class->SUPER::new(@_);
+
+ # Bugzilla::Bug->new always returns something, but sets $self->{error}
+ # if the bug wasn't found in the database.
+ if (!$self) {
+ my $error_self = {};
+ if (ref $param) {
+ $error_self->{bug_id} = $param->{name};
+ $error_self->{error} = 'InvalidBugId';
+ }
+ else {
+ $error_self->{bug_id} = $param;
+ $error_self->{error} = 'NotFound';
}
+ bless $error_self, $class;
+ return $error_self;
+ }
- return $self;
+ return $self;
}
sub initialize {
- $_[0]->_create_cf_accessors();
+ $_[0]->_create_cf_accessors();
}
sub object_cache_key {
- my $class = shift;
- my $key = $class->SUPER::object_cache_key(@_)
- || return;
- return $key . ',' . Bugzilla->user->id;
+ my $class = shift;
+ my $key = $class->SUPER::object_cache_key(@_) || return;
+ return $key . ',' . Bugzilla->user->id;
}
sub check {
- my $class = shift;
- my ($param, $field) = @_;
+ my $class = shift;
+ my ($param, $field) = @_;
- # Bugzilla::Bug throws lots of special errors, so we don't call
- # SUPER::check, we just call our new and do our own checks.
- my $id = ref($param)
- ? ($param->{id} = trim($param->{id}))
- : ($param = trim($param));
- ThrowUserError('improper_bug_id_field_value', { field => $field }) unless defined $id;
+ # Bugzilla::Bug throws lots of special errors, so we don't call
+ # SUPER::check, we just call our new and do our own checks.
+ my $id
+ = ref($param) ? ($param->{id} = trim($param->{id})) : ($param = trim($param));
+ ThrowUserError('improper_bug_id_field_value', {field => $field})
+ unless defined $id;
- my $self = $class->new($param);
+ my $self = $class->new($param);
- if ($self->{error}) {
- # For error messages, use the id that was returned by new(), because
- # it's cleaned up.
- $id = $self->id;
+ if ($self->{error}) {
- if ($self->{error} eq 'NotFound') {
- ThrowUserError("bug_id_does_not_exist", { bug_id => $id });
- }
- if ($self->{error} eq 'InvalidBugId') {
- ThrowUserError("improper_bug_id_field_value",
- { bug_id => $id,
- field => $field });
- }
- }
+ # For error messages, use the id that was returned by new(), because
+ # it's cleaned up.
+ $id = $self->id;
- unless ($field && $field =~ /^(dependson|blocked|dup_id)$/) {
- $self->check_is_visible($id);
+ if ($self->{error} eq 'NotFound') {
+ ThrowUserError("bug_id_does_not_exist", {bug_id => $id});
}
- return $self;
+ if ($self->{error} eq 'InvalidBugId') {
+ ThrowUserError("improper_bug_id_field_value", {bug_id => $id, field => $field});
+ }
+ }
+
+ unless ($field && $field =~ /^(dependson|blocked|dup_id)$/) {
+ $self->check_is_visible($id);
+ }
+ return $self;
}
sub check_for_edit {
- my $class = shift;
- my $bug = $class->check(@_);
+ my $class = shift;
+ my $bug = $class->check(@_);
- Bugzilla->user->can_edit_product($bug->product_id)
- || ThrowUserError("product_edit_denied", { product => $bug->product });
+ Bugzilla->user->can_edit_product($bug->product_id)
+ || ThrowUserError("product_edit_denied", {product => $bug->product});
- return $bug;
+ return $bug;
}
sub check_is_visible {
- my ($self, $input_id) = @_;
- $input_id ||= $self->id;
- my $user = Bugzilla->user;
+ my ($self, $input_id) = @_;
+ $input_id ||= $self->id;
+ my $user = Bugzilla->user;
- if (!$user->can_see_bug($self->id)) {
- # The error the user sees depends on whether or not they are
- # logged in (i.e. $user->id contains the user's positive integer ID).
- # If we are validating an alias, then use it in the error message
- # instead of its corresponding bug ID, to not disclose it.
- if ($user->id) {
- ThrowUserError("bug_access_denied", { bug_id => $input_id });
- } else {
- ThrowUserError("bug_access_query", { bug_id => $input_id });
- }
- }
-}
+ if (!$user->can_see_bug($self->id)) {
-sub match {
- my $class = shift;
- my ($params) = @_;
-
- # Allow matching certain fields by name (in addition to matching by ID).
- my %translate_fields = (
- assigned_to => 'Bugzilla::User',
- qa_contact => 'Bugzilla::User',
- reporter => 'Bugzilla::User',
- product => 'Bugzilla::Product',
- component => 'Bugzilla::Component',
- );
- my %translated;
-
- foreach my $field (keys %translate_fields) {
- my @ids;
- # Convert names to ids. We use "exists" everywhere since people can
- # legally specify "undef" to mean IS NULL (even though most of these
- # fields can't be NULL, people can still specify it...).
- if (exists $params->{$field}) {
- my $names = $params->{$field};
- my $type = $translate_fields{$field};
- my $param = $type eq 'Bugzilla::User' ? 'login_name' : 'name';
- # We call Bugzilla::Object::match directly to avoid the
- # Bugzilla::User::match implementation which is different.
- my $objects = Bugzilla::Object::match($type, { $param => $names });
- push(@ids, map { $_->id } @$objects);
- }
- # You can also specify ids directly as arguments to this function,
- # so include them in the list if they have been specified.
- if (exists $params->{"${field}_id"}) {
- my $current_ids = $params->{"${field}_id"};
- my @id_array = ref $current_ids ? @$current_ids : ($current_ids);
- push(@ids, @id_array);
- }
- # We do this "or" instead of a "scalar(@ids)" to handle the case
- # when people passed only invalid object names. Otherwise we'd
- # end up with a SUPER::match call with zero criteria (which dies).
- if (exists $params->{$field} or exists $params->{"${field}_id"}) {
- $translated{$field} = scalar(@ids) == 1 ? $ids[0] : \@ids;
- }
+ # The error the user sees depends on whether or not they are
+ # logged in (i.e. $user->id contains the user's positive integer ID).
+ # If we are validating an alias, then use it in the error message
+ # instead of its corresponding bug ID, to not disclose it.
+ if ($user->id) {
+ ThrowUserError("bug_access_denied", {bug_id => $input_id});
}
-
- # The user fields don't have an _id on the end of them in the database,
- # but the product & component fields do, so we have to have separate
- # code to deal with the different sets of fields here.
- foreach my $field (qw(assigned_to qa_contact reporter)) {
- delete $params->{"${field}_id"};
- $params->{$field} = $translated{$field}
- if exists $translated{$field};
- }
- foreach my $field (qw(product component)) {
- delete $params->{$field};
- $params->{"${field}_id"} = $translated{$field}
- if exists $translated{$field};
+ else {
+ ThrowUserError("bug_access_query", {bug_id => $input_id});
}
+ }
+}
- return $class->SUPER::match(@_);
+sub match {
+ my $class = shift;
+ my ($params) = @_;
+
+ # Allow matching certain fields by name (in addition to matching by ID).
+ my %translate_fields = (
+ assigned_to => 'Bugzilla::User',
+ qa_contact => 'Bugzilla::User',
+ reporter => 'Bugzilla::User',
+ product => 'Bugzilla::Product',
+ component => 'Bugzilla::Component',
+ );
+ my %translated;
+
+ foreach my $field (keys %translate_fields) {
+ my @ids;
+
+ # Convert names to ids. We use "exists" everywhere since people can
+ # legally specify "undef" to mean IS NULL (even though most of these
+ # fields can't be NULL, people can still specify it...).
+ if (exists $params->{$field}) {
+ my $names = $params->{$field};
+ my $type = $translate_fields{$field};
+ my $param = $type eq 'Bugzilla::User' ? 'login_name' : 'name';
+
+ # We call Bugzilla::Object::match directly to avoid the
+ # Bugzilla::User::match implementation which is different.
+ my $objects = Bugzilla::Object::match($type, {$param => $names});
+ push(@ids, map { $_->id } @$objects);
+ }
+
+ # You can also specify ids directly as arguments to this function,
+ # so include them in the list if they have been specified.
+ if (exists $params->{"${field}_id"}) {
+ my $current_ids = $params->{"${field}_id"};
+ my @id_array = ref $current_ids ? @$current_ids : ($current_ids);
+ push(@ids, @id_array);
+ }
+
+ # We do this "or" instead of a "scalar(@ids)" to handle the case
+ # when people passed only invalid object names. Otherwise we'd
+ # end up with a SUPER::match call with zero criteria (which dies).
+ if (exists $params->{$field} or exists $params->{"${field}_id"}) {
+ $translated{$field} = scalar(@ids) == 1 ? $ids[0] : \@ids;
+ }
+ }
+
+ # The user fields don't have an _id on the end of them in the database,
+ # but the product & component fields do, so we have to have separate
+ # code to deal with the different sets of fields here.
+ foreach my $field (qw(assigned_to qa_contact reporter)) {
+ delete $params->{"${field}_id"};
+ $params->{$field} = $translated{$field} if exists $translated{$field};
+ }
+ foreach my $field (qw(product component)) {
+ delete $params->{$field};
+ $params->{"${field}_id"} = $translated{$field} if exists $translated{$field};
+ }
+
+ return $class->SUPER::match(@_);
}
# Helps load up information for bugs for show_bug.cgi and other situations
# that will need to access info on lots of bugs.
sub preload {
- my ($class, $bugs) = @_;
- my $user = Bugzilla->user;
-
- # It would be faster but MUCH more complicated to select all the
- # deps for the entire list in one SQL statement. If we ever have
- # a profile that proves that that's necessary, we can switch over
- # to the more complex method.
- my @all_dep_ids;
- foreach my $bug (@$bugs) {
- push @all_dep_ids, @{ $bug->blocked }, @{ $bug->dependson };
- push @all_dep_ids, @{ $bug->duplicate_ids };
- push @all_dep_ids, @{ $bug->_preload_referenced_bugs };
- }
- @all_dep_ids = uniq @all_dep_ids;
- # If we don't do this, can_see_bug will do one call per bug in
- # the dependency and duplicate lists, in Bugzilla::Template::get_bug_link.
- $user->visible_bugs(\@all_dep_ids);
+ my ($class, $bugs) = @_;
+ my $user = Bugzilla->user;
+
+ # It would be faster but MUCH more complicated to select all the
+ # deps for the entire list in one SQL statement. If we ever have
+ # a profile that proves that that's necessary, we can switch over
+ # to the more complex method.
+ my @all_dep_ids;
+ foreach my $bug (@$bugs) {
+ push @all_dep_ids, @{$bug->blocked}, @{$bug->dependson};
+ push @all_dep_ids, @{$bug->duplicate_ids};
+ push @all_dep_ids, @{$bug->_preload_referenced_bugs};
+ }
+ @all_dep_ids = uniq @all_dep_ids;
+
+ # If we don't do this, can_see_bug will do one call per bug in
+ # the dependency and duplicate lists, in Bugzilla::Template::get_bug_link.
+ $user->visible_bugs(\@all_dep_ids);
}
# Helps load up bugs referenced in comments by retrieving them with a single
# query from the database and injecting bug objects into the object-cache.
sub _preload_referenced_bugs {
- my $self = shift;
+ my $self = shift;
- # inject current duplicates into the object-cache first
- foreach my $bug (@{ $self->duplicates }) {
- $bug->object_cache_set() unless Bugzilla::Bug->object_cache_get($bug->id);
- }
+ # inject current duplicates into the object-cache first
+ foreach my $bug (@{$self->duplicates}) {
+ $bug->object_cache_set() unless Bugzilla::Bug->object_cache_get($bug->id);
+ }
- # preload bugs from comments
- my $referenced_bug_ids = _extract_bug_ids($self->comments);
- my @ref_bug_ids = grep { !Bugzilla::Bug->object_cache_get($_) } @$referenced_bug_ids;
+ # preload bugs from comments
+ my $referenced_bug_ids = _extract_bug_ids($self->comments);
+ my @ref_bug_ids
+ = grep { !Bugzilla::Bug->object_cache_get($_) } @$referenced_bug_ids;
- # inject into object-cache
- my $referenced_bugs = Bugzilla::Bug->new_from_list(\@ref_bug_ids);
- $_->object_cache_set() foreach @$referenced_bugs;
+ # inject into object-cache
+ my $referenced_bugs = Bugzilla::Bug->new_from_list(\@ref_bug_ids);
+ $_->object_cache_set() foreach @$referenced_bugs;
- return $referenced_bug_ids;
+ return $referenced_bug_ids;
}
# Extract bug IDs mentioned in comments. This is much faster than calling quoteUrls().
sub _extract_bug_ids {
- my $comments = shift;
- my @bug_ids;
-
- my $params = Bugzilla->params;
- my @urlbases = ($params->{'urlbase'});
- push(@urlbases, $params->{'sslbase'}) if $params->{'sslbase'};
- my $urlbase_re = '(?:' . join('|', map { qr/$_/ } @urlbases) . ')';
- my $bug_word = template_var('terms')->{bug};
- my $bugs_word = template_var('terms')->{bugs};
-
- foreach my $comment (@$comments) {
- if ($comment->type == CMT_HAS_DUPE || $comment->type == CMT_DUPE_OF) {
- push @bug_ids, $comment->extra_data;
- next;
- }
- my $s = $comment->already_wrapped ? qr/\s/ : qr/\h/;
- my $text = $comment->body;
- # Full bug links
- push @bug_ids, $text =~ /\b$urlbase_re\Qshow_bug.cgi?id=\E([0-9]+)(?:\#c[0-9]+)?/g;
- # bug X
- my $bug_re = qr/\Q$bug_word\E$s*\#?$s*([0-9]+)/i;
- push @bug_ids, $text =~ /\b$bug_re/g;
- # bugs X, Y, Z
- my $bugs_re = qr/\Q$bugs_word\E$s*\#?$s*([0-9]+)(?:$s*,$s*\#?$s*([0-9]+))+/i;
- push @bug_ids, $text =~ /\b$bugs_re/g;
- # Old duplicate markers
- push @bug_ids, $text =~ /(?<=^\*\*\*\ This\ bug\ has\ been\ marked\ as\ a\ duplicate\ of\ )([0-9]+)(?=\ \*\*\*\Z)/;
- }
- # Make sure to filter invalid bug IDs.
- @bug_ids = grep { $_ < MAX_INT_32 } @bug_ids;
- return [uniq @bug_ids];
-}
+ my $comments = shift;
+ my @bug_ids;
-sub possible_duplicates {
- my ($class, $params) = @_;
- my $short_desc = $params->{summary};
- my $products = $params->{products} || [];
- my $limit = $params->{limit} || MAX_POSSIBLE_DUPLICATES;
- $limit = MAX_POSSIBLE_DUPLICATES if $limit > MAX_POSSIBLE_DUPLICATES;
- $products = [$products] if !ref($products) eq 'ARRAY';
-
- my $orig_limit = $limit;
- detaint_natural($limit)
- || ThrowCodeError('param_must_be_numeric',
- { function => 'possible_duplicates',
- param => $orig_limit });
+ my $params = Bugzilla->params;
+ my @urlbases = ($params->{'urlbase'});
+ push(@urlbases, $params->{'sslbase'}) if $params->{'sslbase'};
+ my $urlbase_re = '(?:' . join('|', map {qr/$_/} @urlbases) . ')';
+ my $bug_word = template_var('terms')->{bug};
+ my $bugs_word = template_var('terms')->{bugs};
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
- my @words = split(/[\b\s]+/, $short_desc || '');
- # Remove leading/trailing punctuation from words
- foreach my $word (@words) {
- $word =~ s/(?:^\W+|\W+$)//g;
+ foreach my $comment (@$comments) {
+ if ($comment->type == CMT_HAS_DUPE || $comment->type == CMT_DUPE_OF) {
+ push @bug_ids, $comment->extra_data;
+ next;
}
- # And make sure that each word is longer than 2 characters.
- @words = grep { defined $_ and length($_) > 2 } @words;
+ my $s = $comment->already_wrapped ? qr/\s/ : qr/\h/;
+ my $text = $comment->body;
- return [] if !@words;
+ # Full bug links
+ push @bug_ids,
+ $text =~ /\b$urlbase_re\Qshow_bug.cgi?id=\E([0-9]+)(?:\#c[0-9]+)?/g;
- my ($where_sql, $relevance_sql);
- if ($dbh->FULLTEXT_OR) {
- my $joined_terms = join($dbh->FULLTEXT_OR, @words);
- ($where_sql, $relevance_sql) =
- $dbh->sql_fulltext_search('bugs_fulltext.short_desc', $joined_terms);
- $relevance_sql ||= $where_sql;
- }
- else {
- my (@where, @relevance);
- foreach my $word (@words) {
- my ($term, $rel_term) = $dbh->sql_fulltext_search(
- 'bugs_fulltext.short_desc', $word);
- push(@where, $term);
- push(@relevance, $rel_term || $term);
- }
+ # bug X
+ my $bug_re = qr/\Q$bug_word\E$s*\#?$s*([0-9]+)/i;
+ push @bug_ids, $text =~ /\b$bug_re/g;
+
+ # bugs X, Y, Z
+ my $bugs_re = qr/\Q$bugs_word\E$s*\#?$s*([0-9]+)(?:$s*,$s*\#?$s*([0-9]+))+/i;
+ push @bug_ids, $text =~ /\b$bugs_re/g;
+
+ # Old duplicate markers
+ push @bug_ids, $text
+ =~ /(?<=^\*\*\*\ This\ bug\ has\ been\ marked\ as\ a\ duplicate\ of\ )([0-9]+)(?=\ \*\*\*\Z)/;
+ }
+
+ # Make sure to filter invalid bug IDs.
+ @bug_ids = grep { $_ < MAX_INT_32 } @bug_ids;
+ return [uniq @bug_ids];
+}
- $where_sql = join(' OR ', @where);
- $relevance_sql = join(' + ', @relevance);
+sub possible_duplicates {
+ my ($class, $params) = @_;
+ my $short_desc = $params->{summary};
+ my $products = $params->{products} || [];
+ my $limit = $params->{limit} || MAX_POSSIBLE_DUPLICATES;
+ $limit = MAX_POSSIBLE_DUPLICATES if $limit > MAX_POSSIBLE_DUPLICATES;
+ $products = [$products] if !ref($products) eq 'ARRAY';
+
+ my $orig_limit = $limit;
+ detaint_natural($limit)
+ || ThrowCodeError('param_must_be_numeric',
+ {function => 'possible_duplicates', param => $orig_limit});
+
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ my @words = split(/[\b\s]+/, $short_desc || '');
+
+ # Remove leading/trailing punctuation from words
+ foreach my $word (@words) {
+ $word =~ s/(?:^\W+|\W+$)//g;
+ }
+
+ # And make sure that each word is longer than 2 characters.
+ @words = grep { defined $_ and length($_) > 2 } @words;
+
+ return [] if !@words;
+
+ my ($where_sql, $relevance_sql);
+ if ($dbh->FULLTEXT_OR) {
+ my $joined_terms = join($dbh->FULLTEXT_OR, @words);
+ ($where_sql, $relevance_sql)
+ = $dbh->sql_fulltext_search('bugs_fulltext.short_desc', $joined_terms);
+ $relevance_sql ||= $where_sql;
+ }
+ else {
+ my (@where, @relevance);
+ foreach my $word (@words) {
+ my ($term, $rel_term)
+ = $dbh->sql_fulltext_search('bugs_fulltext.short_desc', $word);
+ push(@where, $term);
+ push(@relevance, $rel_term || $term);
}
- my $product_ids = join(',', map { $_->id } @$products);
- my $product_sql = $product_ids ? "AND product_id IN ($product_ids)" : "";
+ $where_sql = join(' OR ', @where);
+ $relevance_sql = join(' + ', @relevance);
+ }
+
+ my $product_ids = join(',', map { $_->id } @$products);
+ my $product_sql = $product_ids ? "AND product_id IN ($product_ids)" : "";
- # Because we collapse duplicates, we want to get slightly more bugs
- # than were actually asked for.
- my $sql_limit = $limit + 5;
+ # Because we collapse duplicates, we want to get slightly more bugs
+ # than were actually asked for.
+ my $sql_limit = $limit + 5;
- my $possible_dupes = $dbh->selectall_arrayref(
- "SELECT bugs.bug_id AS bug_id, bugs.resolution AS resolution,
+ my $possible_dupes = $dbh->selectall_arrayref(
+ "SELECT bugs.bug_id AS bug_id, bugs.resolution AS resolution,
($relevance_sql) AS relevance
FROM bugs
INNER JOIN bugs_fulltext ON bugs.bug_id = bugs_fulltext.bug_id
WHERE ($where_sql) $product_sql
- ORDER BY relevance DESC, bug_id DESC " .
- $dbh->sql_limit($sql_limit), {Slice=>{}});
-
- my @actual_dupe_ids;
- # Resolve duplicates into their ultimate target duplicates.
- foreach my $bug (@$possible_dupes) {
- my $push_id = $bug->{bug_id};
- if ($bug->{resolution} && $bug->{resolution} eq 'DUPLICATE') {
- $push_id = _resolve_ultimate_dup_id($bug->{bug_id});
- }
- push(@actual_dupe_ids, $push_id);
- }
- @actual_dupe_ids = uniq @actual_dupe_ids;
- if (scalar @actual_dupe_ids > $limit) {
- @actual_dupe_ids = @actual_dupe_ids[0..($limit-1)];
+ ORDER BY relevance DESC, bug_id DESC " . $dbh->sql_limit($sql_limit),
+ {Slice => {}}
+ );
+
+ my @actual_dupe_ids;
+
+ # Resolve duplicates into their ultimate target duplicates.
+ foreach my $bug (@$possible_dupes) {
+ my $push_id = $bug->{bug_id};
+ if ($bug->{resolution} && $bug->{resolution} eq 'DUPLICATE') {
+ $push_id = _resolve_ultimate_dup_id($bug->{bug_id});
}
+ push(@actual_dupe_ids, $push_id);
+ }
+ @actual_dupe_ids = uniq @actual_dupe_ids;
+ if (scalar @actual_dupe_ids > $limit) {
+ @actual_dupe_ids = @actual_dupe_ids[0 .. ($limit - 1)];
+ }
- my $visible = $user->visible_bugs(\@actual_dupe_ids);
- return $class->new_from_list($visible);
+ my $visible = $user->visible_bugs(\@actual_dupe_ids);
+ return $class->new_from_list($visible);
}
# Docs for create() (there's no POD in this file yet, but we very
@@ -680,591 +693,614 @@ sub possible_duplicates {
#
# C<assigned_to> - The full login name of the user who the bug is
# initially assigned to.
-# C<qa_contact> - The full login name of the QA Contact for this bug.
+# C<qa_contact> - The full login name of the QA Contact for this bug.
# Will be ignored if C<useqacontact> is off.
#
-# C<estimated_time> - For time-tracking. Will be ignored if
+# C<estimated_time> - For time-tracking. Will be ignored if
# C<timetrackinggroup> is not set, or if the current
# user is not a member of the timetrackinggroup.
# C<deadline> - For time-tracking. Will be ignored for the same
# reasons as C<estimated_time>.
sub create {
- my ($class, $params) = @_;
- my $dbh = Bugzilla->dbh;
-
- $dbh->bz_start_transaction();
-
- # These fields have default values which we can use if they are undefined.
- $params->{bug_severity} = Bugzilla->params->{defaultseverity}
- unless defined $params->{bug_severity};
- $params->{priority} = Bugzilla->params->{defaultpriority}
- unless defined $params->{priority};
- $params->{op_sys} = Bugzilla->params->{defaultopsys}
- unless defined $params->{op_sys};
- $params->{rep_platform} = Bugzilla->params->{defaultplatform}
- unless defined $params->{rep_platform};
- # Make sure a comment is always defined.
- $params->{comment} = '' unless defined $params->{comment};
-
- $class->check_required_create_fields($params);
- $params = $class->run_create_validators($params);
-
- # These are not a fields in the bugs table, so we don't pass them to
- # insert_create_data.
- my $bug_aliases = delete $params->{alias};
- my $cc_ids = delete $params->{cc};
- my $groups = delete $params->{groups};
- my $depends_on = delete $params->{dependson};
- my $blocked = delete $params->{blocked};
- my $keywords = delete $params->{keywords};
- my $creation_comment = delete $params->{comment};
- my $see_also = delete $params->{see_also};
-
- # We don't want the bug to appear in the system until it's correctly
- # protected by groups.
- my $timestamp = delete $params->{creation_ts};
-
- my $ms_values = $class->_extract_multi_selects($params);
- my $bug = $class->insert_create_data($params);
-
- # Add the group restrictions
- my $sth_group = $dbh->prepare(
- 'INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
- foreach my $group (@$groups) {
- $sth_group->execute($bug->bug_id, $group->id);
- }
-
- $dbh->do('UPDATE bugs SET creation_ts = ? WHERE bug_id = ?', undef,
- $timestamp, $bug->bug_id);
- # Update the bug instance as well
- $bug->{creation_ts} = $timestamp;
-
- # Add the CCs
- my $sth_cc = $dbh->prepare('INSERT INTO cc (bug_id, who) VALUES (?,?)');
- foreach my $user_id (@$cc_ids) {
- $sth_cc->execute($bug->bug_id, $user_id);
- }
-
- # Add in keywords
- my $sth_keyword = $dbh->prepare(
- 'INSERT INTO keywords (bug_id, keywordid) VALUES (?, ?)');
- foreach my $keyword_id (map($_->id, @$keywords)) {
- $sth_keyword->execute($bug->bug_id, $keyword_id);
- }
-
- # Set up dependencies (blocked/dependson)
- my $sth_deps = $dbh->prepare(
- 'INSERT INTO dependencies (blocked, dependson) VALUES (?, ?)');
- my $sth_bug_time = $dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
-
- foreach my $depends_on_id (@$depends_on) {
- $sth_deps->execute($bug->bug_id, $depends_on_id);
- # Log the reverse action on the other bug.
- LogActivityEntry($depends_on_id, 'blocked', '', $bug->bug_id,
- $bug->{reporter_id}, $timestamp);
- $sth_bug_time->execute($timestamp, $depends_on_id);
- }
- foreach my $blocked_id (@$blocked) {
- $sth_deps->execute($blocked_id, $bug->bug_id);
- # Log the reverse action on the other bug.
- LogActivityEntry($blocked_id, 'dependson', '', $bug->bug_id,
- $bug->{reporter_id}, $timestamp);
- $sth_bug_time->execute($timestamp, $blocked_id);
- }
-
- # Insert the values into the multiselect value tables
- foreach my $field (keys %$ms_values) {
- $dbh->do("DELETE FROM bug_$field where bug_id = ?",
- undef, $bug->bug_id);
- foreach my $value ( @{$ms_values->{$field}} ) {
- $dbh->do("INSERT INTO bug_$field (bug_id, value) VALUES (?,?)",
- undef, $bug->bug_id, $value);
- }
- }
-
- # Insert any see_also values
- if ($see_also) {
- my $see_also_array = $see_also;
- if (!ref $see_also_array) {
- $see_also = trim($see_also);
- $see_also_array = [ split(/[\s,]+/, $see_also) ];
- }
- foreach my $value (@$see_also_array) {
- $bug->add_see_also($value);
- }
- foreach my $see_also (@{ $bug->see_also }) {
- $see_also->insert_create_data($see_also);
- }
- foreach my $ref_bug (@{ $bug->{_update_ref_bugs} || [] }) {
- $ref_bug->update();
- }
- delete $bug->{_update_ref_bugs};
- }
-
- # Comment #0 handling...
-
- # We now have a bug id so we can fill this out
- $creation_comment->{'bug_id'} = $bug->id;
-
- # Insert the comment. We always insert a comment on bug creation,
- # but sometimes it's blank.
- Bugzilla::Comment->insert_create_data($creation_comment);
-
- # Set up aliases
- my $sth_aliases = $dbh->prepare('INSERT INTO bugs_aliases (alias, bug_id) VALUES (?, ?)');
- foreach my $alias (@$bug_aliases) {
- trick_taint($alias);
- $sth_aliases->execute($alias, $bug->bug_id);
- }
+ my ($class, $params) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+
+ # These fields have default values which we can use if they are undefined.
+ $params->{bug_severity} = Bugzilla->params->{defaultseverity}
+ unless defined $params->{bug_severity};
+ $params->{priority} = Bugzilla->params->{defaultpriority}
+ unless defined $params->{priority};
+ $params->{op_sys} = Bugzilla->params->{defaultopsys}
+ unless defined $params->{op_sys};
+ $params->{rep_platform} = Bugzilla->params->{defaultplatform}
+ unless defined $params->{rep_platform};
+
+ # Make sure a comment is always defined.
+ $params->{comment} = '' unless defined $params->{comment};
+
+ $class->check_required_create_fields($params);
+ $params = $class->run_create_validators($params);
+
+ # These are not a fields in the bugs table, so we don't pass them to
+ # insert_create_data.
+ my $bug_aliases = delete $params->{alias};
+ my $cc_ids = delete $params->{cc};
+ my $groups = delete $params->{groups};
+ my $depends_on = delete $params->{dependson};
+ my $blocked = delete $params->{blocked};
+ my $keywords = delete $params->{keywords};
+ my $creation_comment = delete $params->{comment};
+ my $see_also = delete $params->{see_also};
+
+ # We don't want the bug to appear in the system until it's correctly
+ # protected by groups.
+ my $timestamp = delete $params->{creation_ts};
+
+ my $ms_values = $class->_extract_multi_selects($params);
+ my $bug = $class->insert_create_data($params);
+
+ # Add the group restrictions
+ my $sth_group
+ = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
+ foreach my $group (@$groups) {
+ $sth_group->execute($bug->bug_id, $group->id);
+ }
+
+ $dbh->do('UPDATE bugs SET creation_ts = ? WHERE bug_id = ?',
+ undef, $timestamp, $bug->bug_id);
+
+ # Update the bug instance as well
+ $bug->{creation_ts} = $timestamp;
+
+ # Add the CCs
+ my $sth_cc = $dbh->prepare('INSERT INTO cc (bug_id, who) VALUES (?,?)');
+ foreach my $user_id (@$cc_ids) {
+ $sth_cc->execute($bug->bug_id, $user_id);
+ }
+
+ # Add in keywords
+ my $sth_keyword
+ = $dbh->prepare('INSERT INTO keywords (bug_id, keywordid) VALUES (?, ?)');
+ foreach my $keyword_id (map($_->id, @$keywords)) {
+ $sth_keyword->execute($bug->bug_id, $keyword_id);
+ }
+
+ # Set up dependencies (blocked/dependson)
+ my $sth_deps = $dbh->prepare(
+ 'INSERT INTO dependencies (blocked, dependson) VALUES (?, ?)');
+ my $sth_bug_time
+ = $dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
+
+ foreach my $depends_on_id (@$depends_on) {
+ $sth_deps->execute($bug->bug_id, $depends_on_id);
+
+ # Log the reverse action on the other bug.
+ LogActivityEntry($depends_on_id, 'blocked', '', $bug->bug_id,
+ $bug->{reporter_id}, $timestamp);
+ $sth_bug_time->execute($timestamp, $depends_on_id);
+ }
+ foreach my $blocked_id (@$blocked) {
+ $sth_deps->execute($blocked_id, $bug->bug_id);
+
+ # Log the reverse action on the other bug.
+ LogActivityEntry($blocked_id, 'dependson', '', $bug->bug_id,
+ $bug->{reporter_id}, $timestamp);
+ $sth_bug_time->execute($timestamp, $blocked_id);
+ }
+
+ # Insert the values into the multiselect value tables
+ foreach my $field (keys %$ms_values) {
+ $dbh->do("DELETE FROM bug_$field where bug_id = ?", undef, $bug->bug_id);
+ foreach my $value (@{$ms_values->{$field}}) {
+ $dbh->do("INSERT INTO bug_$field (bug_id, value) VALUES (?,?)",
+ undef, $bug->bug_id, $value);
+ }
+ }
+
+ # Insert any see_also values
+ if ($see_also) {
+ my $see_also_array = $see_also;
+ if (!ref $see_also_array) {
+ $see_also = trim($see_also);
+ $see_also_array = [split(/[\s,]+/, $see_also)];
+ }
+ foreach my $value (@$see_also_array) {
+ $bug->add_see_also($value);
+ }
+ foreach my $see_also (@{$bug->see_also}) {
+ $see_also->insert_create_data($see_also);
+ }
+ foreach my $ref_bug (@{$bug->{_update_ref_bugs} || []}) {
+ $ref_bug->update();
+ }
+ delete $bug->{_update_ref_bugs};
+ }
+
+ # Comment #0 handling...
+
+ # We now have a bug id so we can fill this out
+ $creation_comment->{'bug_id'} = $bug->id;
+
+ # Insert the comment. We always insert a comment on bug creation,
+ # but sometimes it's blank.
+ Bugzilla::Comment->insert_create_data($creation_comment);
+
+ # Set up aliases
+ my $sth_aliases
+ = $dbh->prepare('INSERT INTO bugs_aliases (alias, bug_id) VALUES (?, ?)');
+ foreach my $alias (@$bug_aliases) {
+ trick_taint($alias);
+ $sth_aliases->execute($alias, $bug->bug_id);
+ }
- Bugzilla::Hook::process('bug_end_of_create', { bug => $bug,
- timestamp => $timestamp,
- });
+ Bugzilla::Hook::process('bug_end_of_create',
+ {bug => $bug, timestamp => $timestamp,});
- $dbh->bz_commit_transaction();
+ $bug->_sync_fulltext(new_bug => 1);
- # Because MySQL doesn't support transactions on the fulltext table,
- # we do this after we've committed the transaction. That way we're
- # sure we're inserting a good Bug ID.
- $bug->_sync_fulltext( new_bug => 1 );
+ $dbh->bz_commit_transaction();
- return $bug;
+ return $bug;
}
sub run_create_validators {
- my $class = shift;
- my $params = $class->SUPER::run_create_validators(@_);
-
- # Add classification for checking mandatory fields which depend on it
- $params->{classification} = $params->{product}->classification->name;
-
- my @mandatory_fields = @{ Bugzilla->fields({ is_mandatory => 1,
- enter_bug => 1,
- obsolete => 0 }) };
- foreach my $field (@mandatory_fields) {
- $class->_check_field_is_mandatory($params->{$field->name}, $field,
- $params);
- }
-
- my $product = delete $params->{product};
- $params->{product_id} = $product->id;
- my $component = delete $params->{component};
- $params->{component_id} = $component->id;
-
- # Callers cannot set reporter, creation_ts, or delta_ts.
- $params->{reporter} = $class->_check_reporter();
- $params->{delta_ts} = $params->{creation_ts};
-
- if ($params->{estimated_time}) {
- $params->{remaining_time} = $params->{estimated_time};
- }
-
- $class->_check_strict_isolation($params->{cc}, $params->{assigned_to},
- $params->{qa_contact}, $product);
+ my $class = shift;
+ my $params = $class->SUPER::run_create_validators(@_);
- # You can't set these fields.
- delete $params->{lastdiffed};
- delete $params->{bug_id};
- delete $params->{classification};
+ # Add classification for checking mandatory fields which depend on it
+ $params->{classification} = $params->{product}->classification->name;
- Bugzilla::Hook::process('bug_end_of_create_validators',
- { params => $params });
+ my @mandatory_fields
+ = @{Bugzilla->fields({is_mandatory => 1, enter_bug => 1, obsolete => 0})};
+ foreach my $field (@mandatory_fields) {
+ $class->_check_field_is_mandatory($params->{$field->name}, $field, $params);
+ }
- # And this is not a valid DB field, it's just used as part of
- # _check_dependencies to avoid running it twice for both blocked
- # and dependson.
- delete $params->{_dependencies_validated};
+ my $product = delete $params->{product};
+ $params->{product_id} = $product->id;
+ my $component = delete $params->{component};
+ $params->{component_id} = $component->id;
- return $params;
-}
+ # Callers cannot set reporter, creation_ts, or delta_ts.
+ $params->{reporter} = $class->_check_reporter();
+ $params->{delta_ts} = $params->{creation_ts};
-sub update {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
+ if ($params->{estimated_time}) {
+ $params->{remaining_time} = $params->{estimated_time};
+ }
- # XXX This is just a temporary hack until all updating happens
- # inside this function.
- my $delta_ts = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $class->_check_strict_isolation($params->{cc}, $params->{assigned_to},
+ $params->{qa_contact}, $product);
- $dbh->bz_start_transaction();
+ # You can't set these fields.
+ delete $params->{lastdiffed};
+ delete $params->{bug_id};
+ delete $params->{classification};
- my ($changes, $old_bug) = $self->SUPER::update(@_);
+ Bugzilla::Hook::process('bug_end_of_create_validators', {params => $params});
- Bugzilla::Hook::process('bug_start_of_update',
- { timestamp => $delta_ts, bug => $self,
- old_bug => $old_bug, changes => $changes });
+ # And this is not a valid DB field, it's just used as part of
+ # _check_dependencies to avoid running it twice for both blocked
+ # and dependson.
+ delete $params->{_dependencies_validated};
- # Certain items in $changes have to be fixed so that they hold
- # a name instead of an ID.
- foreach my $field (qw(product_id component_id)) {
- my $change = delete $changes->{$field};
- if ($change) {
- my $new_field = $field;
- $new_field =~ s/_id$//;
- $changes->{$new_field} =
- [$self->{"_old_${new_field}_name"}, $self->$new_field];
- }
- }
- foreach my $field (qw(qa_contact assigned_to)) {
- if ($changes->{$field}) {
- my ($from, $to) = @{ $changes->{$field} };
- $from = $old_bug->$field->login if $from;
- $to = $self->$field->login if $to;
- $changes->{$field} = [$from, $to];
- }
- }
-
- # CC
- my @old_cc = map {$_->id} @{$old_bug->cc_users};
- my @new_cc = map {$_->id} @{$self->cc_users};
- my ($removed_cc, $added_cc) = diff_arrays(\@old_cc, \@new_cc);
-
- if (scalar @$removed_cc) {
- $dbh->do('DELETE FROM cc WHERE bug_id = ? AND '
- . $dbh->sql_in('who', $removed_cc), undef, $self->id);
- }
- foreach my $user_id (@$added_cc) {
- $dbh->do('INSERT INTO cc (bug_id, who) VALUES (?,?)',
- undef, $self->id, $user_id);
- }
- # If any changes were found, record it in the activity log
- if (scalar @$removed_cc || scalar @$added_cc) {
- my $removed_users = Bugzilla::User->new_from_list($removed_cc);
- my $added_users = Bugzilla::User->new_from_list($added_cc);
- my $removed_names = join(', ', (map {$_->login} @$removed_users));
- my $added_names = join(', ', (map {$_->login} @$added_users));
- $changes->{cc} = [$removed_names, $added_names];
- }
-
- # Aliases
- my $old_aliases = $old_bug->alias;
- my $new_aliases = $self->alias;
- my ($removed_aliases, $added_aliases) = diff_arrays($old_aliases, $new_aliases);
-
- foreach my $alias (@$removed_aliases) {
- $dbh->do('DELETE FROM bugs_aliases WHERE bug_id = ? AND alias = ?',
- undef, $self->id, $alias);
- }
- foreach my $alias (@$added_aliases) {
- trick_taint($alias);
- $dbh->do('INSERT INTO bugs_aliases (bug_id, alias) VALUES (?,?)',
- undef, $self->id, $alias);
- }
- # If any changes were found, record it in the activity log
- if (scalar @$removed_aliases || scalar @$added_aliases) {
- $changes->{alias} = [join(', ', @$removed_aliases), join(', ', @$added_aliases)];
- }
-
- # Keywords
- my @old_kw_ids = map { $_->id } @{$old_bug->keyword_objects};
- my @new_kw_ids = map { $_->id } @{$self->keyword_objects};
-
- my ($removed_kw, $added_kw) = diff_arrays(\@old_kw_ids, \@new_kw_ids);
-
- if (scalar @$removed_kw) {
- $dbh->do('DELETE FROM keywords WHERE bug_id = ? AND '
- . $dbh->sql_in('keywordid', $removed_kw), undef, $self->id);
- }
- foreach my $keyword_id (@$added_kw) {
- $dbh->do('INSERT INTO keywords (bug_id, keywordid) VALUES (?,?)',
- undef, $self->id, $keyword_id);
- }
- # If any changes were found, record it in the activity log
- if (scalar @$removed_kw || scalar @$added_kw) {
- my $removed_keywords = Bugzilla::Keyword->new_from_list($removed_kw);
- my $added_keywords = Bugzilla::Keyword->new_from_list($added_kw);
- my $removed_names = join(', ', (map {$_->name} @$removed_keywords));
- my $added_names = join(', ', (map {$_->name} @$added_keywords));
- $changes->{keywords} = [$removed_names, $added_names];
- }
-
- # Dependencies
- foreach my $pair ([qw(dependson blocked)], [qw(blocked dependson)]) {
- my ($type, $other) = @$pair;
- my $old = $old_bug->$type;
- my $new = $self->$type;
-
- my ($removed, $added) = diff_arrays($old, $new);
- foreach my $removed_id (@$removed) {
- $dbh->do("DELETE FROM dependencies WHERE $type = ? AND $other = ?",
- undef, $removed_id, $self->id);
-
- # Add an activity entry for the other bug.
- LogActivityEntry($removed_id, $other, $self->id, '',
- $user->id, $delta_ts);
- # Update delta_ts on the other bug so that we trigger mid-airs.
- $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
- undef, $delta_ts, $removed_id);
- }
- foreach my $added_id (@$added) {
- $dbh->do("INSERT INTO dependencies ($type, $other) VALUES (?,?)",
- undef, $added_id, $self->id);
-
- # Add an activity entry for the other bug.
- LogActivityEntry($added_id, $other, '', $self->id,
- $user->id, $delta_ts);
- # Update delta_ts on the other bug so that we trigger mid-airs.
- $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
- undef, $delta_ts, $added_id);
- }
-
- if (scalar(@$removed) || scalar(@$added)) {
- $changes->{$type} = [join(', ', @$removed), join(', ', @$added)];
- }
- }
-
- # Groups
- my %old_groups = map {$_->id => $_} @{$old_bug->groups_in};
- my %new_groups = map {$_->id => $_} @{$self->groups_in};
- my ($removed_gr, $added_gr) = diff_arrays([keys %old_groups],
- [keys %new_groups]);
- if (scalar @$removed_gr || scalar @$added_gr) {
- if (@$removed_gr) {
- my $qmarks = join(',', ('?') x @$removed_gr);
- $dbh->do("DELETE FROM bug_group_map
- WHERE bug_id = ? AND group_id IN ($qmarks)", undef,
- $self->id, @$removed_gr);
- }
- my $sth_insert = $dbh->prepare(
- 'INSERT INTO bug_group_map (bug_id, group_id) VALUES (?,?)');
- foreach my $gid (@$added_gr) {
- $sth_insert->execute($self->id, $gid);
- }
- my @removed_names = map { $old_groups{$_}->name } @$removed_gr;
- my @added_names = map { $new_groups{$_}->name } @$added_gr;
- $changes->{'bug_group'} = [join(', ', @removed_names),
- join(', ', @added_names)];
- }
-
- # Comments
- foreach my $comment (@{$self->{added_comments} || []}) {
- # Override the Comment's timestamp to be identical to the update
- # timestamp.
- $comment->{bug_when} = $delta_ts;
- $comment = Bugzilla::Comment->insert_create_data($comment);
- if ($comment->work_time) {
- LogActivityEntry($self->id, "work_time", "", $comment->work_time,
- $user->id, $delta_ts);
- }
- }
-
- # Comment Privacy
- foreach my $comment (@{$self->{comment_isprivate} || []}) {
- $comment->update();
-
- my ($from, $to)
- = $comment->is_private ? (0, 1) : (1, 0);
- LogActivityEntry($self->id, "longdescs.isprivate", $from, $to,
- $user->id, $delta_ts, $comment->id);
- }
-
- # Clear the cache of comments
- delete $self->{comments};
-
- # Insert the values into the multiselect value tables
- my @multi_selects = grep {$_->type == FIELD_TYPE_MULTI_SELECT}
- Bugzilla->active_custom_fields;
- foreach my $field (@multi_selects) {
- my $name = $field->name;
- my ($removed, $added) = diff_arrays($old_bug->$name, $self->$name);
- if (scalar @$removed || scalar @$added) {
- $changes->{$name} = [join(', ', @$removed), join(', ', @$added)];
-
- $dbh->do("DELETE FROM bug_$name where bug_id = ?",
- undef, $self->id);
- foreach my $value (@{$self->$name}) {
- $dbh->do("INSERT INTO bug_$name (bug_id, value) VALUES (?,?)",
- undef, $self->id, $value);
- }
- }
- }
-
- # See Also
-
- my ($removed_see, $added_see) =
- diff_arrays($old_bug->see_also, $self->see_also, 'name');
-
- $_->remove_from_db foreach @$removed_see;
- $_->insert_create_data($_) foreach @$added_see;
-
- # If any changes were found, record it in the activity log
- if (scalar @$removed_see || scalar @$added_see) {
- $changes->{see_also} = [join(', ', map { $_->name } @$removed_see),
- join(', ', map { $_->name } @$added_see)];
- }
+ return $params;
+}
- # Flags
- my ($removed, $added) = Bugzilla::Flag->update_flags($self, $old_bug, $delta_ts);
- if ($removed || $added) {
- $changes->{'flagtypes.name'} = [$removed, $added];
- }
+sub update {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
- $_->update foreach @{ $self->{_update_ref_bugs} || [] };
- delete $self->{_update_ref_bugs};
-
- # Log bugs_activity items
- # XXX Eventually, when bugs_activity is able to track the dupe_id,
- # this code should go below the duplicates-table-updating code below.
- foreach my $field (keys %$changes) {
- my $change = $changes->{$field};
- my $from = defined $change->[0] ? $change->[0] : '';
- my $to = defined $change->[1] ? $change->[1] : '';
- LogActivityEntry($self->id, $field, $from, $to,
- $user->id, $delta_ts);
- }
+ # XXX This is just a temporary hack until all updating happens
+ # inside this function.
+ my $delta_ts = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- # Check if we have to update the duplicates table and the other bug.
- my ($old_dup, $cur_dup) = ($old_bug->dup_id || 0, $self->dup_id || 0);
- if ($old_dup != $cur_dup) {
- $dbh->do("DELETE FROM duplicates WHERE dupe = ?", undef, $self->id);
- if ($cur_dup) {
- $dbh->do('INSERT INTO duplicates (dupe, dupe_of) VALUES (?,?)',
- undef, $self->id, $cur_dup);
- if (my $update_dup = delete $self->{_dup_for_update}) {
- $update_dup->update();
- }
- }
+ $dbh->bz_start_transaction();
- $changes->{'dup_id'} = [$old_dup || undef, $cur_dup || undef];
- }
+ my ($changes, $old_bug) = $self->SUPER::update(@_);
- Bugzilla::Hook::process('bug_end_of_update',
- { bug => $self, timestamp => $delta_ts, changes => $changes,
- old_bug => $old_bug });
-
- # If any change occurred, refresh the timestamp of the bug.
- if (scalar(keys %$changes) || $self->{added_comments}
- || $self->{comment_isprivate})
+ Bugzilla::Hook::process(
+ 'bug_start_of_update',
{
- $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
- undef, ($delta_ts, $self->id));
- $self->{delta_ts} = $delta_ts;
- }
-
- # Update last-visited
- if ($user->is_involved_in_bug($self)) {
- $self->update_user_last_visit($user, $delta_ts);
- }
-
- # If a user is no longer involved, remove their last visit entry
- my $last_visits =
- Bugzilla::BugUserLastVisit->match({ bug_id => $self->id });
- foreach my $lv (@$last_visits) {
- $lv->remove_from_db() unless $lv->user->is_involved_in_bug($self);
- }
-
- # Update bug ignore data if user wants to ignore mail for this bug
- if (exists $self->{'bug_ignored'}) {
- my $bug_ignored_changed;
- if ($self->{'bug_ignored'} && !$user->is_bug_ignored($self->id)) {
- $dbh->do('INSERT INTO email_bug_ignore
- (user_id, bug_id) VALUES (?, ?)',
- undef, $user->id, $self->id);
- $bug_ignored_changed = 1;
-
- }
- elsif (!$self->{'bug_ignored'} && $user->is_bug_ignored($self->id)) {
- $dbh->do('DELETE FROM email_bug_ignore
- WHERE user_id = ? AND bug_id = ?',
- undef, $user->id, $self->id);
- $bug_ignored_changed = 1;
- }
- delete $user->{bugs_ignored} if $bug_ignored_changed;
- }
-
- $dbh->bz_commit_transaction();
-
- # The only problem with this here is that update() is often called
- # in the middle of a transaction, and if that transaction is rolled
- # back, this change will *not* be rolled back. As we expect rollbacks
- # to be extremely rare, that is OK for us.
- $self->_sync_fulltext(
- update_short_desc => $changes->{short_desc},
- update_comments => $self->{added_comments} || $self->{comment_isprivate}
+ timestamp => $delta_ts,
+ bug => $self,
+ old_bug => $old_bug,
+ changes => $changes
+ }
+ );
+
+ # Certain items in $changes have to be fixed so that they hold
+ # a name instead of an ID.
+ foreach my $field (qw(product_id component_id)) {
+ my $change = delete $changes->{$field};
+ if ($change) {
+ my $new_field = $field;
+ $new_field =~ s/_id$//;
+ $changes->{$new_field} = [$self->{"_old_${new_field}_name"}, $self->$new_field];
+ }
+ }
+ foreach my $field (qw(qa_contact assigned_to)) {
+ if ($changes->{$field}) {
+ my ($from, $to) = @{$changes->{$field}};
+ $from = $old_bug->$field->login if $from;
+ $to = $self->$field->login if $to;
+ $changes->{$field} = [$from, $to];
+ }
+ }
+
+ # CC
+ my @old_cc = map { $_->id } @{$old_bug->cc_users};
+ my @new_cc = map { $_->id } @{$self->cc_users};
+ my ($removed_cc, $added_cc) = diff_arrays(\@old_cc, \@new_cc);
+
+ if (scalar @$removed_cc) {
+ $dbh->do(
+ 'DELETE FROM cc WHERE bug_id = ? AND ' . $dbh->sql_in('who', $removed_cc),
+ undef, $self->id);
+ }
+ foreach my $user_id (@$added_cc) {
+ $dbh->do('INSERT INTO cc (bug_id, who) VALUES (?,?)',
+ undef, $self->id, $user_id);
+ }
+
+ # If any changes were found, record it in the activity log
+ if (scalar @$removed_cc || scalar @$added_cc) {
+ my $removed_users = Bugzilla::User->new_from_list($removed_cc);
+ my $added_users = Bugzilla::User->new_from_list($added_cc);
+ my $removed_names = join(', ', (map { $_->login } @$removed_users));
+ my $added_names = join(', ', (map { $_->login } @$added_users));
+ $changes->{cc} = [$removed_names, $added_names];
+ }
+
+ # Aliases
+ my $old_aliases = $old_bug->alias;
+ my $new_aliases = $self->alias;
+ my ($removed_aliases, $added_aliases) = diff_arrays($old_aliases, $new_aliases);
+
+ foreach my $alias (@$removed_aliases) {
+ $dbh->do('DELETE FROM bugs_aliases WHERE bug_id = ? AND alias = ?',
+ undef, $self->id, $alias);
+ }
+ foreach my $alias (@$added_aliases) {
+ trick_taint($alias);
+ $dbh->do('INSERT INTO bugs_aliases (bug_id, alias) VALUES (?,?)',
+ undef, $self->id, $alias);
+ }
+
+ # If any changes were found, record it in the activity log
+ if (scalar @$removed_aliases || scalar @$added_aliases) {
+ $changes->{alias}
+ = [join(', ', @$removed_aliases), join(', ', @$added_aliases)];
+ }
+
+ # Keywords
+ my @old_kw_ids = map { $_->id } @{$old_bug->keyword_objects};
+ my @new_kw_ids = map { $_->id } @{$self->keyword_objects};
+
+ my ($removed_kw, $added_kw) = diff_arrays(\@old_kw_ids, \@new_kw_ids);
+
+ if (scalar @$removed_kw) {
+ $dbh->do(
+ 'DELETE FROM keywords WHERE bug_id = ? AND '
+ . $dbh->sql_in('keywordid', $removed_kw),
+ undef, $self->id
);
-
- # Remove obsolete internal variables.
- delete $self->{'_old_assigned_to'};
- delete $self->{'_old_qa_contact'};
-
- # Also flush the visible_bugs cache for this bug as the user's
- # relationship with this bug may have changed.
- delete $user->{_visible_bugs_cache}->{$self->id};
-
- return $changes;
+ }
+ foreach my $keyword_id (@$added_kw) {
+ $dbh->do('INSERT INTO keywords (bug_id, keywordid) VALUES (?,?)',
+ undef, $self->id, $keyword_id);
+ }
+
+ # If any changes were found, record it in the activity log
+ if (scalar @$removed_kw || scalar @$added_kw) {
+ my $removed_keywords = Bugzilla::Keyword->new_from_list($removed_kw);
+ my $added_keywords = Bugzilla::Keyword->new_from_list($added_kw);
+ my $removed_names = join(', ', (map { $_->name } @$removed_keywords));
+ my $added_names = join(', ', (map { $_->name } @$added_keywords));
+ $changes->{keywords} = [$removed_names, $added_names];
+ }
+
+ # Dependencies
+ foreach my $pair ([qw(dependson blocked)], [qw(blocked dependson)]) {
+ my ($type, $other) = @$pair;
+ my $old = $old_bug->$type;
+ my $new = $self->$type;
+
+ my ($removed, $added) = diff_arrays($old, $new);
+ foreach my $removed_id (@$removed) {
+ $dbh->do("DELETE FROM dependencies WHERE $type = ? AND $other = ?",
+ undef, $removed_id, $self->id);
+
+ # Add an activity entry for the other bug.
+ LogActivityEntry($removed_id, $other, $self->id, '', $user->id, $delta_ts);
+
+ # Update delta_ts on the other bug so that we trigger mid-airs.
+ $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+ undef, $delta_ts, $removed_id);
+ }
+ foreach my $added_id (@$added) {
+ $dbh->do("INSERT INTO dependencies ($type, $other) VALUES (?,?)",
+ undef, $added_id, $self->id);
+
+ # Add an activity entry for the other bug.
+ LogActivityEntry($added_id, $other, '', $self->id, $user->id, $delta_ts);
+
+ # Update delta_ts on the other bug so that we trigger mid-airs.
+ $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+ undef, $delta_ts, $added_id);
+ }
+
+ if (scalar(@$removed) || scalar(@$added)) {
+ $changes->{$type} = [join(', ', @$removed), join(', ', @$added)];
+ }
+ }
+
+ # Groups
+ my %old_groups = map { $_->id => $_ } @{$old_bug->groups_in};
+ my %new_groups = map { $_->id => $_ } @{$self->groups_in};
+ my ($removed_gr, $added_gr)
+ = diff_arrays([keys %old_groups], [keys %new_groups]);
+ if (scalar @$removed_gr || scalar @$added_gr) {
+ if (@$removed_gr) {
+ my $qmarks = join(',', ('?') x @$removed_gr);
+ $dbh->do(
+ "DELETE FROM bug_group_map
+ WHERE bug_id = ? AND group_id IN ($qmarks)", undef, $self->id,
+ @$removed_gr
+ );
+ }
+ my $sth_insert
+ = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) VALUES (?,?)');
+ foreach my $gid (@$added_gr) {
+ $sth_insert->execute($self->id, $gid);
+ }
+ my @removed_names = map { $old_groups{$_}->name } @$removed_gr;
+ my @added_names = map { $new_groups{$_}->name } @$added_gr;
+ $changes->{'bug_group'}
+ = [join(', ', @removed_names), join(', ', @added_names)];
+ }
+
+ # Comments
+ foreach my $comment (@{$self->{added_comments} || []}) {
+
+ # Override the Comment's timestamp to be identical to the update
+ # timestamp.
+ $comment->{bug_when} = $delta_ts;
+ $comment = Bugzilla::Comment->insert_create_data($comment);
+ if ($comment->work_time) {
+ LogActivityEntry($self->id, "work_time", "", $comment->work_time, $user->id,
+ $delta_ts);
+ }
+ }
+
+ # Comment Privacy
+ foreach my $comment (@{$self->{comment_isprivate} || []}) {
+ $comment->update();
+
+ my ($from, $to) = $comment->is_private ? (0, 1) : (1, 0);
+ LogActivityEntry($self->id, "longdescs.isprivate", $from, $to, $user->id,
+ $delta_ts, $comment->id);
+ }
+
+ # Clear the cache of comments
+ delete $self->{comments};
+
+ # Insert the values into the multiselect value tables
+ my @multi_selects
+ = grep { $_->type == FIELD_TYPE_MULTI_SELECT } Bugzilla->active_custom_fields;
+ foreach my $field (@multi_selects) {
+ my $name = $field->name;
+ my ($removed, $added) = diff_arrays($old_bug->$name, $self->$name);
+ if (scalar @$removed || scalar @$added) {
+ $changes->{$name} = [join(', ', @$removed), join(', ', @$added)];
+
+ $dbh->do("DELETE FROM bug_$name where bug_id = ?", undef, $self->id);
+ foreach my $value (@{$self->$name}) {
+ $dbh->do("INSERT INTO bug_$name (bug_id, value) VALUES (?,?)",
+ undef, $self->id, $value);
+ }
+ }
+ }
+
+ # See Also
+
+ my ($removed_see, $added_see)
+ = diff_arrays($old_bug->see_also, $self->see_also, 'name');
+
+ $_->remove_from_db foreach @$removed_see;
+ $_->insert_create_data($_) foreach @$added_see;
+
+ # If any changes were found, record it in the activity log
+ if (scalar @$removed_see || scalar @$added_see) {
+ $changes->{see_also} = [
+ join(', ', map { $_->name } @$removed_see),
+ join(', ', map { $_->name } @$added_see)
+ ];
+ }
+
+ # Flags
+ my ($removed, $added)
+ = Bugzilla::Flag->update_flags($self, $old_bug, $delta_ts);
+ if ($removed || $added) {
+ $changes->{'flagtypes.name'} = [$removed, $added];
+ }
+
+ $_->update foreach @{$self->{_update_ref_bugs} || []};
+ delete $self->{_update_ref_bugs};
+
+ # Log bugs_activity items
+ # XXX Eventually, when bugs_activity is able to track the dupe_id,
+ # this code should go below the duplicates-table-updating code below.
+ foreach my $field (keys %$changes) {
+ my $change = $changes->{$field};
+ my $from = defined $change->[0] ? $change->[0] : '';
+ my $to = defined $change->[1] ? $change->[1] : '';
+ LogActivityEntry($self->id, $field, $from, $to, $user->id, $delta_ts);
+ }
+
+ # Check if we have to update the duplicates table and the other bug.
+ my ($old_dup, $cur_dup) = ($old_bug->dup_id || 0, $self->dup_id || 0);
+ if ($old_dup != $cur_dup) {
+ $dbh->do("DELETE FROM duplicates WHERE dupe = ?", undef, $self->id);
+ if ($cur_dup) {
+ $dbh->do('INSERT INTO duplicates (dupe, dupe_of) VALUES (?,?)',
+ undef, $self->id, $cur_dup);
+ if (my $update_dup = delete $self->{_dup_for_update}) {
+ $update_dup->update();
+ }
+ }
+
+ $changes->{'dup_id'} = [$old_dup || undef, $cur_dup || undef];
+ }
+
+ Bugzilla::Hook::process(
+ 'bug_end_of_update',
+ {
+ bug => $self,
+ timestamp => $delta_ts,
+ changes => $changes,
+ old_bug => $old_bug
+ }
+ );
+
+ # If any change occurred, refresh the timestamp of the bug.
+ if ( scalar(keys %$changes)
+ || $self->{added_comments}
+ || $self->{comment_isprivate})
+ {
+ $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+ undef, ($delta_ts, $self->id));
+ $self->{delta_ts} = $delta_ts;
+ }
+
+ # Update last-visited
+ if ($user->is_involved_in_bug($self)) {
+ $self->update_user_last_visit($user, $delta_ts);
+ }
+
+ # If a user is no longer involved, remove their last visit entry
+ my $last_visits = Bugzilla::BugUserLastVisit->match({bug_id => $self->id});
+ foreach my $lv (@$last_visits) {
+ $lv->remove_from_db() unless $lv->user->is_involved_in_bug($self);
+ }
+
+ # Update bug ignore data if user wants to ignore mail for this bug
+ if (exists $self->{'bug_ignored'}) {
+ my $bug_ignored_changed;
+ if ($self->{'bug_ignored'} && !$user->is_bug_ignored($self->id)) {
+ $dbh->do(
+ 'INSERT INTO email_bug_ignore
+ (user_id, bug_id) VALUES (?, ?)', undef, $user->id, $self->id
+ );
+ $bug_ignored_changed = 1;
+
+ }
+ elsif (!$self->{'bug_ignored'} && $user->is_bug_ignored($self->id)) {
+ $dbh->do(
+ 'DELETE FROM email_bug_ignore
+ WHERE user_id = ? AND bug_id = ?', undef, $user->id, $self->id
+ );
+ $bug_ignored_changed = 1;
+ }
+ delete $user->{bugs_ignored} if $bug_ignored_changed;
+ }
+
+ $self->_sync_fulltext(
+ update_short_desc => $changes->{short_desc},
+ update_comments => $self->{added_comments} || $self->{comment_isprivate}
+ );
+
+ $dbh->bz_commit_transaction();
+
+ # Remove obsolete internal variables.
+ delete $self->{'_old_assigned_to'};
+ delete $self->{'_old_qa_contact'};
+
+ # Also flush the visible_bugs cache for this bug as the user's
+ # relationship with this bug may have changed.
+ delete $user->{_visible_bugs_cache}->{$self->id};
+
+ return $changes;
}
# Used by create().
# We need to handle multi-select fields differently than normal fields,
# because they're arrays and don't go into the bugs table.
sub _extract_multi_selects {
- my ($invocant, $params) = @_;
-
- my @multi_selects = grep {$_->type == FIELD_TYPE_MULTI_SELECT}
- Bugzilla->active_custom_fields;
- my %ms_values;
- foreach my $field (@multi_selects) {
- my $name = $field->name;
- if (exists $params->{$name}) {
- my $array = delete($params->{$name}) || [];
- $ms_values{$name} = $array;
- }
+ my ($invocant, $params) = @_;
+
+ my @multi_selects
+ = grep { $_->type == FIELD_TYPE_MULTI_SELECT } Bugzilla->active_custom_fields;
+ my %ms_values;
+ foreach my $field (@multi_selects) {
+ my $name = $field->name;
+ if (exists $params->{$name}) {
+ my $array = delete($params->{$name}) || [];
+ $ms_values{$name} = $array;
}
- return \%ms_values;
+ }
+ return \%ms_values;
}
# Should be called any time you update short_desc or change a comment.
sub _sync_fulltext {
- my ($self, %options) = @_;
- my $dbh = Bugzilla->dbh;
-
- my($all_comments, $public_comments);
- if ($options{new_bug} || $options{update_comments}) {
- my $comments = $dbh->selectall_arrayref(
- 'SELECT thetext, isprivate FROM longdescs WHERE bug_id = ?',
- undef, $self->id);
- $all_comments = join("\n", map { $_->[0] } @$comments);
- my @no_private = grep { !$_->[1] } @$comments;
- $public_comments = join("\n", map { $_->[0] } @no_private);
- }
-
- if ($options{new_bug}) {
- $dbh->do('INSERT INTO bugs_fulltext (bug_id, short_desc, comments,
+ my ($self, %options) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my ($all_comments, $public_comments);
+ if ($options{new_bug} || $options{update_comments}) {
+ my $comments
+ = $dbh->selectall_arrayref(
+ 'SELECT thetext, isprivate FROM longdescs WHERE bug_id = ?',
+ undef, $self->id);
+ $all_comments = join("\n", map { $_->[0] } @$comments);
+ my @no_private = grep { !$_->[1] } @$comments;
+ $public_comments = join("\n", map { $_->[0] } @no_private);
+ }
+
+ if ($options{new_bug}) {
+ $dbh->do(
+ 'INSERT INTO bugs_fulltext (bug_id, short_desc, comments,
comments_noprivate)
- VALUES (?, ?, ?, ?)',
- undef,
- $self->id, $self->short_desc, $all_comments, $public_comments);
- } else {
- my(@names, @values);
- if ($options{update_short_desc}) {
- push @names, 'short_desc';
- push @values, $self->short_desc;
- }
- if ($options{update_comments}) {
- push @names, ('comments', 'comments_noprivate');
- push @values, ($all_comments, $public_comments);
- }
- if (@names) {
- $dbh->do('UPDATE bugs_fulltext SET ' .
- join(', ', map { "$_ = ?" } @names) .
- ' WHERE bug_id = ?',
- undef,
- @values, $self->id);
- }
+ VALUES (?, ?, ?, ?)', undef, $self->id, $self->short_desc,
+ $all_comments, $public_comments
+ );
+ }
+ else {
+ my (@names, @values);
+ if ($options{update_short_desc}) {
+ push @names, 'short_desc';
+ push @values, $self->short_desc;
}
+ if ($options{update_comments}) {
+ push @names, ('comments', 'comments_noprivate');
+ push @values, ($all_comments, $public_comments);
+ }
+ if (@names) {
+ $dbh->do(
+ 'UPDATE bugs_fulltext SET '
+ . join(', ', map {"$_ = ?"} @names)
+ . ' WHERE bug_id = ?',
+ undef, @values, $self->id
+ );
+ }
+ }
}
sub remove_from_db {
- my ($self) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ ThrowCodeError("bug_error", {bug => $self}) if $self->{'error'};
- ThrowCodeError("bug_error", { bug => $self }) if $self->{'error'};
+ my $bug_id = $self->{'bug_id'};
+ $self->SUPER::remove_from_db();
- my $bug_id = $self->{'bug_id'};
- $self->SUPER::remove_from_db();
- # The bugs_fulltext table doesn't support foreign keys.
- $dbh->do("DELETE FROM bugs_fulltext WHERE bug_id = ?", undef, $bug_id);
+ # The bugs_fulltext table doesn't support foreign keys.
+ $dbh->do("DELETE FROM bugs_fulltext WHERE bug_id = ?", undef, $bug_id);
}
#####################################################################
@@ -1272,96 +1308,93 @@ sub remove_from_db {
#####################################################################
sub send_changes {
- my ($self, $changes, $vars) = @_;
-
- my $user = Bugzilla->user;
-
- my $old_qa = $changes->{'qa_contact'}
- ? $changes->{'qa_contact'}->[0] : '';
- my $old_own = $changes->{'assigned_to'}
- ? $changes->{'assigned_to'}->[0] : '';
- my $old_cc = $changes->{cc}
- ? $changes->{cc}->[0] : '';
-
- my %forced = (
- cc => [split(/[,;]+/, $old_cc)],
- owner => $old_own,
- qacontact => $old_qa,
- changer => $user,
- );
-
- _send_bugmail({ id => $self->id, type => 'bug', forced => \%forced },
- $vars);
-
- # If the bug was marked as a duplicate, we need to notify users on the
- # other bug of any changes to that bug.
- my $new_dup_id = $changes->{'dup_id'} ? $changes->{'dup_id'}->[1] : undef;
- if ($new_dup_id) {
- _send_bugmail({ forced => { changer => $user }, type => "dupe",
- id => $new_dup_id }, $vars);
- }
-
- # If there were changes in dependencies, we need to notify those
- # dependencies.
- if ($changes->{'bug_status'}) {
- my ($old_status, $new_status) = @{ $changes->{'bug_status'} };
-
- # If this bug has changed from opened to closed or vice-versa,
- # then all of the bugs we block need to be notified.
- if (is_open_state($old_status) ne is_open_state($new_status)) {
- my $params = { forced => { changer => $user },
- type => 'dep',
- dep_only => 1,
- blocker => $self,
- changes => $changes };
-
- foreach my $id (@{ $self->blocked }) {
- $params->{id} = $id;
- _send_bugmail($params, $vars);
- }
- }
- }
-
- # To get a list of all changed dependencies, convert the "changes" arrays
- # into a long string, then collapse that string into unique numbers in
- # a hash.
- my $all_changed_deps = join(', ', @{ $changes->{'dependson'} || [] });
- $all_changed_deps = join(', ', @{ $changes->{'blocked'} || [] },
- $all_changed_deps);
- my %changed_deps = map { $_ => 1 } split(', ', $all_changed_deps);
- # When clearning one field (say, blocks) and filling in the other
- # (say, dependson), an empty string can get into the hash and cause
- # an error later.
- delete $changed_deps{''};
-
- foreach my $id (sort { $a <=> $b } (keys %changed_deps)) {
- _send_bugmail({ forced => { changer => $user }, type => "dep",
- id => $id }, $vars);
- }
-
- # Sending emails for the referenced bugs.
- foreach my $ref_bug_id (uniq @{ $self->{see_also_changes} || [] }) {
- _send_bugmail({ forced => { changer => $user },
- id => $ref_bug_id }, $vars);
- }
+ my ($self, $changes, $vars) = @_;
+
+ my $user = Bugzilla->user;
+
+ my $old_qa = $changes->{'qa_contact'} ? $changes->{'qa_contact'}->[0] : '';
+ my $old_own = $changes->{'assigned_to'} ? $changes->{'assigned_to'}->[0] : '';
+ my $old_cc = $changes->{cc} ? $changes->{cc}->[0] : '';
+
+ my %forced = (
+ cc => [split(/[,;]+/, $old_cc)],
+ owner => $old_own,
+ qacontact => $old_qa,
+ changer => $user,
+ );
+
+ _send_bugmail({id => $self->id, type => 'bug', forced => \%forced}, $vars);
+
+ # If the bug was marked as a duplicate, we need to notify users on the
+ # other bug of any changes to that bug.
+ my $new_dup_id = $changes->{'dup_id'} ? $changes->{'dup_id'}->[1] : undef;
+ if ($new_dup_id) {
+ _send_bugmail({forced => {changer => $user}, type => "dupe", id => $new_dup_id},
+ $vars);
+ }
+
+ # If there were changes in dependencies, we need to notify those
+ # dependencies.
+ if ($changes->{'bug_status'}) {
+ my ($old_status, $new_status) = @{$changes->{'bug_status'}};
+
+ # If this bug has changed from opened to closed or vice-versa,
+ # then all of the bugs we block need to be notified.
+ if (is_open_state($old_status) ne is_open_state($new_status)) {
+ my $params = {
+ forced => {changer => $user},
+ type => 'dep',
+ dep_only => 1,
+ blocker => $self,
+ changes => $changes
+ };
+
+ foreach my $id (@{$self->blocked}) {
+ $params->{id} = $id;
+ _send_bugmail($params, $vars);
+ }
+ }
+ }
+
+ # To get a list of all changed dependencies, convert the "changes" arrays
+ # into a long string, then collapse that string into unique numbers in
+ # a hash.
+ my $all_changed_deps = join(', ', @{$changes->{'dependson'} || []});
+ $all_changed_deps
+ = join(', ', @{$changes->{'blocked'} || []}, $all_changed_deps);
+ my %changed_deps = map { $_ => 1 } split(', ', $all_changed_deps);
+
+ # When clearning one field (say, blocks) and filling in the other
+ # (say, dependson), an empty string can get into the hash and cause
+ # an error later.
+ delete $changed_deps{''};
+
+ foreach my $id (sort { $a <=> $b } (keys %changed_deps)) {
+ _send_bugmail({forced => {changer => $user}, type => "dep", id => $id}, $vars);
+ }
+
+ # Sending emails for the referenced bugs.
+ foreach my $ref_bug_id (uniq @{$self->{see_also_changes} || []}) {
+ _send_bugmail({forced => {changer => $user}, id => $ref_bug_id}, $vars);
+ }
}
sub _send_bugmail {
- my ($params, $vars) = @_;
+ my ($params, $vars) = @_;
- require Bugzilla::BugMail;
+ require Bugzilla::BugMail;
- my $results =
- Bugzilla::BugMail::Send($params->{'id'}, $params->{'forced'}, $params);
+ my $results
+ = Bugzilla::BugMail::Send($params->{'id'}, $params->{'forced'}, $params);
- if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
- my $template = Bugzilla->template;
- $vars->{$_} = $params->{$_} foreach keys %$params;
- $vars->{'sent_bugmail'} = $results;
- $template->process("bug/process/results.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- $vars->{'header_done'} = 1;
- }
+ if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
+ my $template = Bugzilla->template;
+ $vars->{$_} = $params->{$_} foreach keys %$params;
+ $vars->{'sent_bugmail'} = $results;
+ $template->process("bug/process/results.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ $vars->{'header_done'} = 1;
+ }
}
#####################################################################
@@ -1369,928 +1402,956 @@ sub _send_bugmail {
#####################################################################
sub _check_alias {
- my ($invocant, $aliases) = @_;
- $aliases = ref $aliases ? $aliases : [split(/[\s,]+/, $aliases)];
+ my ($invocant, $aliases) = @_;
+ $aliases = ref $aliases ? $aliases : [split(/[\s,]+/, $aliases)];
- # Remove empty aliases
- @$aliases = grep { $_ } @$aliases;
+ # Remove empty aliases
+ @$aliases = grep {$_} @$aliases;
- foreach my $alias (@$aliases) {
- $alias = trim($alias);
+ foreach my $alias (@$aliases) {
+ $alias = trim($alias);
- # Make sure the alias isn't too long.
- if (length($alias) > 40) {
- ThrowUserError("alias_too_long");
- }
- # Make sure the alias isn't just a number.
- if ($alias =~ /^\d+$/) {
- ThrowUserError("alias_is_numeric", { alias => $alias });
- }
- # Make sure the alias has no commas or spaces.
- if ($alias =~ /[, ]/) {
- ThrowUserError("alias_has_comma_or_space", { alias => $alias });
- }
- # Make sure the alias is unique, or that it's already our alias.
- my $other_bug = new Bugzilla::Bug($alias);
- if (!$other_bug->{error}
- && (!ref $invocant || $other_bug->id != $invocant->id))
- {
- ThrowUserError("alias_in_use", { alias => $alias,
- bug_id => $other_bug->id });
- }
+ # Make sure the alias isn't too long.
+ if (length($alias) > 40) {
+ ThrowUserError("alias_too_long");
}
- return $aliases;
-}
+ # Make sure the alias isn't just a number.
+ if ($alias =~ /^\d+$/) {
+ ThrowUserError("alias_is_numeric", {alias => $alias});
+ }
-sub _check_assigned_to {
- my ($invocant, $assignee, undef, $params) = @_;
- my $user = Bugzilla->user;
- my $component = blessed($invocant) ? $invocant->component_obj
- : $params->{component};
-
- # Default assignee is the component owner.
- my $id;
- # If this is a new bug, you can only set the assignee if you have editbugs.
- # If you didn't specify the assignee, we use the default assignee.
- if (!ref $invocant
- && (!$user->in_group('editbugs', $component->product_id) || !$assignee))
+ # Make sure the alias has no commas or spaces.
+ if ($alias =~ /[, ]/) {
+ ThrowUserError("alias_has_comma_or_space", {alias => $alias});
+ }
+
+ # Make sure the alias is unique, or that it's already our alias.
+ my $other_bug = new Bugzilla::Bug($alias);
+ if (!$other_bug->{error} && (!ref $invocant || $other_bug->id != $invocant->id))
{
- $id = $component->default_assignee->id;
- } else {
- if (!ref $assignee) {
- $assignee = trim($assignee);
- # When updating a bug, assigned_to can't be empty.
- ThrowUserError("reassign_to_empty") if ref $invocant && !$assignee;
- $assignee = Bugzilla::User->check($assignee);
- }
- $id = $assignee->id;
- # create() checks this another way, so we don't have to run this
- # check during create().
- $invocant->_check_strict_isolation_for_user($assignee) if ref $invocant;
+ ThrowUserError("alias_in_use", {alias => $alias, bug_id => $other_bug->id});
}
- return $id;
+ }
+
+ return $aliases;
}
-sub _check_bug_file_loc {
- my ($invocant, $url) = @_;
- $url = '' if !defined($url);
- $url = trim($url);
- # On bug entry, if bug_file_loc is "http://", the default, use an
- # empty value instead. However, on bug editing people can set that
- # back if they *really* want to.
- if (!ref $invocant && $url eq 'http://') {
- $url = '';
- }
- return $url;
+sub _check_assigned_to {
+ my ($invocant, $assignee, undef, $params) = @_;
+ my $user = Bugzilla->user;
+ my $component
+ = blessed($invocant) ? $invocant->component_obj : $params->{component};
+
+ # Default assignee is the component owner.
+ my $id;
+
+ # If this is a new bug, you can only set the assignee if you have editbugs.
+ # If you didn't specify the assignee, we use the default assignee.
+ if (!ref $invocant
+ && (!$user->in_group('editbugs', $component->product_id) || !$assignee))
+ {
+ $id = $component->default_assignee->id;
+ }
+ else {
+ if (!ref $assignee) {
+ $assignee = trim($assignee);
+
+ # When updating a bug, assigned_to can't be empty.
+ ThrowUserError("reassign_to_empty") if ref $invocant && !$assignee;
+ $assignee = Bugzilla::User->check($assignee);
+ }
+ $id = $assignee->id;
+
+ # create() checks this another way, so we don't have to run this
+ # check during create().
+ $invocant->_check_strict_isolation_for_user($assignee) if ref $invocant;
+ }
+ return $id;
}
-sub _check_bug_status {
- my ($invocant, $new_status, undef, $params) = @_;
- my $user = Bugzilla->user;
- my @valid_statuses;
- my $old_status; # Note that this is undef for new bugs.
+sub _check_bug_file_loc {
+ my ($invocant, $url) = @_;
+ $url = '' if !defined($url);
+ $url = trim($url);
- my ($product, $comment);
- if (ref $invocant) {
- @valid_statuses = @{$invocant->statuses_available};
- $product = $invocant->product_obj;
- $old_status = $invocant->status;
- my $comments = $invocant->{added_comments} || [];
- $comment = $comments->[-1];
- }
- else {
- $product = $params->{product};
- $comment = $params->{comment};
- @valid_statuses = @{ Bugzilla::Bug->statuses_available($product) };
- }
+ # On bug entry, if bug_file_loc is "http://", the default, use an
+ # empty value instead. However, on bug editing people can set that
+ # back if they *really* want to.
+ if (!ref $invocant && $url eq 'http://') {
+ $url = '';
+ }
+ return $url;
+}
- # Check permissions for users filing new bugs.
- if (!ref $invocant) {
- if ($user->in_group('editbugs', $product->id)
- || $user->in_group('canconfirm', $product->id)) {
- # If the user with privs hasn't selected another status,
- # select the first one of the list.
- unless ($new_status) {
- if (scalar(@valid_statuses) == 1) {
- $new_status = $valid_statuses[0];
- }
- else {
- $new_status = ($valid_statuses[0]->name ne 'UNCONFIRMED') ?
- $valid_statuses[0] : $valid_statuses[1];
- }
- }
+sub _check_bug_status {
+ my ($invocant, $new_status, undef, $params) = @_;
+ my $user = Bugzilla->user;
+ my @valid_statuses;
+ my $old_status; # Note that this is undef for new bugs.
+
+ my ($product, $comment);
+ if (ref $invocant) {
+ @valid_statuses = @{$invocant->statuses_available};
+ $product = $invocant->product_obj;
+ $old_status = $invocant->status;
+ my $comments = $invocant->{added_comments} || [];
+ $comment = $comments->[-1];
+ }
+ else {
+ $product = $params->{product};
+ $comment = $params->{comment};
+ @valid_statuses = @{Bugzilla::Bug->statuses_available($product)};
+ }
+
+ # Check permissions for users filing new bugs.
+ if (!ref $invocant) {
+ if ( $user->in_group('editbugs', $product->id)
+ || $user->in_group('canconfirm', $product->id))
+ {
+ # If the user with privs hasn't selected another status,
+ # select the first one of the list.
+ unless ($new_status) {
+ if (scalar(@valid_statuses) == 1) {
+ $new_status = $valid_statuses[0];
}
else {
- # A user with no privs cannot choose the initial status.
- # If UNCONFIRMED is valid for this product, use it; else
- # use the first bug status available.
- if (grep {$_->name eq 'UNCONFIRMED'} @valid_statuses) {
- $new_status = 'UNCONFIRMED';
- }
- else {
- $new_status = $valid_statuses[0];
- }
+ $new_status
+ = ($valid_statuses[0]->name ne 'UNCONFIRMED')
+ ? $valid_statuses[0]
+ : $valid_statuses[1];
}
+ }
}
+ else {
+ # A user with no privs cannot choose the initial status.
+ # If UNCONFIRMED is valid for this product, use it; else
+ # use the first bug status available.
+ if (grep { $_->name eq 'UNCONFIRMED' } @valid_statuses) {
+ $new_status = 'UNCONFIRMED';
+ }
+ else {
+ $new_status = $valid_statuses[0];
+ }
+ }
+ }
+
+ # Time to validate the bug status.
+ $new_status = Bugzilla::Status->check($new_status) unless ref($new_status);
+
+ # We skip this check if we are changing from a status to itself.
+ if ((!$old_status || $old_status->id != $new_status->id)
+ && !grep { $_->name eq $new_status->name } @valid_statuses)
+ {
+ ThrowUserError('illegal_bug_status_transition',
+ {old => $old_status, new => $new_status});
+ }
+
+ # Check if a comment is required for this change.
+ if ($new_status->comment_required_on_change_from($old_status)
+ && !$comment->{'thetext'})
+ {
+ ThrowUserError(
+ 'comment_required',
+ {
+ old => $old_status ? $old_status->name : undef,
+ new => $new_status->name,
+ field => 'bug_status'
+ }
+ );
+ }
- # Time to validate the bug status.
- $new_status = Bugzilla::Status->check($new_status) unless ref($new_status);
- # We skip this check if we are changing from a status to itself.
- if ( (!$old_status || $old_status->id != $new_status->id)
- && !grep {$_->name eq $new_status->name} @valid_statuses)
- {
- ThrowUserError('illegal_bug_status_transition',
- { old => $old_status, new => $new_status });
- }
+ if (
+ ref $invocant && (
+ $new_status->name eq 'IN_PROGRESS'
- # Check if a comment is required for this change.
- if ($new_status->comment_required_on_change_from($old_status)
- && !$comment->{'thetext'})
- {
- ThrowUserError('comment_required',
- { old => $old_status ? $old_status->name : undef,
- new => $new_status->name, field => 'bug_status' });
- }
-
- if (ref $invocant
- && ($new_status->name eq 'IN_PROGRESS'
- # Backwards-compat for the old default workflow.
- or $new_status->name eq 'ASSIGNED')
- && Bugzilla->params->{"usetargetmilestone"}
- && Bugzilla->params->{"musthavemilestoneonaccept"}
- # musthavemilestoneonaccept applies only if at least two
- # target milestones are defined for the product.
- && scalar(@{ $product->milestones }) > 1
- && $invocant->target_milestone eq $product->default_milestone)
- {
- ThrowUserError("milestone_required", { bug => $invocant });
- }
+ # Backwards-compat for the old default workflow.
+ or $new_status->name eq 'ASSIGNED'
+ )
+ && Bugzilla->params->{"usetargetmilestone"}
+ && Bugzilla->params->{"musthavemilestoneonaccept"}
- if (!blessed $invocant) {
- $params->{everconfirmed} = $new_status->name eq 'UNCONFIRMED' ? 0 : 1;
- }
+ # musthavemilestoneonaccept applies only if at least two
+ # target milestones are defined for the product.
+ && scalar(@{$product->milestones}) > 1
+ && $invocant->target_milestone eq $product->default_milestone
+ )
+ {
+ ThrowUserError("milestone_required", {bug => $invocant});
+ }
+
+ if (!blessed $invocant) {
+ $params->{everconfirmed} = $new_status->name eq 'UNCONFIRMED' ? 0 : 1;
+ }
- return $new_status->name;
+ return $new_status->name;
}
sub _check_cc {
- my ($invocant, $ccs, undef, $params) = @_;
- my $component = blessed($invocant) ? $invocant->component_obj
- : $params->{component};
- return [map {$_->id} @{$component->initial_cc}] unless $ccs;
-
- # Allow comma-separated input as well as arrayrefs.
- $ccs = [split(/[,;]+/, $ccs)] if !ref $ccs;
-
- my %cc_ids;
- foreach my $person (@$ccs) {
- $person = trim($person);
- next unless $person;
- my $id = login_to_id($person, THROW_ERROR);
- $cc_ids{$id} = 1;
- }
+ my ($invocant, $ccs, undef, $params) = @_;
+ my $component
+ = blessed($invocant) ? $invocant->component_obj : $params->{component};
+ return [map { $_->id } @{$component->initial_cc}] unless $ccs;
+
+ # Allow comma-separated input as well as arrayrefs.
+ $ccs = [split(/[,;]+/, $ccs)] if !ref $ccs;
+
+ my %cc_ids;
+ foreach my $person (@$ccs) {
+ $person = trim($person);
+ next unless $person;
+ my $id = login_to_id($person, THROW_ERROR);
+ $cc_ids{$id} = 1;
+ }
- # Enforce Default CC
- $cc_ids{$_->id} = 1 foreach (@{$component->initial_cc});
+ # Enforce Default CC
+ $cc_ids{$_->id} = 1 foreach (@{$component->initial_cc});
- return [keys %cc_ids];
+ return [keys %cc_ids];
}
sub _check_comment {
- my ($invocant, $comment_txt, undef, $params) = @_;
+ my ($invocant, $comment_txt, undef, $params) = @_;
- # Comment can be empty. We should force it to be empty if the text is undef
- if (!defined $comment_txt) {
- $comment_txt = '';
- }
+ # Comment can be empty. We should force it to be empty if the text is undef
+ if (!defined $comment_txt) {
+ $comment_txt = '';
+ }
- # Load up some data
- my $isprivate = delete $params->{comment_is_private};
- my $timestamp = $params->{creation_ts};
+ # Load up some data
+ my $isprivate = delete $params->{comment_is_private};
+ my $timestamp = $params->{creation_ts};
- # Create the new comment so we can check it
- my $comment = {
- thetext => $comment_txt,
- bug_when => $timestamp,
- };
+ # Create the new comment so we can check it
+ my $comment = {thetext => $comment_txt, bug_when => $timestamp,};
- # We don't include the "isprivate" column unless it was specified.
- # This allows it to fall back to its database default.
- if (defined $isprivate) {
- $comment->{isprivate} = $isprivate;
- }
+ # We don't include the "isprivate" column unless it was specified.
+ # This allows it to fall back to its database default.
+ if (defined $isprivate) {
+ $comment->{isprivate} = $isprivate;
+ }
- # Validate comment. We have to do this special as a comment normally
- # requires a bug to be already created. For a new bug, the first comment
- # obviously can't get the bug if the bug is created after this
- # (see bug 590334)
- Bugzilla::Comment->check_required_create_fields($comment);
- $comment = Bugzilla::Comment->run_create_validators($comment,
- { skip => ['bug_id'] }
- );
+ # Validate comment. We have to do this special as a comment normally
+ # requires a bug to be already created. For a new bug, the first comment
+ # obviously can't get the bug if the bug is created after this
+ # (see bug 590334)
+ Bugzilla::Comment->check_required_create_fields($comment);
+ $comment
+ = Bugzilla::Comment->run_create_validators($comment, {skip => ['bug_id']});
- return $comment;
+ return $comment;
}
sub _check_commenton {
- my ($invocant, $new_value, $field, $params) = @_;
+ my ($invocant, $new_value, $field, $params) = @_;
- my $has_comment =
- ref($invocant) ? $invocant->{added_comments}
- : (defined $params->{comment}
- and $params->{comment}->{thetext} ne '');
+ my $has_comment
+ = ref($invocant)
+ ? $invocant->{added_comments}
+ : (defined $params->{comment} and $params->{comment}->{thetext} ne '');
- my $is_changing = ref($invocant) ? $invocant->$field ne $new_value
- : $new_value ne '';
+ my $is_changing
+ = ref($invocant) ? $invocant->$field ne $new_value : $new_value ne '';
- if ($is_changing && !$has_comment) {
- my $old_value = ref($invocant) ? $invocant->$field : undef;
- ThrowUserError('comment_required',
- { field => $field, old => $old_value, new => $new_value });
- }
+ if ($is_changing && !$has_comment) {
+ my $old_value = ref($invocant) ? $invocant->$field : undef;
+ ThrowUserError('comment_required',
+ {field => $field, old => $old_value, new => $new_value});
+ }
}
sub _check_component {
- my ($invocant, $name, undef, $params) = @_;
- $name = trim($name);
- $name || ThrowUserError("require_component");
- my $product = blessed($invocant) ? $invocant->product_obj
- : $params->{product};
- my $old_comp = blessed($invocant) ? $invocant->component : '';
- my $object = Bugzilla::Component->check({ product => $product, name => $name });
- if ($object->name ne $old_comp && !$object->is_active) {
- ThrowUserError('value_inactive', { class => ref($object), value => $name });
- }
- return $object;
+ my ($invocant, $name, undef, $params) = @_;
+ $name = trim($name);
+ $name || ThrowUserError("require_component");
+ my $product = blessed($invocant) ? $invocant->product_obj : $params->{product};
+ my $old_comp = blessed($invocant) ? $invocant->component : '';
+ my $object = Bugzilla::Component->check({product => $product, name => $name});
+ if ($object->name ne $old_comp && !$object->is_active) {
+ ThrowUserError('value_inactive', {class => ref($object), value => $name});
+ }
+ return $object;
}
sub _check_creation_ts {
- return Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ return Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
}
sub _check_deadline {
- my ($invocant, $date) = @_;
+ my ($invocant, $date) = @_;
- # When filing bugs, we're forgiving and just return undef if
- # the user isn't a timetracker. When updating bugs, check_can_change_field
- # controls permissions, so we don't want to check them here.
- if (!ref $invocant and !Bugzilla->user->is_timetracker) {
- return undef;
- }
+ # When filing bugs, we're forgiving and just return undef if
+ # the user isn't a timetracker. When updating bugs, check_can_change_field
+ # controls permissions, so we don't want to check them here.
+ if (!ref $invocant and !Bugzilla->user->is_timetracker) {
+ return undef;
+ }
- # Validate entered deadline
- $date = trim($date);
- return undef if !$date;
- validate_date($date)
- || ThrowUserError('illegal_date', { date => $date,
- format => 'YYYY-MM-DD' });
- return $date;
+ # Validate entered deadline
+ $date = trim($date);
+ return undef if !$date;
+ validate_date($date)
+ || ThrowUserError('illegal_date', {date => $date, format => 'YYYY-MM-DD'});
+ return $date;
}
# Takes two comma/space-separated strings and returns arrayrefs
# of valid bug IDs.
sub _check_dependencies {
- my ($invocant, $value, $field, $params) = @_;
+ my ($invocant, $value, $field, $params) = @_;
- return $value if $params->{_dependencies_validated};
+ return $value if $params->{_dependencies_validated};
- if (!ref $invocant) {
- # Only editbugs users can set dependencies on bug entry.
- return ([], []) unless Bugzilla->user->in_group(
- 'editbugs', $params->{product}->id);
- }
+ if (!ref $invocant) {
+
+ # Only editbugs users can set dependencies on bug entry.
+ return ([], [])
+ unless Bugzilla->user->in_group('editbugs', $params->{product}->id);
+ }
- # This is done this way so that dependson and blocked can be in
- # VALIDATORS, meaning that they can be in VALIDATOR_DEPENDENCIES,
- # which means that they can be checked in the right order during
- # bug creation.
- my $opposite = $field eq 'dependson' ? 'blocked' : 'dependson';
- my %deps_in = ($field => $value || '',
- $opposite => $params->{$opposite} || '');
-
- foreach my $type (qw(dependson blocked)) {
- my @bug_ids = ref($deps_in{$type})
- ? @{$deps_in{$type}}
- : split(/[\s,]+/, $deps_in{$type});
- # Eliminate nulls.
- @bug_ids = grep {$_} @bug_ids;
-
- my @check_access = @bug_ids;
- # When we're updating a bug, only added or removed bug_ids are
- # checked for whether or not we can see/edit those bugs.
- if (ref $invocant) {
- my $old = $invocant->$type;
- my ($removed, $added) = diff_arrays($old, \@bug_ids);
- @check_access = (@$added, @$removed);
-
- # Check field permissions if we've changed anything.
- if (@check_access) {
- my $privs;
- if (!$invocant->check_can_change_field($type, 0, 1, \$privs)) {
- ThrowUserError('illegal_change', { field => $type,
- privs => $privs });
- }
- }
+ # This is done this way so that dependson and blocked can be in
+ # VALIDATORS, meaning that they can be in VALIDATOR_DEPENDENCIES,
+ # which means that they can be checked in the right order during
+ # bug creation.
+ my $opposite = $field eq 'dependson' ? 'blocked' : 'dependson';
+ my %deps_in = ($field => $value || '', $opposite => $params->{$opposite} || '');
+
+ foreach my $type (qw(dependson blocked)) {
+ my @bug_ids
+ = ref($deps_in{$type})
+ ? @{$deps_in{$type}}
+ : split(/[\s,]+/, $deps_in{$type});
+
+ # Eliminate nulls.
+ @bug_ids = grep {$_} @bug_ids;
+
+ my @check_access = @bug_ids;
+
+ # When we're updating a bug, only added or removed bug_ids are
+ # checked for whether or not we can see/edit those bugs.
+ if (ref $invocant) {
+ my $old = $invocant->$type;
+ my ($removed, $added) = diff_arrays($old, \@bug_ids);
+ @check_access = (@$added, @$removed);
+
+ # Check field permissions if we've changed anything.
+ if (@check_access) {
+ my $privs;
+ if (!$invocant->check_can_change_field($type, 0, 1, \$privs)) {
+ ThrowUserError('illegal_change', {field => $type, privs => $privs});
}
+ }
+ }
- my $user = Bugzilla->user;
- foreach my $modified_id (@check_access) {
- my $delta_bug = $invocant->check($modified_id);
- # Under strict isolation, you can't modify a bug if you can't
- # edit it, even if you can see it.
- if (Bugzilla->params->{"strict_isolation"}) {
- if (!$user->can_edit_product($delta_bug->{'product_id'})) {
- ThrowUserError("illegal_change_deps", {field => $type});
- }
- }
+ my $user = Bugzilla->user;
+ foreach my $modified_id (@check_access) {
+ my $delta_bug = $invocant->check($modified_id);
+
+ # Under strict isolation, you can't modify a bug if you can't
+ # edit it, even if you can see it.
+ if (Bugzilla->params->{"strict_isolation"}) {
+ if (!$user->can_edit_product($delta_bug->{'product_id'})) {
+ ThrowUserError("illegal_change_deps", {field => $type});
}
- # Replace all aliases by their corresponding bug ID.
- @bug_ids = map { $_ =~ /^(\d+)$/ ? $1 : $invocant->check($_, $type)->id } @bug_ids;
- $deps_in{$type} = \@bug_ids;
+ }
}
- # And finally, check for dependency loops.
- my $bug_id = ref($invocant) ? $invocant->id : 0;
- my %deps = ValidateDependencies($deps_in{dependson}, $deps_in{blocked},
- $bug_id);
+ # Replace all aliases by their corresponding bug ID.
+ @bug_ids
+ = map { $_ =~ /^(\d+)$/ ? $1 : $invocant->check($_, $type)->id } @bug_ids;
+ $deps_in{$type} = \@bug_ids;
+ }
- $params->{$opposite} = $deps{$opposite};
- $params->{_dependencies_validated} = 1;
- return $deps{$field};
+ # And finally, check for dependency loops.
+ my $bug_id = ref($invocant) ? $invocant->id : 0;
+ my %deps
+ = ValidateDependencies($deps_in{dependson}, $deps_in{blocked}, $bug_id);
+
+ $params->{$opposite} = $deps{$opposite};
+ $params->{_dependencies_validated} = 1;
+ return $deps{$field};
}
sub _check_dup_id {
- my ($self, $dupe_of) = @_;
- my $dbh = Bugzilla->dbh;
-
- # Store the bug ID/alias passed by the user for visibility checks.
- my $orig_dupe_of = $dupe_of = trim($dupe_of);
- $dupe_of || ThrowCodeError('undefined_field', { field => 'dup_id' });
- # Validate the bug ID. The second argument will force check() to only
- # make sure that the bug exists, and convert the alias to the bug ID
- # if a string is passed. Group restrictions are checked below.
- my $dupe_of_bug = $self->check($dupe_of, 'dup_id');
- $dupe_of = $dupe_of_bug->id;
-
- # If the dupe is unchanged, we have nothing more to check.
- return $dupe_of if ($self->dup_id && $self->dup_id == $dupe_of);
-
- # If we come here, then the duplicate is new. We have to make sure
- # that we can view/change it (issue A on bug 96085).
- $dupe_of_bug->check_is_visible($orig_dupe_of);
-
- # Make sure a loop isn't created when marking this bug
- # as duplicate.
- _resolve_ultimate_dup_id($self->id, $dupe_of, 1);
-
- my $cur_dup = $self->dup_id || 0;
- if ($cur_dup != $dupe_of && Bugzilla->params->{'commentonduplicate'}
- && !$self->{added_comments})
- {
- ThrowUserError('comment_required');
+ my ($self, $dupe_of) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # Store the bug ID/alias passed by the user for visibility checks.
+ my $orig_dupe_of = $dupe_of = trim($dupe_of);
+ $dupe_of || ThrowCodeError('undefined_field', {field => 'dup_id'});
+
+ # Validate the bug ID. The second argument will force check() to only
+ # make sure that the bug exists, and convert the alias to the bug ID
+ # if a string is passed. Group restrictions are checked below.
+ my $dupe_of_bug = $self->check($dupe_of, 'dup_id');
+ $dupe_of = $dupe_of_bug->id;
+
+ # If the dupe is unchanged, we have nothing more to check.
+ return $dupe_of if ($self->dup_id && $self->dup_id == $dupe_of);
+
+ # If we come here, then the duplicate is new. We have to make sure
+ # that we can view/change it (issue A on bug 96085).
+ $dupe_of_bug->check_is_visible($orig_dupe_of);
+
+ # Make sure a loop isn't created when marking this bug
+ # as duplicate.
+ _resolve_ultimate_dup_id($self->id, $dupe_of, 1);
+
+ my $cur_dup = $self->dup_id || 0;
+ if ( $cur_dup != $dupe_of
+ && Bugzilla->params->{'commentonduplicate'}
+ && !$self->{added_comments})
+ {
+ ThrowUserError('comment_required');
+ }
+
+ # Should we add the reporter to the CC list of the new bug?
+ # If they can see the bug...
+ if ($self->reporter->can_see_bug($dupe_of)) {
+
+ # We only add them if they're not the reporter of the other bug.
+ $self->{_add_dup_cc} = 1 if $dupe_of_bug->reporter->id != $self->reporter->id;
+ }
+
+ # What if the reporter currently can't see the new bug? In the browser
+ # interface, we prompt the user. In other interfaces, we default to
+ # not adding the user, as the safest option.
+ elsif (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
+
+ # If we've already confirmed whether the user should be added...
+ my $cgi = Bugzilla->cgi;
+ my $add_confirmed = $cgi->param('confirm_add_duplicate');
+ if (defined $add_confirmed) {
+ $self->{_add_dup_cc} = $add_confirmed;
}
+ else {
+ # Note that here we don't check if the user is already the reporter
+ # of the dupe_of bug, since we already checked if they can *see*
+ # the bug, above. People might have reporter_accessible turned
+ # off, but cclist_accessible turned on, so they might want to
+ # add the reporter even though they're already the reporter of the
+ # dup_of bug.
+ my $vars = {};
+ my $template = Bugzilla->template;
- # Should we add the reporter to the CC list of the new bug?
- # If they can see the bug...
- if ($self->reporter->can_see_bug($dupe_of)) {
- # We only add them if they're not the reporter of the other bug.
- $self->{_add_dup_cc} = 1
- if $dupe_of_bug->reporter->id != $self->reporter->id;
- }
- # What if the reporter currently can't see the new bug? In the browser
- # interface, we prompt the user. In other interfaces, we default to
- # not adding the user, as the safest option.
- elsif (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
- # If we've already confirmed whether the user should be added...
- my $cgi = Bugzilla->cgi;
- my $add_confirmed = $cgi->param('confirm_add_duplicate');
- if (defined $add_confirmed) {
- $self->{_add_dup_cc} = $add_confirmed;
- }
- else {
- # Note that here we don't check if the user is already the reporter
- # of the dupe_of bug, since we already checked if they can *see*
- # the bug, above. People might have reporter_accessible turned
- # off, but cclist_accessible turned on, so they might want to
- # add the reporter even though they're already the reporter of the
- # dup_of bug.
- my $vars = {};
- my $template = Bugzilla->template;
- # Ask the user what they want to do about the reporter.
- $vars->{'cclist_accessible'} = $dupe_of_bug->cclist_accessible;
- $vars->{'original_bug_id'} = $dupe_of;
- $vars->{'duplicate_bug_id'} = $self->id;
- print $cgi->header();
- $template->process("bug/process/confirm-duplicate.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
+ # Ask the user what they want to do about the reporter.
+ $vars->{'cclist_accessible'} = $dupe_of_bug->cclist_accessible;
+ $vars->{'original_bug_id'} = $dupe_of;
+ $vars->{'duplicate_bug_id'} = $self->id;
+ print $cgi->header();
+ $template->process("bug/process/confirm-duplicate.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
+ }
- return $dupe_of;
+ return $dupe_of;
}
sub _check_groups {
- my ($invocant, $group_names, undef, $params) = @_;
-
- my $bug_id = blessed($invocant) ? $invocant->id : undef;
- my $product = blessed($invocant) ? $invocant->product_obj
- : $params->{product};
- my %add_groups;
-
- # In email or WebServices, when the "groups" item actually
- # isn't specified, then just add the default groups.
- if (!defined $group_names) {
- my $available = $product->groups_available;
- foreach my $group (@$available) {
- $add_groups{$group->id} = $group if $group->{is_default};
- }
+ my ($invocant, $group_names, undef, $params) = @_;
+
+ my $bug_id = blessed($invocant) ? $invocant->id : undef;
+ my $product = blessed($invocant) ? $invocant->product_obj : $params->{product};
+ my %add_groups;
+
+ # In email or WebServices, when the "groups" item actually
+ # isn't specified, then just add the default groups.
+ if (!defined $group_names) {
+ my $available = $product->groups_available;
+ foreach my $group (@$available) {
+ $add_groups{$group->id} = $group if $group->{is_default};
}
- else {
- # Allow a comma-separated list, for email_in.pl.
- $group_names = [map { trim($_) } split(',', $group_names)]
- if !ref $group_names;
-
- # First check all the groups they chose to set.
- my %args = ( product => $product->name, bug_id => $bug_id, action => 'add' );
- foreach my $name (@$group_names) {
- my $group = Bugzilla::Group->check_no_disclose({ %args, name => $name });
-
- if (!$product->group_is_settable($group)) {
- ThrowUserError('group_restriction_not_allowed', { %args, name => $name });
- }
- $add_groups{$group->id} = $group;
- }
+ }
+ else {
+ # Allow a comma-separated list, for email_in.pl.
+ $group_names = [map { trim($_) } split(',', $group_names)] if !ref $group_names;
+
+ # First check all the groups they chose to set.
+ my %args = (product => $product->name, bug_id => $bug_id, action => 'add');
+ foreach my $name (@$group_names) {
+ my $group = Bugzilla::Group->check_no_disclose({%args, name => $name});
+
+ if (!$product->group_is_settable($group)) {
+ ThrowUserError('group_restriction_not_allowed', {%args, name => $name});
+ }
+ $add_groups{$group->id} = $group;
}
+ }
- # Now enforce mandatory groups.
- $add_groups{$_->id} = $_ foreach @{ $product->groups_mandatory };
+ # Now enforce mandatory groups.
+ $add_groups{$_->id} = $_ foreach @{$product->groups_mandatory};
- my @add_groups = values %add_groups;
- return \@add_groups;
+ my @add_groups = values %add_groups;
+ return \@add_groups;
}
sub _check_keywords {
- my ($invocant, $keywords_in, undef, $params) = @_;
+ my ($invocant, $keywords_in, undef, $params) = @_;
- return [] if !defined $keywords_in;
+ return [] if !defined $keywords_in;
- my $keyword_array = $keywords_in;
- if (!ref $keyword_array) {
- $keywords_in = trim($keywords_in);
- $keyword_array = [split(/[\s,]+/, $keywords_in)];
- }
-
- my %keywords;
- foreach my $keyword (@$keyword_array) {
- next unless $keyword;
- my $obj = Bugzilla::Keyword->check($keyword);
- $keywords{$obj->id} = $obj;
- }
- return [values %keywords];
+ my $keyword_array = $keywords_in;
+ if (!ref $keyword_array) {
+ $keywords_in = trim($keywords_in);
+ $keyword_array = [split(/[\s,]+/, $keywords_in)];
+ }
+
+ my %keywords;
+ foreach my $keyword (@$keyword_array) {
+ next unless $keyword;
+ my $obj = Bugzilla::Keyword->check($keyword);
+ $keywords{$obj->id} = $obj;
+ }
+ return [values %keywords];
}
sub _check_product {
- my ($invocant, $name) = @_;
- $name = trim($name);
- # If we're updating the bug and they haven't changed the product,
- # always allow it.
- if (ref $invocant && lc($invocant->product_obj->name) eq lc($name)) {
- return $invocant->product_obj;
- }
- # Check that the product exists and that the user
- # is allowed to enter bugs into this product.
- my $product = Bugzilla->user->can_enter_product($name, THROW_ERROR);
- return $product;
+ my ($invocant, $name) = @_;
+ $name = trim($name);
+
+ # If we're updating the bug and they haven't changed the product,
+ # always allow it.
+ if (ref $invocant && lc($invocant->product_obj->name) eq lc($name)) {
+ return $invocant->product_obj;
+ }
+
+ # Check that the product exists and that the user
+ # is allowed to enter bugs into this product.
+ my $product = Bugzilla->user->can_enter_product($name, THROW_ERROR);
+ return $product;
}
sub _check_priority {
- my ($invocant, $priority) = @_;
- if (!ref $invocant && !Bugzilla->params->{'letsubmitterchoosepriority'}) {
- $priority = Bugzilla->params->{'defaultpriority'};
- }
- return $invocant->_check_select_field($priority, 'priority');
+ my ($invocant, $priority) = @_;
+ if (!ref $invocant && !Bugzilla->params->{'letsubmitterchoosepriority'}) {
+ $priority = Bugzilla->params->{'defaultpriority'};
+ }
+ return $invocant->_check_select_field($priority, 'priority');
}
sub _check_qa_contact {
- my ($invocant, $qa_contact, undef, $params) = @_;
- $qa_contact = trim($qa_contact) if !ref $qa_contact;
- my $component = blessed($invocant) ? $invocant->component_obj
- : $params->{component};
- if (!ref $invocant) {
- # Bugs get no QA Contact on creation if useqacontact is off.
- return undef if !Bugzilla->params->{useqacontact};
- # Set the default QA Contact if one isn't specified or if the
- # user doesn't have editbugs.
- if (!Bugzilla->user->in_group('editbugs', $component->product_id)
- || !$qa_contact)
- {
- return $component->default_qa_contact ? $component->default_qa_contact->id : undef;
- }
+ my ($invocant, $qa_contact, undef, $params) = @_;
+ $qa_contact = trim($qa_contact) if !ref $qa_contact;
+ my $component
+ = blessed($invocant) ? $invocant->component_obj : $params->{component};
+ if (!ref $invocant) {
+
+ # Bugs get no QA Contact on creation if useqacontact is off.
+ return undef if !Bugzilla->params->{useqacontact};
+
+ # Set the default QA Contact if one isn't specified or if the
+ # user doesn't have editbugs.
+ if ( !Bugzilla->user->in_group('editbugs', $component->product_id)
+ || !$qa_contact)
+ {
+ return $component->default_qa_contact
+ ? $component->default_qa_contact->id
+ : undef;
}
+ }
- # If a QA Contact was specified or if we're updating, check
- # the QA Contact for validity.
- my $id;
- if ($qa_contact) {
- $qa_contact = Bugzilla::User->check($qa_contact) if !ref $qa_contact;
- $id = $qa_contact->id;
- # create() checks this another way, so we don't have to run this
- # check during create().
- # If there is no QA contact, this check is not required.
- $invocant->_check_strict_isolation_for_user($qa_contact)
- if (ref $invocant && $id);
- }
+ # If a QA Contact was specified or if we're updating, check
+ # the QA Contact for validity.
+ my $id;
+ if ($qa_contact) {
+ $qa_contact = Bugzilla::User->check($qa_contact) if !ref $qa_contact;
+ $id = $qa_contact->id;
+
+ # create() checks this another way, so we don't have to run this
+ # check during create().
+ # If there is no QA contact, this check is not required.
+ $invocant->_check_strict_isolation_for_user($qa_contact)
+ if (ref $invocant && $id);
+ }
- # "0" always means "undef", for QA Contact.
- return $id || undef;
+ # "0" always means "undef", for QA Contact.
+ return $id || undef;
}
sub _check_reporter {
- my $invocant = shift;
- my $reporter;
- if (ref $invocant) {
- # You cannot change the reporter of a bug.
- $reporter = $invocant->reporter->id;
- }
- else {
- # On bug creation, the reporter is the logged in user
- # (meaning that they must be logged in first!).
- Bugzilla->login(LOGIN_REQUIRED);
- $reporter = Bugzilla->user->id;
- }
- return $reporter;
+ my $invocant = shift;
+ my $reporter;
+ if (ref $invocant) {
+
+ # You cannot change the reporter of a bug.
+ $reporter = $invocant->reporter->id;
+ }
+ else {
+ # On bug creation, the reporter is the logged in user
+ # (meaning that they must be logged in first!).
+ Bugzilla->login(LOGIN_REQUIRED);
+ $reporter = Bugzilla->user->id;
+ }
+ return $reporter;
}
sub _check_resolution {
- my ($invocant, $resolution, undef, $params) = @_;
- $resolution = trim($resolution);
- my $status = ref($invocant) ? $invocant->status->name
- : $params->{bug_status};
- my $is_open = ref($invocant) ? $invocant->status->is_open
- : is_open_state($status);
-
- # Throw a special error for resolving bugs without a resolution
- # (or trying to change the resolution to '' on a closed bug without
- # using clear_resolution).
- ThrowUserError('missing_resolution', { status => $status })
- if !$resolution && !$is_open;
-
- # Make sure this is a valid resolution.
- $resolution = $invocant->_check_select_field($resolution, 'resolution');
-
- # Don't allow open bugs to have resolutions.
- ThrowUserError('resolution_not_allowed') if $is_open;
-
- # Check noresolveonopenblockers.
- my $dependson = ref($invocant) ? $invocant->dependson
- : ($params->{dependson} || []);
- if (Bugzilla->params->{"noresolveonopenblockers"}
- && $resolution eq 'FIXED'
- && (!ref $invocant or !$invocant->resolution
- or $resolution ne $invocant->resolution)
- && scalar @$dependson)
- {
- my $dep_bugs = Bugzilla::Bug->new_from_list($dependson);
- my $count_open = grep { $_->isopened } @$dep_bugs;
- if ($count_open) {
- my $bug_id = ref($invocant) ? $invocant->id : undef;
- ThrowUserError("still_unresolved_bugs",
- { bug_id => $bug_id, dep_count => $count_open });
- }
- }
-
- # Check if they're changing the resolution and need to comment.
- if (Bugzilla->params->{'commentonchange_resolution'}) {
- $invocant->_check_commenton($resolution, 'resolution', $params);
- }
-
- return $resolution;
+ my ($invocant, $resolution, undef, $params) = @_;
+ $resolution = trim($resolution);
+ my $status = ref($invocant) ? $invocant->status->name : $params->{bug_status};
+ my $is_open
+ = ref($invocant) ? $invocant->status->is_open : is_open_state($status);
+
+ # Throw a special error for resolving bugs without a resolution
+ # (or trying to change the resolution to '' on a closed bug without
+ # using clear_resolution).
+ ThrowUserError('missing_resolution', {status => $status})
+ if !$resolution && !$is_open;
+
+ # Make sure this is a valid resolution.
+ $resolution = $invocant->_check_select_field($resolution, 'resolution');
+
+ # Don't allow open bugs to have resolutions.
+ ThrowUserError('resolution_not_allowed') if $is_open;
+
+ # Check noresolveonopenblockers.
+ my $dependson
+ = ref($invocant) ? $invocant->dependson : ($params->{dependson} || []);
+ if (
+ Bugzilla->params->{"noresolveonopenblockers"}
+ && $resolution eq 'FIXED'
+ && ( !ref $invocant
+ or !$invocant->resolution
+ or $resolution ne $invocant->resolution)
+ && scalar @$dependson
+ )
+ {
+ my $dep_bugs = Bugzilla::Bug->new_from_list($dependson);
+ my $count_open = grep { $_->isopened } @$dep_bugs;
+ if ($count_open) {
+ my $bug_id = ref($invocant) ? $invocant->id : undef;
+ ThrowUserError("still_unresolved_bugs",
+ {bug_id => $bug_id, dep_count => $count_open});
+ }
+ }
+
+ # Check if they're changing the resolution and need to comment.
+ if (Bugzilla->params->{'commentonchange_resolution'}) {
+ $invocant->_check_commenton($resolution, 'resolution', $params);
+ }
+
+ return $resolution;
}
sub _check_short_desc {
- my ($invocant, $short_desc) = @_;
- # Set the parameter to itself, but cleaned up
- $short_desc = clean_text($short_desc) if $short_desc;
+ my ($invocant, $short_desc) = @_;
- if (!defined $short_desc || $short_desc eq '') {
- ThrowUserError("require_summary");
- }
- if (length($short_desc) > MAX_FREETEXT_LENGTH) {
- ThrowUserError('freetext_too_long',
- { field => 'short_desc', text => $short_desc });
- }
- return $short_desc;
+ # Set the parameter to itself, but cleaned up
+ $short_desc = clean_text($short_desc) if $short_desc;
+
+ if (!defined $short_desc || $short_desc eq '') {
+ ThrowUserError("require_summary");
+ }
+ if (length($short_desc) > MAX_FREETEXT_LENGTH) {
+ ThrowUserError('freetext_too_long',
+ {field => 'short_desc', text => $short_desc});
+ }
+ return $short_desc;
}
sub _check_status_whiteboard { return defined $_[1] ? $_[1] : ''; }
# Unlike other checkers, this one doesn't return anything.
sub _check_strict_isolation {
- my ($invocant, $ccs, $assignee, $qa_contact, $product) = @_;
- return unless Bugzilla->params->{'strict_isolation'};
-
- if (ref $invocant) {
- my $original = $invocant->new($invocant->id);
-
- # We only check people if they've been added. This way, if
- # strict_isolation is turned on when there are invalid users
- # on bugs, people can still add comments and so on.
- my @old_cc = map { $_->id } @{$original->cc_users};
- my @new_cc = map { $_->id } @{$invocant->cc_users};
- my ($removed, $added) = diff_arrays(\@old_cc, \@new_cc);
- $ccs = Bugzilla::User->new_from_list($added);
-
- $assignee = $invocant->assigned_to
- if $invocant->assigned_to->id != $original->assigned_to->id;
- if ($invocant->qa_contact
- && (!$original->qa_contact
- || $invocant->qa_contact->id != $original->qa_contact->id))
- {
- $qa_contact = $invocant->qa_contact;
- }
- $product = $invocant->product_obj;
+ my ($invocant, $ccs, $assignee, $qa_contact, $product) = @_;
+ return unless Bugzilla->params->{'strict_isolation'};
+
+ if (ref $invocant) {
+ my $original = $invocant->new($invocant->id);
+
+ # We only check people if they've been added. This way, if
+ # strict_isolation is turned on when there are invalid users
+ # on bugs, people can still add comments and so on.
+ my @old_cc = map { $_->id } @{$original->cc_users};
+ my @new_cc = map { $_->id } @{$invocant->cc_users};
+ my ($removed, $added) = diff_arrays(\@old_cc, \@new_cc);
+ $ccs = Bugzilla::User->new_from_list($added);
+
+ $assignee = $invocant->assigned_to
+ if $invocant->assigned_to->id != $original->assigned_to->id;
+ if (
+ $invocant->qa_contact
+ && (!$original->qa_contact
+ || $invocant->qa_contact->id != $original->qa_contact->id)
+ )
+ {
+ $qa_contact = $invocant->qa_contact;
}
+ $product = $invocant->product_obj;
+ }
- my @related_users = @$ccs;
- push(@related_users, $assignee) if $assignee;
+ my @related_users = @$ccs;
+ push(@related_users, $assignee) if $assignee;
- if (Bugzilla->params->{'useqacontact'} && $qa_contact) {
- push(@related_users, $qa_contact);
- }
+ if (Bugzilla->params->{'useqacontact'} && $qa_contact) {
+ push(@related_users, $qa_contact);
+ }
- @related_users = @{Bugzilla::User->new_from_list(\@related_users)}
- if !ref $invocant;
-
- # For each unique user in @related_users...(assignee and qa_contact
- # could be duplicates of users in the CC list)
- my %unique_users = map {$_->id => $_} @related_users;
- my @blocked_users;
- foreach my $id (keys %unique_users) {
- my $related_user = $unique_users{$id};
- if (!$related_user->can_edit_product($product->id) ||
- !$related_user->can_see_product($product->name)) {
- push (@blocked_users, $related_user->login);
- }
+ @related_users = @{Bugzilla::User->new_from_list(\@related_users)}
+ if !ref $invocant;
+
+ # For each unique user in @related_users...(assignee and qa_contact
+ # could be duplicates of users in the CC list)
+ my %unique_users = map { $_->id => $_ } @related_users;
+ my @blocked_users;
+ foreach my $id (keys %unique_users) {
+ my $related_user = $unique_users{$id};
+ if ( !$related_user->can_edit_product($product->id)
+ || !$related_user->can_see_product($product->name))
+ {
+ push(@blocked_users, $related_user->login);
}
- if (scalar(@blocked_users)) {
- my %vars = ( users => \@blocked_users,
- product => $product->name );
- if (ref $invocant) {
- $vars{'bug_id'} = $invocant->id;
- }
- else {
- $vars{'new'} = 1;
- }
- ThrowUserError("invalid_user_group", \%vars);
+ }
+ if (scalar(@blocked_users)) {
+ my %vars = (users => \@blocked_users, product => $product->name);
+ if (ref $invocant) {
+ $vars{'bug_id'} = $invocant->id;
}
+ else {
+ $vars{'new'} = 1;
+ }
+ ThrowUserError("invalid_user_group", \%vars);
+ }
}
# This is used by various set_ checkers, to make their code simpler.
sub _check_strict_isolation_for_user {
- my ($self, $user) = @_;
- return unless Bugzilla->params->{"strict_isolation"};
- if (!$user->can_edit_product($self->{product_id})) {
- ThrowUserError('invalid_user_group',
- { users => $user->login,
- product => $self->product,
- bug_id => $self->id });
- }
+ my ($self, $user) = @_;
+ return unless Bugzilla->params->{"strict_isolation"};
+ if (!$user->can_edit_product($self->{product_id})) {
+ ThrowUserError('invalid_user_group',
+ {users => $user->login, product => $self->product, bug_id => $self->id});
+ }
}
sub _check_tag_name {
- my ($invocant, $tag) = @_;
+ my ($invocant, $tag) = @_;
- $tag = clean_text($tag);
- $tag || ThrowUserError('no_tag_to_edit');
- ThrowUserError('tag_name_too_long') if length($tag) > MAX_LEN_QUERY_NAME;
- trick_taint($tag);
- # Tags are all lowercase.
- return lc($tag);
+ $tag = clean_text($tag);
+ $tag || ThrowUserError('no_tag_to_edit');
+ ThrowUserError('tag_name_too_long') if length($tag) > MAX_LEN_QUERY_NAME;
+ trick_taint($tag);
+
+ # Tags are all lowercase.
+ return lc($tag);
}
sub _check_target_milestone {
- my ($invocant, $target, undef, $params) = @_;
- my $product = blessed($invocant) ? $invocant->product_obj
- : $params->{product};
- my $old_target = blessed($invocant) ? $invocant->target_milestone : '';
- $target = trim($target);
- $target = $product->default_milestone if !defined $target;
- my $object = Bugzilla::Milestone->check(
- { product => $product, name => $target });
- if ($old_target && $object->name ne $old_target && !$object->is_active) {
- ThrowUserError('value_inactive', { class => ref($object), value => $target });
- }
- return $object->name;
+ my ($invocant, $target, undef, $params) = @_;
+ my $product = blessed($invocant) ? $invocant->product_obj : $params->{product};
+ my $old_target = blessed($invocant) ? $invocant->target_milestone : '';
+ $target = trim($target);
+ $target = $product->default_milestone if !defined $target;
+ my $object = Bugzilla::Milestone->check({product => $product, name => $target});
+ if ($old_target && $object->name ne $old_target && !$object->is_active) {
+ ThrowUserError('value_inactive', {class => ref($object), value => $target});
+ }
+ return $object->name;
}
sub _check_time_field {
- my ($invocant, $value, $field, $params) = @_;
+ my ($invocant, $value, $field, $params) = @_;
- # When filing bugs, we're forgiving and just return 0 if
- # the user isn't a timetracker. When updating bugs, check_can_change_field
- # controls permissions, so we don't want to check them here.
- if (!ref $invocant and !Bugzilla->user->is_timetracker) {
- return 0;
- }
+ # When filing bugs, we're forgiving and just return 0 if
+ # the user isn't a timetracker. When updating bugs, check_can_change_field
+ # controls permissions, so we don't want to check them here.
+ if (!ref $invocant and !Bugzilla->user->is_timetracker) {
+ return 0;
+ }
- # check_time is in Bugzilla::Object.
- return $invocant->check_time($value, $field, $params);
+ # check_time is in Bugzilla::Object.
+ return $invocant->check_time($value, $field, $params);
}
sub _check_version {
- my ($invocant, $version, undef, $params) = @_;
- $version = trim($version);
- my $product = blessed($invocant) ? $invocant->product_obj
- : $params->{product};
- my $old_vers = blessed($invocant) ? $invocant->version : '';
- my $object = Bugzilla::Version->check({ product => $product, name => $version });
- if ($object->name ne $old_vers && !$object->is_active) {
- ThrowUserError('value_inactive', { class => ref($object), value => $version });
- }
- return $object->name;
+ my ($invocant, $version, undef, $params) = @_;
+ $version = trim($version);
+ my $product = blessed($invocant) ? $invocant->product_obj : $params->{product};
+ my $old_vers = blessed($invocant) ? $invocant->version : '';
+ my $object = Bugzilla::Version->check({product => $product, name => $version});
+ if ($object->name ne $old_vers && !$object->is_active) {
+ ThrowUserError('value_inactive', {class => ref($object), value => $version});
+ }
+ return $object->name;
}
# Custom Field Validators
sub _check_field_is_mandatory {
- my ($invocant, $value, $field, $params) = @_;
+ my ($invocant, $value, $field, $params) = @_;
- if (!blessed($field)) {
- $field = Bugzilla::Field->new({ name => $field });
- return if !$field;
- }
+ if (!blessed($field)) {
+ $field = Bugzilla::Field->new({name => $field});
+ return if !$field;
+ }
- return if !$field->is_mandatory;
+ return if !$field->is_mandatory;
- return if !$field->is_visible_on_bug($params || $invocant);
+ return if !$field->is_visible_on_bug($params || $invocant);
- return if ($field->type == FIELD_TYPE_SINGLE_SELECT
- && scalar @{ get_legal_field_values($field->name) } == 1);
+ return
+ if ($field->type == FIELD_TYPE_SINGLE_SELECT
+ && scalar @{get_legal_field_values($field->name)} == 1);
- return if ($field->type == FIELD_TYPE_MULTI_SELECT
- && !scalar @{ get_legal_field_values($field->name) });
+ return
+ if ($field->type == FIELD_TYPE_MULTI_SELECT
+ && !scalar @{get_legal_field_values($field->name)});
- if (ref($value) eq 'ARRAY') {
- $value = join('', @$value);
- }
+ if (ref($value) eq 'ARRAY') {
+ $value = join('', @$value);
+ }
- $value = trim($value);
- if (!defined($value)
- or $value eq ""
- or ($value eq '---' and $field->type == FIELD_TYPE_SINGLE_SELECT)
- or ($value =~ EMPTY_DATETIME_REGEX
- and $field->type == FIELD_TYPE_DATETIME))
- {
- ThrowUserError('required_field', { field => $field });
- }
+ $value = trim($value);
+ if ( !defined($value)
+ or $value eq ""
+ or ($value eq '---' and $field->type == FIELD_TYPE_SINGLE_SELECT)
+ or ($value =~ EMPTY_DATETIME_REGEX and $field->type == FIELD_TYPE_DATETIME))
+ {
+ ThrowUserError('required_field', {field => $field});
+ }
}
sub _check_date_field {
- my ($invocant, $date) = @_;
- return $invocant->_check_datetime_field($date, undef, {date_only => 1});
+ my ($invocant, $date) = @_;
+ return $invocant->_check_datetime_field($date, undef, {date_only => 1});
}
sub _check_datetime_field {
- my ($invocant, $date_time, $field, $params) = @_;
-
- # Empty datetimes are empty strings or strings only containing
- # 0's, whitespace, and punctuation.
- if ($date_time =~ /^[\s0[:punct:]]*$/) {
- return undef;
- }
-
- $date_time = trim($date_time);
- my ($date, $time) = split(' ', $date_time);
- if ($date && !validate_date($date)) {
- ThrowUserError('illegal_date', { date => $date,
- format => 'YYYY-MM-DD' });
- }
- if ($time && $params->{date_only}) {
- ThrowUserError('illegal_date', { date => $date_time,
- format => 'YYYY-MM-DD' });
- }
- if ($time && !validate_time($time)) {
- ThrowUserError('illegal_time', { 'time' => $time,
- format => 'HH:MM:SS' });
- }
- return $date_time
+ my ($invocant, $date_time, $field, $params) = @_;
+
+ # Empty datetimes are empty strings or strings only containing
+ # 0's, whitespace, and punctuation.
+ if ($date_time =~ /^[\s0[:punct:]]*$/) {
+ return undef;
+ }
+
+ $date_time = trim($date_time);
+ my ($date, $time) = split(' ', $date_time);
+ if ($date && !validate_date($date)) {
+ ThrowUserError('illegal_date', {date => $date, format => 'YYYY-MM-DD'});
+ }
+ if ($time && $params->{date_only}) {
+ ThrowUserError('illegal_date', {date => $date_time, format => 'YYYY-MM-DD'});
+ }
+ if ($time && !validate_time($time)) {
+ ThrowUserError('illegal_time', {'time' => $time, format => 'HH:MM:SS'});
+ }
+ return $date_time;
}
sub _check_default_field { return defined $_[1] ? trim($_[1]) : ''; }
sub _check_freetext_field {
- my ($invocant, $text, $field) = @_;
+ my ($invocant, $text, $field) = @_;
- $text = (defined $text) ? trim($text) : '';
- if (length($text) > MAX_FREETEXT_LENGTH) {
- ThrowUserError('freetext_too_long',
- { field => $field, text => $text });
- }
- return $text;
+ $text = (defined $text) ? trim($text) : '';
+ if (length($text) > MAX_FREETEXT_LENGTH) {
+ ThrowUserError('freetext_too_long', {field => $field, text => $text});
+ }
+ return $text;
}
sub _check_multi_select_field {
- my ($invocant, $values, $field) = @_;
-
- # Allow users (mostly email_in.pl) to specify multi-selects as
- # comma-separated values.
- if (defined $values and !ref $values) {
- # We don't split on spaces because multi-select values can and often
- # do have spaces in them. (Theoretically they can have commas in them
- # too, but that's much less common and people should be able to work
- # around it pretty cleanly, if they want to use email_in.pl.)
- $values = [split(',', $values)];
- }
+ my ($invocant, $values, $field) = @_;
- return [] if !$values;
- my @checked_values;
- foreach my $value (@$values) {
- push(@checked_values, $invocant->_check_select_field($value, $field));
- }
- return \@checked_values;
+ # Allow users (mostly email_in.pl) to specify multi-selects as
+ # comma-separated values.
+ if (defined $values and !ref $values) {
+
+ # We don't split on spaces because multi-select values can and often
+ # do have spaces in them. (Theoretically they can have commas in them
+ # too, but that's much less common and people should be able to work
+ # around it pretty cleanly, if they want to use email_in.pl.)
+ $values = [split(',', $values)];
+ }
+
+ return [] if !$values;
+ my @checked_values;
+ foreach my $value (@$values) {
+ push(@checked_values, $invocant->_check_select_field($value, $field));
+ }
+ return \@checked_values;
}
sub _check_select_field {
- my ($invocant, $value, $field) = @_;
- my $object = Bugzilla::Field::Choice->type($field)->check($value);
- return $object->name;
+ my ($invocant, $value, $field) = @_;
+ my $object = Bugzilla::Field::Choice->type($field)->check($value);
+ return $object->name;
}
sub _check_bugid_field {
- my ($invocant, $value, $field) = @_;
- return undef if !$value;
-
- # check that the value is a valid, visible bug id
- my $checked_id = $invocant->check($value, $field)->id;
-
- # check for loop (can't have a loop if this is a new bug)
- if (ref $invocant) {
- _check_relationship_loop($field, $invocant->bug_id, $checked_id);
- }
+ my ($invocant, $value, $field) = @_;
+ return undef if !$value;
+
+ # check that the value is a valid, visible bug id
+ my $checked_id = $invocant->check($value, $field)->id;
+
+ # check for loop (can't have a loop if this is a new bug)
+ if (ref $invocant) {
+ _check_relationship_loop($field, $invocant->bug_id, $checked_id);
+ }
- return $checked_id;
+ return $checked_id;
}
sub _check_textarea_field {
- my ($invocant, $text, $field) = @_;
+ my ($invocant, $text, $field) = @_;
- $text = (defined $text) ? trim($text) : '';
+ $text = (defined $text) ? trim($text) : '';
- # Web browsers submit newlines as \r\n.
- # Sanitize all input to match the web standard.
- # XMLRPC input could be either \n or \r\n
- $text =~ s/\r?\n/\r\n/g;
+ # Web browsers submit newlines as \r\n.
+ # Sanitize all input to match the web standard.
+ # XMLRPC input could be either \n or \r\n
+ $text =~ s/\r?\n/\r\n/g;
- return $text;
+ return $text;
}
sub _check_integer_field {
- my ($invocant, $value, $field) = @_;
- $value = defined($value) ? trim($value) : '';
+ my ($invocant, $value, $field) = @_;
+ $value = defined($value) ? trim($value) : '';
- if ($value eq '') {
- return 0;
- }
+ if ($value eq '') {
+ return 0;
+ }
- my $orig_value = $value;
- if (!detaint_signed($value)) {
- ThrowUserError("number_not_integer",
- {field => $field, num => $orig_value});
- }
- elsif (abs($value) > MAX_INT_32) {
- ThrowUserError("number_too_large",
- {field => $field, num => $orig_value, max_num => MAX_INT_32});
- }
+ my $orig_value = $value;
+ if (!detaint_signed($value)) {
+ ThrowUserError("number_not_integer", {field => $field, num => $orig_value});
+ }
+ elsif (abs($value) > MAX_INT_32) {
+ ThrowUserError("number_too_large",
+ {field => $field, num => $orig_value, max_num => MAX_INT_32});
+ }
- return $value;
+ return $value;
}
sub _check_relationship_loop {
- # Generates a dependency tree for a given bug. Calls itself recursively
- # to generate sub-trees for the bug's dependencies.
- my ($field, $bug_id, $dep_id, $ids) = @_;
-
- # Don't do anything if this bug doesn't have any dependencies.
- return unless defined($dep_id);
-
- # Check whether we have seen this bug yet
- $ids = {} unless defined $ids;
- $ids->{$bug_id} = 1;
- if ($ids->{$dep_id}) {
- ThrowUserError("relationship_loop_single", {
- 'bug_id' => $bug_id,
- 'dep_id' => $dep_id,
- 'field_name' => $field});
- }
-
- # Get this dependency's record from the database
- my $dbh = Bugzilla->dbh;
- my $next_dep_id = $dbh->selectrow_array(
- "SELECT $field FROM bugs WHERE bug_id = ?", undef, $dep_id);
- _check_relationship_loop($field, $dep_id, $next_dep_id, $ids);
+ # Generates a dependency tree for a given bug. Calls itself recursively
+ # to generate sub-trees for the bug's dependencies.
+ my ($field, $bug_id, $dep_id, $ids) = @_;
+
+ # Don't do anything if this bug doesn't have any dependencies.
+ return unless defined($dep_id);
+
+ # Check whether we have seen this bug yet
+ $ids = {} unless defined $ids;
+ $ids->{$bug_id} = 1;
+ if ($ids->{$dep_id}) {
+ ThrowUserError("relationship_loop_single",
+ {'bug_id' => $bug_id, 'dep_id' => $dep_id, 'field_name' => $field});
+ }
+
+ # Get this dependency's record from the database
+ my $dbh = Bugzilla->dbh;
+ my $next_dep_id
+ = $dbh->selectrow_array("SELECT $field FROM bugs WHERE bug_id = ?",
+ undef, $dep_id);
+
+ _check_relationship_loop($field, $dep_id, $next_dep_id, $ids);
}
#####################################################################
@@ -2298,63 +2359,63 @@ sub _check_relationship_loop {
#####################################################################
sub fields {
- my $class = shift;
-
- my @fields =
- (
- # Standard Fields
- # Keep this ordering in sync with bugzilla.dtd.
- qw(bug_id alias creation_ts short_desc delta_ts
- reporter_accessible cclist_accessible
- classification_id classification
- product component version rep_platform op_sys
- bug_status resolution dup_id see_also
- bug_file_loc status_whiteboard keywords
- priority bug_severity target_milestone
- dependson blocked everconfirmed
- reporter assigned_to cc estimated_time
- remaining_time actual_time deadline),
-
- # Conditional Fields
- Bugzilla->params->{'useqacontact'} ? "qa_contact" : (),
- # Custom Fields
- map { $_->name } Bugzilla->active_custom_fields
- );
- Bugzilla::Hook::process('bug_fields', {'fields' => \@fields} );
-
- return @fields;
+ my $class = shift;
+
+ my @fields = (
+
+ # Standard Fields
+ # Keep this ordering in sync with bugzilla.dtd.
+ qw(bug_id alias creation_ts short_desc delta_ts
+ reporter_accessible cclist_accessible
+ classification_id classification
+ product component version rep_platform op_sys
+ bug_status resolution dup_id see_also
+ bug_file_loc status_whiteboard keywords
+ priority bug_severity target_milestone
+ dependson blocked everconfirmed
+ reporter assigned_to cc estimated_time
+ remaining_time actual_time deadline),
+
+ # Conditional Fields
+ Bugzilla->params->{'useqacontact'} ? "qa_contact" : (),
+
+ # Custom Fields
+ map { $_->name } Bugzilla->active_custom_fields
+ );
+ Bugzilla::Hook::process('bug_fields', {'fields' => \@fields});
+
+ return @fields;
}
#####################################################################
-# Mutators
+# Mutators
#####################################################################
# To run check_can_change_field.
sub _set_global_validator {
- my ($self, $value, $field) = @_;
- my $current = $self->$field;
- my $privs;
-
- if (ref $current && ref($current) ne 'ARRAY'
- && $current->isa('Bugzilla::Object')) {
- $current = $current->id ;
- }
- if (ref $value && ref($value) ne 'ARRAY'
- && $value->isa('Bugzilla::Object')) {
- $value = $value->id ;
- }
- my $can = $self->check_can_change_field($field, $current, $value, \$privs);
- if (!$can) {
- if ($field eq 'assigned_to' || $field eq 'qa_contact') {
- $value = Bugzilla::User->new($value)->login;
- $current = Bugzilla::User->new($current)->login;
- }
- ThrowUserError('illegal_change', { field => $field,
- oldvalue => $current,
- newvalue => $value,
- privs => $privs });
- }
- $self->_check_field_is_mandatory($value, $field);
+ my ($self, $value, $field) = @_;
+ my $current = $self->$field;
+ my $privs;
+
+ if ( ref $current
+ && ref($current) ne 'ARRAY'
+ && $current->isa('Bugzilla::Object'))
+ {
+ $current = $current->id;
+ }
+ if (ref $value && ref($value) ne 'ARRAY' && $value->isa('Bugzilla::Object')) {
+ $value = $value->id;
+ }
+ my $can = $self->check_can_change_field($field, $current, $value, \$privs);
+ if (!$can) {
+ if ($field eq 'assigned_to' || $field eq 'qa_contact') {
+ $value = Bugzilla::User->new($value)->login;
+ $current = Bugzilla::User->new($current)->login;
+ }
+ ThrowUserError('illegal_change',
+ {field => $field, oldvalue => $current, newvalue => $value, privs => $privs});
+ }
+ $self->_check_field_is_mandatory($value, $field);
}
@@ -2365,359 +2426,384 @@ sub _set_global_validator {
# Note that if you are changing multiple bugs at once, you must pass
# other_bugs to set_all in order for it to behave properly.
sub set_all {
- my $self = shift;
- my ($input_params) = @_;
-
- # Clone the data as we are going to alter it, and this would affect
- # subsequent bugs when calling set_all() again, as some fields would
- # be modified or no longer defined.
- my $params = {};
- %$params = %$input_params;
-
- # You cannot mark bugs as duplicate when changing several bugs at once
- # (because currently there is no way to check for duplicate loops in that
- # situation). You also cannot set the alias of several bugs at once.
- if ($params->{other_bugs} and scalar @{ $params->{other_bugs} } > 1) {
- ThrowUserError('dupe_not_allowed') if exists $params->{dup_id};
- ThrowUserError('multiple_alias_not_allowed')
- if defined $params->{alias};
- }
-
- # For security purposes, and because lots of other checks depend on it,
- # we set the product first before anything else.
- my $product_changed; # Used only for strict_isolation checks.
- if (exists $params->{'product'}) {
- $product_changed = $self->_set_product($params->{'product'}, $params);
- }
-
- # strict_isolation checks mean that we should set the groups
- # immediately after changing the product.
- $self->_add_remove($params, 'groups');
-
- if (exists $params->{'dependson'} or exists $params->{'blocked'}) {
- my %set_deps;
- foreach my $name (qw(dependson blocked)) {
- my @dep_ids = @{ $self->$name };
- # If only one of the two fields was passed in, then we need to
- # retain the current value for the other one.
- if (!exists $params->{$name}) {
- $set_deps{$name} = \@dep_ids;
- next;
- }
-
- # Explicitly setting them to a particular value overrides
- # add/remove.
- if (exists $params->{$name}->{set}) {
- $set_deps{$name} = $params->{$name}->{set};
- next;
- }
-
- foreach my $add (@{ $params->{$name}->{add} || [] }) {
- push(@dep_ids, $add) if !grep($_ == $add, @dep_ids);
- }
- foreach my $remove (@{ $params->{$name}->{remove} || [] }) {
- @dep_ids = grep($_ != $remove, @dep_ids);
- }
- $set_deps{$name} = \@dep_ids;
- }
-
- $self->set_dependencies($set_deps{'dependson'}, $set_deps{'blocked'});
- }
-
- if (exists $params->{'keywords'}) {
- # Sorting makes the order "add, remove, set", just like for other
- # fields.
- foreach my $action (sort keys %{ $params->{'keywords'} }) {
- $self->modify_keywords($params->{'keywords'}->{$action}, $action);
- }
- }
+ my $self = shift;
+ my ($input_params) = @_;
+
+ # Clone the data as we are going to alter it, and this would affect
+ # subsequent bugs when calling set_all() again, as some fields would
+ # be modified or no longer defined.
+ my $params = {};
+ %$params = %$input_params;
+
+ # You cannot mark bugs as duplicate when changing several bugs at once
+ # (because currently there is no way to check for duplicate loops in that
+ # situation). You also cannot set the alias of several bugs at once.
+ if ($params->{other_bugs} and scalar @{$params->{other_bugs}} > 1) {
+ ThrowUserError('dupe_not_allowed') if exists $params->{dup_id};
+ ThrowUserError('multiple_alias_not_allowed') if defined $params->{alias};
+ }
+
+ # For security purposes, and because lots of other checks depend on it,
+ # we set the product first before anything else.
+ my $product_changed; # Used only for strict_isolation checks.
+ if (exists $params->{'product'}) {
+ $product_changed = $self->_set_product($params->{'product'}, $params);
+ }
+
+ # strict_isolation checks mean that we should set the groups
+ # immediately after changing the product.
+ $self->_add_remove($params, 'groups');
+
+ if (exists $params->{'dependson'} or exists $params->{'blocked'}) {
+ my %set_deps;
+ foreach my $name (qw(dependson blocked)) {
+ my @dep_ids = @{$self->$name};
+
+ # If only one of the two fields was passed in, then we need to
+ # retain the current value for the other one.
+ if (!exists $params->{$name}) {
+ $set_deps{$name} = \@dep_ids;
+ next;
+ }
+
+ # Explicitly setting them to a particular value overrides
+ # add/remove.
+ if (exists $params->{$name}->{set}) {
+ $set_deps{$name} = $params->{$name}->{set};
+ next;
+ }
+
+ foreach my $add (@{$params->{$name}->{add} || []}) {
+ push(@dep_ids, $add) if !grep($_ == $add, @dep_ids);
+ }
+ foreach my $remove (@{$params->{$name}->{remove} || []}) {
+ @dep_ids = grep($_ != $remove, @dep_ids);
+ }
+ $set_deps{$name} = \@dep_ids;
+ }
+
+ $self->set_dependencies($set_deps{'dependson'}, $set_deps{'blocked'});
+ }
+
+ if (exists $params->{'keywords'}) {
+
+ # Sorting makes the order "add, remove, set", just like for other
+ # fields.
+ foreach my $action (sort keys %{$params->{'keywords'}}) {
+ $self->modify_keywords($params->{'keywords'}->{$action}, $action);
+ }
+ }
+
+ if (exists $params->{'comment'} or exists $params->{'work_time'}) {
+
+ # Add a comment as needed to each bug. This is done early because
+ # there are lots of things that want to check if we added a comment.
+ $self->add_comment(
+ $params->{'comment'}->{'body'},
+ {
+ isprivate => $params->{'comment'}->{'is_private'},
+ work_time => $params->{'work_time'}
+ }
+ );
+ }
- if (exists $params->{'comment'} or exists $params->{'work_time'}) {
- # Add a comment as needed to each bug. This is done early because
- # there are lots of things that want to check if we added a comment.
- $self->add_comment($params->{'comment'}->{'body'},
- { isprivate => $params->{'comment'}->{'is_private'},
- work_time => $params->{'work_time'} });
- }
+ if (exists $params->{alias} && $params->{alias}{set}) {
+ my ($removed_aliases, $added_aliases)
+ = diff_arrays($self->alias, $params->{alias}{set});
+ $params->{alias} = {add => $added_aliases, remove => $removed_aliases,};
+ }
- if (exists $params->{alias} && $params->{alias}{set}) {
- my ($removed_aliases, $added_aliases) = diff_arrays(
- $self->alias, $params->{alias}{set});
- $params->{alias} = {
- add => $added_aliases,
- remove => $removed_aliases,
- };
- }
+ my %normal_set_all;
+ foreach my $name (keys %$params) {
- my %normal_set_all;
- foreach my $name (keys %$params) {
- # These are handled separately below.
- if ($self->can("set_$name")) {
- $normal_set_all{$name} = $params->{$name};
- }
+ # These are handled separately below.
+ if ($self->can("set_$name")) {
+ $normal_set_all{$name} = $params->{$name};
}
- $self->SUPER::set_all(\%normal_set_all);
+ }
+ $self->SUPER::set_all(\%normal_set_all);
- $self->reset_assigned_to if $params->{'reset_assigned_to'};
- $self->reset_qa_contact if $params->{'reset_qa_contact'};
+ $self->reset_assigned_to if $params->{'reset_assigned_to'};
+ $self->reset_qa_contact if $params->{'reset_qa_contact'};
- $self->_add_remove($params, 'see_also');
+ $self->_add_remove($params, 'see_also');
- # And set custom fields.
- my @custom_fields = Bugzilla->active_custom_fields;
- foreach my $field (@custom_fields) {
- my $fname = $field->name;
- if (exists $params->{$fname}) {
- $self->set_custom_field($field, $params->{$fname});
- }
+ # And set custom fields.
+ my @custom_fields = Bugzilla->active_custom_fields;
+ foreach my $field (@custom_fields) {
+ my $fname = $field->name;
+ if (exists $params->{$fname}) {
+ $self->set_custom_field($field, $params->{$fname});
}
+ }
- $self->_add_remove($params, 'cc');
- $self->_add_remove($params, 'alias');
+ $self->_add_remove($params, 'cc');
+ $self->_add_remove($params, 'alias');
- # Theoretically you could move a product without ever specifying
- # a new assignee or qa_contact, or adding/removing any CCs. So,
- # we have to check that the current assignee, qa, and CCs are still
- # valid if we've switched products, under strict_isolation. We can only
- # do that here, because if they *did* change the assignee, qa, or CC,
- # then we don't want to check the original ones, only the new ones.
- $self->_check_strict_isolation() if $product_changed;
+ # Theoretically you could move a product without ever specifying
+ # a new assignee or qa_contact, or adding/removing any CCs. So,
+ # we have to check that the current assignee, qa, and CCs are still
+ # valid if we've switched products, under strict_isolation. We can only
+ # do that here, because if they *did* change the assignee, qa, or CC,
+ # then we don't want to check the original ones, only the new ones.
+ $self->_check_strict_isolation() if $product_changed;
}
# Helper for set_all that helps with fields that have an "add/remove"
# pattern instead of a "set_" pattern.
sub _add_remove {
- my ($self, $params, $name) = @_;
- my @add = @{ $params->{$name}->{add} || [] };
- my @remove = @{ $params->{$name}->{remove} || [] };
- $name =~ s/s$// if $name ne 'alias';
- my $add_method = "add_$name";
- my $remove_method = "remove_$name";
- $self->$add_method($_) foreach @add;
- $self->$remove_method($_) foreach @remove;
+ my ($self, $params, $name) = @_;
+ my @add = @{$params->{$name}->{add} || []};
+ my @remove = @{$params->{$name}->{remove} || []};
+ $name =~ s/s$// if $name ne 'alias';
+ my $add_method = "add_$name";
+ my $remove_method = "remove_$name";
+ $self->$add_method($_) foreach @add;
+ $self->$remove_method($_) foreach @remove;
}
sub set_assigned_to {
- my ($self, $value) = @_;
- $self->set('assigned_to', $value);
- # Store the old assignee. check_can_change_field() needs it.
- $self->{'_old_assigned_to'} = $self->{'assigned_to_obj'}->id;
- delete $self->{'assigned_to_obj'};
+ my ($self, $value) = @_;
+ $self->set('assigned_to', $value);
+
+ # Store the old assignee. check_can_change_field() needs it.
+ $self->{'_old_assigned_to'} = $self->{'assigned_to_obj'}->id;
+ delete $self->{'assigned_to_obj'};
}
+
sub reset_assigned_to {
- my $self = shift;
- my $comp = $self->component_obj;
- $self->set_assigned_to($comp->default_assignee);
+ my $self = shift;
+ my $comp = $self->component_obj;
+ $self->set_assigned_to($comp->default_assignee);
}
sub set_bug_ignored { $_[0]->set('bug_ignored', $_[1]); }
sub set_cclist_accessible { $_[0]->set('cclist_accessible', $_[1]); }
sub set_comment_is_private {
- my ($self, $comments, $isprivate) = @_;
- $self->{comment_isprivate} ||= [];
- my $is_insider = Bugzilla->user->is_insider;
+ my ($self, $comments, $isprivate) = @_;
+ $self->{comment_isprivate} ||= [];
+ my $is_insider = Bugzilla->user->is_insider;
- $comments = { $comments => $isprivate } unless ref $comments;
+ $comments = {$comments => $isprivate} unless ref $comments;
- foreach my $comment (@{$self->comments}) {
- # Skip unmodified comment privacy.
- next unless exists $comments->{$comment->id};
+ foreach my $comment (@{$self->comments}) {
- my $isprivate = delete $comments->{$comment->id} ? 1 : 0;
- if ($isprivate != $comment->is_private) {
- ThrowUserError('user_not_insider') unless $is_insider;
- $comment->set_is_private($isprivate);
- push @{$self->{comment_isprivate}}, $comment;
- }
+ # Skip unmodified comment privacy.
+ next unless exists $comments->{$comment->id};
+
+ my $isprivate = delete $comments->{$comment->id} ? 1 : 0;
+ if ($isprivate != $comment->is_private) {
+ ThrowUserError('user_not_insider') unless $is_insider;
+ $comment->set_is_private($isprivate);
+ push @{$self->{comment_isprivate}}, $comment;
}
+ }
- # If there are still entries in $comments, then they are illegal.
- ThrowUserError('comment_invalid_isprivate', { id => join(', ', keys %$comments) })
- if scalar keys %$comments;
-
- # If no comment privacy has been modified, remove this key.
- delete $self->{comment_isprivate} unless scalar @{$self->{comment_isprivate}};
-}
-
-sub set_component {
- my ($self, $name) = @_;
- my $old_comp = $self->component_obj;
- my $component = $self->_check_component($name);
- if ($old_comp->id != $component->id) {
- $self->{component_id} = $component->id;
- $self->{component} = $component->name;
- $self->{component_obj} = $component;
- # For update()
- $self->{_old_component_name} = $old_comp->name;
- # Add in the Default CC of the new Component;
- foreach my $cc (@{$component->initial_cc}) {
- $self->add_cc($cc);
- }
+ # If there are still entries in $comments, then they are illegal.
+ ThrowUserError('comment_invalid_isprivate', {id => join(', ', keys %$comments)})
+ if scalar keys %$comments;
+
+ # If no comment privacy has been modified, remove this key.
+ delete $self->{comment_isprivate} unless scalar @{$self->{comment_isprivate}};
+}
+
+sub set_component {
+ my ($self, $name) = @_;
+ my $old_comp = $self->component_obj;
+ my $component = $self->_check_component($name);
+ if ($old_comp->id != $component->id) {
+ $self->{component_id} = $component->id;
+ $self->{component} = $component->name;
+ $self->{component_obj} = $component;
+
+ # For update()
+ $self->{_old_component_name} = $old_comp->name;
+
+ # Add in the Default CC of the new Component;
+ foreach my $cc (@{$component->initial_cc}) {
+ $self->add_cc($cc);
}
+ }
}
+
sub set_custom_field {
- my ($self, $field, $value) = @_;
+ my ($self, $field, $value) = @_;
- if (ref $value eq 'ARRAY' && $field->type != FIELD_TYPE_MULTI_SELECT) {
- $value = $value->[0];
- }
- ThrowCodeError('field_not_custom', { field => $field }) if !$field->custom;
- $self->set($field->name, $value);
+ if (ref $value eq 'ARRAY' && $field->type != FIELD_TYPE_MULTI_SELECT) {
+ $value = $value->[0];
+ }
+ ThrowCodeError('field_not_custom', {field => $field}) if !$field->custom;
+ $self->set($field->name, $value);
}
sub set_deadline { $_[0]->set('deadline', $_[1]); }
+
sub set_dependencies {
- my ($self, $dependson, $blocked) = @_;
- my %extra = ( blocked => $blocked );
- $dependson = $self->_check_dependencies($dependson, 'dependson', \%extra);
- $blocked = $extra{blocked};
- # These may already be detainted, but all setters are supposed to
- # detaint their input if they've run a validator (just as though
- # we had used Bugzilla::Object::set), so we do that here.
- detaint_natural($_) foreach (@$dependson, @$blocked);
- $self->{'dependson'} = $dependson;
- $self->{'blocked'} = $blocked;
- delete $self->{depends_on_obj};
- delete $self->{blocks_obj};
+ my ($self, $dependson, $blocked) = @_;
+ my %extra = (blocked => $blocked);
+ $dependson = $self->_check_dependencies($dependson, 'dependson', \%extra);
+ $blocked = $extra{blocked};
+
+ # These may already be detainted, but all setters are supposed to
+ # detaint their input if they've run a validator (just as though
+ # we had used Bugzilla::Object::set), so we do that here.
+ detaint_natural($_) foreach (@$dependson, @$blocked);
+ $self->{'dependson'} = $dependson;
+ $self->{'blocked'} = $blocked;
+ delete $self->{depends_on_obj};
+ delete $self->{blocks_obj};
}
sub _clear_dup_id { $_[0]->{dup_id} = undef; }
+
sub set_dup_id {
- my ($self, $dup_id) = @_;
- my $old = $self->dup_id || 0;
- $self->set('dup_id', $dup_id);
- my $new = $self->dup_id;
- return if $old == $new;
-
- # Make sure that we have the DUPLICATE resolution. This is needed
- # if somebody calls set_dup_id without calling set_bug_status or
- # set_resolution.
- if ($self->resolution ne 'DUPLICATE') {
- # Even if the current status is VERIFIED, we change it back to
- # RESOLVED (or whatever the duplicate_or_move_bug_status is) here,
- # because that's the same thing the UI does when you click on the
- # "Mark as Duplicate" link. If people really want to retain their
- # current status, they can use set_bug_status and set the DUPLICATE
- # resolution before getting here.
- $self->set_bug_status(
- Bugzilla->params->{'duplicate_or_move_bug_status'},
- { resolution => 'DUPLICATE' });
- }
-
- # Update the other bug.
- my $dupe_of = new Bugzilla::Bug($self->dup_id);
- if (delete $self->{_add_dup_cc}) {
- $dupe_of->add_cc($self->reporter);
- }
- $dupe_of->add_comment("", { type => CMT_HAS_DUPE,
- extra_data => $self->id });
- $self->{_dup_for_update} = $dupe_of;
-
- # Now make sure that we add a duplicate comment on *this* bug.
- # (Change an existing comment into a dup comment, if there is one,
- # or add an empty dup comment.)
- if ($self->{added_comments}) {
- my @normal = grep { !defined $_->{type} || $_->{type} == CMT_NORMAL }
- @{ $self->{added_comments} };
- # Turn the last one into a dup comment.
- $normal[-1]->{type} = CMT_DUPE_OF;
- $normal[-1]->{extra_data} = $self->dup_id;
- }
- else {
- $self->add_comment('', { type => CMT_DUPE_OF,
- extra_data => $self->dup_id });
- }
+ my ($self, $dup_id) = @_;
+ my $old = $self->dup_id || 0;
+ $self->set('dup_id', $dup_id);
+ my $new = $self->dup_id;
+ return if $old == $new;
+
+ # Make sure that we have the DUPLICATE resolution. This is needed
+ # if somebody calls set_dup_id without calling set_bug_status or
+ # set_resolution.
+ if ($self->resolution ne 'DUPLICATE') {
+
+ # Even if the current status is VERIFIED, we change it back to
+ # RESOLVED (or whatever the duplicate_or_move_bug_status is) here,
+ # because that's the same thing the UI does when you click on the
+ # "Mark as Duplicate" link. If people really want to retain their
+ # current status, they can use set_bug_status and set the DUPLICATE
+ # resolution before getting here.
+ $self->set_bug_status(Bugzilla->params->{'duplicate_or_move_bug_status'},
+ {resolution => 'DUPLICATE'});
+ }
+
+ # Update the other bug.
+ my $dupe_of = new Bugzilla::Bug($self->dup_id);
+ if (delete $self->{_add_dup_cc}) {
+ $dupe_of->add_cc($self->reporter);
+ }
+ $dupe_of->add_comment("", {type => CMT_HAS_DUPE, extra_data => $self->id});
+ $self->{_dup_for_update} = $dupe_of;
+
+ # Now make sure that we add a duplicate comment on *this* bug.
+ # (Change an existing comment into a dup comment, if there is one,
+ # or add an empty dup comment.)
+ if ($self->{added_comments}) {
+ my @normal = grep { !defined $_->{type} || $_->{type} == CMT_NORMAL }
+ @{$self->{added_comments}};
+
+ # Turn the last one into a dup comment.
+ $normal[-1]->{type} = CMT_DUPE_OF;
+ $normal[-1]->{extra_data} = $self->dup_id;
+ }
+ else {
+ $self->add_comment('', {type => CMT_DUPE_OF, extra_data => $self->dup_id});
+ }
}
sub set_estimated_time { $_[0]->set('estimated_time', $_[1]); }
-sub _set_everconfirmed { $_[0]->set('everconfirmed', $_[1]); }
+sub _set_everconfirmed { $_[0]->set('everconfirmed', $_[1]); }
+
sub set_flags {
- my ($self, $flags, $new_flags) = @_;
+ my ($self, $flags, $new_flags) = @_;
- Bugzilla::Flag->set_flag($self, $_) foreach (@$flags, @$new_flags);
+ Bugzilla::Flag->set_flag($self, $_) foreach (@$flags, @$new_flags);
}
-sub set_op_sys { $_[0]->set('op_sys', $_[1]); }
-sub set_platform { $_[0]->set('rep_platform', $_[1]); }
-sub set_priority { $_[0]->set('priority', $_[1]); }
+sub set_op_sys { $_[0]->set('op_sys', $_[1]); }
+sub set_platform { $_[0]->set('rep_platform', $_[1]); }
+sub set_priority { $_[0]->set('priority', $_[1]); }
+
# For security reasons, you have to use set_all to change the product.
# See the strict_isolation check in set_all for an explanation.
sub _set_product {
- my ($self, $name, $params) = @_;
- my $old_product = $self->product_obj;
- my $product = $self->_check_product($name);
-
- my $product_changed = 0;
- if ($old_product->id != $product->id) {
- $self->{product_id} = $product->id;
- $self->{product} = $product->name;
- $self->{product_obj} = $product;
- # For update()
- $self->{_old_product_name} = $old_product->name;
- # Delete fields that depend upon the old Product value.
- delete $self->{choices};
- $product_changed = 1;
- }
+ my ($self, $name, $params) = @_;
+ my $old_product = $self->product_obj;
+ my $product = $self->_check_product($name);
- $params ||= {};
- # We delete these so that they're not set again later in set_all.
- my $comp_name = delete $params->{component} || $self->component;
- my $vers_name = delete $params->{version} || $self->version;
- my $tm_name = delete $params->{target_milestone};
- # This way, if usetargetmilestone is off and we've changed products,
- # set_target_milestone will reset our target_milestone to
- # $product->default_milestone. But if we haven't changed products,
- # we don't reset anything.
- if (!defined $tm_name
- && (Bugzilla->params->{'usetargetmilestone'} || !$product_changed))
- {
- $tm_name = $self->target_milestone;
- }
+ my $product_changed = 0;
+ if ($old_product->id != $product->id) {
+ $self->{product_id} = $product->id;
+ $self->{product} = $product->name;
+ $self->{product_obj} = $product;
- if ($product_changed && Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
- # Try to set each value with the new product.
- # Have to set error_mode because Throw*Error calls exit() otherwise.
- my $old_error_mode = Bugzilla->error_mode;
- Bugzilla->error_mode(ERROR_MODE_DIE);
- my $component_ok = eval { $self->set_component($comp_name); 1; };
- my $version_ok = eval { $self->set_version($vers_name); 1; };
- my $milestone_ok = 1;
- # Reporters can move bugs between products but not set the TM.
- if ($self->check_can_change_field('target_milestone', 0, 1)) {
- $milestone_ok = eval { $self->set_target_milestone($tm_name); 1; };
- }
- else {
- # Have to set this directly to bypass the validators.
- $self->{target_milestone} = $product->default_milestone;
- }
- # If there were any errors thrown, make sure we don't mess up any
- # other part of Bugzilla that checks $@.
- undef $@;
- Bugzilla->error_mode($old_error_mode);
-
- my $verified = $params->{product_change_confirmed};
- my %vars;
- if (!$verified || !$component_ok || !$version_ok || !$milestone_ok) {
- $vars{defaults} = {
- # Note that because of the eval { set } above, these are
- # already set correctly if they're valid, otherwise they're
- # set to some invalid value which the template will ignore.
- component => $self->component,
- version => $self->version,
- milestone => $milestone_ok ? $self->target_milestone
- : $product->default_milestone
- };
- $vars{components} = [map { $_->name } grep($_->is_active, @{$product->components})];
- $vars{milestones} = [map { $_->name } grep($_->is_active, @{$product->milestones})];
- $vars{versions} = [map { $_->name } grep($_->is_active, @{$product->versions})];
- }
+ # For update()
+ $self->{_old_product_name} = $old_product->name;
- if (!$verified) {
- $vars{verify_bug_groups} = 1;
- my $dbh = Bugzilla->dbh;
- my @idlist = ($self->id);
- push(@idlist, map {$_->id} @{ $params->{other_bugs} })
- if $params->{other_bugs};
- @idlist = uniq @idlist;
- # Get the ID of groups which are no longer valid in the new product.
- my $gids = $dbh->selectcol_arrayref(
- 'SELECT bgm.group_id
+ # Delete fields that depend upon the old Product value.
+ delete $self->{choices};
+ $product_changed = 1;
+ }
+
+ $params ||= {};
+
+ # We delete these so that they're not set again later in set_all.
+ my $comp_name = delete $params->{component} || $self->component;
+ my $vers_name = delete $params->{version} || $self->version;
+ my $tm_name = delete $params->{target_milestone};
+
+ # This way, if usetargetmilestone is off and we've changed products,
+ # set_target_milestone will reset our target_milestone to
+ # $product->default_milestone. But if we haven't changed products,
+ # we don't reset anything.
+ if (!defined $tm_name
+ && (Bugzilla->params->{'usetargetmilestone'} || !$product_changed))
+ {
+ $tm_name = $self->target_milestone;
+ }
+
+ if ($product_changed && Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
+
+ # Try to set each value with the new product.
+ # Have to set error_mode because Throw*Error calls exit() otherwise.
+ my $old_error_mode = Bugzilla->error_mode;
+ Bugzilla->error_mode(ERROR_MODE_DIE);
+ my $component_ok = eval { $self->set_component($comp_name); 1; };
+ my $version_ok = eval { $self->set_version($vers_name); 1; };
+ my $milestone_ok = 1;
+
+ # Reporters can move bugs between products but not set the TM.
+ if ($self->check_can_change_field('target_milestone', 0, 1)) {
+ $milestone_ok = eval { $self->set_target_milestone($tm_name); 1; };
+ }
+ else {
+ # Have to set this directly to bypass the validators.
+ $self->{target_milestone} = $product->default_milestone;
+ }
+
+ # If there were any errors thrown, make sure we don't mess up any
+ # other part of Bugzilla that checks $@.
+ undef $@;
+ Bugzilla->error_mode($old_error_mode);
+
+ my $verified = $params->{product_change_confirmed};
+ my %vars;
+ if (!$verified || !$component_ok || !$version_ok || !$milestone_ok) {
+ $vars{defaults} = {
+
+ # Note that because of the eval { set } above, these are
+ # already set correctly if they're valid, otherwise they're
+ # set to some invalid value which the template will ignore.
+ component => $self->component,
+ version => $self->version,
+ milestone => $milestone_ok
+ ? $self->target_milestone
+ : $product->default_milestone
+ };
+ $vars{components}
+ = [map { $_->name } grep($_->is_active, @{$product->components})];
+ $vars{milestones}
+ = [map { $_->name } grep($_->is_active, @{$product->milestones})];
+ $vars{versions} = [map { $_->name } grep($_->is_active, @{$product->versions})];
+ }
+
+ if (!$verified) {
+ $vars{verify_bug_groups} = 1;
+ my $dbh = Bugzilla->dbh;
+ my @idlist = ($self->id);
+ push(@idlist, map { $_->id } @{$params->{other_bugs}}) if $params->{other_bugs};
+ @idlist = uniq @idlist;
+
+ # Get the ID of groups which are no longer valid in the new product.
+ my $gids = $dbh->selectcol_arrayref(
+ 'SELECT bgm.group_id
FROM bug_group_map AS bgm
WHERE bgm.bug_id IN (' . join(',', ('?') x @idlist) . ')
AND bgm.group_id NOT IN
@@ -2726,159 +2812,172 @@ sub _set_product {
WHERE gcm.product_id = ?
AND ( (gcm.membercontrol != ?
AND gcm.group_id IN ('
- . Bugzilla->user->groups_as_string . '))
- OR gcm.othercontrol != ?) )',
- undef, (@idlist, $product->id, CONTROLMAPNA, CONTROLMAPNA));
- $vars{'old_groups'} = Bugzilla::Group->new_from_list($gids);
-
- # Did we come here from editing multiple bugs? (affects how we
- # show optional group changes)
- $vars{multiple_bugs} = (@idlist > 1) ? 1 : 0;
- }
-
- if (%vars) {
- $vars{product} = $product;
- $vars{bug} = $self;
- my $template = Bugzilla->template;
- $template->process("bug/process/verify-new-product.html.tmpl",
- \%vars) || ThrowTemplateError($template->error());
- exit;
- }
+ . Bugzilla->user->groups_as_string . '))
+ OR gcm.othercontrol != ?) )', undef,
+ (@idlist, $product->id, CONTROLMAPNA, CONTROLMAPNA)
+ );
+ $vars{'old_groups'} = Bugzilla::Group->new_from_list($gids);
+
+ # Did we come here from editing multiple bugs? (affects how we
+ # show optional group changes)
+ $vars{multiple_bugs} = (@idlist > 1) ? 1 : 0;
+ }
+
+ if (%vars) {
+ $vars{product} = $product;
+ $vars{bug} = $self;
+ my $template = Bugzilla->template;
+ $template->process("bug/process/verify-new-product.html.tmpl", \%vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+ }
+ else {
+ # When we're not in the browser (or we didn't change the product), we
+ # just die if any of these are invalid.
+ $self->set_component($comp_name);
+ $self->set_version($vers_name);
+ if ($product_changed
+ and !$self->check_can_change_field('target_milestone', 0, 1))
+ {
+ # Have to set this directly to bypass the validators.
+ $self->{target_milestone} = $product->default_milestone;
}
else {
- # When we're not in the browser (or we didn't change the product), we
- # just die if any of these are invalid.
- $self->set_component($comp_name);
- $self->set_version($vers_name);
- if ($product_changed
- and !$self->check_can_change_field('target_milestone', 0, 1))
- {
- # Have to set this directly to bypass the validators.
- $self->{target_milestone} = $product->default_milestone;
- }
- else {
- $self->set_target_milestone($tm_name);
- }
+ $self->set_target_milestone($tm_name);
}
+ }
- if ($product_changed) {
- # Remove groups that can't be set in the new product.
- # We copy this array because the original array is modified while we're
- # working, and that confuses "foreach".
- my @current_groups = @{$self->groups_in};
- foreach my $group (@current_groups) {
- if (!$product->group_is_valid($group)) {
- $self->remove_group($group);
- }
- }
+ if ($product_changed) {
- # Make sure the bug is in all the mandatory groups for the new product.
- foreach my $group (@{$product->groups_mandatory}) {
- $self->add_group($group);
- }
+ # Remove groups that can't be set in the new product.
+ # We copy this array because the original array is modified while we're
+ # working, and that confuses "foreach".
+ my @current_groups = @{$self->groups_in};
+ foreach my $group (@current_groups) {
+ if (!$product->group_is_valid($group)) {
+ $self->remove_group($group);
+ }
}
-
- return $product_changed;
+
+ # Make sure the bug is in all the mandatory groups for the new product.
+ foreach my $group (@{$product->groups_mandatory}) {
+ $self->add_group($group);
+ }
+ }
+
+ return $product_changed;
}
sub set_qa_contact {
- my ($self, $value) = @_;
- $self->set('qa_contact', $value);
- # Store the old QA contact. check_can_change_field() needs it.
- if ($self->{'qa_contact_obj'}) {
- $self->{'_old_qa_contact'} = $self->{'qa_contact_obj'}->id;
- }
- delete $self->{'qa_contact_obj'};
+ my ($self, $value) = @_;
+ $self->set('qa_contact', $value);
+
+ # Store the old QA contact. check_can_change_field() needs it.
+ if ($self->{'qa_contact_obj'}) {
+ $self->{'_old_qa_contact'} = $self->{'qa_contact_obj'}->id;
+ }
+ delete $self->{'qa_contact_obj'};
}
+
sub reset_qa_contact {
- my $self = shift;
- my $comp = $self->component_obj;
- $self->set_qa_contact($comp->default_qa_contact);
+ my $self = shift;
+ my $comp = $self->component_obj;
+ $self->set_qa_contact($comp->default_qa_contact);
}
sub set_remaining_time { $_[0]->set('remaining_time', $_[1]); }
+
# Used only when closing a bug or moving between closed states.
sub _zero_remaining_time { $_[0]->{'remaining_time'} = 0; }
sub set_reporter_accessible { $_[0]->set('reporter_accessible', $_[1]); }
+
sub set_resolution {
- my ($self, $value, $params) = @_;
-
- my $old_res = $self->resolution;
- $self->set('resolution', $value);
- delete $self->{choices};
- my $new_res = $self->resolution;
+ my ($self, $value, $params) = @_;
- if ($new_res ne $old_res) {
- # Clear the dup_id if we're leaving the dup resolution.
- if ($old_res eq 'DUPLICATE') {
- $self->_clear_dup_id();
- }
- # Duplicates should have no remaining time left.
- elsif ($new_res eq 'DUPLICATE' && $self->remaining_time != 0) {
- $self->_zero_remaining_time();
- }
+ my $old_res = $self->resolution;
+ $self->set('resolution', $value);
+ delete $self->{choices};
+ my $new_res = $self->resolution;
+
+ if ($new_res ne $old_res) {
+
+ # Clear the dup_id if we're leaving the dup resolution.
+ if ($old_res eq 'DUPLICATE') {
+ $self->_clear_dup_id();
}
-
- # We don't check if we're entering or leaving the dup resolution here,
- # because we could be moving from being a dup of one bug to being a dup
- # of another, theoretically. Note that this code block will also run
- # when going between different closed states.
- if ($self->resolution eq 'DUPLICATE') {
- if (my $dup_id = $params->{dup_id}) {
- $self->set_dup_id($dup_id);
- }
- elsif (!$self->dup_id) {
- ThrowUserError('dupe_id_required');
- }
+
+ # Duplicates should have no remaining time left.
+ elsif ($new_res eq 'DUPLICATE' && $self->remaining_time != 0) {
+ $self->_zero_remaining_time();
}
+ }
- # This method has handled dup_id, so set_all doesn't have to worry
- # about it now.
- delete $params->{dup_id};
+ # We don't check if we're entering or leaving the dup resolution here,
+ # because we could be moving from being a dup of one bug to being a dup
+ # of another, theoretically. Note that this code block will also run
+ # when going between different closed states.
+ if ($self->resolution eq 'DUPLICATE') {
+ if (my $dup_id = $params->{dup_id}) {
+ $self->set_dup_id($dup_id);
+ }
+ elsif (!$self->dup_id) {
+ ThrowUserError('dupe_id_required');
+ }
+ }
+
+ # This method has handled dup_id, so set_all doesn't have to worry
+ # about it now.
+ delete $params->{dup_id};
}
+
sub clear_resolution {
- my $self = shift;
- if (!$self->status->is_open) {
- ThrowUserError('resolution_cant_clear', { bug_id => $self->id });
- }
- $self->{'resolution'} = '';
- $self->_clear_dup_id;
+ my $self = shift;
+ if (!$self->status->is_open) {
+ ThrowUserError('resolution_cant_clear', {bug_id => $self->id});
+ }
+ $self->{'resolution'} = '';
+ $self->_clear_dup_id;
}
-sub set_severity { $_[0]->set('bug_severity', $_[1]); }
+sub set_severity { $_[0]->set('bug_severity', $_[1]); }
+
sub set_bug_status {
- my ($self, $status, $params) = @_;
- my $old_status = $self->status;
- $self->set('bug_status', $status);
- delete $self->{'status'};
- delete $self->{'statuses_available'};
- delete $self->{'choices'};
- my $new_status = $self->status;
-
- if ($new_status->is_open) {
- # Check for the everconfirmed transition
- $self->_set_everconfirmed($new_status->name eq 'UNCONFIRMED' ? 0 : 1);
- $self->clear_resolution();
- # Calling clear_resolution handled the "resolution" and "dup_id"
- # setting, so set_all doesn't have to worry about them.
- delete $params->{resolution};
- delete $params->{dup_id};
+ my ($self, $status, $params) = @_;
+ my $old_status = $self->status;
+ $self->set('bug_status', $status);
+ delete $self->{'status'};
+ delete $self->{'statuses_available'};
+ delete $self->{'choices'};
+ my $new_status = $self->status;
+
+ if ($new_status->is_open) {
+
+ # Check for the everconfirmed transition
+ $self->_set_everconfirmed($new_status->name eq 'UNCONFIRMED' ? 0 : 1);
+ $self->clear_resolution();
+
+ # Calling clear_resolution handled the "resolution" and "dup_id"
+ # setting, so set_all doesn't have to worry about them.
+ delete $params->{resolution};
+ delete $params->{dup_id};
+ }
+ else {
+ # We do this here so that we can make sure closed statuses have
+ # resolutions.
+ my $resolution = $self->resolution;
+
+ # We need to check "defined" to prevent people from passing
+ # a blank resolution in the WebService, which would otherwise fail
+ # silently.
+ if (defined $params->{resolution}) {
+ $resolution = delete $params->{resolution};
}
- else {
- # We do this here so that we can make sure closed statuses have
- # resolutions.
- my $resolution = $self->resolution;
- # We need to check "defined" to prevent people from passing
- # a blank resolution in the WebService, which would otherwise fail
- # silently.
- if (defined $params->{resolution}) {
- $resolution = delete $params->{resolution};
- }
- $self->set_resolution($resolution, $params);
+ $self->set_resolution($resolution, $params);
- # Changing between closed statuses zeros the remaining time.
- if ($new_status->id != $old_status->id && $self->remaining_time != 0) {
- $self->_zero_remaining_time();
- }
+ # Changing between closed statuses zeros the remaining time.
+ if ($new_status->id != $old_status->id && $self->remaining_time != 0) {
+ $self->_zero_remaining_time();
}
+ }
}
sub set_status_whiteboard { $_[0]->set('status_whiteboard', $_[1]); }
sub set_summary { $_[0]->set('short_desc', $_[1]); }
@@ -2895,373 +2994,390 @@ sub set_version { $_[0]->set('version', $_[1]); }
# Accepts a User object or a username. Adds the user only if they
# don't already exist as a CC on the bug.
sub add_cc {
- my ($self, $user_or_name) = @_;
- return if !$user_or_name;
- my $user = ref $user_or_name ? $user_or_name
- : Bugzilla::User->check($user_or_name);
- $self->_check_strict_isolation_for_user($user);
- my $cc_users = $self->cc_users;
- push(@$cc_users, $user) if !grep($_->id == $user->id, @$cc_users);
+ my ($self, $user_or_name) = @_;
+ return if !$user_or_name;
+ my $user
+ = ref $user_or_name ? $user_or_name : Bugzilla::User->check($user_or_name);
+ $self->_check_strict_isolation_for_user($user);
+ my $cc_users = $self->cc_users;
+ push(@$cc_users, $user) if !grep($_->id == $user->id, @$cc_users);
}
# Accepts a User object or a username. Removes the User if they exist
# in the list, but doesn't throw an error if they don't exist.
sub remove_cc {
- my ($self, $user_or_name) = @_;
- my $user = ref $user_or_name ? $user_or_name
- : Bugzilla::User->check($user_or_name);
- my $currentUser = Bugzilla->user;
- if (!$self->user->{'canedit'} && $user->id != $currentUser->id) {
- ThrowUserError('cc_remove_denied');
- }
- my $cc_users = $self->cc_users;
- @$cc_users = grep { $_->id != $user->id } @$cc_users;
+ my ($self, $user_or_name) = @_;
+ my $user
+ = ref $user_or_name ? $user_or_name : Bugzilla::User->check($user_or_name);
+ my $currentUser = Bugzilla->user;
+ if (!$self->user->{'canedit'} && $user->id != $currentUser->id) {
+ ThrowUserError('cc_remove_denied');
+ }
+ my $cc_users = $self->cc_users;
+ @$cc_users = grep { $_->id != $user->id } @$cc_users;
}
sub add_alias {
- my ($self, $alias) = @_;
- return if !$alias;
- my $aliases = $self->_check_alias($alias);
- $alias = $aliases->[0];
- my @new_aliases;
- my $found = 0;
- foreach my $old_alias (@{ $self->alias }) {
- if (lc($old_alias) eq lc($alias)) {
- push(@new_aliases, $alias);
- $found = 1;
- }
- else {
- push(@new_aliases, $old_alias);
- }
+ my ($self, $alias) = @_;
+ return if !$alias;
+ my $aliases = $self->_check_alias($alias);
+ $alias = $aliases->[0];
+ my @new_aliases;
+ my $found = 0;
+ foreach my $old_alias (@{$self->alias}) {
+ if (lc($old_alias) eq lc($alias)) {
+ push(@new_aliases, $alias);
+ $found = 1;
+ }
+ else {
+ push(@new_aliases, $old_alias);
}
- push(@new_aliases, $alias) if !$found;
- $self->{alias} = \@new_aliases;
+ }
+ push(@new_aliases, $alias) if !$found;
+ $self->{alias} = \@new_aliases;
}
sub remove_alias {
- my ($self, $alias) = @_;
- my $bug_aliases = $self->alias;
- @$bug_aliases = grep { $_ ne $alias } @$bug_aliases;
+ my ($self, $alias) = @_;
+ my $bug_aliases = $self->alias;
+ @$bug_aliases = grep { $_ ne $alias } @$bug_aliases;
}
# $bug->add_comment("comment", {isprivate => 1, work_time => 10.5,
# type => CMT_NORMAL, extra_data => $data});
sub add_comment {
- my ($self, $comment, $params) = @_;
+ my ($self, $comment, $params) = @_;
- $params ||= {};
+ $params ||= {};
- # Fill out info that doesn't change and callers may not pass in
- $params->{'bug_id'} = $self;
- $params->{'thetext'} = defined($comment) ? $comment : '';
+ # Fill out info that doesn't change and callers may not pass in
+ $params->{'bug_id'} = $self;
+ $params->{'thetext'} = defined($comment) ? $comment : '';
- # Validate all the entered data
- Bugzilla::Comment->check_required_create_fields($params);
- $params = Bugzilla::Comment->run_create_validators($params);
+ # Validate all the entered data
+ Bugzilla::Comment->check_required_create_fields($params);
+ $params = Bugzilla::Comment->run_create_validators($params);
- # This makes it so we won't create new comments when there is nothing
- # to add
- if ($params->{'thetext'} eq ''
- && !($params->{type} || abs($params->{work_time} || 0)))
- {
- return;
- }
+ # This makes it so we won't create new comments when there is nothing
+ # to add
+ if ($params->{'thetext'} eq ''
+ && !($params->{type} || abs($params->{work_time} || 0)))
+ {
+ return;
+ }
- # If the user has explicitly set remaining_time, this will be overridden
- # later in set_all. But if they haven't, this keeps remaining_time
- # up-to-date.
- if ($params->{work_time}) {
- $self->set_remaining_time(max($self->remaining_time - $params->{work_time}, 0));
- }
+ # If the user has explicitly set remaining_time, this will be overridden
+ # later in set_all. But if they haven't, this keeps remaining_time
+ # up-to-date.
+ if ($params->{work_time}) {
+ $self->set_remaining_time(max($self->remaining_time - $params->{work_time}, 0));
+ }
- $self->{added_comments} ||= [];
+ $self->{added_comments} ||= [];
- push(@{$self->{added_comments}}, $params);
+ push(@{$self->{added_comments}}, $params);
}
sub modify_keywords {
- my ($self, $keywords, $action) = @_;
+ my ($self, $keywords, $action) = @_;
- if (!$action || !grep { $action eq $_ } qw(add remove set)) {
- $action = 'set';
- }
+ if (!$action || !grep { $action eq $_ } qw(add remove set)) {
+ $action = 'set';
+ }
- $keywords = $self->_check_keywords($keywords);
- my @old_keywords = @{ $self->keyword_objects };
- my @result;
+ $keywords = $self->_check_keywords($keywords);
+ my @old_keywords = @{$self->keyword_objects};
+ my @result;
- if ($action eq 'set') {
- @result = @$keywords;
+ if ($action eq 'set') {
+ @result = @$keywords;
+ }
+ else {
+ # We're adding or deleting specific keywords.
+ my %keys = map { $_->id => $_ } @old_keywords;
+ if ($action eq 'add') {
+ $keys{$_->id} = $_ foreach @$keywords;
}
else {
- # We're adding or deleting specific keywords.
- my %keys = map { $_->id => $_ } @old_keywords;
- if ($action eq 'add') {
- $keys{$_->id} = $_ foreach @$keywords;
- }
- else {
- delete $keys{$_->id} foreach @$keywords;
- }
- @result = values %keys;
+ delete $keys{$_->id} foreach @$keywords;
}
+ @result = values %keys;
+ }
- # Check if anything was added or removed.
- my @old_ids = map { $_->id } @old_keywords;
- my @new_ids = map { $_->id } @result;
- my ($removed, $added) = diff_arrays(\@old_ids, \@new_ids);
- my $any_changes = scalar @$removed || scalar @$added;
+ # Check if anything was added or removed.
+ my @old_ids = map { $_->id } @old_keywords;
+ my @new_ids = map { $_->id } @result;
+ my ($removed, $added) = diff_arrays(\@old_ids, \@new_ids);
+ my $any_changes = scalar @$removed || scalar @$added;
- # Make sure we retain the sort order.
- @result = sort {lc($a->name) cmp lc($b->name)} @result;
+ # Make sure we retain the sort order.
+ @result = sort { lc($a->name) cmp lc($b->name) } @result;
- if ($any_changes) {
- my $privs;
- my $new = join(', ', (map {$_->name} @result));
- my $check = $self->check_can_change_field('keywords', 0, 1, \$privs)
- || ThrowUserError('illegal_change', { field => 'keywords',
- oldvalue => $self->keywords,
- newvalue => $new,
- privs => $privs });
- }
-
- $self->{'keyword_objects'} = \@result;
+ if ($any_changes) {
+ my $privs;
+ my $new = join(', ', (map { $_->name } @result));
+ my $check
+ = $self->check_can_change_field('keywords', 0, 1, \$privs) || ThrowUserError(
+ 'illegal_change',
+ {
+ field => 'keywords',
+ oldvalue => $self->keywords,
+ newvalue => $new,
+ privs => $privs
+ }
+ );
+ }
+
+ $self->{'keyword_objects'} = \@result;
}
sub add_group {
- my ($self, $group) = @_;
+ my ($self, $group) = @_;
- # If the user enters "FoO" but the DB has "Foo", $group->name would
- # return "Foo" and thus revealing the existence of the group name.
- # So we have to store and pass the name as entered by the user to
- # the error message, if we have it.
- my $group_name = blessed($group) ? $group->name : $group;
- my $args = { name => $group_name, product => $self->product,
- bug_id => $self->id, action => 'add' };
+ # If the user enters "FoO" but the DB has "Foo", $group->name would
+ # return "Foo" and thus revealing the existence of the group name.
+ # So we have to store and pass the name as entered by the user to
+ # the error message, if we have it.
+ my $group_name = blessed($group) ? $group->name : $group;
+ my $args = {
+ name => $group_name,
+ product => $self->product,
+ bug_id => $self->id,
+ action => 'add'
+ };
- $group = Bugzilla::Group->check_no_disclose($args) if !blessed $group;
+ $group = Bugzilla::Group->check_no_disclose($args) if !blessed $group;
- # If the bug is already in this group, then there is nothing to do.
- return if $self->in_group($group);
+ # If the bug is already in this group, then there is nothing to do.
+ return if $self->in_group($group);
- # Make sure that bugs in this product can actually be restricted
- # to this group by the current user.
- $self->product_obj->group_is_settable($group)
- || ThrowUserError('group_restriction_not_allowed', $args);
+ # Make sure that bugs in this product can actually be restricted
+ # to this group by the current user.
+ $self->product_obj->group_is_settable($group)
+ || ThrowUserError('group_restriction_not_allowed', $args);
- # OtherControl people can add groups only during a product change,
- # and only when the group is not NA for them.
- if (!Bugzilla->user->in_group($group->name)) {
- my $controls = $self->product_obj->group_controls->{$group->id};
- if (!$self->{_old_product_name}
- || $controls->{othercontrol} == CONTROLMAPNA)
- {
- ThrowUserError('group_restriction_not_allowed', $args);
- }
+ # OtherControl people can add groups only during a product change,
+ # and only when the group is not NA for them.
+ if (!Bugzilla->user->in_group($group->name)) {
+ my $controls = $self->product_obj->group_controls->{$group->id};
+ if (!$self->{_old_product_name} || $controls->{othercontrol} == CONTROLMAPNA) {
+ ThrowUserError('group_restriction_not_allowed', $args);
}
+ }
- my $current_groups = $self->groups_in;
- push(@$current_groups, $group);
+ my $current_groups = $self->groups_in;
+ push(@$current_groups, $group);
}
sub remove_group {
- my ($self, $group) = @_;
+ my ($self, $group) = @_;
- # See add_group() for the reason why we store the user input.
- my $group_name = blessed($group) ? $group->name : $group;
- my $args = { name => $group_name, product => $self->product,
- bug_id => $self->id, action => 'remove' };
+ # See add_group() for the reason why we store the user input.
+ my $group_name = blessed($group) ? $group->name : $group;
+ my $args = {
+ name => $group_name,
+ product => $self->product,
+ bug_id => $self->id,
+ action => 'remove'
+ };
- $group = Bugzilla::Group->check_no_disclose($args) if !blessed $group;
+ $group = Bugzilla::Group->check_no_disclose($args) if !blessed $group;
- # If the bug isn't in this group, then either the name is misspelled,
- # or the group really doesn't exist. Let the user know about this problem.
- $self->in_group($group) || ThrowUserError('group_invalid_removal', $args);
+ # If the bug isn't in this group, then either the name is misspelled,
+ # or the group really doesn't exist. Let the user know about this problem.
+ $self->in_group($group) || ThrowUserError('group_invalid_removal', $args);
- # Check if this is a valid group for this product. You can *always*
- # remove a group that is not valid for this product (set_product does this).
- # This particularly happens when we're moving a bug to a new product.
- # You still have to be a member of an inactive group to remove it.
- if ($self->product_obj->group_is_valid($group)) {
- my $controls = $self->product_obj->group_controls->{$group->id};
+ # Check if this is a valid group for this product. You can *always*
+ # remove a group that is not valid for this product (set_product does this).
+ # This particularly happens when we're moving a bug to a new product.
+ # You still have to be a member of an inactive group to remove it.
+ if ($self->product_obj->group_is_valid($group)) {
+ my $controls = $self->product_obj->group_controls->{$group->id};
- # Nobody can ever remove a Mandatory group, unless it became inactive.
- if ($controls->{membercontrol} == CONTROLMAPMANDATORY && $group->is_active) {
- ThrowUserError('group_invalid_removal', $args);
- }
+ # Nobody can ever remove a Mandatory group, unless it became inactive.
+ if ($controls->{membercontrol} == CONTROLMAPMANDATORY && $group->is_active) {
+ ThrowUserError('group_invalid_removal', $args);
+ }
- # OtherControl people can remove groups only during a product change,
- # and only when they are non-Mandatory and non-NA.
- if (!Bugzilla->user->in_group($group->name)) {
- if (!$self->{_old_product_name}
- || $controls->{othercontrol} == CONTROLMAPMANDATORY
- || $controls->{othercontrol} == CONTROLMAPNA)
- {
- ThrowUserError('group_invalid_removal', $args);
- }
- }
+ # OtherControl people can remove groups only during a product change,
+ # and only when they are non-Mandatory and non-NA.
+ if (!Bugzilla->user->in_group($group->name)) {
+ if (!$self->{_old_product_name}
+ || $controls->{othercontrol} == CONTROLMAPMANDATORY
+ || $controls->{othercontrol} == CONTROLMAPNA)
+ {
+ ThrowUserError('group_invalid_removal', $args);
+ }
}
+ }
- my $current_groups = $self->groups_in;
- @$current_groups = grep { $_->id != $group->id } @$current_groups;
+ my $current_groups = $self->groups_in;
+ @$current_groups = grep { $_->id != $group->id } @$current_groups;
}
sub add_see_also {
- my ($self, $input, $skip_recursion) = @_;
+ my ($self, $input, $skip_recursion) = @_;
- # This is needed by xt/search.t.
- $input = $input->name if blessed($input);
+ # This is needed by xt/search.t.
+ $input = $input->name if blessed($input);
- $input = trim($input);
- return if !$input;
+ $input = trim($input);
+ return if !$input;
- my ($class, $uri) = Bugzilla::BugUrl->class_for($input);
+ my ($class, $uri) = Bugzilla::BugUrl->class_for($input);
- my $params = { value => $uri, bug_id => $self, class => $class };
- $class->check_required_create_fields($params);
+ my $params = {value => $uri, bug_id => $self, class => $class};
+ $class->check_required_create_fields($params);
- my $field_values = $class->run_create_validators($params);
- my $value = $field_values->{value}->as_string;
- trick_taint($value);
- $field_values->{value} = $value;
-
- # We only add the new URI if it hasn't been added yet. URIs are
- # case-sensitive, but most of our DBs are case-insensitive, so we do
- # this check case-insensitively.
- if (!grep { lc($_->name) eq lc($value) } @{ $self->see_also }) {
- my $privs;
- my $can = $self->check_can_change_field('see_also', '', $value, \$privs);
- if (!$can) {
- ThrowUserError('illegal_change', { field => 'see_also',
- newvalue => $value,
- privs => $privs });
- }
- # If this is a link to a local bug then save the
- # ref bug id for sending changes email.
- my $ref_bug = delete $field_values->{ref_bug};
- if ($class->isa('Bugzilla::BugUrl::Bugzilla::Local')
- and !$skip_recursion
- and $ref_bug->check_can_change_field('see_also', '', $self->id, \$privs))
- {
- $ref_bug->add_see_also($self->id, 'skip_recursion');
- push @{ $self->{_update_ref_bugs} }, $ref_bug;
- push @{ $self->{see_also_changes} }, $ref_bug->id;
- }
- push @{ $self->{see_also} }, bless ($field_values, $class);
- }
-}
-
-sub remove_see_also {
- my ($self, $url, $skip_recursion) = @_;
- my $see_also = $self->see_also;
-
- # This is needed by xt/search.t.
- $url = $url->name if blessed($url);
-
- my ($removed_bug_url, $new_see_also) =
- part { lc($_->name) ne lc($url) } @$see_also;
+ my $field_values = $class->run_create_validators($params);
+ my $value = $field_values->{value}->as_string;
+ trick_taint($value);
+ $field_values->{value} = $value;
+ # We only add the new URI if it hasn't been added yet. URIs are
+ # case-sensitive, but most of our DBs are case-insensitive, so we do
+ # this check case-insensitively.
+ if (!grep { lc($_->name) eq lc($value) } @{$self->see_also}) {
my $privs;
- my $can = $self->check_can_change_field('see_also', $see_also, $new_see_also, \$privs);
+ my $can = $self->check_can_change_field('see_also', '', $value, \$privs);
if (!$can) {
- ThrowUserError('illegal_change', { field => 'see_also',
- oldvalue => $url,
- privs => $privs });
+ ThrowUserError('illegal_change',
+ {field => 'see_also', newvalue => $value, privs => $privs});
}
- # Since we remove also the url from the referenced bug,
- # we need to notify changes for that bug too.
- $removed_bug_url = $removed_bug_url->[0];
- if (!$skip_recursion and $removed_bug_url
- and $removed_bug_url->isa('Bugzilla::BugUrl::Bugzilla::Local')
- and $removed_bug_url->ref_bug_url)
+ # If this is a link to a local bug then save the
+ # ref bug id for sending changes email.
+ my $ref_bug = delete $field_values->{ref_bug};
+ if ( $class->isa('Bugzilla::BugUrl::Bugzilla::Local')
+ and !$skip_recursion
+ and $ref_bug->check_can_change_field('see_also', '', $self->id, \$privs))
{
- my $ref_bug
- = Bugzilla::Bug->check($removed_bug_url->ref_bug_url->bug_id);
+ $ref_bug->add_see_also($self->id, 'skip_recursion');
+ push @{$self->{_update_ref_bugs}}, $ref_bug;
+ push @{$self->{see_also_changes}}, $ref_bug->id;
+ }
+ push @{$self->{see_also}}, bless($field_values, $class);
+ }
+}
- if (Bugzilla->user->can_edit_product($ref_bug->product_id)
- and $ref_bug->check_can_change_field('see_also', $self->id, '', \$privs))
- {
- my $self_url = $removed_bug_url->local_uri($self->id);
- $ref_bug->remove_see_also($self_url, 'skip_recursion');
- push @{ $self->{_update_ref_bugs} }, $ref_bug;
- push @{ $self->{see_also_changes} }, $ref_bug->id;
- }
+sub remove_see_also {
+ my ($self, $url, $skip_recursion) = @_;
+ my $see_also = $self->see_also;
+
+ # This is needed by xt/search.t.
+ $url = $url->name if blessed($url);
+
+ my ($removed_bug_url, $new_see_also)
+ = part { lc($_->name) ne lc($url) } @$see_also;
+
+ my $privs;
+ my $can = $self->check_can_change_field('see_also', $see_also, $new_see_also,
+ \$privs);
+ if (!$can) {
+ ThrowUserError('illegal_change',
+ {field => 'see_also', oldvalue => $url, privs => $privs});
+ }
+
+ # Since we remove also the url from the referenced bug,
+ # we need to notify changes for that bug too.
+ $removed_bug_url = $removed_bug_url->[0];
+ if ( !$skip_recursion
+ and $removed_bug_url
+ and $removed_bug_url->isa('Bugzilla::BugUrl::Bugzilla::Local')
+ and $removed_bug_url->ref_bug_url)
+ {
+ my $ref_bug = Bugzilla::Bug->check($removed_bug_url->ref_bug_url->bug_id);
+
+ if (Bugzilla->user->can_edit_product($ref_bug->product_id)
+ and $ref_bug->check_can_change_field('see_also', $self->id, '', \$privs))
+ {
+ my $self_url = $removed_bug_url->local_uri($self->id);
+ $ref_bug->remove_see_also($self_url, 'skip_recursion');
+ push @{$self->{_update_ref_bugs}}, $ref_bug;
+ push @{$self->{see_also_changes}}, $ref_bug->id;
}
+ }
- $self->{see_also} = $new_see_also || [];
+ $self->{see_also} = $new_see_also || [];
}
sub add_tag {
- my ($self, $tag) = @_;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
- $tag = $self->_check_tag_name($tag);
-
- my $tag_id = $user->tags->{$tag}->{id};
- # If this tag doesn't exist for this user yet, create it.
- if (!$tag_id) {
- $dbh->do('INSERT INTO tag (user_id, name) VALUES (?, ?)',
- undef, ($user->id, $tag));
-
- $tag_id = $dbh->selectrow_array('SELECT id FROM tag
- WHERE name = ? AND user_id = ?',
- undef, ($tag, $user->id));
- # The list has changed.
- delete $user->{tags};
- }
- # Do nothing if this tag is already set for this bug.
- return if grep { $_ eq $tag } @{$self->tags};
+ my ($self, $tag) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ $tag = $self->_check_tag_name($tag);
+
+ my $tag_id = $user->tags->{$tag}->{id};
+
+ # If this tag doesn't exist for this user yet, create it.
+ if (!$tag_id) {
+ $dbh->do('INSERT INTO tag (user_id, name) VALUES (?, ?)',
+ undef, ($user->id, $tag));
+
+ $tag_id = $dbh->selectrow_array(
+ 'SELECT id FROM tag
+ WHERE name = ? AND user_id = ?', undef,
+ ($tag, $user->id)
+ );
- # Increment the counter. Do it before the SQL call below,
- # to not count the tag twice.
- $user->tags->{$tag}->{bug_count}++;
+ # The list has changed.
+ delete $user->{tags};
+ }
- $dbh->do('INSERT INTO bug_tag (bug_id, tag_id) VALUES (?, ?)',
- undef, ($self->id, $tag_id));
+ # Do nothing if this tag is already set for this bug.
+ return if grep { $_ eq $tag } @{$self->tags};
- push(@{$self->{tags}}, $tag);
+ # Increment the counter. Do it before the SQL call below,
+ # to not count the tag twice.
+ $user->tags->{$tag}->{bug_count}++;
+
+ $dbh->do('INSERT INTO bug_tag (bug_id, tag_id) VALUES (?, ?)',
+ undef, ($self->id, $tag_id));
+
+ push(@{$self->{tags}}, $tag);
}
sub remove_tag {
- my ($self, $tag) = @_;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
- $tag = $self->_check_tag_name($tag);
+ my ($self, $tag) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ $tag = $self->_check_tag_name($tag);
- my $tag_id = exists $user->tags->{$tag} ? $user->tags->{$tag}->{id} : undef;
- # Do nothing if the user doesn't use this tag, or didn't set it for this bug.
- return unless ($tag_id && grep { $_ eq $tag } @{$self->tags});
+ my $tag_id = exists $user->tags->{$tag} ? $user->tags->{$tag}->{id} : undef;
- $dbh->do('DELETE FROM bug_tag WHERE bug_id = ? AND tag_id = ?',
- undef, ($self->id, $tag_id));
+ # Do nothing if the user doesn't use this tag, or didn't set it for this bug.
+ return unless ($tag_id && grep { $_ eq $tag } @{$self->tags});
- $self->{tags} = [grep { $_ ne $tag } @{$self->tags}];
+ $dbh->do('DELETE FROM bug_tag WHERE bug_id = ? AND tag_id = ?',
+ undef, ($self->id, $tag_id));
- # Decrement the counter, and delete the tag if no bugs are using it anymore.
- if (!--$user->tags->{$tag}->{bug_count}) {
- $dbh->do('DELETE FROM tag WHERE name = ? AND user_id = ?',
- undef, ($tag, $user->id));
+ $self->{tags} = [grep { $_ ne $tag } @{$self->tags}];
- # The list has changed.
- delete $user->{tags};
- }
+ # Decrement the counter, and delete the tag if no bugs are using it anymore.
+ if (!--$user->tags->{$tag}->{bug_count}) {
+ $dbh->do('DELETE FROM tag WHERE name = ? AND user_id = ?',
+ undef, ($tag, $user->id));
+
+ # The list has changed.
+ delete $user->{tags};
+ }
}
sub tags {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
-
- # This method doesn't support several users using the same bug object.
- if (!exists $self->{tags}) {
- $self->{tags} = $dbh->selectcol_arrayref(
- 'SELECT name FROM bug_tag
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ # This method doesn't support several users using the same bug object.
+ if (!exists $self->{tags}) {
+ $self->{tags} = $dbh->selectcol_arrayref(
+ 'SELECT name FROM bug_tag
INNER JOIN tag ON tag.id = bug_tag.tag_id
- WHERE bug_id = ? AND user_id = ?',
- undef, ($self->id, $user->id));
- }
- return $self->{tags};
+ WHERE bug_id = ? AND user_id = ?', undef, ($self->id, $user->id)
+ );
+ }
+ return $self->{tags};
}
#####################################################################
@@ -3271,30 +3387,30 @@ sub tags {
# These are accessors that don't need to access the database.
# Keep them in alphabetical order.
-sub bug_file_loc { return $_[0]->{bug_file_loc} }
-sub bug_id { return $_[0]->{bug_id} }
-sub bug_severity { return $_[0]->{bug_severity} }
-sub bug_status { return $_[0]->{bug_status} }
-sub cclist_accessible { return $_[0]->{cclist_accessible} }
-sub component_id { return $_[0]->{component_id} }
-sub creation_ts { return $_[0]->{creation_ts} }
-sub estimated_time { return $_[0]->{estimated_time} }
-sub deadline { return $_[0]->{deadline} }
-sub delta_ts { return $_[0]->{delta_ts} }
-sub error { return $_[0]->{error} }
-sub everconfirmed { return $_[0]->{everconfirmed} }
-sub lastdiffed { return $_[0]->{lastdiffed} }
-sub op_sys { return $_[0]->{op_sys} }
-sub priority { return $_[0]->{priority} }
-sub product_id { return $_[0]->{product_id} }
-sub remaining_time { return $_[0]->{remaining_time} }
+sub bug_file_loc { return $_[0]->{bug_file_loc} }
+sub bug_id { return $_[0]->{bug_id} }
+sub bug_severity { return $_[0]->{bug_severity} }
+sub bug_status { return $_[0]->{bug_status} }
+sub cclist_accessible { return $_[0]->{cclist_accessible} }
+sub component_id { return $_[0]->{component_id} }
+sub creation_ts { return $_[0]->{creation_ts} }
+sub estimated_time { return $_[0]->{estimated_time} }
+sub deadline { return $_[0]->{deadline} }
+sub delta_ts { return $_[0]->{delta_ts} }
+sub error { return $_[0]->{error} }
+sub everconfirmed { return $_[0]->{everconfirmed} }
+sub lastdiffed { return $_[0]->{lastdiffed} }
+sub op_sys { return $_[0]->{op_sys} }
+sub priority { return $_[0]->{priority} }
+sub product_id { return $_[0]->{product_id} }
+sub remaining_time { return $_[0]->{remaining_time} }
sub reporter_accessible { return $_[0]->{reporter_accessible} }
-sub rep_platform { return $_[0]->{rep_platform} }
-sub resolution { return $_[0]->{resolution} }
-sub short_desc { return $_[0]->{short_desc} }
-sub status_whiteboard { return $_[0]->{status_whiteboard} }
-sub target_milestone { return $_[0]->{target_milestone} }
-sub version { return $_[0]->{version} }
+sub rep_platform { return $_[0]->{rep_platform} }
+sub resolution { return $_[0]->{resolution} }
+sub short_desc { return $_[0]->{short_desc} }
+sub status_whiteboard { return $_[0]->{status_whiteboard} }
+sub target_milestone { return $_[0]->{target_milestone} }
+sub version { return $_[0]->{version} }
#####################################################################
# Complex Accessors
@@ -3313,674 +3429,713 @@ sub version { return $_[0]->{version} }
# security holes.
sub dup_id {
- my ($self) = @_;
- return $self->{'dup_id'} if exists $self->{'dup_id'};
+ my ($self) = @_;
+ return $self->{'dup_id'} if exists $self->{'dup_id'};
- $self->{'dup_id'} = undef;
- return if $self->{'error'};
+ $self->{'dup_id'} = undef;
+ return if $self->{'error'};
- if ($self->{'resolution'} eq 'DUPLICATE') {
- my $dbh = Bugzilla->dbh;
- $self->{'dup_id'} =
- $dbh->selectrow_array(q{SELECT dupe_of
+ if ($self->{'resolution'} eq 'DUPLICATE') {
+ my $dbh = Bugzilla->dbh;
+ $self->{'dup_id'} = $dbh->selectrow_array(
+ q{SELECT dupe_of
FROM duplicates
- WHERE dupe = ?},
- undef,
- $self->{'bug_id'});
- }
- return $self->{'dup_id'};
+ WHERE dupe = ?}, undef, $self->{'bug_id'}
+ );
+ }
+ return $self->{'dup_id'};
}
sub _resolve_ultimate_dup_id {
- my ($bug_id, $dupe_of, $loops_are_an_error) = @_;
- my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare('SELECT dupe_of FROM duplicates WHERE dupe = ?');
-
- my $this_dup = $dupe_of || $dbh->selectrow_array($sth, undef, $bug_id);
- my $last_dup = $bug_id;
-
- my %dupes;
- while ($this_dup) {
- if ($this_dup == $bug_id) {
- if ($loops_are_an_error) {
- ThrowUserError('dupe_loop_detected', { bug_id => $bug_id,
- dupe_of => $dupe_of });
- }
- else {
- return $last_dup;
- }
- }
- # If $dupes{$this_dup} is already set to 1, then a loop
- # already exists which does not involve this bug.
- # As the user is not responsible for this loop, do not
- # prevent them from marking this bug as a duplicate.
- return $last_dup if exists $dupes{$this_dup};
- $dupes{$this_dup} = 1;
- $last_dup = $this_dup;
- $this_dup = $dbh->selectrow_array($sth, undef, $this_dup);
+ my ($bug_id, $dupe_of, $loops_are_an_error) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare('SELECT dupe_of FROM duplicates WHERE dupe = ?');
+
+ my $this_dup = $dupe_of || $dbh->selectrow_array($sth, undef, $bug_id);
+ my $last_dup = $bug_id;
+
+ my %dupes;
+ while ($this_dup) {
+ if ($this_dup == $bug_id) {
+ if ($loops_are_an_error) {
+ ThrowUserError('dupe_loop_detected', {bug_id => $bug_id, dupe_of => $dupe_of});
+ }
+ else {
+ return $last_dup;
+ }
}
- return $last_dup;
+ # If $dupes{$this_dup} is already set to 1, then a loop
+ # already exists which does not involve this bug.
+ # As the user is not responsible for this loop, do not
+ # prevent them from marking this bug as a duplicate.
+ return $last_dup if exists $dupes{$this_dup};
+ $dupes{$this_dup} = 1;
+ $last_dup = $this_dup;
+ $this_dup = $dbh->selectrow_array($sth, undef, $this_dup);
+ }
+
+ return $last_dup;
}
sub actual_time {
- my ($self) = @_;
- return $self->{'actual_time'} if exists $self->{'actual_time'};
+ my ($self) = @_;
+ return $self->{'actual_time'} if exists $self->{'actual_time'};
- if ( $self->{'error'} || !Bugzilla->user->is_timetracker ) {
- $self->{'actual_time'} = undef;
- return $self->{'actual_time'};
- }
+ if ($self->{'error'} || !Bugzilla->user->is_timetracker) {
+ $self->{'actual_time'} = undef;
+ return $self->{'actual_time'};
+ }
- my $sth = Bugzilla->dbh->prepare("SELECT SUM(work_time)
+ my $sth = Bugzilla->dbh->prepare(
+ "SELECT SUM(work_time)
FROM longdescs
- WHERE longdescs.bug_id=?");
- $sth->execute($self->{bug_id});
- $self->{'actual_time'} = $sth->fetchrow_array();
- return $self->{'actual_time'};
+ WHERE longdescs.bug_id=?"
+ );
+ $sth->execute($self->{bug_id});
+ $self->{'actual_time'} = $sth->fetchrow_array();
+ return $self->{'actual_time'};
}
sub alias {
- my ($self) = @_;
- return $self->{'alias'} if exists $self->{'alias'};
- return [] if $self->{'error'};
+ my ($self) = @_;
+ return $self->{'alias'} if exists $self->{'alias'};
+ return [] if $self->{'error'};
- my $dbh = Bugzilla->dbh;
- $self->{'alias'} = $dbh->selectcol_arrayref(
- q{SELECT alias FROM bugs_aliases WHERE bug_id = ? ORDER BY alias},
- undef, $self->bug_id);
+ my $dbh = Bugzilla->dbh;
+ $self->{'alias'}
+ = $dbh->selectcol_arrayref(
+ q{SELECT alias FROM bugs_aliases WHERE bug_id = ? ORDER BY alias},
+ undef, $self->bug_id);
- return $self->{'alias'};
+ return $self->{'alias'};
}
sub any_flags_requesteeble {
- my ($self) = @_;
- return $self->{'any_flags_requesteeble'}
- if exists $self->{'any_flags_requesteeble'};
- return 0 if $self->{'error'};
+ my ($self) = @_;
+ return $self->{'any_flags_requesteeble'}
+ if exists $self->{'any_flags_requesteeble'};
+ return 0 if $self->{'error'};
- my $any_flags_requesteeble =
- grep { $_->is_requestable && $_->is_requesteeble } @{$self->flag_types};
- # Useful in case a flagtype is no longer requestable but a requestee
- # has been set before we turned off that bit.
- $any_flags_requesteeble ||= grep { $_->requestee_id } @{$self->flags};
- $self->{'any_flags_requesteeble'} = $any_flags_requesteeble;
+ my $any_flags_requesteeble
+ = grep { $_->is_requestable && $_->is_requesteeble } @{$self->flag_types};
- return $self->{'any_flags_requesteeble'};
+ # Useful in case a flagtype is no longer requestable but a requestee
+ # has been set before we turned off that bit.
+ $any_flags_requesteeble ||= grep { $_->requestee_id } @{$self->flags};
+ $self->{'any_flags_requesteeble'} = $any_flags_requesteeble;
+
+ return $self->{'any_flags_requesteeble'};
}
sub attachments {
- my ($self) = @_;
- return $self->{'attachments'} if exists $self->{'attachments'};
- return [] if $self->{'error'};
+ my ($self) = @_;
+ return $self->{'attachments'} if exists $self->{'attachments'};
+ return [] if $self->{'error'};
- $self->{'attachments'} =
- Bugzilla::Attachment->get_attachments_by_bug($self, {preload => 1});
- $_->object_cache_set() foreach @{ $self->{'attachments'} };
- return $self->{'attachments'};
+ $self->{'attachments'}
+ = Bugzilla::Attachment->get_attachments_by_bug($self, {preload => 1});
+ $_->object_cache_set() foreach @{$self->{'attachments'}};
+ return $self->{'attachments'};
}
sub assigned_to {
- my ($self) = @_;
- return $self->{'assigned_to_obj'} if exists $self->{'assigned_to_obj'};
- $self->{'assigned_to'} = 0 if $self->{'error'};
- $self->{'assigned_to_obj'} ||= new Bugzilla::User({ id => $self->{'assigned_to'}, cache => 1 });
- return $self->{'assigned_to_obj'};
+ my ($self) = @_;
+ return $self->{'assigned_to_obj'} if exists $self->{'assigned_to_obj'};
+ $self->{'assigned_to'} = 0 if $self->{'error'};
+ $self->{'assigned_to_obj'}
+ ||= new Bugzilla::User({id => $self->{'assigned_to'}, cache => 1});
+ return $self->{'assigned_to_obj'};
}
sub blocked {
- my ($self) = @_;
- return $self->{'blocked'} if exists $self->{'blocked'};
- return [] if $self->{'error'};
- $self->{'blocked'} = EmitDependList("dependson", "blocked", $self->bug_id);
- return $self->{'blocked'};
+ my ($self) = @_;
+ return $self->{'blocked'} if exists $self->{'blocked'};
+ return [] if $self->{'error'};
+ $self->{'blocked'} = EmitDependList("dependson", "blocked", $self->bug_id);
+ return $self->{'blocked'};
}
sub blocks_obj {
- my ($self) = @_;
- $self->{blocks_obj} ||= $self->_bugs_in_order($self->blocked);
- return $self->{blocks_obj};
+ my ($self) = @_;
+ $self->{blocks_obj} ||= $self->_bugs_in_order($self->blocked);
+ return $self->{blocks_obj};
}
sub bug_group {
- my ($self) = @_;
- return join(', ', (map { $_->name } @{$self->groups_in}));
+ my ($self) = @_;
+ return join(', ', (map { $_->name } @{$self->groups_in}));
}
sub related_bugs {
- my ($self, $relationship) = @_;
- return [] if $self->{'error'};
+ my ($self, $relationship) = @_;
+ return [] if $self->{'error'};
- my $field_name = $relationship->name;
- $self->{'related_bugs'}->{$field_name} ||= $self->match({$field_name => $self->id});
- return $self->{'related_bugs'}->{$field_name};
+ my $field_name = $relationship->name;
+ $self->{'related_bugs'}->{$field_name}
+ ||= $self->match({$field_name => $self->id});
+ return $self->{'related_bugs'}->{$field_name};
}
sub cc {
- my ($self) = @_;
- return $self->{'cc'} if exists $self->{'cc'};
- return [] if $self->{'error'};
+ my ($self) = @_;
+ return $self->{'cc'} if exists $self->{'cc'};
+ return [] if $self->{'error'};
- my $dbh = Bugzilla->dbh;
- $self->{'cc'} = $dbh->selectcol_arrayref(
- q{SELECT profiles.login_name FROM cc, profiles
+ my $dbh = Bugzilla->dbh;
+ $self->{'cc'} = $dbh->selectcol_arrayref(
+ q{SELECT profiles.login_name FROM cc, profiles
WHERE bug_id = ?
AND cc.who = profiles.userid
- ORDER BY profiles.login_name},
- undef, $self->bug_id);
+ ORDER BY profiles.login_name}, undef, $self->bug_id
+ );
- return $self->{'cc'};
+ return $self->{'cc'};
}
# XXX Eventually this will become the standard "cc" method used everywhere.
sub cc_users {
- my $self = shift;
- return $self->{'cc_users'} if exists $self->{'cc_users'};
- return [] if $self->{'error'};
-
- my $dbh = Bugzilla->dbh;
- my $cc_ids = $dbh->selectcol_arrayref(
- 'SELECT who FROM cc WHERE bug_id = ?', undef, $self->id);
- $self->{'cc_users'} = Bugzilla::User->new_from_list($cc_ids);
- return $self->{'cc_users'};
+ my $self = shift;
+ return $self->{'cc_users'} if exists $self->{'cc_users'};
+ return [] if $self->{'error'};
+
+ my $dbh = Bugzilla->dbh;
+ my $cc_ids = $dbh->selectcol_arrayref('SELECT who FROM cc WHERE bug_id = ?',
+ undef, $self->id);
+ $self->{'cc_users'} = Bugzilla::User->new_from_list($cc_ids);
+ return $self->{'cc_users'};
}
sub component {
- my ($self) = @_;
- return '' if $self->{error};
- $self->{component} //= $self->component_obj->name;
- return $self->{component};
+ my ($self) = @_;
+ return '' if $self->{error};
+ $self->{component} //= $self->component_obj->name;
+ return $self->{component};
}
# XXX Eventually this will replace component()
sub component_obj {
- my ($self) = @_;
- return $self->{component_obj} if defined $self->{component_obj};
- return {} if $self->{error};
- $self->{component_obj} =
- new Bugzilla::Component({ id => $self->{component_id}, cache => 1 });
- return $self->{component_obj};
+ my ($self) = @_;
+ return $self->{component_obj} if defined $self->{component_obj};
+ return {} if $self->{error};
+ $self->{component_obj}
+ = new Bugzilla::Component({id => $self->{component_id}, cache => 1});
+ return $self->{component_obj};
}
sub classification_id {
- my ($self) = @_;
- return 0 if $self->{error};
- $self->{classification_id} //= $self->product_obj->classification_id;
- return $self->{classification_id};
+ my ($self) = @_;
+ return 0 if $self->{error};
+ $self->{classification_id} //= $self->product_obj->classification_id;
+ return $self->{classification_id};
}
sub classification {
- my ($self) = @_;
- return '' if $self->{error};
- $self->{classification} //= $self->product_obj->classification->name;
- return $self->{classification};
+ my ($self) = @_;
+ return '' if $self->{error};
+ $self->{classification} //= $self->product_obj->classification->name;
+ return $self->{classification};
}
sub default_bug_status {
- my $class = shift;
- # XXX This should just call new_bug_statuses when the UI accepts closed
- # bug statuses instead of accepting them as a parameter.
- my @statuses = @_;
-
- my $status;
- if (scalar(@statuses) == 1) {
- $status = $statuses[0]->name;
- }
- else {
- $status = ($statuses[0]->name ne 'UNCONFIRMED')
- ? $statuses[0]->name : $statuses[1]->name;
- }
+ my $class = shift;
+
+ # XXX This should just call new_bug_statuses when the UI accepts closed
+ # bug statuses instead of accepting them as a parameter.
+ my @statuses = @_;
- return $status;
+ my $status;
+ if (scalar(@statuses) == 1) {
+ $status = $statuses[0]->name;
+ }
+ else {
+ $status
+ = ($statuses[0]->name ne 'UNCONFIRMED')
+ ? $statuses[0]->name
+ : $statuses[1]->name;
+ }
+
+ return $status;
}
sub dependson {
- my ($self) = @_;
- return $self->{'dependson'} if exists $self->{'dependson'};
- return [] if $self->{'error'};
- $self->{'dependson'} =
- EmitDependList("blocked", "dependson", $self->bug_id);
- return $self->{'dependson'};
+ my ($self) = @_;
+ return $self->{'dependson'} if exists $self->{'dependson'};
+ return [] if $self->{'error'};
+ $self->{'dependson'} = EmitDependList("blocked", "dependson", $self->bug_id);
+ return $self->{'dependson'};
}
sub depends_on_obj {
- my ($self) = @_;
- $self->{depends_on_obj} ||= $self->_bugs_in_order($self->dependson);
- return $self->{depends_on_obj};
+ my ($self) = @_;
+ $self->{depends_on_obj} ||= $self->_bugs_in_order($self->dependson);
+ return $self->{depends_on_obj};
}
sub duplicates {
- my $self = shift;
- return $self->{duplicates} if exists $self->{duplicates};
- return [] if $self->{error};
- $self->{duplicates} = Bugzilla::Bug->new_from_list($self->duplicate_ids);
- return $self->{duplicates};
+ my $self = shift;
+ return $self->{duplicates} if exists $self->{duplicates};
+ return [] if $self->{error};
+ $self->{duplicates} = Bugzilla::Bug->new_from_list($self->duplicate_ids);
+ return $self->{duplicates};
}
sub duplicate_ids {
- my $self = shift;
- return $self->{duplicate_ids} if exists $self->{duplicate_ids};
- return [] if $self->{error};
+ my $self = shift;
+ return $self->{duplicate_ids} if exists $self->{duplicate_ids};
+ return [] if $self->{error};
- my $dbh = Bugzilla->dbh;
- $self->{duplicate_ids} =
- $dbh->selectcol_arrayref('SELECT dupe FROM duplicates WHERE dupe_of = ?',
- undef, $self->id);
- return $self->{duplicate_ids};
+ my $dbh = Bugzilla->dbh;
+ $self->{duplicate_ids}
+ = $dbh->selectcol_arrayref('SELECT dupe FROM duplicates WHERE dupe_of = ?',
+ undef, $self->id);
+ return $self->{duplicate_ids};
}
sub flag_types {
- my ($self) = @_;
- return $self->{'flag_types'} if exists $self->{'flag_types'};
- return [] if $self->{'error'};
+ my ($self) = @_;
+ return $self->{'flag_types'} if exists $self->{'flag_types'};
+ return [] if $self->{'error'};
- my $vars = { target_type => 'bug',
- product_id => $self->{product_id},
- component_id => $self->{component_id},
- bug_id => $self->bug_id };
+ my $vars = {
+ target_type => 'bug',
+ product_id => $self->{product_id},
+ component_id => $self->{component_id},
+ bug_id => $self->bug_id
+ };
- $self->{'flag_types'} = Bugzilla::Flag->_flag_types($vars);
- return $self->{'flag_types'};
+ $self->{'flag_types'} = Bugzilla::Flag->_flag_types($vars);
+ return $self->{'flag_types'};
}
sub flags {
- my $self = shift;
+ my $self = shift;
- # Don't cache it as it must be in sync with ->flag_types.
- $self->{flags} = [map { @{$_->{flags}} } @{$self->flag_types}];
- return $self->{flags};
+ # Don't cache it as it must be in sync with ->flag_types.
+ $self->{flags} = [map { @{$_->{flags}} } @{$self->flag_types}];
+ return $self->{flags};
}
sub isopened {
- my $self = shift;
- unless (exists $self->{isopened}) {
- $self->{isopened} = is_open_state($self->{bug_status}) ? 1 : 0;
- }
- return $self->{isopened};
+ my $self = shift;
+ unless (exists $self->{isopened}) {
+ $self->{isopened} = is_open_state($self->{bug_status}) ? 1 : 0;
+ }
+ return $self->{isopened};
}
sub keywords {
- my ($self) = @_;
- return join(', ', (map { $_->name } @{$self->keyword_objects}));
+ my ($self) = @_;
+ return join(', ', (map { $_->name } @{$self->keyword_objects}));
}
# XXX At some point, this should probably replace the normal "keywords" sub.
sub keyword_objects {
- my $self = shift;
- return $self->{'keyword_objects'} if defined $self->{'keyword_objects'};
- return [] if $self->{'error'};
+ my $self = shift;
+ return $self->{'keyword_objects'} if defined $self->{'keyword_objects'};
+ return [] if $self->{'error'};
- my $dbh = Bugzilla->dbh;
- my $ids = $dbh->selectcol_arrayref(
- "SELECT keywordid FROM keywords WHERE bug_id = ?", undef, $self->id);
- $self->{'keyword_objects'} = Bugzilla::Keyword->new_from_list($ids);
- return $self->{'keyword_objects'};
+ my $dbh = Bugzilla->dbh;
+ my $ids
+ = $dbh->selectcol_arrayref("SELECT keywordid FROM keywords WHERE bug_id = ?",
+ undef, $self->id);
+ $self->{'keyword_objects'} = Bugzilla::Keyword->new_from_list($ids);
+ return $self->{'keyword_objects'};
}
sub comments {
- my ($self, $params) = @_;
- return [] if $self->{'error'};
- $params ||= {};
-
- if (!defined $self->{'comments'}) {
- $self->{'comments'} = Bugzilla::Comment->match({ bug_id => $self->id });
- my $count = 0;
- state $is_mysql = Bugzilla->dbh->isa('Bugzilla::DB::Mysql') ? 1 : 0;
- foreach my $comment (@{ $self->{'comments'} }) {
- $comment->{count} = $count++;
- $comment->{bug} = $self;
- # XXX - hack for MySQL. Convert [U+....] back into its Unicode
- # equivalent for characters above U+FFFF as MySQL older than 5.5.3
- # cannot store them, see Bugzilla::Comment::_check_thetext().
- if ($is_mysql) {
- # Perl 5.13.8 and older complain about non-characters.
- no warnings 'utf8';
- $comment->{thetext} =~ s/\x{FDD0}\[U\+((?:[1-9A-F]|10)[0-9A-F]{4})\]\x{FDD1}/chr(hex $1)/eg
- }
- }
- # Some bugs may have no comments when upgrading old installations.
- Bugzilla::Comment->preload($self->{'comments'}) if $count;
- }
- my @comments = @{ $self->{'comments'} };
-
- my $order = $params->{order}
- || Bugzilla->user->setting('comment_sort_order');
- if ($order ne 'oldest_to_newest') {
- @comments = reverse @comments;
- if ($order eq 'newest_to_oldest_desc_first') {
- unshift(@comments, pop @comments);
- }
- }
-
- if ($params->{after}) {
- my $from = datetime_from($params->{after});
- @comments = grep { datetime_from($_->creation_ts) > $from } @comments;
- }
- if ($params->{to}) {
- my $to = datetime_from($params->{to});
- @comments = grep { datetime_from($_->creation_ts) <= $to } @comments;
- }
- return \@comments;
+ my ($self, $params) = @_;
+ return [] if $self->{'error'};
+ $params ||= {};
+
+ if (!defined $self->{'comments'}) {
+ $self->{'comments'} = Bugzilla::Comment->match({bug_id => $self->id});
+ my $count = 0;
+ state $is_mysql = Bugzilla->dbh->isa('Bugzilla::DB::Mysql') ? 1 : 0;
+ foreach my $comment (@{$self->{'comments'}}) {
+ $comment->{count} = $count++;
+ $comment->{bug} = $self;
+
+ # XXX - hack for MySQL. Convert [U+....] back into its Unicode
+ # equivalent for characters above U+FFFF as MySQL older than 5.5.3
+ # cannot store them, see Bugzilla::Comment::_check_thetext().
+ if ($is_mysql) {
+
+ # Perl 5.13.8 and older complain about non-characters.
+ no warnings 'utf8';
+ $comment->{thetext}
+ =~ s/\x{FDD0}\[U\+((?:[1-9A-F]|10)[0-9A-F]{4})\]\x{FDD1}/chr(hex $1)/eg;
+ }
+ }
+
+ # Some bugs may have no comments when upgrading old installations.
+ Bugzilla::Comment->preload($self->{'comments'}) if $count;
+ }
+ my @comments = @{$self->{'comments'}};
+
+ my $order = $params->{order} || Bugzilla->user->setting('comment_sort_order');
+ if ($order ne 'oldest_to_newest') {
+ @comments = reverse @comments;
+ if ($order eq 'newest_to_oldest_desc_first') {
+ unshift(@comments, pop @comments);
+ }
+ }
+
+ if ($params->{after}) {
+ my $from = datetime_from($params->{after});
+ @comments = grep { datetime_from($_->creation_ts) > $from } @comments;
+ }
+ if ($params->{to}) {
+ my $to = datetime_from($params->{to});
+ @comments = grep { datetime_from($_->creation_ts) <= $to } @comments;
+ }
+ return \@comments;
}
sub new_bug_statuses {
- my ($class, $product) = @_;
- my $user = Bugzilla->user;
+ my ($class, $product) = @_;
+ my $user = Bugzilla->user;
- # Construct the list of allowable statuses.
- my @statuses = @{ Bugzilla::Bug->statuses_available($product) };
+ # Construct the list of allowable statuses.
+ my @statuses = @{Bugzilla::Bug->statuses_available($product)};
- # If the user has no privs...
- unless ($user->in_group('editbugs', $product->id)
- || $user->in_group('canconfirm', $product->id))
- {
- # ... use UNCONFIRMED if available, else use the first status of the list.
- my ($unconfirmed) = grep { $_->name eq 'UNCONFIRMED' } @statuses;
-
- # Because of an apparent Perl bug, "$unconfirmed || $statuses[0]" doesn't
- # work, so we're using an "?:" operator. See bug 603314 for details.
- @statuses = ($unconfirmed ? $unconfirmed : $statuses[0]);
- }
+ # If the user has no privs...
+ unless ($user->in_group('editbugs', $product->id)
+ || $user->in_group('canconfirm', $product->id))
+ {
+ # ... use UNCONFIRMED if available, else use the first status of the list.
+ my ($unconfirmed) = grep { $_->name eq 'UNCONFIRMED' } @statuses;
+
+ # Because of an apparent Perl bug, "$unconfirmed || $statuses[0]" doesn't
+ # work, so we're using an "?:" operator. See bug 603314 for details.
+ @statuses = ($unconfirmed ? $unconfirmed : $statuses[0]);
+ }
- return \@statuses;
+ return \@statuses;
}
# This is needed by xt/search.t.
sub percentage_complete {
- my $self = shift;
- return undef if $self->{'error'} || !Bugzilla->user->is_timetracker;
- my $remaining = $self->remaining_time;
- my $actual = $self->actual_time;
- my $total = $remaining + $actual;
- return undef if $total == 0;
- # Search.pm truncates this value to an integer, so we want to as well,
- # since this is mostly used in a test where its value needs to be
- # identical to what the database will return.
- return int(100 * ($actual / $total));
+ my $self = shift;
+ return undef if $self->{'error'} || !Bugzilla->user->is_timetracker;
+ my $remaining = $self->remaining_time;
+ my $actual = $self->actual_time;
+ my $total = $remaining + $actual;
+ return undef if $total == 0;
+
+ # Search.pm truncates this value to an integer, so we want to as well,
+ # since this is mostly used in a test where its value needs to be
+ # identical to what the database will return.
+ return int(100 * ($actual / $total));
}
sub product {
- my ($self) = @_;
- return '' if $self->{error};
- $self->{product} //= $self->product_obj->name;
- return $self->{product};
+ my ($self) = @_;
+ return '' if $self->{error};
+ $self->{product} //= $self->product_obj->name;
+ return $self->{product};
}
# XXX This should eventually replace the "product" subroutine.
sub product_obj {
- my $self = shift;
- return {} if $self->{error};
- $self->{product_obj} ||=
- new Bugzilla::Product({ id => $self->{product_id}, cache => 1 });
- return $self->{product_obj};
+ my $self = shift;
+ return {} if $self->{error};
+ $self->{product_obj}
+ ||= new Bugzilla::Product({id => $self->{product_id}, cache => 1});
+ return $self->{product_obj};
}
sub qa_contact {
- my ($self) = @_;
- return $self->{'qa_contact_obj'} if exists $self->{'qa_contact_obj'};
- return undef if $self->{'error'};
+ my ($self) = @_;
+ return $self->{'qa_contact_obj'} if exists $self->{'qa_contact_obj'};
+ return undef if $self->{'error'};
- if (Bugzilla->params->{'useqacontact'} && $self->{'qa_contact'}) {
- $self->{'qa_contact_obj'} = new Bugzilla::User({ id => $self->{'qa_contact'}, cache => 1 });
- } else {
- $self->{'qa_contact_obj'} = undef;
- }
- return $self->{'qa_contact_obj'};
+ if (Bugzilla->params->{'useqacontact'} && $self->{'qa_contact'}) {
+ $self->{'qa_contact_obj'}
+ = new Bugzilla::User({id => $self->{'qa_contact'}, cache => 1});
+ }
+ else {
+ $self->{'qa_contact_obj'} = undef;
+ }
+ return $self->{'qa_contact_obj'};
}
sub reporter {
- my ($self) = @_;
- return $self->{'reporter'} if exists $self->{'reporter'};
- $self->{'reporter_id'} = 0 if $self->{'error'};
- $self->{'reporter'} = new Bugzilla::User({ id => $self->{'reporter_id'}, cache => 1 });
- return $self->{'reporter'};
+ my ($self) = @_;
+ return $self->{'reporter'} if exists $self->{'reporter'};
+ $self->{'reporter_id'} = 0 if $self->{'error'};
+ $self->{'reporter'}
+ = new Bugzilla::User({id => $self->{'reporter_id'}, cache => 1});
+ return $self->{'reporter'};
}
sub see_also {
- my ($self) = @_;
- return [] if $self->{'error'};
- if (!exists $self->{see_also}) {
- my $ids = Bugzilla->dbh->selectcol_arrayref(
- 'SELECT id FROM bug_see_also WHERE bug_id = ?',
- undef, $self->id);
+ my ($self) = @_;
+ return [] if $self->{'error'};
+ if (!exists $self->{see_also}) {
+ my $ids
+ = Bugzilla->dbh->selectcol_arrayref(
+ 'SELECT id FROM bug_see_also WHERE bug_id = ?',
+ undef, $self->id);
- my $bug_urls = Bugzilla::BugUrl->new_from_list($ids);
+ my $bug_urls = Bugzilla::BugUrl->new_from_list($ids);
- $self->{see_also} = $bug_urls;
- }
- return $self->{see_also};
+ $self->{see_also} = $bug_urls;
+ }
+ return $self->{see_also};
}
sub status {
- my $self = shift;
- return undef if $self->{'error'};
+ my $self = shift;
+ return undef if $self->{'error'};
- $self->{'status'} ||= new Bugzilla::Status({name => $self->{'bug_status'}});
- return $self->{'status'};
+ $self->{'status'} ||= new Bugzilla::Status({name => $self->{'bug_status'}});
+ return $self->{'status'};
}
sub statuses_available {
- my ($invocant, $product) = @_;
+ my ($invocant, $product) = @_;
- my @statuses;
+ my @statuses;
- if (ref $invocant) {
- return [] if $invocant->{'error'};
+ if (ref $invocant) {
+ return [] if $invocant->{'error'};
- return $invocant->{'statuses_available'}
- if defined $invocant->{'statuses_available'};
+ return $invocant->{'statuses_available'}
+ if defined $invocant->{'statuses_available'};
- @statuses = @{ $invocant->status->can_change_to };
- $product = $invocant->product_obj;
- } else {
- @statuses = @{ Bugzilla::Status->can_change_to };
- }
+ @statuses = @{$invocant->status->can_change_to};
+ $product = $invocant->product_obj;
+ }
+ else {
+ @statuses = @{Bugzilla::Status->can_change_to};
+ }
- # UNCONFIRMED is only a valid status if it is enabled in this product.
- if (!$product->allows_unconfirmed) {
- @statuses = grep { $_->name ne 'UNCONFIRMED' } @statuses;
- }
+ # UNCONFIRMED is only a valid status if it is enabled in this product.
+ if (!$product->allows_unconfirmed) {
+ @statuses = grep { $_->name ne 'UNCONFIRMED' } @statuses;
+ }
- if (ref $invocant) {
- my $available = $invocant->_refine_available_statuses(@statuses);
- $invocant->{'statuses_available'} = $available;
- return $available;
- }
+ if (ref $invocant) {
+ my $available = $invocant->_refine_available_statuses(@statuses);
+ $invocant->{'statuses_available'} = $available;
+ return $available;
+ }
- return \@statuses;
+ return \@statuses;
}
sub _refine_available_statuses {
- my $self = shift;
- my @statuses = @_;
-
- my @available;
- foreach my $status (@statuses) {
- # Make sure this is a legal status transition
- next if !$self->check_can_change_field(
- 'bug_status', $self->status->name, $status->name);
- push(@available, $status);
- }
+ my $self = shift;
+ my @statuses = @_;
- # If this bug has an inactive status set, it should still be in the list.
- if (!grep($_->name eq $self->status->name, @available)) {
- unshift(@available, $self->status);
- }
-
- return \@available;
+ my @available;
+ foreach my $status (@statuses) {
+
+ # Make sure this is a legal status transition
+ next
+ if !$self->check_can_change_field('bug_status', $self->status->name,
+ $status->name);
+ push(@available, $status);
+ }
+
+ # If this bug has an inactive status set, it should still be in the list.
+ if (!grep($_->name eq $self->status->name, @available)) {
+ unshift(@available, $self->status);
+ }
+
+ return \@available;
}
sub show_attachment_flags {
- my ($self) = @_;
- return $self->{'show_attachment_flags'}
- if exists $self->{'show_attachment_flags'};
- return 0 if $self->{'error'};
+ my ($self) = @_;
+ return $self->{'show_attachment_flags'}
+ if exists $self->{'show_attachment_flags'};
+ return 0 if $self->{'error'};
- # The number of types of flags that can be set on attachments to this bug
- # and the number of flags on those attachments. One of these counts must be
- # greater than zero in order for the "flags" column to appear in the table
- # of attachments.
- my $num_attachment_flag_types = Bugzilla::FlagType::count(
- { 'target_type' => 'attachment',
- 'product_id' => $self->{'product_id'},
- 'component_id' => $self->{'component_id'} });
- my $num_attachment_flags = Bugzilla::Flag->count(
- { 'target_type' => 'attachment',
- 'bug_id' => $self->bug_id });
+ # The number of types of flags that can be set on attachments to this bug
+ # and the number of flags on those attachments. One of these counts must be
+ # greater than zero in order for the "flags" column to appear in the table
+ # of attachments.
+ my $num_attachment_flag_types = Bugzilla::FlagType::count({
+ 'target_type' => 'attachment',
+ 'product_id' => $self->{'product_id'},
+ 'component_id' => $self->{'component_id'}
+ });
+ my $num_attachment_flags = Bugzilla::Flag->count(
+ {'target_type' => 'attachment', 'bug_id' => $self->bug_id});
- $self->{'show_attachment_flags'} =
- ($num_attachment_flag_types || $num_attachment_flags);
+ $self->{'show_attachment_flags'}
+ = ($num_attachment_flag_types || $num_attachment_flags);
- return $self->{'show_attachment_flags'};
+ return $self->{'show_attachment_flags'};
}
sub groups {
- my $self = shift;
- return $self->{'groups'} if exists $self->{'groups'};
- return [] if $self->{'error'};
+ my $self = shift;
+ return $self->{'groups'} if exists $self->{'groups'};
+ return [] if $self->{'error'};
+
+ my $dbh = Bugzilla->dbh;
+ my @groups;
+
+ # Some of this stuff needs to go into Bugzilla::User
+
+ # For every group, we need to know if there is ANY bug_group_map
+ # record putting the current bug in that group and if there is ANY
+ # user_group_map record putting the user in that group.
+ # The LEFT JOINs are checking for record existence.
+ #
+ my $grouplist = Bugzilla->user->groups_as_string;
+ my $sth
+ = $dbh->prepare("SELECT DISTINCT groups.id, name, description,"
+ . " CASE WHEN bug_group_map.group_id IS NOT NULL"
+ . " THEN 1 ELSE 0 END,"
+ . " CASE WHEN groups.id IN($grouplist) THEN 1 ELSE 0 END,"
+ . " isactive, membercontrol, othercontrol"
+ . " FROM groups"
+ . " LEFT JOIN bug_group_map"
+ . " ON bug_group_map.group_id = groups.id"
+ . " AND bug_id = ?"
+ . " LEFT JOIN group_control_map"
+ . " ON group_control_map.group_id = groups.id"
+ . " AND group_control_map.product_id = ? "
+ . " WHERE isbuggroup = 1"
+ . " ORDER BY description");
+ $sth->execute($self->{'bug_id'}, $self->{'product_id'});
+
+ while (
+ my (
+ $groupid, $name, $description, $ison,
+ $ingroup, $isactive, $membercontrol, $othercontrol
+ )
+ = $sth->fetchrow_array()
+ )
+ {
+
+ $membercontrol ||= 0;
+
+ # For product groups, we only want to use the group if either
+ # (1) The bit is set and not required, or
+ # (2) The group is Shown or Default for members and
+ # the user is a member of the group.
+ if (
+ $ison
+ || ( $isactive
+ && $ingroup
+ && ( ($membercontrol == CONTROLMAPDEFAULT)
+ || ($membercontrol == CONTROLMAPSHOWN)))
+ )
+ {
+ my $ismandatory = $isactive && ($membercontrol == CONTROLMAPMANDATORY);
- my $dbh = Bugzilla->dbh;
- my @groups;
-
- # Some of this stuff needs to go into Bugzilla::User
-
- # For every group, we need to know if there is ANY bug_group_map
- # record putting the current bug in that group and if there is ANY
- # user_group_map record putting the user in that group.
- # The LEFT JOINs are checking for record existence.
- #
- my $grouplist = Bugzilla->user->groups_as_string;
- my $sth = $dbh->prepare(
- "SELECT DISTINCT groups.id, name, description," .
- " CASE WHEN bug_group_map.group_id IS NOT NULL" .
- " THEN 1 ELSE 0 END," .
- " CASE WHEN groups.id IN($grouplist) THEN 1 ELSE 0 END," .
- " isactive, membercontrol, othercontrol" .
- " FROM groups" .
- " LEFT JOIN bug_group_map" .
- " ON bug_group_map.group_id = groups.id" .
- " AND bug_id = ?" .
- " LEFT JOIN group_control_map" .
- " ON group_control_map.group_id = groups.id" .
- " AND group_control_map.product_id = ? " .
- " WHERE isbuggroup = 1" .
- " ORDER BY description");
- $sth->execute($self->{'bug_id'},
- $self->{'product_id'});
-
- while (my ($groupid, $name, $description, $ison, $ingroup, $isactive,
- $membercontrol, $othercontrol) = $sth->fetchrow_array()) {
-
- $membercontrol ||= 0;
-
- # For product groups, we only want to use the group if either
- # (1) The bit is set and not required, or
- # (2) The group is Shown or Default for members and
- # the user is a member of the group.
- if ($ison ||
- ($isactive && $ingroup
- && (($membercontrol == CONTROLMAPDEFAULT)
- || ($membercontrol == CONTROLMAPSHOWN))
- ))
+ push(
+ @groups,
{
- my $ismandatory = $isactive
- && ($membercontrol == CONTROLMAPMANDATORY);
-
- push (@groups, { "bit" => $groupid,
- "name" => $name,
- "ison" => $ison,
- "ingroup" => $ingroup,
- "mandatory" => $ismandatory,
- "description" => $description });
+ "bit" => $groupid,
+ "name" => $name,
+ "ison" => $ison,
+ "ingroup" => $ingroup,
+ "mandatory" => $ismandatory,
+ "description" => $description
}
+ );
}
+ }
- $self->{'groups'} = \@groups;
+ $self->{'groups'} = \@groups;
- return $self->{'groups'};
+ return $self->{'groups'};
}
sub groups_in {
- my $self = shift;
- return $self->{'groups_in'} if exists $self->{'groups_in'};
- return [] if $self->{'error'};
- my $group_ids = Bugzilla->dbh->selectcol_arrayref(
- 'SELECT group_id FROM bug_group_map WHERE bug_id = ?',
- undef, $self->id);
- $self->{'groups_in'} = Bugzilla::Group->new_from_list($group_ids);
- return $self->{'groups_in'};
+ my $self = shift;
+ return $self->{'groups_in'} if exists $self->{'groups_in'};
+ return [] if $self->{'error'};
+ my $group_ids
+ = Bugzilla->dbh->selectcol_arrayref(
+ 'SELECT group_id FROM bug_group_map WHERE bug_id = ?',
+ undef, $self->id);
+ $self->{'groups_in'} = Bugzilla::Group->new_from_list($group_ids);
+ return $self->{'groups_in'};
}
sub in_group {
- my ($self, $group) = @_;
- return grep($_->id == $group->id, @{$self->groups_in}) ? 1 : 0;
+ my ($self, $group) = @_;
+ return grep($_->id == $group->id, @{$self->groups_in}) ? 1 : 0;
}
sub user {
- my $self = shift;
- return $self->{'user'} if exists $self->{'user'};
- return {} if $self->{'error'};
-
- my $user = Bugzilla->user;
- my $prod_id = $self->{'product_id'};
-
- my $editbugs = $user->in_group('editbugs', $prod_id);
- my $is_reporter = $user->id == $self->{reporter_id} ? 1 : 0;
- my $is_assignee = $user->id == $self->{'assigned_to'} ? 1 : 0;
- my $is_qa_contact = Bugzilla->params->{'useqacontact'}
- && $self->{'qa_contact'}
- && $user->id == $self->{'qa_contact'} ? 1 : 0;
-
- my $canedit = $editbugs || $is_assignee || $is_qa_contact;
- my $canconfirm = $editbugs || $user->in_group('canconfirm', $prod_id);
- my $has_any_role = $is_reporter || $is_assignee || $is_qa_contact;
-
- $self->{'user'} = {canconfirm => $canconfirm,
- canedit => $canedit,
- isreporter => $is_reporter,
- has_any_role => $has_any_role};
- return $self->{'user'};
+ my $self = shift;
+ return $self->{'user'} if exists $self->{'user'};
+ return {} if $self->{'error'};
+
+ my $user = Bugzilla->user;
+ my $prod_id = $self->{'product_id'};
+
+ my $editbugs = $user->in_group('editbugs', $prod_id);
+ my $is_reporter = $user->id == $self->{reporter_id} ? 1 : 0;
+ my $is_assignee = $user->id == $self->{'assigned_to'} ? 1 : 0;
+ my $is_qa_contact
+ = Bugzilla->params->{'useqacontact'}
+ && $self->{'qa_contact'}
+ && $user->id == $self->{'qa_contact'} ? 1 : 0;
+
+ my $canedit = $editbugs || $is_assignee || $is_qa_contact;
+ my $canconfirm = $editbugs || $user->in_group('canconfirm', $prod_id);
+ my $has_any_role = $is_reporter || $is_assignee || $is_qa_contact;
+
+ $self->{'user'} = {
+ canconfirm => $canconfirm,
+ canedit => $canedit,
+ isreporter => $is_reporter,
+ has_any_role => $has_any_role
+ };
+ return $self->{'user'};
}
# This is intended to get values that can be selected by the user in the
# UI. It should not be used for security or validation purposes.
sub choices {
- my $self = shift;
- return $self->{'choices'} if exists $self->{'choices'};
- return {} if $self->{'error'};
- my $user = Bugzilla->user;
-
- my @products = @{ $user->get_enterable_products };
- # The current product is part of the popup, even if new bugs are no longer
- # allowed for that product
- if (!grep($_->name eq $self->product_obj->name, @products)) {
- unshift(@products, $self->product_obj);
- }
- my %class_ids = map { $_->classification_id => 1 } @products;
- my $classifications =
- Bugzilla::Classification->new_from_list([keys %class_ids]);
-
- my %choices = (
- bug_status => $self->statuses_available,
- classification => $classifications,
- product => \@products,
- component => $self->product_obj->components,
- version => $self->product_obj->versions,
- target_milestone => $self->product_obj->milestones,
- );
-
- my $resolution_field = new Bugzilla::Field({ name => 'resolution' });
- # Don't include the empty resolution in drop-downs.
- my @resolutions = grep($_->name, @{ $resolution_field->legal_values });
- $choices{'resolution'} = \@resolutions;
-
- foreach my $key (keys %choices) {
- my $value = $self->$key;
- $choices{$key} = [grep { $_->is_active || $_->name eq $value } @{ $choices{$key} }];
- }
-
- $self->{'choices'} = \%choices;
- return $self->{'choices'};
+ my $self = shift;
+ return $self->{'choices'} if exists $self->{'choices'};
+ return {} if $self->{'error'};
+ my $user = Bugzilla->user;
+
+ my @products = @{$user->get_enterable_products};
+
+ # The current product is part of the popup, even if new bugs are no longer
+ # allowed for that product
+ if (!grep($_->name eq $self->product_obj->name, @products)) {
+ unshift(@products, $self->product_obj);
+ }
+ my %class_ids = map { $_->classification_id => 1 } @products;
+ my $classifications
+ = Bugzilla::Classification->new_from_list([keys %class_ids]);
+
+ my %choices = (
+ bug_status => $self->statuses_available,
+ classification => $classifications,
+ product => \@products,
+ component => $self->product_obj->components,
+ version => $self->product_obj->versions,
+ target_milestone => $self->product_obj->milestones,
+ );
+
+ my $resolution_field = new Bugzilla::Field({name => 'resolution'});
+
+ # Don't include the empty resolution in drop-downs.
+ my @resolutions = grep($_->name, @{$resolution_field->legal_values});
+ $choices{'resolution'} = \@resolutions;
+
+ foreach my $key (keys %choices) {
+ my $value = $self->$key;
+ $choices{$key}
+ = [grep { $_->is_active || $_->name eq $value } @{$choices{$key}}];
+ }
+
+ $self->{'choices'} = \%choices;
+ return $self->{'choices'};
}
# Convenience Function. If you need speed, use this. If you need
@@ -3989,11 +4144,11 @@ sub choices {
# Queries the database for the bug with a given alias, and returns
# the ID of the bug if it exists or the undefined value if it doesn't.
sub bug_alias_to_id {
- my ($alias) = @_;
- my $dbh = Bugzilla->dbh;
- trick_taint($alias);
- return $dbh->selectrow_array(
- "SELECT bug_id FROM bugs_aliases WHERE alias = ?", undef, $alias);
+ my ($alias) = @_;
+ my $dbh = Bugzilla->dbh;
+ trick_taint($alias);
+ return $dbh->selectrow_array("SELECT bug_id FROM bugs_aliases WHERE alias = ?",
+ undef, $alias);
}
#####################################################################
@@ -4003,21 +4158,26 @@ sub bug_alias_to_id {
# Returns a list of currently active and editable bug fields,
# including multi-select fields.
sub editable_bug_fields {
- my @fields = Bugzilla->dbh->bz_table_columns('bugs');
- # Add multi-select fields
- push(@fields, map { $_->name } @{Bugzilla->fields({obsolete => 0,
- type => FIELD_TYPE_MULTI_SELECT})});
- # Obsolete custom fields are not editable.
- my @obsolete_fields = @{ Bugzilla->fields({obsolete => 1, custom => 1}) };
- @obsolete_fields = map { $_->name } @obsolete_fields;
- foreach my $remove ("bug_id", "reporter", "creation_ts", "delta_ts",
- "lastdiffed", @obsolete_fields)
- {
- my $location = firstidx { $_ eq $remove } @fields;
- # Ensure field exists before attempting to remove it.
- splice(@fields, $location, 1) if ($location > -1);
- }
- return @fields;
+ my @fields = Bugzilla->dbh->bz_table_columns('bugs');
+
+ # Add multi-select fields
+ push(@fields,
+ map { $_->name }
+ @{Bugzilla->fields({obsolete => 0, type => FIELD_TYPE_MULTI_SELECT})});
+
+ # Obsolete custom fields are not editable.
+ my @obsolete_fields = @{Bugzilla->fields({obsolete => 1, custom => 1})};
+ @obsolete_fields = map { $_->name } @obsolete_fields;
+ foreach
+ my $remove ("bug_id", "reporter", "creation_ts", "delta_ts", "lastdiffed",
+ @obsolete_fields)
+ {
+ my $location = firstidx { $_ eq $remove } @fields;
+
+ # Ensure field exists before attempting to remove it.
+ splice(@fields, $location, 1) if ($location > -1);
+ }
+ return @fields;
}
# XXX - When Bug::update() will be implemented, we should make this routine
@@ -4025,103 +4185,107 @@ sub editable_bug_fields {
# Join with bug_status and bugs tables to show bugs with open statuses first,
# and then the others
sub EmitDependList {
- my ($my_field, $target_field, $bug_id, $exclude_resolved) = @_;
- my $cache = Bugzilla->request_cache->{bug_dependency_list} ||= {};
+ my ($my_field, $target_field, $bug_id, $exclude_resolved) = @_;
+ my $cache = Bugzilla->request_cache->{bug_dependency_list} ||= {};
- my $dbh = Bugzilla->dbh;
- $exclude_resolved = $exclude_resolved ? 1 : 0;
- my $is_open_clause = $exclude_resolved ? 'AND is_open = 1' : '';
+ my $dbh = Bugzilla->dbh;
+ $exclude_resolved = $exclude_resolved ? 1 : 0;
+ my $is_open_clause = $exclude_resolved ? 'AND is_open = 1' : '';
- $cache->{"${target_field}_sth_$exclude_resolved"} ||= $dbh->prepare(
- "SELECT $target_field
+ $cache->{"${target_field}_sth_$exclude_resolved"} ||= $dbh->prepare(
+ "SELECT $target_field
FROM dependencies
INNER JOIN bugs ON dependencies.$target_field = bugs.bug_id
INNER JOIN bug_status ON bugs.bug_status = bug_status.value
WHERE $my_field = ? $is_open_clause
- ORDER BY is_open DESC, $target_field");
+ ORDER BY is_open DESC, $target_field"
+ );
- return $dbh->selectcol_arrayref(
- $cache->{"${target_field}_sth_$exclude_resolved"},
- undef, $bug_id);
+ return $dbh->selectcol_arrayref(
+ $cache->{"${target_field}_sth_$exclude_resolved"},
+ undef, $bug_id);
}
# Creates a lot of bug objects in the same order as the input array.
sub _bugs_in_order {
- my ($self, $bug_ids) = @_;
- return [] unless @$bug_ids;
+ my ($self, $bug_ids) = @_;
+ return [] unless @$bug_ids;
- my %bug_map;
- my $dbh = Bugzilla->dbh;
+ my %bug_map;
+ my $dbh = Bugzilla->dbh;
- # there's no need to load bugs from the database if they are already in the
- # object-cache
- my @missing_ids;
- foreach my $bug_id (@$bug_ids) {
- if (my $bug = Bugzilla::Bug->object_cache_get($bug_id)) {
- $bug_map{$bug_id} = $bug;
- }
- else {
- push @missing_ids, $bug_id;
- }
+ # there's no need to load bugs from the database if they are already in the
+ # object-cache
+ my @missing_ids;
+ foreach my $bug_id (@$bug_ids) {
+ if (my $bug = Bugzilla::Bug->object_cache_get($bug_id)) {
+ $bug_map{$bug_id} = $bug;
}
- if (@missing_ids) {
- my $bugs = Bugzilla::Bug->new_from_list(\@missing_ids);
- $bug_map{$_->id} = $_ foreach @$bugs;
+ else {
+ push @missing_ids, $bug_id;
}
+ }
+ if (@missing_ids) {
+ my $bugs = Bugzilla::Bug->new_from_list(\@missing_ids);
+ $bug_map{$_->id} = $_ foreach @$bugs;
+ }
- # Dependencies are often displayed using their aliases instead of their
- # bug ID. Load them all at once.
- my $rows = $dbh->selectall_arrayref(
- 'SELECT bug_id, alias FROM bugs_aliases WHERE ' .
- $dbh->sql_in('bug_id', $bug_ids) . ' ORDER BY alias');
+ # Dependencies are often displayed using their aliases instead of their
+ # bug ID. Load them all at once.
+ my $rows
+ = $dbh->selectall_arrayref('SELECT bug_id, alias FROM bugs_aliases WHERE '
+ . $dbh->sql_in('bug_id', $bug_ids)
+ . ' ORDER BY alias');
- foreach my $row (@$rows) {
- my ($bug_id, $alias) = @$row;
- $bug_map{$bug_id}->{alias} ||= [];
- push @{ $bug_map{$bug_id}->{alias} }, $alias;
- }
- # Make sure all bugs have their alias attribute set.
- $bug_map{$_}->{alias} ||= [] foreach @$bug_ids;
+ foreach my $row (@$rows) {
+ my ($bug_id, $alias) = @$row;
+ $bug_map{$bug_id}->{alias} ||= [];
+ push @{$bug_map{$bug_id}->{alias}}, $alias;
+ }
+
+ # Make sure all bugs have their alias attribute set.
+ $bug_map{$_}->{alias} ||= [] foreach @$bug_ids;
- return [ map { $bug_map{$_} } @$bug_ids ];
+ return [map { $bug_map{$_} } @$bug_ids];
}
# Get the activity of a bug, starting from $starttime (if given).
# This routine assumes Bugzilla::Bug->check has been previously called.
sub get_activity {
- my ($self, $attach_id, $starttime, $include_comment_tags) = @_;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
-
- # Arguments passed to the SQL query.
- my @args = ($self->id);
-
- # Only consider changes since $starttime, if given.
- my $datepart = "";
- if (defined $starttime) {
- trick_taint($starttime);
- push (@args, $starttime);
- $datepart = "AND bug_when > ?";
- }
-
- my $attachpart = "";
- if ($attach_id) {
- push(@args, $attach_id);
- $attachpart = "AND bugs_activity.attach_id = ?";
- }
-
- # Only includes attachments the user is allowed to see.
- my $suppjoins = "";
- my $suppwhere = "";
- if (!$user->is_insider) {
- $suppjoins = "LEFT JOIN attachments
+ my ($self, $attach_id, $starttime, $include_comment_tags) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ # Arguments passed to the SQL query.
+ my @args = ($self->id);
+
+ # Only consider changes since $starttime, if given.
+ my $datepart = "";
+ if (defined $starttime) {
+ trick_taint($starttime);
+ push(@args, $starttime);
+ $datepart = "AND bug_when > ?";
+ }
+
+ my $attachpart = "";
+ if ($attach_id) {
+ push(@args, $attach_id);
+ $attachpart = "AND bugs_activity.attach_id = ?";
+ }
+
+ # Only includes attachments the user is allowed to see.
+ my $suppjoins = "";
+ my $suppwhere = "";
+ if (!$user->is_insider) {
+ $suppjoins = "LEFT JOIN attachments
ON attachments.attach_id = bugs_activity.attach_id";
- $suppwhere = "AND COALESCE(attachments.isprivate, 0) = 0";
- }
+ $suppwhere = "AND COALESCE(attachments.isprivate, 0) = 0";
+ }
- my $query = "SELECT fielddefs.name, bugs_activity.attach_id, " .
- $dbh->sql_date_format('bugs_activity.bug_when', '%Y.%m.%d %H:%i:%s') .
- " AS bug_when, bugs_activity.removed, bugs_activity.added, profiles.login_name,
+ my $query
+ = "SELECT fielddefs.name, bugs_activity.attach_id, "
+ . $dbh->sql_date_format('bugs_activity.bug_when', '%Y.%m.%d %H:%i:%s')
+ . " AS bug_when, bugs_activity.removed, bugs_activity.added, profiles.login_name,
bugs_activity.comment_id
FROM bugs_activity
$suppjoins
@@ -4134,24 +4298,26 @@ sub get_activity {
$attachpart
$suppwhere ";
- if (Bugzilla->params->{'comment_taggers_group'}
- && $include_comment_tags
- && !$attach_id)
- {
- # Only includes comment tag activity for comments the user is allowed to see.
- $suppjoins = "";
- $suppwhere = "";
- if (!Bugzilla->user->is_insider) {
- $suppjoins = "INNER JOIN longdescs
+ if ( Bugzilla->params->{'comment_taggers_group'}
+ && $include_comment_tags
+ && !$attach_id)
+ {
+ # Only includes comment tag activity for comments the user is allowed to see.
+ $suppjoins = "";
+ $suppwhere = "";
+ if (!Bugzilla->user->is_insider) {
+ $suppjoins = "INNER JOIN longdescs
ON longdescs.comment_id = longdescs_tags_activity.comment_id";
- $suppwhere = "AND longdescs.isprivate = 0";
- }
+ $suppwhere = "AND longdescs.isprivate = 0";
+ }
- $query .= "
+ $query .= "
UNION ALL
SELECT 'comment_tag' AS name,
- NULL AS attach_id," .
- $dbh->sql_date_format('longdescs_tags_activity.bug_when', '%Y.%m.%d %H:%i:%s') . " AS bug_when,
+ NULL AS attach_id,"
+ . $dbh->sql_date_format('longdescs_tags_activity.bug_when',
+ '%Y.%m.%d %H:%i:%s')
+ . " AS bug_when,
longdescs_tags_activity.removed,
longdescs_tags_activity.added,
profiles.login_name,
@@ -4163,168 +4329,177 @@ sub get_activity {
$datepart
$suppwhere
";
- push @args, $self->id;
- push @args, $starttime if defined $starttime;
- }
-
- $query .= "ORDER BY bug_when, comment_id";
+ push @args, $self->id;
+ push @args, $starttime if defined $starttime;
+ }
- my $list = $dbh->selectall_arrayref($query, undef, @args);
+ $query .= "ORDER BY bug_when, comment_id";
- my @operations;
- my $operation = {};
- my $changes = [];
- my $incomplete_data = 0;
+ my $list = $dbh->selectall_arrayref($query, undef, @args);
- foreach my $entry (@$list) {
- my ($fieldname, $attachid, $when, $removed, $added, $who, $comment_id) = @$entry;
- my %change;
- my $activity_visible = 1;
+ my @operations;
+ my $operation = {};
+ my $changes = [];
+ my $incomplete_data = 0;
- # check if the user should see this field's activity
- if (grep { $fieldname eq $_ } TIMETRACKING_FIELDS) {
- $activity_visible = $user->is_timetracker;
- }
- elsif ($fieldname eq 'longdescs.isprivate'
- && !$user->is_insider && $added)
- {
- $activity_visible = 0;
- }
- else {
- $activity_visible = 1;
- }
+ foreach my $entry (@$list) {
+ my ($fieldname, $attachid, $when, $removed, $added, $who, $comment_id)
+ = @$entry;
+ my %change;
+ my $activity_visible = 1;
- if ($activity_visible) {
- # Check for the results of an old Bugzilla data corruption bug
- if (($added eq '?' && $removed eq '?')
- || ($added =~ /^\? / || $removed =~ /^\? /)) {
- $incomplete_data = 1;
- }
-
- # An operation, done by 'who' at time 'when', has a number of
- # 'changes' associated with it.
- # If this is the start of a new operation, store the data from the
- # previous one, and set up the new one.
- if ($operation->{'who'}
- && ($who ne $operation->{'who'}
- || $when ne $operation->{'when'}))
- {
- $operation->{'changes'} = $changes;
- push (@operations, $operation);
-
- # Create new empty anonymous data structures.
- $operation = {};
- $changes = [];
- }
-
- # If this is the same field as the previous item, then concatenate
- # the data into the same change.
- if ($operation->{'who'} && $who eq $operation->{'who'}
- && $when eq $operation->{'when'}
- && $fieldname eq $operation->{'fieldname'}
- && ($comment_id || 0) == ($operation->{'comment_id'} || 0)
- && ($attachid || 0) == ($operation->{'attachid'} || 0))
- {
- my $old_change = pop @$changes;
- $removed = join_activity_entries($fieldname, $old_change->{'removed'}, $removed);
- $added = join_activity_entries($fieldname, $old_change->{'added'}, $added);
- }
- $operation->{'who'} = $who;
- $operation->{'when'} = $when;
- $operation->{'fieldname'} = $change{'fieldname'} = $fieldname;
- $operation->{'attachid'} = $change{'attachid'} = $attachid;
- $change{'removed'} = $removed;
- $change{'added'} = $added;
-
- if ($comment_id) {
- $operation->{comment_id} = $change{'comment'} = Bugzilla::Comment->new($comment_id);
- }
-
- push (@$changes, \%change);
- }
+ # check if the user should see this field's activity
+ if (grep { $fieldname eq $_ } TIMETRACKING_FIELDS) {
+ $activity_visible = $user->is_timetracker;
}
-
- if ($operation->{'who'}) {
- $operation->{'changes'} = $changes;
- push (@operations, $operation);
+ elsif ($fieldname eq 'longdescs.isprivate' && !$user->is_insider && $added) {
+ $activity_visible = 0;
}
+ else {
+ $activity_visible = 1;
+ }
+
+ if ($activity_visible) {
- return(\@operations, $incomplete_data);
+ # Check for the results of an old Bugzilla data corruption bug
+ if ( ($added eq '?' && $removed eq '?')
+ || ($added =~ /^\? / || $removed =~ /^\? /))
+ {
+ $incomplete_data = 1;
+ }
+
+ # An operation, done by 'who' at time 'when', has a number of
+ # 'changes' associated with it.
+ # If this is the start of a new operation, store the data from the
+ # previous one, and set up the new one.
+ if ($operation->{'who'}
+ && ($who ne $operation->{'who'} || $when ne $operation->{'when'}))
+ {
+ $operation->{'changes'} = $changes;
+ push(@operations, $operation);
+
+ # Create new empty anonymous data structures.
+ $operation = {};
+ $changes = [];
+ }
+
+ # If this is the same field as the previous item, then concatenate
+ # the data into the same change.
+ if ( $operation->{'who'}
+ && $who eq $operation->{'who'}
+ && $when eq $operation->{'when'}
+ && $fieldname eq $operation->{'fieldname'}
+ && ($comment_id || 0) == ($operation->{'comment_id'} || 0)
+ && ($attachid || 0) == ($operation->{'attachid'} || 0))
+ {
+ my $old_change = pop @$changes;
+ $removed
+ = join_activity_entries($fieldname, $old_change->{'removed'}, $removed);
+ $added = join_activity_entries($fieldname, $old_change->{'added'}, $added);
+ }
+ $operation->{'who'} = $who;
+ $operation->{'when'} = $when;
+ $operation->{'fieldname'} = $change{'fieldname'} = $fieldname;
+ $operation->{'attachid'} = $change{'attachid'} = $attachid;
+ $change{'removed'} = $removed;
+ $change{'added'} = $added;
+
+ if ($comment_id) {
+ $operation->{comment_id} = $change{'comment'}
+ = Bugzilla::Comment->new($comment_id);
+ }
+
+ push(@$changes, \%change);
+ }
+ }
+
+ if ($operation->{'who'}) {
+ $operation->{'changes'} = $changes;
+ push(@operations, $operation);
+ }
+
+ return (\@operations, $incomplete_data);
}
# Update the bugs_activity table to reflect changes made in bugs.
sub LogActivityEntry {
- my ($bug_id, $field, $removed, $added, $user_id, $timestamp, $comment_id,
- $attach_id) = @_;
- my $sth = Bugzilla->dbh->prepare_cached(
- 'INSERT INTO bugs_activity
+ my ($bug_id, $field, $removed, $added, $user_id, $timestamp, $comment_id,
+ $attach_id)
+ = @_;
+ my $sth = Bugzilla->dbh->prepare_cached(
+ 'INSERT INTO bugs_activity
(bug_id, who, bug_when, fieldid, removed, added, comment_id, attach_id)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
-
- # in the case of CCs, deps, and keywords, there's a possibility that someone
- # might try to add or remove a lot of them at once, which might take more
- # space than the activity table allows. We'll solve this by splitting it
- # into multiple entries if it's too long.
- while ($removed || $added) {
- my ($removestr, $addstr) = ($removed, $added);
- if (length($removestr) > MAX_LINE_LENGTH) {
- my $commaposition = find_wrap_point($removed, MAX_LINE_LENGTH);
- $removestr = substr($removed, 0, $commaposition);
- $removed = substr($removed, $commaposition);
- } else {
- $removed = ""; # no more entries
- }
- if (length($addstr) > MAX_LINE_LENGTH) {
- my $commaposition = find_wrap_point($added, MAX_LINE_LENGTH);
- $addstr = substr($added, 0, $commaposition);
- $added = substr($added, $commaposition);
- } else {
- $added = ""; # no more entries
- }
- trick_taint($addstr);
- trick_taint($removestr);
- my $fieldid = get_field_id($field);
- $sth->execute($bug_id, $user_id, $timestamp, $fieldid, $removestr,
- $addstr, $comment_id, $attach_id);
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
+ );
+
+ # in the case of CCs, deps, and keywords, there's a possibility that someone
+ # might try to add or remove a lot of them at once, which might take more
+ # space than the activity table allows. We'll solve this by splitting it
+ # into multiple entries if it's too long.
+ while ($removed || $added) {
+ my ($removestr, $addstr) = ($removed, $added);
+ if (length($removestr) > MAX_LINE_LENGTH) {
+ my $commaposition = find_wrap_point($removed, MAX_LINE_LENGTH);
+ $removestr = substr($removed, 0, $commaposition);
+ $removed = substr($removed, $commaposition);
}
+ else {
+ $removed = ""; # no more entries
+ }
+ if (length($addstr) > MAX_LINE_LENGTH) {
+ my $commaposition = find_wrap_point($added, MAX_LINE_LENGTH);
+ $addstr = substr($added, 0, $commaposition);
+ $added = substr($added, $commaposition);
+ }
+ else {
+ $added = ""; # no more entries
+ }
+ trick_taint($addstr);
+ trick_taint($removestr);
+ my $fieldid = get_field_id($field);
+ $sth->execute(
+ $bug_id, $user_id, $timestamp, $fieldid,
+ $removestr, $addstr, $comment_id, $attach_id
+ );
+ }
}
# Update bug_user_last_visit table
sub update_user_last_visit {
- my ($self, $user, $last_visit_ts) = @_;
- my $lv = Bugzilla::BugUserLastVisit->match({ bug_id => $self->id,
- user_id => $user->id })->[0];
+ my ($self, $user, $last_visit_ts) = @_;
+ my $lv = Bugzilla::BugUserLastVisit->match(
+ {bug_id => $self->id, user_id => $user->id})->[0];
- if ($lv) {
- $lv->set(last_visit_ts => $last_visit_ts);
- $lv->update;
- }
- else {
- Bugzilla::BugUserLastVisit->create({ bug_id => $self->id,
- user_id => $user->id,
- last_visit_ts => $last_visit_ts });
- }
+ if ($lv) {
+ $lv->set(last_visit_ts => $last_visit_ts);
+ $lv->update;
+ }
+ else {
+ Bugzilla::BugUserLastVisit->create(
+ {bug_id => $self->id, user_id => $user->id, last_visit_ts => $last_visit_ts});
+ }
}
# Convert WebService API and email_in.pl field names to internal DB field
# names.
sub map_fields {
- my ($params, $except) = @_;
-
- my %field_values;
- foreach my $field (keys %$params) {
- # Don't allow setting private fields via email_in or the WebService.
- next if $field =~ /^_/;
- my $field_name;
- if ($except->{$field}) {
- $field_name = $field;
- }
- else {
- $field_name = FIELD_MAP->{$field} || $field;
- }
- $field_values{$field_name} = $params->{$field};
+ my ($params, $except) = @_;
+
+ my %field_values;
+ foreach my $field (keys %$params) {
+
+ # Don't allow setting private fields via email_in or the WebService.
+ next if $field =~ /^_/;
+ my $field_name;
+ if ($except->{$field}) {
+ $field_name = $field;
+ }
+ else {
+ $field_name = FIELD_MAP->{$field} || $field;
}
- return \%field_values;
+ $field_values{$field_name} = $params->{$field};
+ }
+ return \%field_values;
}
################################################################################
@@ -4344,164 +4519,187 @@ sub map_fields {
# $PrivilegesRequired - return the reason of the failure, if any
################################################################################
sub check_can_change_field {
- my $self = shift;
- my ($field, $oldvalue, $newvalue, $PrivilegesRequired) = (@_);
- my $user = Bugzilla->user;
+ my $self = shift;
+ my ($field, $oldvalue, $newvalue, $PrivilegesRequired) = (@_);
+ my $user = Bugzilla->user;
+
+ $oldvalue = defined($oldvalue) ? $oldvalue : '';
+ $newvalue = defined($newvalue) ? $newvalue : '';
+
+ # Return true if they haven't changed this field at all.
+ if ($oldvalue eq $newvalue) {
+ return 1;
+ }
+ elsif (ref($newvalue) eq 'ARRAY' && ref($oldvalue) eq 'ARRAY') {
+ my ($removed, $added) = diff_arrays($oldvalue, $newvalue);
+ return 1 if !scalar(@$removed) && !scalar(@$added);
+ }
+ elsif (trim($oldvalue) eq trim($newvalue)) {
+ return 1;
- $oldvalue = defined($oldvalue) ? $oldvalue : '';
- $newvalue = defined($newvalue) ? $newvalue : '';
-
- # Return true if they haven't changed this field at all.
- if ($oldvalue eq $newvalue) {
- return 1;
- } elsif (ref($newvalue) eq 'ARRAY' && ref($oldvalue) eq 'ARRAY') {
- my ($removed, $added) = diff_arrays($oldvalue, $newvalue);
- return 1 if !scalar(@$removed) && !scalar(@$added);
- } elsif (trim($oldvalue) eq trim($newvalue)) {
- return 1;
# numeric fields need to be compared using ==
- } elsif (($field eq 'estimated_time' || $field eq 'remaining_time'
- || $field eq 'work_time')
- && $oldvalue == $newvalue)
+ }
+ elsif (
+ (
+ $field eq 'estimated_time'
+ || $field eq 'remaining_time'
+ || $field eq 'work_time'
+ )
+ && $oldvalue == $newvalue
+ )
+ {
+ return 1;
+ }
+
+ my @priv_results;
+ Bugzilla::Hook::process(
+ 'bug_check_can_change_field',
{
- return 1;
+ bug => $self,
+ field => $field,
+ new_value => $newvalue,
+ old_value => $oldvalue,
+ priv_results => \@priv_results
+ }
+ );
+ if (my $priv_required = first { $_ > 0 } @priv_results) {
+ $$PrivilegesRequired = $priv_required;
+ return 0;
+ }
+ my $allow_found = first { $_ == 0 } @priv_results;
+ if (defined $allow_found) {
+ return 1;
+ }
+
+ # Allow anyone to change comments, or set flags
+ if ($field =~ /^longdesc/ || $field eq 'flagtypes.name') {
+ return 1;
+ }
+
+# If the user isn't allowed to change a field, we must tell them who can.
+# We store the required permission set into the $PrivilegesRequired
+# variable which gets passed to the error template.
+#
+# $PrivilegesRequired = PRIVILEGES_REQUIRED_NONE : no privileges required;
+# $PrivilegesRequired = PRIVILEGES_REQUIRED_REPORTER : the reporter, assignee or an empowered user;
+# $PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE : the assignee or an empowered user;
+# $PrivilegesRequired = PRIVILEGES_REQUIRED_EMPOWERED : an empowered user.
+
+ # Only users in the time-tracking group can change time-tracking fields,
+ # including the deadline.
+ if (grep { $_ eq $field } (TIMETRACKING_FIELDS, 'deadline')) {
+ if (!$user->is_timetracker) {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_EMPOWERED;
+ return 0;
+ }
+ }
+
+ # Allow anyone with (product-specific) "editbugs" privs to change anything.
+ if ($user->in_group('editbugs', $self->{'product_id'})) {
+ return 1;
+ }
+
+ # *Only* users with (product-specific) "canconfirm" privs can confirm bugs.
+ if ($self->_changes_everconfirmed($field, $oldvalue, $newvalue)) {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_EMPOWERED;
+ return $user->in_group('canconfirm', $self->{'product_id'});
+ }
+
+ # Make sure that a valid bug ID has been given.
+ if (!$self->{'error'}) {
+
+ # Allow the assignee to change anything else.
+ if ( $self->{'assigned_to'} == $user->id
+ || $self->{'_old_assigned_to'} && $self->{'_old_assigned_to'} == $user->id)
+ {
+ return 1;
}
- my @priv_results;
- Bugzilla::Hook::process('bug_check_can_change_field',
- { bug => $self, field => $field,
- new_value => $newvalue, old_value => $oldvalue,
- priv_results => \@priv_results });
- if (my $priv_required = first { $_ > 0 } @priv_results) {
- $$PrivilegesRequired = $priv_required;
- return 0;
- }
- my $allow_found = first { $_ == 0 } @priv_results;
- if (defined $allow_found) {
- return 1;
+ # Allow the QA contact to change anything else.
+ if (
+ Bugzilla->params->{'useqacontact'}
+ && ( ($self->{'qa_contact'} && $self->{'qa_contact'} == $user->id)
+ || ($self->{'_old_qa_contact'} && $self->{'_old_qa_contact'} == $user->id))
+ )
+ {
+ return 1;
}
+ }
- # Allow anyone to change comments, or set flags
- if ($field =~ /^longdesc/ || $field eq 'flagtypes.name') {
- return 1;
- }
+ # At this point, the user is either the reporter or an
+ # unprivileged user. We first check for fields the reporter
+ # is not allowed to change.
- # If the user isn't allowed to change a field, we must tell them who can.
- # We store the required permission set into the $PrivilegesRequired
- # variable which gets passed to the error template.
- #
- # $PrivilegesRequired = PRIVILEGES_REQUIRED_NONE : no privileges required;
- # $PrivilegesRequired = PRIVILEGES_REQUIRED_REPORTER : the reporter, assignee or an empowered user;
- # $PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE : the assignee or an empowered user;
- # $PrivilegesRequired = PRIVILEGES_REQUIRED_EMPOWERED : an empowered user.
-
- # Only users in the time-tracking group can change time-tracking fields,
- # including the deadline.
- if (grep { $_ eq $field } (TIMETRACKING_FIELDS, 'deadline')) {
- if (!$user->is_timetracker) {
- $$PrivilegesRequired = PRIVILEGES_REQUIRED_EMPOWERED;
- return 0;
- }
- }
-
- # Allow anyone with (product-specific) "editbugs" privs to change anything.
- if ($user->in_group('editbugs', $self->{'product_id'})) {
- return 1;
- }
+ # The reporter may not:
+ # - reassign bugs, unless the bugs are assigned to them;
+ # in that case we will have already returned 1 above
+ # when checking for the assignee of the bug.
+ if ($field eq 'assigned_to') {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
+ return 0;
+ }
- # *Only* users with (product-specific) "canconfirm" privs can confirm bugs.
- if ($self->_changes_everconfirmed($field, $oldvalue, $newvalue)) {
- $$PrivilegesRequired = PRIVILEGES_REQUIRED_EMPOWERED;
- return $user->in_group('canconfirm', $self->{'product_id'});
- }
+ # - change the QA contact
+ if ($field eq 'qa_contact') {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
+ return 0;
+ }
- # Make sure that a valid bug ID has been given.
- if (!$self->{'error'}) {
- # Allow the assignee to change anything else.
- if ($self->{'assigned_to'} == $user->id
- || $self->{'_old_assigned_to'} && $self->{'_old_assigned_to'} == $user->id)
- {
- return 1;
- }
+ # - change the target milestone
+ if ($field eq 'target_milestone') {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
+ return 0;
+ }
- # Allow the QA contact to change anything else.
- if (Bugzilla->params->{'useqacontact'}
- && (($self->{'qa_contact'} && $self->{'qa_contact'} == $user->id)
- || ($self->{'_old_qa_contact'} && $self->{'_old_qa_contact'} == $user->id)))
- {
- return 1;
- }
- }
+ # - change the priority (unless they could have set it originally)
+ if ($field eq 'priority' && !Bugzilla->params->{'letsubmitterchoosepriority'}) {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
+ return 0;
+ }
- # At this point, the user is either the reporter or an
- # unprivileged user. We first check for fields the reporter
- # is not allowed to change.
-
- # The reporter may not:
- # - reassign bugs, unless the bugs are assigned to them;
- # in that case we will have already returned 1 above
- # when checking for the assignee of the bug.
- if ($field eq 'assigned_to') {
- $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
- return 0;
- }
- # - change the QA contact
- if ($field eq 'qa_contact') {
- $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
- return 0;
- }
- # - change the target milestone
- if ($field eq 'target_milestone') {
- $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
- return 0;
- }
- # - change the priority (unless they could have set it originally)
- if ($field eq 'priority'
- && !Bugzilla->params->{'letsubmitterchoosepriority'})
- {
- $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
- return 0;
- }
- # - unconfirm bugs (confirming them is handled above)
- if ($field eq 'everconfirmed') {
- $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
- return 0;
- }
- # - change the status from one open state to another
- if ($field eq 'bug_status'
- && is_open_state($oldvalue) && is_open_state($newvalue))
- {
- $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
- return 0;
- }
+ # - unconfirm bugs (confirming them is handled above)
+ if ($field eq 'everconfirmed') {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
+ return 0;
+ }
+
+ # - change the status from one open state to another
+ if ( $field eq 'bug_status'
+ && is_open_state($oldvalue)
+ && is_open_state($newvalue))
+ {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
+ return 0;
+ }
- # The reporter is allowed to change anything else.
- if (!$self->{'error'} && $self->{'reporter_id'} == $user->id) {
- return 1;
- }
+ # The reporter is allowed to change anything else.
+ if (!$self->{'error'} && $self->{'reporter_id'} == $user->id) {
+ return 1;
+ }
- # If we haven't returned by this point, then the user doesn't
- # have the necessary permissions to change this field.
- $$PrivilegesRequired = PRIVILEGES_REQUIRED_REPORTER;
- return 0;
+ # If we haven't returned by this point, then the user doesn't
+ # have the necessary permissions to change this field.
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_REPORTER;
+ return 0;
}
# A helper for check_can_change_field
sub _changes_everconfirmed {
- my ($self, $field, $old, $new) = @_;
- return 1 if $field eq 'everconfirmed';
- if ($field eq 'bug_status') {
- if ($self->everconfirmed) {
- # Moving a confirmed bug to UNCONFIRMED will change everconfirmed.
- return 1 if $new eq 'UNCONFIRMED';
- }
- else {
- # Moving an unconfirmed bug to an open state that isn't
- # UNCONFIRMED will confirm the bug.
- return 1 if (is_open_state($new) and $new ne 'UNCONFIRMED');
- }
+ my ($self, $field, $old, $new) = @_;
+ return 1 if $field eq 'everconfirmed';
+ if ($field eq 'bug_status') {
+ if ($self->everconfirmed) {
+
+ # Moving a confirmed bug to UNCONFIRMED will change everconfirmed.
+ return 1 if $new eq 'UNCONFIRMED';
}
- return 0;
+ else {
+ # Moving an unconfirmed bug to an open state that isn't
+ # UNCONFIRMED will confirm the bug.
+ return 1 if (is_open_state($new) and $new ne 'UNCONFIRMED');
+ }
+ }
+ return 0;
}
#
@@ -4510,72 +4708,77 @@ sub _changes_everconfirmed {
# Validate and return a hash of dependencies
sub ValidateDependencies {
- my $fields = {};
- # These can be arrayrefs or they can be strings.
- $fields->{'dependson'} = shift;
- $fields->{'blocked'} = shift;
- my $id = shift || 0;
-
- unless (defined($fields->{'dependson'})
- || defined($fields->{'blocked'}))
- {
- return;
- }
-
- my $dbh = Bugzilla->dbh;
- my %deps;
- my %deptree;
- my %sth;
- $sth{dependson} = $dbh->prepare('SELECT dependson FROM dependencies WHERE blocked = ?');
- $sth{blocked} = $dbh->prepare('SELECT blocked FROM dependencies WHERE dependson = ?');
-
- foreach my $pair (["blocked", "dependson"], ["dependson", "blocked"]) {
- my ($me, $target) = @{$pair};
- $deptree{$target} = [];
- $deps{$target} = [];
- next unless $fields->{$target};
-
- my %seen;
- my $target_array = ref($fields->{$target}) ? $fields->{$target}
- : [split(/[\s,]+/, $fields->{$target})];
- foreach my $i (@$target_array) {
- if ($id == $i) {
- ThrowUserError("dependency_loop_single");
- }
- if (!exists $seen{$i}) {
- push(@{$deptree{$target}}, $i);
- $seen{$i} = 1;
- }
- }
- # populate $deps{$target} as first-level deps only.
- # and find remainder of dependency tree in $deptree{$target}
- @{$deps{$target}} = @{$deptree{$target}};
- my @stack = @{$deps{$target}};
- while (@stack) {
- my $i = shift @stack;
- my $dep_list = $dbh->selectcol_arrayref($sth{$target}, undef, $i);
- foreach my $t (@$dep_list) {
- # ignore any _current_ dependencies involving this bug,
- # as they will be overwritten with data from the form.
- if ($t != $id && !exists $seen{$t}) {
- push(@{$deptree{$target}}, $t);
- push @stack, $t;
- $seen{$t} = 1;
- }
- }
+ my $fields = {};
+
+ # These can be arrayrefs or they can be strings.
+ $fields->{'dependson'} = shift;
+ $fields->{'blocked'} = shift;
+ my $id = shift || 0;
+
+ unless (defined($fields->{'dependson'}) || defined($fields->{'blocked'})) {
+ return;
+ }
+
+ my $dbh = Bugzilla->dbh;
+ my %deps;
+ my %deptree;
+ my %sth;
+ $sth{dependson}
+ = $dbh->prepare('SELECT dependson FROM dependencies WHERE blocked = ?');
+ $sth{blocked}
+ = $dbh->prepare('SELECT blocked FROM dependencies WHERE dependson = ?');
+
+ foreach my $pair (["blocked", "dependson"], ["dependson", "blocked"]) {
+ my ($me, $target) = @{$pair};
+ $deptree{$target} = [];
+ $deps{$target} = [];
+ next unless $fields->{$target};
+
+ my %seen;
+ my $target_array
+ = ref($fields->{$target})
+ ? $fields->{$target}
+ : [split(/[\s,]+/, $fields->{$target})];
+ foreach my $i (@$target_array) {
+ if ($id == $i) {
+ ThrowUserError("dependency_loop_single");
+ }
+ if (!exists $seen{$i}) {
+ push(@{$deptree{$target}}, $i);
+ $seen{$i} = 1;
+ }
+ }
+
+ # populate $deps{$target} as first-level deps only.
+ # and find remainder of dependency tree in $deptree{$target}
+ @{$deps{$target}} = @{$deptree{$target}};
+ my @stack = @{$deps{$target}};
+ while (@stack) {
+ my $i = shift @stack;
+ my $dep_list = $dbh->selectcol_arrayref($sth{$target}, undef, $i);
+ foreach my $t (@$dep_list) {
+
+ # ignore any _current_ dependencies involving this bug,
+ # as they will be overwritten with data from the form.
+ if ($t != $id && !exists $seen{$t}) {
+ push(@{$deptree{$target}}, $t);
+ push @stack, $t;
+ $seen{$t} = 1;
}
+ }
}
+ }
- my @deps = @{$deptree{'dependson'}};
- my @blocks = @{$deptree{'blocked'}};
- my %union = ();
- my %isect = ();
- foreach my $b (@deps, @blocks) { $union{$b}++ && $isect{$b}++ }
- my @isect = keys %isect;
- if (scalar(@isect) > 0) {
- ThrowUserError("dependency_loop_multi", {'deps' => \@isect});
- }
- return %deps;
+ my @deps = @{$deptree{'dependson'}};
+ my @blocks = @{$deptree{'blocked'}};
+ my %union = ();
+ my %isect = ();
+ foreach my $b (@deps, @blocks) { $union{$b}++ && $isect{$b}++ }
+ my @isect = keys %isect;
+ if (scalar(@isect) > 0) {
+ ThrowUserError("dependency_loop_multi", {'deps' => \@isect});
+ }
+ return %deps;
}
@@ -4584,51 +4787,52 @@ sub ValidateDependencies {
#####################################################################
sub _create_cf_accessors {
- my ($invocant) = @_;
- my $class = ref($invocant) || $invocant;
- return if Bugzilla->request_cache->{"${class}_cf_accessors_created"};
-
- my $fields = Bugzilla->fields({ custom => 1 });
- foreach my $field (@$fields) {
- my $accessor = $class->_accessor_for($field);
- my $name = "${class}::" . $field->name;
- {
- no strict 'refs';
- next if defined *{$name};
- *{$name} = $accessor;
- }
+ my ($invocant) = @_;
+ my $class = ref($invocant) || $invocant;
+ return if Bugzilla->request_cache->{"${class}_cf_accessors_created"};
+
+ my $fields = Bugzilla->fields({custom => 1});
+ foreach my $field (@$fields) {
+ my $accessor = $class->_accessor_for($field);
+ my $name = "${class}::" . $field->name;
+ {
+ no strict 'refs';
+ next if defined *{$name};
+ *{$name} = $accessor;
}
+ }
- Bugzilla->request_cache->{"${class}_cf_accessors_created"} = 1;
+ Bugzilla->request_cache->{"${class}_cf_accessors_created"} = 1;
}
sub _accessor_for {
- my ($class, $field) = @_;
- if ($field->type == FIELD_TYPE_MULTI_SELECT) {
- return $class->_multi_select_accessor($field->name);
- }
- return $class->_cf_accessor($field->name);
+ my ($class, $field) = @_;
+ if ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ return $class->_multi_select_accessor($field->name);
+ }
+ return $class->_cf_accessor($field->name);
}
sub _cf_accessor {
- my ($class, $field) = @_;
- my $accessor = sub {
- my ($self) = @_;
- return $self->{$field};
- };
- return $accessor;
+ my ($class, $field) = @_;
+ my $accessor = sub {
+ my ($self) = @_;
+ return $self->{$field};
+ };
+ return $accessor;
}
sub _multi_select_accessor {
- my ($class, $field) = @_;
- my $accessor = sub {
- my ($self) = @_;
- $self->{$field} ||= Bugzilla->dbh->selectcol_arrayref(
- "SELECT value FROM bug_$field WHERE bug_id = ? ORDER BY value",
- undef, $self->id);
- return $self->{$field};
- };
- return $accessor;
+ my ($class, $field) = @_;
+ my $accessor = sub {
+ my ($self) = @_;
+ $self->{$field}
+ ||= Bugzilla->dbh->selectcol_arrayref(
+ "SELECT value FROM bug_$field WHERE bug_id = ? ORDER BY value",
+ undef, $self->id);
+ return $self->{$field};
+ };
+ return $accessor;
}
1;
diff --git a/Bugzilla/BugMail.pm b/Bugzilla/BugMail.pm
index 110a1ffaf..18795d735 100644
--- a/Bugzilla/BugMail.pm
+++ b/Bugzilla/BugMail.pm
@@ -27,16 +27,17 @@ use Scalar::Util qw(blessed);
use List::MoreUtils qw(uniq);
use Storable qw(dclone);
-use constant BIT_DIRECT => 1;
-use constant BIT_WATCHING => 2;
+use constant BIT_DIRECT => 1;
+use constant BIT_WATCHING => 2;
sub relationships {
- my $ref = RELATIONSHIPS;
- # Clone it so that we don't modify the constant;
- my %relationships = %$ref;
- Bugzilla::Hook::process('bugmail_relationships',
- { relationships => \%relationships });
- return %relationships;
+ my $ref = RELATIONSHIPS;
+
+ # Clone it so that we don't modify the constant;
+ my %relationships = %$ref;
+ Bugzilla::Hook::process('bugmail_relationships',
+ {relationships => \%relationships});
+ return %relationships;
}
# args: bug_id, and an optional hash ref which may have keys for:
@@ -46,452 +47,482 @@ sub relationships {
# All the names are email addresses, not userids
# values are scalars, except for cc, which is a list
sub Send {
- my ($id, $forced, $params) = @_;
- $params ||= {};
-
- my $dbh = Bugzilla->dbh;
- my $bug = new Bugzilla::Bug($id);
-
- my $start = $bug->lastdiffed;
- my $end = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
-
- # Bugzilla::User objects 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 @assignees = ($bug->assigned_to);
- my @qa_contacts = $bug->qa_contact || ();
-
- my @ccs = @{ $bug->cc_users };
- # 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, Bugzilla::User->check($forced->{'owner'}));
- }
-
- if ($forced->{'qacontact'}) {
- push (@qa_contacts, Bugzilla::User->check($forced->{'qacontact'}));
- }
-
- if ($forced->{'cc'}) {
- foreach my $cc (@{$forced->{'cc'}}) {
- push(@ccs, Bugzilla::User->check($cc));
- }
+ my ($id, $forced, $params) = @_;
+ $params ||= {};
+
+ my $dbh = Bugzilla->dbh;
+ my $bug = new Bugzilla::Bug($id);
+
+ my $start = $bug->lastdiffed;
+ my $end = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ # Bugzilla::User objects 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 @assignees = ($bug->assigned_to);
+ my @qa_contacts = $bug->qa_contact || ();
+
+ my @ccs = @{$bug->cc_users};
+
+ # 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, Bugzilla::User->check($forced->{'owner'}));
+ }
+
+ if ($forced->{'qacontact'}) {
+ push(@qa_contacts, Bugzilla::User->check($forced->{'qacontact'}));
+ }
+
+ if ($forced->{'cc'}) {
+ foreach my $cc (@{$forced->{'cc'}}) {
+ push(@ccs, Bugzilla::User->check($cc));
}
- my %user_cache = map { $_->id => $_ } (@assignees, @qa_contacts, @ccs);
+ }
+ my %user_cache = map { $_->id => $_ } (@assignees, @qa_contacts, @ccs);
+
+ my @diffs;
+ if (!$start) {
+ @diffs = _get_new_bugmail_fields($bug);
+ }
+
+ my $comments = [];
+
+ if ($params->{dep_only}) {
+ push(
+ @diffs,
+ {
+ field_name => 'bug_status',
+ old => $params->{changes}->{bug_status}->[0],
+ new => $params->{changes}->{bug_status}->[1],
+ login_name => $changer->login,
+ who => $changer,
+ blocker => $params->{blocker}
+ },
+ {
+ field_name => 'resolution',
+ old => $params->{changes}->{resolution}->[0],
+ new => $params->{changes}->{resolution}->[1],
+ login_name => $changer->login,
+ who => $changer,
+ blocker => $params->{blocker}
+ }
+ );
+ }
+ else {
+ push(@diffs, _get_diffs($bug, $end, \%user_cache));
- my @diffs;
- if (!$start) {
- @diffs = _get_new_bugmail_fields($bug);
- }
+ $comments = $bug->comments({after => $start, to => $end});
- my $comments = [];
-
- if ($params->{dep_only}) {
- push(@diffs, { field_name => 'bug_status',
- old => $params->{changes}->{bug_status}->[0],
- new => $params->{changes}->{bug_status}->[1],
- login_name => $changer->login,
- who => $changer,
- blocker => $params->{blocker} },
- { field_name => 'resolution',
- old => $params->{changes}->{resolution}->[0],
- new => $params->{changes}->{resolution}->[1],
- login_name => $changer->login,
- who => $changer,
- blocker => $params->{blocker} });
- }
- else {
- push(@diffs, _get_diffs($bug, $end, \%user_cache));
+ # Skip empty comments.
+ @$comments = grep { $_->type || $_->body =~ /\S/ } @$comments;
- $comments = $bug->comments({ after => $start, to => $end });
- # Skip empty comments.
- @$comments = grep { $_->type || $_->body =~ /\S/ } @$comments;
+ # If no changes have been made, there is no need to process further.
+ return {'sent' => []} unless scalar(@diffs) || scalar(@$comments);
+ }
- # If no changes have been made, there is no need to process further.
- return {'sent' => []} unless scalar(@diffs) || scalar(@$comments);
- }
+ ###########################################################################
+ # Start of email filtering code
+ ###########################################################################
- ###########################################################################
- # Start of email filtering code
- ###########################################################################
-
- # A user_id => roles hash to keep track of people.
- my %recipients;
- my %watching;
-
- # We also record bugs that are referenced
- my @referenced_bug_ids = ();
-
- # 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.
-
- # CCs
- $recipients{$_->id}->{+REL_CC} = BIT_DIRECT foreach (@ccs);
-
- # Reporter (there's only ever one)
- $recipients{$bug->reporter->id}->{+REL_REPORTER} = BIT_DIRECT;
-
- # QA Contact
- if (Bugzilla->params->{'useqacontact'}) {
- foreach (@qa_contacts) {
- # QA Contact can be blank; ignore it if so.
- $recipients{$_->id}->{+REL_QA} = BIT_DIRECT if $_;
- }
- }
+ # A user_id => roles hash to keep track of people.
+ my %recipients;
+ my %watching;
- # Assignee
- $recipients{$_->id}->{+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 $change (@diffs) {
- if ($change->{old}) {
- # You can't stop being the reporter, so we don't check that
- # relationship here.
- # Ignore people whose user account has been deleted or renamed.
- if ($change->{field_name} eq 'cc') {
- foreach my $cc_user (split(/[\s,]+/, $change->{old})) {
- my $uid = login_to_id($cc_user);
- $recipients{$uid}->{+REL_CC} = BIT_DIRECT if $uid;
- }
- }
- elsif ($change->{field_name} eq 'qa_contact') {
- my $uid = login_to_id($change->{old});
- $recipients{$uid}->{+REL_QA} = BIT_DIRECT if $uid;
- }
- elsif ($change->{field_name} eq 'assigned_to') {
- my $uid = login_to_id($change->{old});
- $recipients{$uid}->{+REL_ASSIGNEE} = BIT_DIRECT if $uid;
- }
- }
+ # We also record bugs that are referenced
+ my @referenced_bug_ids = ();
- if ($change->{field_name} eq 'dependson' || $change->{field_name} eq 'blocked') {
- push @referenced_bug_ids, split(/[\s,]+/, $change->{old} // '');
- push @referenced_bug_ids, split(/[\s,]+/, $change->{new} // '');
- }
- }
+ # 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.
+
+ # CCs
+ $recipients{$_->id}->{+REL_CC} = BIT_DIRECT foreach (@ccs);
+
+ # Reporter (there's only ever one)
+ $recipients{$bug->reporter->id}->{+REL_REPORTER} = BIT_DIRECT;
- my $referenced_bugs = scalar(@referenced_bug_ids)
- ? Bugzilla::Bug->new_from_list([uniq @referenced_bug_ids])
- : [];
+ # QA Contact
+ if (Bugzilla->params->{'useqacontact'}) {
+ foreach (@qa_contacts) {
- # Make sure %user_cache has every user in it so far referenced
- foreach my $user_id (keys %recipients) {
- $user_cache{$user_id} ||= new Bugzilla::User($user_id);
+ # QA Contact can be blank; ignore it if so.
+ $recipients{$_->id}->{+REL_QA} = BIT_DIRECT if $_;
}
-
- Bugzilla::Hook::process('bugmail_recipients',
- { bug => $bug, recipients => \%recipients,
- users => \%user_cache, diffs => \@diffs });
-
- # We should not assume %recipients to have any entries.
- if (scalar keys %recipients) {
- # 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]);
+ }
+
+ # Assignee
+ $recipients{$_->id}->{+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 $change (@diffs) {
+ if ($change->{old}) {
+
+ # You can't stop being the reporter, so we don't check that
+ # relationship here.
+ # Ignore people whose user account has been deleted or renamed.
+ if ($change->{field_name} eq 'cc') {
+ foreach my $cc_user (split(/[\s,]+/, $change->{old})) {
+ my $uid = login_to_id($cc_user);
+ $recipients{$uid}->{+REL_CC} = BIT_DIRECT if $uid;
}
+ }
+ elsif ($change->{field_name} eq 'qa_contact') {
+ my $uid = login_to_id($change->{old});
+ $recipients{$uid}->{+REL_QA} = BIT_DIRECT if $uid;
+ }
+ elsif ($change->{field_name} eq 'assigned_to') {
+ my $uid = login_to_id($change->{old});
+ $recipients{$uid}->{+REL_ASSIGNEE} = BIT_DIRECT if $uid;
+ }
}
- # 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;
+ if ($change->{field_name} eq 'dependson' || $change->{field_name} eq 'blocked')
+ {
+ push @referenced_bug_ids, split(/[\s,]+/, $change->{old} // '');
+ push @referenced_bug_ids, split(/[\s,]+/, $change->{new} // '');
}
-
- # 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;
-
- # The email client will display the Date: header in the desired timezone,
- # so we can always use UTC here.
- my $date = $params->{dep_only} ? $end : $bug->delta_ts;
- $date = format_time($date, '%a, %d %b %Y %T %z', 'UTC');
-
- foreach my $user_id (keys %recipients) {
- my %rels_which_want;
- my $user = $user_cache{$user_id} ||= new Bugzilla::User($user_id);
- # Deleted users must be excluded.
- next unless $user;
-
- # If email notifications are disabled for this account, or the bug
- # is ignored, there is no need to do additional checks.
- next if ($user->email_disabled || $user->is_bug_ignored($id));
-
- if ($user->can_see_bug($id)) {
- # 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($bug,
- $relationship,
- $start ? \@diffs : [],
- $comments,
- $params->{dep_only},
- $changer))
- {
- $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?
-
- # We shouldn't send mail if this is a dependency mail and the
- # depending bug is not visible to the user.
- # This is to avoid leaking the summary of a confidential bug.
- my $dep_ok = 1;
- if ($params->{dep_only}) {
- $dep_ok = $user->can_see_bug($params->{blocker}->id) ? 1 : 0;
- }
-
- # Email the user if the dep check passed.
- if ($dep_ok) {
- my $sent_mail = sendMail(
- { to => $user,
- bug => $bug,
- comments => $comments,
- date => $date,
- changer => $changer,
- watchers => exists $watching{$user_id} ?
- $watching{$user_id} : undef,
- diffs => \@diffs,
- rels_which_want => \%rels_which_want,
- dep_only => $params->{dep_only},
- referenced_bugs => $referenced_bugs,
- });
- push(@sent, $user->login) if $sent_mail;
- }
- }
+ }
+
+ my $referenced_bugs
+ = scalar(@referenced_bug_ids)
+ ? Bugzilla::Bug->new_from_list([uniq @referenced_bug_ids])
+ : [];
+
+ # Make sure %user_cache has every user in it so far referenced
+ foreach my $user_id (keys %recipients) {
+ $user_cache{$user_id} ||= new Bugzilla::User($user_id);
+ }
+
+ Bugzilla::Hook::process(
+ 'bugmail_recipients',
+ {
+ bug => $bug,
+ recipients => \%recipients,
+ users => \%user_cache,
+ diffs => \@diffs
}
+ );
- # When sending bugmail about a blocker being reopened or resolved,
- # we say nothing about changes in the bug being blocked, so we must
- # not update lastdiffed in this case.
- if (!$params->{dep_only}) {
- $dbh->do('UPDATE bugs SET lastdiffed = ? WHERE bug_id = ?',
- undef, ($end, $id));
- $bug->{lastdiffed} = $end;
- }
+ # We should not assume %recipients to have any entries.
+ if (scalar keys %recipients) {
- return {'sent' => \@sent};
-}
+ # Find all those user-watching anyone on the current list, who is not
+ # on it already themselves.
+ my $involved = join(",", keys %recipients);
-sub sendMail {
- my $params = shift;
-
- my $user = $params->{to};
- my $bug = $params->{bug};
- my @send_comments = @{ $params->{comments} };
- my $date = $params->{date};
- my $changer = $params->{changer};
- my $watchingRef = $params->{watchers};
- my @diffs = @{ $params->{diffs} };
- my $relRef = $params->{rels_which_want};
- my $dep_only = $params->{dep_only};
- my $referenced_bugs = $params->{referenced_bugs};
-
- # Only display changes the user is allowed see.
- my @display_diffs;
-
- foreach my $diff (@diffs) {
- my $add_diff = 0;
-
- if (grep { $_ eq $diff->{field_name} } TIMETRACKING_FIELDS) {
- $add_diff = 1 if $user->is_timetracker;
- }
- elsif (!$diff->{isprivate} || $user->is_insider) {
- $add_diff = 1;
- }
- push(@display_diffs, $diff) if $add_diff;
- }
+ my $userwatchers = $dbh->selectall_arrayref(
+ "SELECT watcher, watched FROM watch
+ WHERE watched IN ($involved)"
+ );
- if (!$user->is_insider) {
- @send_comments = grep { !$_->is_private } @send_comments;
+ # 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]);
}
-
- if (!scalar(@display_diffs) && !scalar(@send_comments)) {
- # Whoops, no differences!
- return 0;
+ }
+
+ # 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;
+
+ # The email client will display the Date: header in the desired timezone,
+ # so we can always use UTC here.
+ my $date = $params->{dep_only} ? $end : $bug->delta_ts;
+ $date = format_time($date, '%a, %d %b %Y %T %z', 'UTC');
+
+ foreach my $user_id (keys %recipients) {
+ my %rels_which_want;
+ my $user = $user_cache{$user_id} ||= new Bugzilla::User($user_id);
+
+ # Deleted users must be excluded.
+ next unless $user;
+
+ # If email notifications are disabled for this account, or the bug
+ # is ignored, there is no need to do additional checks.
+ next if ($user->email_disabled || $user->is_bug_ignored($id));
+
+ if ($user->can_see_bug($id)) {
+
+ # 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(
+ $bug, $relationship, $start ? \@diffs : [],
+ $comments, $params->{dep_only}, $changer
+ ))
+ {
+ $rels_which_want{$relationship} = $recipients{$user_id}->{$relationship};
+ }
+ }
}
- 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);
+ 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?
+
+ # We shouldn't send mail if this is a dependency mail and the
+ # depending bug is not visible to the user.
+ # This is to avoid leaking the summary of a confidential bug.
+ my $dep_ok = 1;
+ if ($params->{dep_only}) {
+ $dep_ok = $user->can_see_bug($params->{blocker}->id) ? 1 : 0;
+ }
+
+ # Email the user if the dep check passed.
+ if ($dep_ok) {
+ my $sent_mail = sendMail({
+ to => $user,
+ bug => $bug,
+ comments => $comments,
+ date => $date,
+ changer => $changer,
+ watchers => exists $watching{$user_id} ? $watching{$user_id} : undef,
+ diffs => \@diffs,
+ rels_which_want => \%rels_which_want,
+ dep_only => $params->{dep_only},
+ referenced_bugs => $referenced_bugs,
+ });
+ push(@sent, $user->login) if $sent_mail;
+ }
}
+ }
- my %relationships = relationships();
- my @headerrel = map { $relationships{$_} } @reasons;
- my @watchingrel = map { $relationships{$_} } @reasons_watch;
- push(@headerrel, 'None') unless @headerrel;
- push(@watchingrel, 'None') unless @watchingrel;
- push @watchingrel, map { Bugzilla::User->new($_)->login } @$watchingRef;
+ # When sending bugmail about a blocker being reopened or resolved,
+ # we say nothing about changes in the bug being blocked, so we must
+ # not update lastdiffed in this case.
+ if (!$params->{dep_only}) {
+ $dbh->do('UPDATE bugs SET lastdiffed = ? WHERE bug_id = ?', undef, ($end, $id));
+ $bug->{lastdiffed} = $end;
+ }
- my @changedfields = uniq map { $_->{field_name} } @display_diffs;
+ return {'sent' => \@sent};
+}
- # Add attachments.created to changedfields if one or more
- # comments contain information about a new attachment
- if (grep($_->type == CMT_ATTACHMENT_CREATED, @send_comments)) {
- push(@changedfields, 'attachments.created');
+sub sendMail {
+ my $params = shift;
+
+ my $user = $params->{to};
+ my $bug = $params->{bug};
+ my @send_comments = @{$params->{comments}};
+ my $date = $params->{date};
+ my $changer = $params->{changer};
+ my $watchingRef = $params->{watchers};
+ my @diffs = @{$params->{diffs}};
+ my $relRef = $params->{rels_which_want};
+ my $dep_only = $params->{dep_only};
+ my $referenced_bugs = $params->{referenced_bugs};
+
+ # Only display changes the user is allowed see.
+ my @display_diffs;
+
+ foreach my $diff (@diffs) {
+ my $add_diff = 0;
+
+ if (grep { $_ eq $diff->{field_name} } TIMETRACKING_FIELDS) {
+ $add_diff = 1 if $user->is_timetracker;
}
-
- my $bugmailtype = "changed";
- $bugmailtype = "new" if !$bug->lastdiffed;
- $bugmailtype = "dep_changed" if $dep_only;
-
- my $vars = {
- date => $date,
- to_user => $user,
- bug => $bug,
- reasons => \@reasons,
- reasons_watch => \@reasons_watch,
- reasonsheader => join(" ", @headerrel),
- reasonswatchheader => join(" ", @watchingrel),
- changer => $changer,
- diffs => \@display_diffs,
- changedfields => \@changedfields,
- referenced_bugs => $user->visible_bugs($referenced_bugs),
- new_comments => \@send_comments,
- threadingmarker => build_thread_marker($bug->id, $user->id, !$bug->lastdiffed),
- bugmailtype => $bugmailtype,
- };
- if (Bugzilla->params->{'use_mailer_queue'}) {
- enqueue($vars);
- } else {
- MessageToMTA(_generate_bugmail($vars));
+ elsif (!$diff->{isprivate} || $user->is_insider) {
+ $add_diff = 1;
}
-
- return 1;
+ push(@display_diffs, $diff) if $add_diff;
+ }
+
+ if (!$user->is_insider) {
+ @send_comments = grep { !$_->is_private } @send_comments;
+ }
+
+ if (!scalar(@display_diffs) && !scalar(@send_comments)) {
+
+ # Whoops, no differences!
+ return 0;
+ }
+
+ 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 %relationships = relationships();
+ my @headerrel = map { $relationships{$_} } @reasons;
+ my @watchingrel = map { $relationships{$_} } @reasons_watch;
+ push(@headerrel, 'None') unless @headerrel;
+ push(@watchingrel, 'None') unless @watchingrel;
+ push @watchingrel, map { Bugzilla::User->new($_)->login } @$watchingRef;
+
+ my @changedfields = uniq map { $_->{field_name} } @display_diffs;
+
+ # Add attachments.created to changedfields if one or more
+ # comments contain information about a new attachment
+ if (grep($_->type == CMT_ATTACHMENT_CREATED, @send_comments)) {
+ push(@changedfields, 'attachments.created');
+ }
+
+ my $bugmailtype = "changed";
+ $bugmailtype = "new" if !$bug->lastdiffed;
+ $bugmailtype = "dep_changed" if $dep_only;
+
+ my $vars = {
+ date => $date,
+ to_user => $user,
+ bug => $bug,
+ reasons => \@reasons,
+ reasons_watch => \@reasons_watch,
+ reasonsheader => join(" ", @headerrel),
+ reasonswatchheader => join(" ", @watchingrel),
+ changer => $changer,
+ diffs => \@display_diffs,
+ changedfields => \@changedfields,
+ referenced_bugs => $user->visible_bugs($referenced_bugs),
+ new_comments => \@send_comments,
+ threadingmarker => build_thread_marker($bug->id, $user->id, !$bug->lastdiffed),
+ bugmailtype => $bugmailtype,
+ };
+ if (Bugzilla->params->{'use_mailer_queue'}) {
+ enqueue($vars);
+ }
+ else {
+ MessageToMTA(_generate_bugmail($vars));
+ }
+
+ return 1;
}
sub enqueue {
- my ($vars) = @_;
- # we need to flatten all objects to a hash before pushing to the job queue.
- # the hashes need to be inflated in the dequeue method.
- $vars->{bug} = _flatten_object($vars->{bug});
- $vars->{to_user} = _flatten_object($vars->{to_user});
- $vars->{changer} = _flatten_object($vars->{changer});
- $vars->{new_comments} = [ map { _flatten_object($_) } @{ $vars->{new_comments} } ];
- foreach my $diff (@{ $vars->{diffs} }) {
- $diff->{who} = _flatten_object($diff->{who});
- if (exists $diff->{blocker}) {
- $diff->{blocker} = _flatten_object($diff->{blocker});
- }
+ my ($vars) = @_;
+
+ # we need to flatten all objects to a hash before pushing to the job queue.
+ # the hashes need to be inflated in the dequeue method.
+ $vars->{bug} = _flatten_object($vars->{bug});
+ $vars->{to_user} = _flatten_object($vars->{to_user});
+ $vars->{changer} = _flatten_object($vars->{changer});
+ $vars->{new_comments} = [map { _flatten_object($_) } @{$vars->{new_comments}}];
+ foreach my $diff (@{$vars->{diffs}}) {
+ $diff->{who} = _flatten_object($diff->{who});
+ if (exists $diff->{blocker}) {
+ $diff->{blocker} = _flatten_object($diff->{blocker});
}
- Bugzilla->job_queue->insert('bug_mail', { vars => $vars });
+ }
+ Bugzilla->job_queue->insert('bug_mail', {vars => $vars});
}
sub dequeue {
- my ($payload) = @_;
- # clone the payload so we can modify it without impacting TheSchwartz's
- # ability to process the job when we've finished
- my $vars = dclone($payload);
- # inflate objects
- $vars->{bug} = Bugzilla::Bug->new_from_hash($vars->{bug});
- $vars->{to_user} = Bugzilla::User->new_from_hash($vars->{to_user});
- $vars->{changer} = Bugzilla::User->new_from_hash($vars->{changer});
- $vars->{new_comments} = [ map { Bugzilla::Comment->new_from_hash($_) } @{ $vars->{new_comments} } ];
- foreach my $diff (@{ $vars->{diffs} }) {
- $diff->{who} = Bugzilla::User->new_from_hash($diff->{who});
- if (exists $diff->{blocker}) {
- $diff->{blocker} = Bugzilla::Bug->new_from_hash($diff->{blocker});
- }
+ my ($payload) = @_;
+
+ # clone the payload so we can modify it without impacting TheSchwartz's
+ # ability to process the job when we've finished
+ my $vars = dclone($payload);
+
+ # inflate objects
+ $vars->{bug} = Bugzilla::Bug->new_from_hash($vars->{bug});
+ $vars->{to_user} = Bugzilla::User->new_from_hash($vars->{to_user});
+ $vars->{changer} = Bugzilla::User->new_from_hash($vars->{changer});
+ $vars->{new_comments}
+ = [map { Bugzilla::Comment->new_from_hash($_) } @{$vars->{new_comments}}];
+ foreach my $diff (@{$vars->{diffs}}) {
+ $diff->{who} = Bugzilla::User->new_from_hash($diff->{who});
+ if (exists $diff->{blocker}) {
+ $diff->{blocker} = Bugzilla::Bug->new_from_hash($diff->{blocker});
}
- # generate bugmail and send
- MessageToMTA(_generate_bugmail($vars), 1);
+ }
+
+ # generate bugmail and send
+ MessageToMTA(_generate_bugmail($vars), 1);
}
sub _flatten_object {
- my ($object) = @_;
- # nothing to do if it's already flattened
- return $object unless blessed($object);
- # the same objects are used for each recipient, so cache the flattened hash
- my $cache = Bugzilla->request_cache->{bugmail_flat_objects} ||= {};
- my $key = blessed($object) . '-' . $object->id;
- return $cache->{$key} ||= $object->flatten_to_hash;
+ my ($object) = @_;
+
+ # nothing to do if it's already flattened
+ return $object unless blessed($object);
+
+ # the same objects are used for each recipient, so cache the flattened hash
+ my $cache = Bugzilla->request_cache->{bugmail_flat_objects} ||= {};
+ my $key = blessed($object) . '-' . $object->id;
+ return $cache->{$key} ||= $object->flatten_to_hash;
}
sub _generate_bugmail {
- my ($vars) = @_;
- my $user = $vars->{to_user};
- my $template = Bugzilla->template_inner($user->setting('lang'));
- my ($msg_text, $msg_html, $msg_header);
- state $use_utf8 = Bugzilla->params->{'utf8'};
-
- $template->process("email/bugmail-header.txt.tmpl", $vars, \$msg_header)
- || ThrowTemplateError($template->error());
- $template->process("email/bugmail.txt.tmpl", $vars, \$msg_text)
- || ThrowTemplateError($template->error());
-
- my @parts = (
- Bugzilla::MIME->create(
- attributes => {
- content_type => 'text/plain',
- charset => $use_utf8 ? 'UTF-8' : 'iso-8859-1',
- encoding => 'quoted-printable',
- },
- body_str => $msg_text,
- )
- );
- if ($user->setting('email_format') eq 'html') {
- $template->process("email/bugmail.html.tmpl", $vars, \$msg_html)
- || ThrowTemplateError($template->error());
- push @parts, Bugzilla::MIME->create(
- attributes => {
- content_type => 'text/html',
- charset => $use_utf8 ? 'UTF-8' : 'iso-8859-1',
- encoding => 'quoted-printable',
- },
- body_str => $msg_html,
- );
- }
-
- my $email = Bugzilla::MIME->new($msg_header);
- if (scalar(@parts) == 1) {
- $email->content_type_set($parts[0]->content_type);
- } else {
- $email->content_type_set('multipart/alternative');
- # Some mail clients need same encoding for each part, even empty ones.
- $email->charset_set('UTF-8') if $use_utf8;
- }
- $email->parts_set(\@parts);
- return $email;
+ my ($vars) = @_;
+ my $user = $vars->{to_user};
+ my $template = Bugzilla->template_inner($user->setting('lang'));
+ my ($msg_text, $msg_html, $msg_header);
+ state $use_utf8 = Bugzilla->params->{'utf8'};
+
+ $template->process("email/bugmail-header.txt.tmpl", $vars, \$msg_header)
+ || ThrowTemplateError($template->error());
+ $template->process("email/bugmail.txt.tmpl", $vars, \$msg_text)
+ || ThrowTemplateError($template->error());
+
+ my @parts = (Bugzilla::MIME->create(
+ attributes => {
+ content_type => 'text/plain',
+ charset => $use_utf8 ? 'UTF-8' : 'iso-8859-1',
+ encoding => 'quoted-printable',
+ },
+ body_str => $msg_text,
+ ));
+
+ if ($user->setting('email_format') eq 'html') {
+ $template->process("email/bugmail.html.tmpl", $vars, \$msg_html)
+ || ThrowTemplateError($template->error());
+ push @parts,
+ Bugzilla::MIME->create(
+ attributes => {
+ content_type => 'text/html',
+ charset => $use_utf8 ? 'UTF-8' : 'iso-8859-1',
+ encoding => 'quoted-printable',
+ },
+ body_str => $msg_html,
+ );
+ }
+
+ my $email = Bugzilla::MIME->new($msg_header);
+ if (scalar(@parts) == 1) {
+ $email->content_type_set($parts[0]->content_type);
+ }
+ else {
+ $email->content_type_set('multipart/alternative');
+
+ # Some mail clients need same encoding for each part, even empty ones.
+ $email->charset_set('UTF-8') if $use_utf8;
+ }
+ $email->parts_set(\@parts);
+ return $email;
}
sub _get_diffs {
- my ($bug, $end, $user_cache) = @_;
- my $dbh = Bugzilla->dbh;
-
- my @args = ($bug->id);
- # If lastdiffed is NULL, then we don't limit the search on time.
- my $when_restriction = '';
- if ($bug->lastdiffed) {
- $when_restriction = ' AND bug_when > ? AND bug_when <= ?';
- push @args, ($bug->lastdiffed, $end);
- }
+ my ($bug, $end, $user_cache) = @_;
+ my $dbh = Bugzilla->dbh;
- my $diffs = $dbh->selectall_arrayref(
- "SELECT fielddefs.name AS field_name,
+ my @args = ($bug->id);
+
+ # If lastdiffed is NULL, then we don't limit the search on time.
+ my $when_restriction = '';
+ if ($bug->lastdiffed) {
+ $when_restriction = ' AND bug_when > ? AND bug_when <= ?';
+ push @args, ($bug->lastdiffed, $end);
+ }
+
+ my $diffs = $dbh->selectall_arrayref(
+ "SELECT fielddefs.name AS field_name,
bugs_activity.bug_when, bugs_activity.removed AS old,
bugs_activity.added AS new, bugs_activity.attach_id,
bugs_activity.comment_id, bugs_activity.who
@@ -500,89 +531,92 @@ sub _get_diffs {
ON fielddefs.id = bugs_activity.fieldid
WHERE bugs_activity.bug_id = ?
$when_restriction
- ORDER BY bugs_activity.bug_when, bugs_activity.id",
- {Slice=>{}}, @args);
-
- foreach my $diff (@$diffs) {
- $user_cache->{$diff->{who}} ||= new Bugzilla::User($diff->{who});
- $diff->{who} = $user_cache->{$diff->{who}};
- if ($diff->{attach_id}) {
- $diff->{isprivate} = $dbh->selectrow_array(
- 'SELECT isprivate FROM attachments WHERE attach_id = ?',
- undef, $diff->{attach_id});
- }
- if ($diff->{field_name} eq 'longdescs.isprivate') {
- my $comment = Bugzilla::Comment->new($diff->{comment_id});
- $diff->{num} = $comment->count;
- $diff->{isprivate} = $diff->{new};
- }
+ ORDER BY bugs_activity.bug_when, bugs_activity.id", {Slice => {}}, @args
+ );
+
+ foreach my $diff (@$diffs) {
+ $user_cache->{$diff->{who}} ||= new Bugzilla::User($diff->{who});
+ $diff->{who} = $user_cache->{$diff->{who}};
+ if ($diff->{attach_id}) {
+ $diff->{isprivate}
+ = $dbh->selectrow_array(
+ 'SELECT isprivate FROM attachments WHERE attach_id = ?',
+ undef, $diff->{attach_id});
}
-
- my @changes = ();
- foreach my $diff (@$diffs) {
- # If this is the same field as the previous item, then concatenate
- # the data into the same change.
- if (scalar(@changes)
- && $diff->{field_name} eq $changes[-1]->{field_name}
- && $diff->{bug_when} eq $changes[-1]->{bug_when}
- && $diff->{who} eq $changes[-1]->{who}
- && ($diff->{attach_id} // 0) == ($changes[-1]->{attach_id} // 0)
- && ($diff->{comment_id} // 0) == ($changes[-1]->{comment_id} // 0)
- ) {
- my $old_change = pop @changes;
- $diff->{old} = join_activity_entries($diff->{field_name}, $old_change->{old}, $diff->{old});
- $diff->{new} = join_activity_entries($diff->{field_name}, $old_change->{new}, $diff->{new});
- }
- push @changes, $diff;
+ if ($diff->{field_name} eq 'longdescs.isprivate') {
+ my $comment = Bugzilla::Comment->new($diff->{comment_id});
+ $diff->{num} = $comment->count;
+ $diff->{isprivate} = $diff->{new};
+ }
+ }
+
+ my @changes = ();
+ foreach my $diff (@$diffs) {
+
+ # If this is the same field as the previous item, then concatenate
+ # the data into the same change.
+ if ( scalar(@changes)
+ && $diff->{field_name} eq $changes[-1]->{field_name}
+ && $diff->{bug_when} eq $changes[-1]->{bug_when}
+ && $diff->{who} eq $changes[-1]->{who}
+ && ($diff->{attach_id} // 0) == ($changes[-1]->{attach_id} // 0)
+ && ($diff->{comment_id} // 0) == ($changes[-1]->{comment_id} // 0))
+ {
+ my $old_change = pop @changes;
+ $diff->{old} = join_activity_entries($diff->{field_name}, $old_change->{old},
+ $diff->{old});
+ $diff->{new} = join_activity_entries($diff->{field_name}, $old_change->{new},
+ $diff->{new});
}
+ push @changes, $diff;
+ }
- return @changes;
+ return @changes;
}
sub _get_new_bugmail_fields {
- my $bug = shift;
- my @fields = @{ Bugzilla->fields({obsolete => 0, in_new_bugmail => 1}) };
- my @diffs;
- my $params = Bugzilla->params;
-
- foreach my $field (@fields) {
- my $name = $field->name;
- my $value = $bug->$name;
-
- next if !$field->is_visible_on_bug($bug)
- || ($name eq 'classification' && !$params->{'useclassification'})
- || ($name eq 'status_whiteboard' && !$params->{'usestatuswhiteboard'})
- || ($name eq 'qa_contact' && !$params->{'useqacontact'})
- || ($name eq 'target_milestone' && !$params->{'usetargetmilestone'});
-
- if (ref $value eq 'ARRAY') {
- $value = join(', ', @$value);
- }
- elsif (blessed($value) && $value->isa('Bugzilla::User')) {
- $value = $value->login;
- }
- elsif (blessed($value) && $value->isa('Bugzilla::Object')) {
- $value = $value->name;
- }
- elsif ($name eq 'estimated_time') {
- # "0.00" (which is what we get from the DB) is true,
- # so we explicitly do a numerical comparison with 0.
- $value = 0 if $value == 0;
- }
- elsif ($name eq 'deadline') {
- $value = time2str("%Y-%m-%d", str2time($value)) if $value;
- }
-
- # If there isn't anything to show, don't include this header.
- next unless $value;
+ my $bug = shift;
+ my @fields = @{Bugzilla->fields({obsolete => 0, in_new_bugmail => 1})};
+ my @diffs;
+ my $params = Bugzilla->params;
+
+ foreach my $field (@fields) {
+ my $name = $field->name;
+ my $value = $bug->$name;
+
+ next
+ if !$field->is_visible_on_bug($bug)
+ || ($name eq 'classification' && !$params->{'useclassification'})
+ || ($name eq 'status_whiteboard' && !$params->{'usestatuswhiteboard'})
+ || ($name eq 'qa_contact' && !$params->{'useqacontact'})
+ || ($name eq 'target_milestone' && !$params->{'usetargetmilestone'});
+
+ if (ref $value eq 'ARRAY') {
+ $value = join(', ', @$value);
+ }
+ elsif (blessed($value) && $value->isa('Bugzilla::User')) {
+ $value = $value->login;
+ }
+ elsif (blessed($value) && $value->isa('Bugzilla::Object')) {
+ $value = $value->name;
+ }
+ elsif ($name eq 'estimated_time') {
- push(@diffs, {
- field_name => $name,
- who => $bug->reporter,
- new => $value});
+ # "0.00" (which is what we get from the DB) is true,
+ # so we explicitly do a numerical comparison with 0.
+ $value = 0 if $value == 0;
}
+ elsif ($name eq 'deadline') {
+ $value = time2str("%Y-%m-%d", str2time($value)) if $value;
+ }
+
+ # If there isn't anything to show, don't include this header.
+ next unless $value;
+
+ push(@diffs, {field_name => $name, who => $bug->reporter, new => $value});
+ }
- return @diffs;
+ return @diffs;
}
1;
diff --git a/Bugzilla/BugUrl.pm b/Bugzilla/BugUrl.pm
index 1fe8b3d0c..5d62d1b17 100644
--- a/Bugzilla/BugUrl.pm
+++ b/Bugzilla/BugUrl.pm
@@ -28,49 +28,50 @@ use URI::QueryParam;
use constant DB_TABLE => 'bug_see_also';
use constant NAME_FIELD => 'value';
use constant LIST_ORDER => 'id';
+
# See Also is tracked in bugs_activity.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
- id
- bug_id
- value
- class
+ id
+ bug_id
+ value
+ class
);
# This must be strings with the names of the validations,
# instead of coderefs, because subclasses override these
# validators with their own.
use constant VALIDATORS => {
- value => '_check_value',
- bug_id => '_check_bug_id',
- class => \&_check_class,
+ value => '_check_value',
+ bug_id => '_check_bug_id',
+ class => \&_check_class,
};
# This is the order we go through all of subclasses and
# pick the first one that should handle the url. New
# subclasses should be added at the end of the list.
use constant SUB_CLASSES => qw(
- Bugzilla::BugUrl::Bugzilla::Local
- Bugzilla::BugUrl::Bugzilla
- Bugzilla::BugUrl::Launchpad
- Bugzilla::BugUrl::Flyspray
- Bugzilla::BugUrl::Google
- Bugzilla::BugUrl::Debian
- Bugzilla::BugUrl::JIRA
- Bugzilla::BugUrl::Trac
- Bugzilla::BugUrl::MantisBT
- Bugzilla::BugUrl::SourceForge
- Bugzilla::BugUrl::GitHub
+ Bugzilla::BugUrl::Bugzilla::Local
+ Bugzilla::BugUrl::Bugzilla
+ Bugzilla::BugUrl::Launchpad
+ Bugzilla::BugUrl::Flyspray
+ Bugzilla::BugUrl::Google
+ Bugzilla::BugUrl::Debian
+ Bugzilla::BugUrl::JIRA
+ Bugzilla::BugUrl::Trac
+ Bugzilla::BugUrl::MantisBT
+ Bugzilla::BugUrl::SourceForge
+ Bugzilla::BugUrl::GitHub
);
###############################
#### Accessors ######
###############################
-sub class { return $_[0]->{class} }
+sub class { return $_[0]->{class} }
sub bug_id { return $_[0]->{bug_id} }
###############################
@@ -78,130 +79,127 @@ sub bug_id { return $_[0]->{bug_id} }
###############################
sub new {
- my $class = shift;
- my $param = shift;
-
- if (ref $param) {
- my $bug_id = $param->{bug_id};
- my $name = $param->{name} || $param->{value};
- if (!defined $bug_id) {
- ThrowCodeError('bad_arg',
- { argument => 'bug_id',
- function => "${class}::new" });
- }
- if (!defined $name) {
- ThrowCodeError('bad_arg',
- { argument => 'name',
- function => "${class}::new" });
- }
-
- my $condition = 'bug_id = ? AND value = ?';
- my @values = ($bug_id, $name);
- $param = { condition => $condition, values => \@values };
+ my $class = shift;
+ my $param = shift;
+
+ if (ref $param) {
+ my $bug_id = $param->{bug_id};
+ my $name = $param->{name} || $param->{value};
+ if (!defined $bug_id) {
+ ThrowCodeError('bad_arg', {argument => 'bug_id', function => "${class}::new"});
+ }
+ if (!defined $name) {
+ ThrowCodeError('bad_arg', {argument => 'name', function => "${class}::new"});
}
- unshift @_, $param;
- return $class->SUPER::new(@_);
+ my $condition = 'bug_id = ? AND value = ?';
+ my @values = ($bug_id, $name);
+ $param = {condition => $condition, values => \@values};
+ }
+
+ unshift @_, $param;
+ return $class->SUPER::new(@_);
}
sub _do_list_select {
- my $class = shift;
- my $objects = $class->SUPER::_do_list_select(@_);
+ my $class = shift;
+ my $objects = $class->SUPER::_do_list_select(@_);
- foreach my $object (@$objects) {
- eval "use " . $object->class;
- # If the class cannot be loaded, then we build a generic object.
- bless $object, ($@ ? 'Bugzilla::BugUrl' : $object->class);
- }
+ foreach my $object (@$objects) {
+ eval "use " . $object->class;
+
+ # If the class cannot be loaded, then we build a generic object.
+ bless $object, ($@ ? 'Bugzilla::BugUrl' : $object->class);
+ }
- return $objects
+ return $objects;
}
# This is an abstract method. It must be overridden
# in every subclass.
sub should_handle {
- my ($class, $input) = @_;
- ThrowCodeError('unknown_method',
- { method => "${class}::should_handle" });
+ my ($class, $input) = @_;
+ ThrowCodeError('unknown_method', {method => "${class}::should_handle"});
}
sub class_for {
- my ($class, $value) = @_;
-
- my @sub_classes = $class->SUB_CLASSES;
- Bugzilla::Hook::process("bug_url_sub_classes",
- { sub_classes => \@sub_classes });
-
- my $uri = URI->new($value);
- foreach my $subclass (@sub_classes) {
- eval "use $subclass";
- die $@ if $@;
- return wantarray ? ($subclass, $uri) : $subclass
- if $subclass->should_handle($uri);
- }
+ my ($class, $value) = @_;
- ThrowUserError('bug_url_invalid', { url => $value });
+ my @sub_classes = $class->SUB_CLASSES;
+ Bugzilla::Hook::process("bug_url_sub_classes", {sub_classes => \@sub_classes});
+
+ my $uri = URI->new($value);
+ foreach my $subclass (@sub_classes) {
+ eval "use $subclass";
+ die $@ if $@;
+ return wantarray ? ($subclass, $uri) : $subclass
+ if $subclass->should_handle($uri);
+ }
+
+ ThrowUserError('bug_url_invalid', {url => $value});
}
sub _check_class {
- my ($class, $subclass) = @_;
- eval "use $subclass"; die $@ if $@;
- return $subclass;
+ my ($class, $subclass) = @_;
+ eval "use $subclass";
+ die $@ if $@;
+ return $subclass;
}
sub _check_bug_id {
- my ($class, $bug_id) = @_;
+ my ($class, $bug_id) = @_;
- my $bug;
- if (blessed $bug_id) {
- # We got a bug object passed in, use it
- $bug = $bug_id;
- $bug->check_is_visible;
- }
- else {
- # We got a bug id passed in, check it and get the bug object
- $bug = Bugzilla::Bug->check({ id => $bug_id });
- }
+ my $bug;
+ if (blessed $bug_id) {
+
+ # We got a bug object passed in, use it
+ $bug = $bug_id;
+ $bug->check_is_visible;
+ }
+ else {
+ # We got a bug id passed in, check it and get the bug object
+ $bug = Bugzilla::Bug->check({id => $bug_id});
+ }
- return $bug->id;
+ return $bug->id;
}
sub _check_value {
- my ($class, $uri) = @_;
-
- my $value = $uri->as_string;
-
- if (!$value) {
- ThrowCodeError('param_required',
- { function => 'add_see_also', param => '$value' });
- }
-
- # We assume that the URL is an HTTP URL if there is no (something)://
- # in front.
- if (!$uri->scheme) {
- # This works better than setting $uri->scheme('http'), because
- # that creates URLs like "http:domain.com" and doesn't properly
- # differentiate the path from the domain.
- $uri = new URI("http://$value");
- }
- elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') {
- ThrowUserError('bug_url_invalid', { url => $value, reason => 'http' });
- }
-
- # This stops the following edge cases from being accepted:
- # * show_bug.cgi?id=1
- # * /show_bug.cgi?id=1
- # * http:///show_bug.cgi?id=1
- if (!$uri->authority or $uri->path !~ m{/}) {
- ThrowUserError('bug_url_invalid',
- { url => $value, reason => 'path_only' });
- }
-
- if (length($uri->path) > MAX_BUG_URL_LENGTH) {
- ThrowUserError('bug_url_too_long', { url => $uri->path });
- }
-
- return $uri;
+ my ($class, $uri) = @_;
+
+ my $value = $uri->as_string;
+
+ if (!$value) {
+ ThrowCodeError('param_required',
+ {function => 'add_see_also', param => '$value'});
+ }
+
+ # We assume that the URL is an HTTP URL if there is no (something)://
+ # in front.
+ if (!$uri->scheme) {
+
+ # This works better than setting $uri->scheme('http'), because
+ # that creates URLs like "http:domain.com" and doesn't properly
+ # differentiate the path from the domain.
+ $uri = new URI("http://$value");
+ }
+ elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') {
+ ThrowUserError('bug_url_invalid', {url => $value, reason => 'http'});
+ }
+
+ # This stops the following edge cases from being accepted:
+ # * show_bug.cgi?id=1
+ # * /show_bug.cgi?id=1
+ # * http:///show_bug.cgi?id=1
+ if (!$uri->authority or $uri->path !~ m{/}) {
+ ThrowUserError('bug_url_invalid', {url => $value, reason => 'path_only'});
+ }
+
+ if (length($uri->path) > MAX_BUG_URL_LENGTH) {
+ ThrowUserError('bug_url_too_long', {url => $uri->path});
+ }
+
+ return $uri;
}
1;
diff --git a/Bugzilla/BugUrl/Bugzilla.pm b/Bugzilla/BugUrl/Bugzilla.pm
index 402ff1509..9d036100f 100644
--- a/Bugzilla/BugUrl/Bugzilla.pm
+++ b/Bugzilla/BugUrl/Bugzilla.pm
@@ -21,37 +21,39 @@ use Bugzilla::Util;
###############################
sub should_handle {
- my ($class, $uri) = @_;
- return ($uri->path =~ /show_bug\.cgi$/) ? 1 : 0;
+ my ($class, $uri) = @_;
+ return ($uri->path =~ /show_bug\.cgi$/) ? 1 : 0;
}
sub _check_value {
- my ($class, $uri) = @_;
-
- $uri = $class->SUPER::_check_value($uri);
-
- my $bug_id = $uri->query_param('id');
- # We don't currently allow aliases, because we can't check to see
- # if somebody's putting both an alias link and a numeric ID link.
- # When we start validating the URL by accessing the other Bugzilla,
- # we can allow aliases.
- detaint_natural($bug_id);
- if (!$bug_id) {
- my $value = $uri->as_string;
- ThrowUserError('bug_url_invalid', { url => $value, reason => 'id' });
- }
-
- # Make sure that "id" is the only query parameter.
- $uri->query("id=$bug_id");
- # And remove any # part if there is one.
- $uri->fragment(undef);
-
- return $uri;
+ my ($class, $uri) = @_;
+
+ $uri = $class->SUPER::_check_value($uri);
+
+ my $bug_id = $uri->query_param('id');
+
+ # We don't currently allow aliases, because we can't check to see
+ # if somebody's putting both an alias link and a numeric ID link.
+ # When we start validating the URL by accessing the other Bugzilla,
+ # we can allow aliases.
+ detaint_natural($bug_id);
+ if (!$bug_id) {
+ my $value = $uri->as_string;
+ ThrowUserError('bug_url_invalid', {url => $value, reason => 'id'});
+ }
+
+ # Make sure that "id" is the only query parameter.
+ $uri->query("id=$bug_id");
+
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
}
sub target_bug_id {
- my ($self) = @_;
- return new URI($self->name)->query_param('id');
+ my ($self) = @_;
+ return new URI($self->name)->query_param('id');
}
1;
diff --git a/Bugzilla/BugUrl/Bugzilla/Local.pm b/Bugzilla/BugUrl/Bugzilla/Local.pm
index 7b9cb6a4f..3fe0fcb5d 100644
--- a/Bugzilla/BugUrl/Bugzilla/Local.pm
+++ b/Bugzilla/BugUrl/Bugzilla/Local.pm
@@ -20,77 +20,75 @@ use Bugzilla::Util;
#### Initialization ####
###############################
-use constant VALIDATOR_DEPENDENCIES => {
- value => ['bug_id'],
-};
+use constant VALIDATOR_DEPENDENCIES => {value => ['bug_id'],};
###############################
#### Methods ####
###############################
sub ref_bug_url {
- my $self = shift;
-
- if (!exists $self->{ref_bug_url}) {
- my $ref_bug_id = new URI($self->name)->query_param('id');
- my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
- my $ref_value = $self->local_uri($self->bug_id);
- $self->{ref_bug_url} =
- new Bugzilla::BugUrl::Bugzilla::Local({ bug_id => $ref_bug->id,
- value => $ref_value });
- }
- return $self->{ref_bug_url};
+ my $self = shift;
+
+ if (!exists $self->{ref_bug_url}) {
+ my $ref_bug_id = new URI($self->name)->query_param('id');
+ my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
+ my $ref_value = $self->local_uri($self->bug_id);
+ $self->{ref_bug_url} = new Bugzilla::BugUrl::Bugzilla::Local(
+ {bug_id => $ref_bug->id, value => $ref_value});
+ }
+ return $self->{ref_bug_url};
}
sub should_handle {
- my ($class, $uri) = @_;
-
- # Check if it is either a bug id number or an alias.
- return 1 if $uri->as_string =~ m/^\w+$/;
-
- # Check if it is a local Bugzilla uri and call
- # Bugzilla::BugUrl::Bugzilla to check if it's a valid Bugzilla
- # see also url.
- my $canonical_local = URI->new($class->local_uri)->canonical;
- if ($canonical_local->authority eq $uri->canonical->authority
- and $canonical_local->path eq $uri->canonical->path)
- {
- return $class->SUPER::should_handle($uri);
- }
-
- return 0;
+ my ($class, $uri) = @_;
+
+ # Check if it is either a bug id number or an alias.
+ return 1 if $uri->as_string =~ m/^\w+$/;
+
+ # Check if it is a local Bugzilla uri and call
+ # Bugzilla::BugUrl::Bugzilla to check if it's a valid Bugzilla
+ # see also url.
+ my $canonical_local = URI->new($class->local_uri)->canonical;
+ if ( $canonical_local->authority eq $uri->canonical->authority
+ and $canonical_local->path eq $uri->canonical->path)
+ {
+ return $class->SUPER::should_handle($uri);
+ }
+
+ return 0;
}
sub _check_value {
- my ($class, $uri, undef, $params) = @_;
-
- # At this point we are going to treat any word as a
- # bug id/alias to the local Bugzilla.
- my $value = $uri->as_string;
- if ($value =~ m/^\w+$/) {
- $uri = new URI($class->local_uri($value));
- } else {
- # It's not a word, then we have to check
- # if it's a valid Bugzilla url.
- $uri = $class->SUPER::_check_value($uri);
- }
-
- my $ref_bug_id = $uri->query_param('id');
- my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
- my $self_bug_id = $params->{bug_id};
- $params->{ref_bug} = $ref_bug;
-
- if ($ref_bug->id == $self_bug_id) {
- ThrowUserError('see_also_self_reference');
- }
-
- return $uri;
+ my ($class, $uri, undef, $params) = @_;
+
+ # At this point we are going to treat any word as a
+ # bug id/alias to the local Bugzilla.
+ my $value = $uri->as_string;
+ if ($value =~ m/^\w+$/) {
+ $uri = new URI($class->local_uri($value));
+ }
+ else {
+ # It's not a word, then we have to check
+ # if it's a valid Bugzilla url.
+ $uri = $class->SUPER::_check_value($uri);
+ }
+
+ my $ref_bug_id = $uri->query_param('id');
+ my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
+ my $self_bug_id = $params->{bug_id};
+ $params->{ref_bug} = $ref_bug;
+
+ if ($ref_bug->id == $self_bug_id) {
+ ThrowUserError('see_also_self_reference');
+ }
+
+ return $uri;
}
sub local_uri {
- my ($self, $bug_id) = @_;
- $bug_id ||= '';
- return correct_urlbase() . "show_bug.cgi?id=$bug_id";
+ my ($self, $bug_id) = @_;
+ $bug_id ||= '';
+ return correct_urlbase() . "show_bug.cgi?id=$bug_id";
}
1;
diff --git a/Bugzilla/BugUrl/Debian.pm b/Bugzilla/BugUrl/Debian.pm
index b726b0b5a..c4e4f459b 100644
--- a/Bugzilla/BugUrl/Debian.pm
+++ b/Bugzilla/BugUrl/Debian.pm
@@ -18,31 +18,35 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
-
- # Debian BTS URLs can look like various things:
- # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234
- # http://bugs.debian.org/1234
- # https://debbugs.gnu.org/cgi/bugreport.cgi?bug=123
- # https://debbugs.gnu.org/123
- return ((lc($uri->authority) eq 'bugs.debian.org'
- or lc($uri->authority) eq 'debbugs.gnu.org')
- and (($uri->path =~ /bugreport\.cgi$/
- and $uri->query_param('bug') =~ m|^\d+$|)
- or $uri->path =~ m|^/\d+$|)) ? 1 : 0;
+ my ($class, $uri) = @_;
+
+ # Debian BTS URLs can look like various things:
+ # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234
+ # http://bugs.debian.org/1234
+ # https://debbugs.gnu.org/cgi/bugreport.cgi?bug=123
+ # https://debbugs.gnu.org/123
+ return (
+ (
+ lc($uri->authority) eq 'bugs.debian.org'
+ or lc($uri->authority) eq 'debbugs.gnu.org'
+ )
+ and
+ (($uri->path =~ /bugreport\.cgi$/ and $uri->query_param('bug') =~ m|^\d+$|)
+ or $uri->path =~ m|^/\d+$|)
+ ) ? 1 : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # This is the shortest standard URL form for Debian BTS URLs,
- # and so we reduce all URLs to this.
- $uri->path =~ m|^/(\d+)$| || $uri->query_param('bug') =~ m|^(\d+)$|;
- $uri = new URI('https://' . $uri->authority . '/' . $1);
+ # This is the shortest standard URL form for Debian BTS URLs,
+ # and so we reduce all URLs to this.
+ $uri->path =~ m|^/(\d+)$| || $uri->query_param('bug') =~ m|^(\d+)$|;
+ $uri = new URI('https://' . $uri->authority . '/' . $1);
- return $uri;
+ return $uri;
}
1;
diff --git a/Bugzilla/BugUrl/Flyspray.pm b/Bugzilla/BugUrl/Flyspray.pm
index 86583fc4b..0edf55141 100644
--- a/Bugzilla/BugUrl/Flyspray.pm
+++ b/Bugzilla/BugUrl/Flyspray.pm
@@ -14,23 +14,23 @@ use base qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
+ my ($class, $uri) = @_;
- # Flyspray URLs look like the following:
- # https://bugs.flyspray.org/task/1237
- # https://bugs.archlinux.org/task/44825
- return ($uri->path_query =~ m|/task/\d+$|) ? 1 : 0;
+ # Flyspray URLs look like the following:
+ # https://bugs.flyspray.org/task/1237
+ # https://bugs.archlinux.org/task/44825
+ return ($uri->path_query =~ m|/task/\d+$|) ? 1 : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # Remove any # part if there is one.
- $uri->fragment(undef);
+ # Remove any # part if there is one.
+ $uri->fragment(undef);
- return $uri;
+ return $uri;
}
1;
diff --git a/Bugzilla/BugUrl/GitHub.pm b/Bugzilla/BugUrl/GitHub.pm
index f14f1d6b0..583837a60 100644
--- a/Bugzilla/BugUrl/GitHub.pm
+++ b/Bugzilla/BugUrl/GitHub.pm
@@ -18,25 +18,25 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
-
- # GitHub issue URLs have only one form:
- # https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/issues/111
- # GitHub pull request URLs have only one form:
- # https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/pull/111
- return (lc($uri->authority) eq 'github.com'
- and $uri->path =~ m!^/[^/]+/[^/]+/(?:issues|pull)/\d+$!) ? 1 : 0;
+ my ($class, $uri) = @_;
+
+# GitHub issue URLs have only one form:
+# https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/issues/111
+# GitHub pull request URLs have only one form:
+# https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/pull/111
+ return (lc($uri->authority) eq 'github.com'
+ and $uri->path =~ m!^/[^/]+/[^/]+/(?:issues|pull)/\d+$!) ? 1 : 0;
}
sub _check_value {
- my ($class, $uri) = @_;
+ my ($class, $uri) = @_;
- $uri = $class->SUPER::_check_value($uri);
+ $uri = $class->SUPER::_check_value($uri);
- # GitHub HTTP URLs redirect to HTTPS, so just use the HTTPS scheme.
- $uri->scheme('https');
+ # GitHub HTTP URLs redirect to HTTPS, so just use the HTTPS scheme.
+ $uri->scheme('https');
- return $uri;
+ return $uri;
}
1;
diff --git a/Bugzilla/BugUrl/Google.pm b/Bugzilla/BugUrl/Google.pm
index 71a9c46fb..106425302 100644
--- a/Bugzilla/BugUrl/Google.pm
+++ b/Bugzilla/BugUrl/Google.pm
@@ -18,27 +18,27 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
+ my ($class, $uri) = @_;
- # Google Code URLs only have one form:
- # http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234
- return (lc($uri->authority) eq 'code.google.com'
- and $uri->path =~ m|^/p/[^/]+/issues/detail$|
- and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0;
+ # Google Code URLs only have one form:
+ # http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234
+ return (lc($uri->authority) eq 'code.google.com'
+ and $uri->path =~ m|^/p/[^/]+/issues/detail$|
+ and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0;
}
sub _check_value {
- my ($class, $uri) = @_;
-
- $uri = $class->SUPER::_check_value($uri);
+ my ($class, $uri) = @_;
- # While Google Code URLs can be either HTTP or HTTPS,
- # always go with the HTTP scheme, as that's the default.
- if ($uri->scheme eq 'https') {
- $uri->scheme('http');
- }
+ $uri = $class->SUPER::_check_value($uri);
- return $uri;
+ # While Google Code URLs can be either HTTP or HTTPS,
+ # always go with the HTTP scheme, as that's the default.
+ if ($uri->scheme eq 'https') {
+ $uri->scheme('http');
+ }
+
+ return $uri;
}
1;
diff --git a/Bugzilla/BugUrl/JIRA.pm b/Bugzilla/BugUrl/JIRA.pm
index e9d2a2d2a..b42b1decc 100644
--- a/Bugzilla/BugUrl/JIRA.pm
+++ b/Bugzilla/BugUrl/JIRA.pm
@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
+ my ($class, $uri) = @_;
- # JIRA URLs have only one basic form (but the jira is optional):
- # https://issues.apache.org/jira/browse/KEY-1234
- # http://issues.example.com/browse/KEY-1234
- return ($uri->path =~ m|/browse/[A-Z][A-Z]+-\d+$|) ? 1 : 0;
+ # JIRA URLs have only one basic form (but the jira is optional):
+ # https://issues.apache.org/jira/browse/KEY-1234
+ # http://issues.example.com/browse/KEY-1234
+ return ($uri->path =~ m|/browse/[A-Z][A-Z]+-\d+$|) ? 1 : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # Make sure there are no query parameters.
- $uri->query(undef);
- # And remove any # part if there is one.
- $uri->fragment(undef);
+ # Make sure there are no query parameters.
+ $uri->query(undef);
- return $uri;
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
}
1;
diff --git a/Bugzilla/BugUrl/Launchpad.pm b/Bugzilla/BugUrl/Launchpad.pm
index 0362747a2..5be8088d1 100644
--- a/Bugzilla/BugUrl/Launchpad.pm
+++ b/Bugzilla/BugUrl/Launchpad.pm
@@ -18,27 +18,28 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
-
- # Launchpad bug URLs can look like various things:
- # https://bugs.launchpad.net/ubuntu/+bug/1234
- # https://launchpad.net/bugs/1234
- # All variations end with either "/bugs/1234" or "/+bug/1234"
- return ($uri->authority =~ /launchpad\.net$/
- and $uri->path =~ m|bugs?/\d+$|) ? 1 : 0;
+ my ($class, $uri) = @_;
+
+ # Launchpad bug URLs can look like various things:
+ # https://bugs.launchpad.net/ubuntu/+bug/1234
+ # https://launchpad.net/bugs/1234
+ # All variations end with either "/bugs/1234" or "/+bug/1234"
+ return ($uri->authority =~ /launchpad\.net$/ and $uri->path =~ m|bugs?/\d+$|)
+ ? 1
+ : 0;
}
sub _check_value {
- my ($class, $uri) = @_;
+ my ($class, $uri) = @_;
- $uri = $class->SUPER::_check_value($uri);
+ $uri = $class->SUPER::_check_value($uri);
- # This is the shortest standard URL form for Launchpad bugs,
- # and so we reduce all URLs to this.
- $uri->path =~ m|bugs?/(\d+)$|;
- $uri = new URI("https://launchpad.net/bugs/$1");
+ # This is the shortest standard URL form for Launchpad bugs,
+ # and so we reduce all URLs to this.
+ $uri->path =~ m|bugs?/(\d+)$|;
+ $uri = new URI("https://launchpad.net/bugs/$1");
- return $uri;
+ return $uri;
}
1;
diff --git a/Bugzilla/BugUrl/MantisBT.pm b/Bugzilla/BugUrl/MantisBT.pm
index 60d3b578e..742ae1a47 100644
--- a/Bugzilla/BugUrl/MantisBT.pm
+++ b/Bugzilla/BugUrl/MantisBT.pm
@@ -18,22 +18,22 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
+ my ($class, $uri) = @_;
- # MantisBT URLs look like the following ('bugs' directory is optional):
- # http://www.mantisbt.org/bugs/view.php?id=1234
- return ($uri->path_query =~ m|view\.php\?id=\d+$|) ? 1 : 0;
+ # MantisBT URLs look like the following ('bugs' directory is optional):
+ # http://www.mantisbt.org/bugs/view.php?id=1234
+ return ($uri->path_query =~ m|view\.php\?id=\d+$|) ? 1 : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # Remove any # part if there is one.
- $uri->fragment(undef);
+ # Remove any # part if there is one.
+ $uri->fragment(undef);
- return $uri;
+ return $uri;
}
1;
diff --git a/Bugzilla/BugUrl/SourceForge.pm b/Bugzilla/BugUrl/SourceForge.pm
index acba0df28..ffdde42f4 100644
--- a/Bugzilla/BugUrl/SourceForge.pm
+++ b/Bugzilla/BugUrl/SourceForge.pm
@@ -18,27 +18,27 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
-
- # SourceForge tracker URLs have only one form:
- # http://sourceforge.net/tracker/?func=detail&aid=111&group_id=111&atid=111
- return (lc($uri->authority) eq 'sourceforge.net'
- and $uri->path =~ m|/tracker/|
- and $uri->query_param('func') eq 'detail'
- and $uri->query_param('aid')
- and $uri->query_param('group_id')
- and $uri->query_param('atid')) ? 1 : 0;
+ my ($class, $uri) = @_;
+
+ # SourceForge tracker URLs have only one form:
+ # http://sourceforge.net/tracker/?func=detail&aid=111&group_id=111&atid=111
+ return (lc($uri->authority) eq 'sourceforge.net'
+ and $uri->path =~ m|/tracker/|
+ and $uri->query_param('func') eq 'detail'
+ and $uri->query_param('aid')
+ and $uri->query_param('group_id')
+ and $uri->query_param('atid')) ? 1 : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # Remove any # part if there is one.
- $uri->fragment(undef);
+ # Remove any # part if there is one.
+ $uri->fragment(undef);
- return $uri;
+ return $uri;
}
1;
diff --git a/Bugzilla/BugUrl/Trac.pm b/Bugzilla/BugUrl/Trac.pm
index fe74abf33..22418a1df 100644
--- a/Bugzilla/BugUrl/Trac.pm
+++ b/Bugzilla/BugUrl/Trac.pm
@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
+ my ($class, $uri) = @_;
- # Trac URLs can look like various things:
- # http://dev.mutt.org/trac/ticket/1234
- # http://trac.roundcube.net/ticket/1484130
- return ($uri->path =~ m|/ticket/\d+$|) ? 1 : 0;
+ # Trac URLs can look like various things:
+ # http://dev.mutt.org/trac/ticket/1234
+ # http://trac.roundcube.net/ticket/1484130
+ return ($uri->path =~ m|/ticket/\d+$|) ? 1 : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # Make sure there are no query parameters.
- $uri->query(undef);
- # And remove any # part if there is one.
- $uri->fragment(undef);
+ # Make sure there are no query parameters.
+ $uri->query(undef);
- return $uri;
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
}
1;
diff --git a/Bugzilla/BugUserLastVisit.pm b/Bugzilla/BugUserLastVisit.pm
index d043b121a..d1c351959 100644
--- a/Bugzilla/BugUserLastVisit.pm
+++ b/Bugzilla/BugUserLastVisit.pm
@@ -25,25 +25,27 @@ use constant LIST_ORDER => 'id';
use constant NAME_FIELD => 'id';
# turn off auditing and exclude these objects from memcached
-use constant { AUDIT_CREATES => 0,
- AUDIT_UPDATES => 0,
- AUDIT_REMOVES => 0,
- USE_MEMCACHED => 0 };
+use constant {
+ AUDIT_CREATES => 0,
+ AUDIT_UPDATES => 0,
+ AUDIT_REMOVES => 0,
+ USE_MEMCACHED => 0
+};
#####################################################################
# Provide accessors for our columns
#####################################################################
-sub id { return $_[0]->{id} }
-sub bug_id { return $_[0]->{bug_id} }
-sub user_id { return $_[0]->{user_id} }
+sub id { return $_[0]->{id} }
+sub bug_id { return $_[0]->{bug_id} }
+sub user_id { return $_[0]->{user_id} }
sub last_visit_ts { return $_[0]->{last_visit_ts} }
sub user {
- my $self = shift;
+ my $self = shift;
- $self->{user} //= Bugzilla::User->new({ id => $self->user_id, cache => 1 });
- return $self->{user};
+ $self->{user} //= Bugzilla::User->new({id => $self->user_id, cache => 1});
+ return $self->{user};
}
1;
diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm
index 30c530b8c..8558a384a 100644
--- a/Bugzilla/CGI.pm
+++ b/Bugzilla/CGI.pm
@@ -22,280 +22,299 @@ use Bugzilla::Search::Recent;
use File::Basename;
sub _init_bz_cgi_globals {
- my $invocant = shift;
- # 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';
-
- # We don't precompile any functions here, that's done specially in
- # mod_perl code.
- $invocant->_setup_symbols(qw(:no_xhtml :oldstyle_urls :private_tempfiles
- :unique_headers));
-}
+ my $invocant = shift;
-BEGIN { __PACKAGE__->_init_bz_cgi_globals() if i_am_cgi(); }
+ # We need to disable output buffering - see bug 179174
+ $| = 1;
-sub new {
- my ($invocant, @args) = @_;
- my $class = ref($invocant) || $invocant;
-
- # Under mod_perl, CGI's global variables get reset on each request,
- # so we need to set them up again every time.
- $class->_init_bz_cgi_globals() if $ENV{MOD_PERL};
-
- my $self = $class->SUPER::new(@args);
-
- # Make sure our outgoing cookie list is empty on each invocation
- $self->{Bugzilla_cookie_list} = [];
-
- # Path-Info is of no use for Bugzilla and interacts badly with IIS.
- # Moreover, it causes unexpected behaviors, such as totally breaking
- # the rendering of pages.
- my $script = basename($0);
- if (my $path_info = $self->path_info) {
- my @whitelist = ("rest.cgi");
- Bugzilla::Hook::process('path_info_whitelist', { whitelist => \@whitelist });
- if (!grep($_ eq $script, @whitelist)) {
- # IIS includes the full path to the script in PATH_INFO,
- # so we have to extract the real PATH_INFO from it,
- # else we will be redirected outside Bugzilla.
- my $script_name = $self->script_name;
- $path_info =~ s/^\Q$script_name\E//;
- if ($script_name && $path_info) {
- print $self->redirect($self->url(-path => 0, -query => 1));
- }
- }
- }
-
- # Send appropriate charset
- $self->charset(Bugzilla->params->{'utf8'} ? 'UTF-8' : '');
+ # 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';
- # Redirect to urlbase/sslbase if we are not viewing an attachment.
- if ($self->url_is_attachment_base and $script ne 'attachment.cgi') {
- $self->redirect_to_urlbase();
- }
+ # We don't precompile any functions here, that's done specially in
+ # mod_perl code.
+ $invocant->_setup_symbols(qw(:no_xhtml :oldstyle_urls :private_tempfiles
+ :unique_headers));
+}
- # 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";
- }
+BEGIN { __PACKAGE__->_init_bz_cgi_globals() if i_am_cgi(); }
- return $self;
+sub new {
+ my ($invocant, @args) = @_;
+ my $class = ref($invocant) || $invocant;
+
+ # Under mod_perl, CGI's global variables get reset on each request,
+ # so we need to set them up again every time.
+ $class->_init_bz_cgi_globals() if $ENV{MOD_PERL};
+
+ my $self = $class->SUPER::new(@args);
+
+ # Make sure our outgoing cookie list is empty on each invocation
+ $self->{Bugzilla_cookie_list} = [];
+
+ # Path-Info is of no use for Bugzilla and interacts badly with IIS.
+ # Moreover, it causes unexpected behaviors, such as totally breaking
+ # the rendering of pages.
+ my $script = basename($0);
+ if (my $path_info = $self->path_info) {
+ my @whitelist = ("rest.cgi");
+ Bugzilla::Hook::process('path_info_whitelist', {whitelist => \@whitelist});
+ if (!grep($_ eq $script, @whitelist)) {
+
+ # IIS includes the full path to the script in PATH_INFO,
+ # so we have to extract the real PATH_INFO from it,
+ # else we will be redirected outside Bugzilla.
+ my $script_name = $self->script_name;
+ $path_info =~ s/^\Q$script_name\E//;
+ if ($script_name && $path_info) {
+ print $self->redirect($self->url(-path => 0, -query => 1));
+ }
+ }
+ }
+
+ # Send appropriate charset
+ $self->charset(Bugzilla->params->{'utf8'} ? 'UTF-8' : '');
+
+ # Redirect to urlbase/sslbase if we are not viewing an attachment.
+ if ($self->url_is_attachment_base and $script ne 'attachment.cgi') {
+ $self->redirect_to_urlbase();
+ }
+
+ # 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) = @_;
+ 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 grep { $_ eq $key } @exclude;
+ # Reconstruct the URL by concatenating the sorted param=value pairs
+ my @parameters;
+ foreach my $key (sort($self->param())) {
- # Remove the Boolean Charts for standard query.cgi fields
- # They are listed in the query URL already
- next if $key =~ /^(field|type|value)(-\d+){3}$/;
+ # Leave this key out if it's in the exclude list
+ next if grep { $_ eq $key } @exclude;
- my $esc_key = url_quote($key);
+ # Remove the Boolean Charts for standard query.cgi fields
+ # They are listed in the query URL already
+ next if $key =~ /^(field|type|value)(-\d+){3}$/;
- foreach my $value ($self->param($key)) {
- # Omit params with an empty value
- if (defined($value) && $value ne '') {
- my $esc_value = url_quote($value);
+ my $esc_key = url_quote($key);
- push(@parameters, "$esc_key=$esc_value");
- }
- }
- }
+ foreach my $value ($self->param($key)) {
- return join("&", @parameters);
-}
+ # Omit params with an empty value
+ if (defined($value) && $value ne '') {
+ my $esc_value = url_quote($value);
-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");
- }
-
- # Custom Search stuff is empty if it's "noop". We also keep around
- # the old Boolean Chart syntax for backwards-compatibility.
- if (($param =~ /\d-\d-\d/ || $param =~ /^[[:alpha:]]\d+$/)
- && defined $self->param($param) && $self->param($param) eq 'noop')
- {
- $self->delete($param);
- }
-
- # Any "join" for custom search that's an AND can be removed, because
- # that's the default.
- if (($param =~ /^j\d+$/ || $param eq 'j_top')
- && $self->param($param) eq 'AND')
- {
- $self->delete($param);
- }
+ push(@parameters, "$esc_key=$esc_value");
+ }
}
+ }
- # Delete leftovers from the login form
- $self->delete('Bugzilla_remember', 'GoAheadAndLogIn');
+ return join("&", @parameters);
+}
- # Delete the token if we're not performing an action which needs it
- unless ((defined $self->param('remtype')
- && ($self->param('remtype') eq 'asdefault'
- || $self->param('remtype') eq 'asnamed'))
- || (defined $self->param('remaction')
- && $self->param('remaction') eq 'forget'))
- {
- $self->delete("token");
- }
+sub clean_search_url {
+ my $self = shift;
- foreach my $num (1,2,3) {
- # If there's no value in the email field, delete the related fields.
- if (!$self->param("email$num")) {
- foreach my $field (qw(type assigned_to reporter qa_contact cc longdesc)) {
- $self->delete("email$field$num");
- }
- }
- }
+ # Delete any empty URL parameter.
+ my @cgi_params = $self->param;
- # chfieldto is set to "Now" by default in query.cgi. But if none
- # of the other chfield parameters are set, it's meaningless.
- if (!defined $self->param('chfieldfrom') && !$self->param('chfield')
- && !defined $self->param('chfieldvalue') && $self->param('chfieldto')
- && lc($self->param('chfieldto')) eq 'now')
- {
- $self->delete('chfieldto');
+ foreach my $param (@cgi_params) {
+ if (defined $self->param($param) && $self->param($param) eq '') {
+ $self->delete($param);
+ $self->delete("${param}_type");
}
- # cmdtype "doit" is the default from query.cgi, but it's only meaningful
- # if there's a remtype parameter.
- if (defined $self->param('cmdtype') && $self->param('cmdtype') eq 'doit'
- && !defined $self->param('remtype'))
+ # Custom Search stuff is empty if it's "noop". We also keep around
+ # the old Boolean Chart syntax for backwards-compatibility.
+ if ( ($param =~ /\d-\d-\d/ || $param =~ /^[[:alpha:]]\d+$/)
+ && defined $self->param($param)
+ && $self->param($param) eq 'noop')
{
- $self->delete('cmdtype');
+ $self->delete($param);
}
- # "Reuse same sort as last time" is actually the default, so we don't
- # need it in the URL.
- if ($self->param('order')
- && $self->param('order') eq 'Reuse same sort as last time')
+ # Any "join" for custom search that's an AND can be removed, because
+ # that's the default.
+ if (($param =~ /^j\d+$/ || $param eq 'j_top') && $self->param($param) eq 'AND')
{
- $self->delete('order');
- }
+ $self->delete($param);
+ }
+ }
+
+ # Delete leftovers from the login form
+ $self->delete('Bugzilla_remember', 'GoAheadAndLogIn');
+
+ # Delete the token if we're not performing an action which needs it
+ unless (
+ (
+ defined $self->param('remtype')
+ && ( $self->param('remtype') eq 'asdefault'
+ || $self->param('remtype') eq 'asnamed')
+ )
+ || (defined $self->param('remaction') && $self->param('remaction') eq 'forget')
+ )
+ {
+ $self->delete("token");
+ }
+
+ foreach my $num (1, 2, 3) {
+
+ # If there's no value in the email field, delete the related fields.
+ if (!$self->param("email$num")) {
+ foreach my $field (qw(type assigned_to reporter qa_contact cc longdesc)) {
+ $self->delete("email$field$num");
+ }
+ }
+ }
+
+ # chfieldto is set to "Now" by default in query.cgi. But if none
+ # of the other chfield parameters are set, it's meaningless.
+ if ( !defined $self->param('chfieldfrom')
+ && !$self->param('chfield')
+ && !defined $self->param('chfieldvalue')
+ && $self->param('chfieldto')
+ && lc($self->param('chfieldto')) eq 'now')
+ {
+ $self->delete('chfieldto');
+ }
+
+ # cmdtype "doit" is the default from query.cgi, but it's only meaningful
+ # if there's a remtype parameter.
+ if ( defined $self->param('cmdtype')
+ && $self->param('cmdtype') eq 'doit'
+ && !defined $self->param('remtype'))
+ {
+ $self->delete('cmdtype');
+ }
+
+ # "Reuse same sort as last time" is actually the default, so we don't
+ # need it in the URL.
+ if ( $self->param('order')
+ && $self->param('order') eq 'Reuse same sort as last time')
+ {
+ $self->delete('order');
+ }
+
+ # list_id is added in buglist.cgi after calling clean_search_url,
+ # and doesn't need to be saved in saved searches.
+ $self->delete('list_id');
+
+ # no_redirect is used internally by redirect_search_url().
+ $self->delete('no_redirect');
+
+ # And now finally, if query_format is our only parameter, that
+ # really means we have no parameters, so we should delete query_format.
+ if ($self->param('query_format') && scalar($self->param()) == 1) {
+ $self->delete('query_format');
+ }
+}
- # list_id is added in buglist.cgi after calling clean_search_url,
- # and doesn't need to be saved in saved searches.
- $self->delete('list_id');
+sub check_etag {
+ my ($self, $valid_etag) = @_;
- # no_redirect is used internally by redirect_search_url().
- $self->delete('no_redirect');
+ # ETag support.
+ my $if_none_match = $self->http('If-None-Match');
+ return if !$if_none_match;
- # And now finally, if query_format is our only parameter, that
- # really means we have no parameters, so we should delete query_format.
- if ($self->param('query_format') && scalar($self->param()) == 1) {
- $self->delete('query_format');
- }
-}
+ my @if_none = split(/[\s,]+/, $if_none_match);
+ foreach my $possible_etag (@if_none) {
-sub check_etag {
- my ($self, $valid_etag) = @_;
-
- # ETag support.
- my $if_none_match = $self->http('If-None-Match');
- return if !$if_none_match;
-
- my @if_none = split(/[\s,]+/, $if_none_match);
- foreach my $possible_etag (@if_none) {
- # remove quotes from begin and end of the string
- $possible_etag =~ s/^\"//g;
- $possible_etag =~ s/\"$//g;
- if ($possible_etag eq $valid_etag or $possible_etag eq '*') {
- return 1;
- }
+ # remove quotes from begin and end of the string
+ $possible_etag =~ s/^\"//g;
+ $possible_etag =~ s/\"$//g;
+ if ($possible_etag eq $valid_etag or $possible_etag eq '*') {
+ return 1;
}
+ }
- return 0;
+ return 0;
}
# 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;
- $self->{_multipart_in_progress} = 1;
- return $headers;
+ 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;
+ $self->{_multipart_in_progress} = 1;
+ return $headers;
}
sub close_standby_message {
- my ($self, $contenttype, $disp, $disp_prefix, $extension) = @_;
- $self->set_dated_content_disp($disp, $disp_prefix, $extension);
-
- if ($self->{_multipart_in_progress}) {
- print $self->multipart_end();
- print $self->multipart_start(-type => $contenttype);
- }
- elsif (!$self->{_header_done}) {
- print $self->header($contenttype);
- }
+ my ($self, $contenttype, $disp, $disp_prefix, $extension) = @_;
+ $self->set_dated_content_disp($disp, $disp_prefix, $extension);
+
+ if ($self->{_multipart_in_progress}) {
+ print $self->multipart_end();
+ print $self->multipart_start(-type => $contenttype);
+ }
+ elsif (!$self->{_header_done}) {
+ print $self->header($contenttype);
+ }
}
our $ALLOW_UNSAFE_RESPONSE = 0;
+
# responding to text/plain or text/html is safe
# responding to any request with a referer header is safe
# some things need to have unsafe responses (attachment.cgi)
# everything else should get a 403.
sub _prevent_unsafe_response {
- my ($self, $headers) = @_;
- my $safe_content_type_re = qr{
+ my ($self, $headers) = @_;
+ my $safe_content_type_re = qr{
^ (*COMMIT) # COMMIT makes the regex faster
# by preventing back-tracking. see also perldoc pelre.
# application/x-javascript, xml, atom+xml, rdf+xml, xml-dtd, and json
@@ -309,12 +328,13 @@ sub _prevent_unsafe_response {
# used for HTTP push responses
| multipart/x-mixed-replace)
}sx;
- my $safe_referer_re = do {
- # Note that urlbase must end with a /.
- # It almost certainly does, but let's be extra careful.
- my $urlbase = correct_urlbase();
- $urlbase =~ s{/$}{};
- qr{
+ my $safe_referer_re = do {
+
+ # Note that urlbase must end with a /.
+ # It almost certainly does, but let's be extra careful.
+ my $urlbase = correct_urlbase();
+ $urlbase =~ s{/$}{};
+ qr{
# Begins with literal urlbase
^ (*COMMIT)
\Q$urlbase\E
@@ -322,374 +342,387 @@ sub _prevent_unsafe_response {
(?: /
| $ )
}sx
- };
-
- return if $ALLOW_UNSAFE_RESPONSE;
-
- if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
- # Safe content types are ones that arn't images.
- # For now let's assume plain text and html are not valid images.
- my $content_type = $headers->{'-type'} // $headers->{'-content_type'} // 'text/html';
- my $is_safe_content_type = $content_type =~ $safe_content_type_re;
-
- # Safe referers are ones that begin with the urlbase.
- my $referer = $self->referer;
- my $is_safe_referer = $referer && $referer =~ $safe_referer_re;
-
- if (!$is_safe_referer && !$is_safe_content_type) {
- print $self->SUPER::header(-type => 'text/html', -status => '403 Forbidden');
- if ($content_type ne 'text/html') {
- print "Untrusted Referer Header\n";
- if ($ENV{MOD_PERL}) {
- my $r = $self->r;
- $r->rflush;
- $r->status(200);
- }
- }
- exit;
+ };
+
+ return if $ALLOW_UNSAFE_RESPONSE;
+
+ if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
+
+ # Safe content types are ones that arn't images.
+ # For now let's assume plain text and html are not valid images.
+ my $content_type = $headers->{'-type'} // $headers->{'-content_type'}
+ // 'text/html';
+ my $is_safe_content_type = $content_type =~ $safe_content_type_re;
+
+ # Safe referers are ones that begin with the urlbase.
+ my $referer = $self->referer;
+ my $is_safe_referer = $referer && $referer =~ $safe_referer_re;
+
+ if (!$is_safe_referer && !$is_safe_content_type) {
+ print $self->SUPER::header(-type => 'text/html', -status => '403 Forbidden');
+ if ($content_type ne 'text/html') {
+ print "Untrusted Referer Header\n";
+ if ($ENV{MOD_PERL}) {
+ my $r = $self->r;
+ $r->rflush;
+ $r->status(200);
}
+ }
+ exit;
}
+ }
}
# Override header so we can add the cookies in
sub header {
- my $self = shift;
-
- my %headers;
- my $user = Bugzilla->user;
-
- # If there's only one parameter, then it's a Content-Type.
- if (scalar(@_) == 1) {
- %headers = ('-type' => shift(@_));
- }
- else {
- %headers = @_;
- }
- $self->_prevent_unsafe_response(\%headers);
-
- if ($self->{'_content_disp'}) {
- $headers{'-content_disposition'} = $self->{'_content_disp'};
- }
-
- if (!$user->id && $user->authorizer->can_login
- && !$self->cookie('Bugzilla_login_request_cookie'))
- {
- my %args;
- $args{'-secure'} = 1 if Bugzilla->params->{ssl_redirect};
-
- $self->send_cookie(-name => 'Bugzilla_login_request_cookie',
- -value => generate_random_password(),
- -httponly => 1,
- %args);
- }
-
- # Add the cookies in if we have any
- if (scalar(@{$self->{Bugzilla_cookie_list}})) {
- $headers{'-cookie'} = $self->{Bugzilla_cookie_list};
- }
+ my $self = shift;
+
+ my %headers;
+ my $user = Bugzilla->user;
+
+ # If there's only one parameter, then it's a Content-Type.
+ if (scalar(@_) == 1) {
+ %headers = ('-type' => shift(@_));
+ }
+ else {
+ %headers = @_;
+ }
+ $self->_prevent_unsafe_response(\%headers);
+
+ if ($self->{'_content_disp'}) {
+ $headers{'-content_disposition'} = $self->{'_content_disp'};
+ }
+
+ if (!$user->id
+ && $user->authorizer->can_login
+ && !$self->cookie('Bugzilla_login_request_cookie'))
+ {
+ my %args;
+ $args{'-secure'} = 1 if Bugzilla->params->{ssl_redirect};
+
+ $self->send_cookie(
+ -name => 'Bugzilla_login_request_cookie',
+ -value => generate_random_password(),
+ -httponly => 1,
+ %args
+ );
+ }
+
+ # Add the cookies in if we have any
+ if (scalar(@{$self->{Bugzilla_cookie_list}})) {
+ $headers{'-cookie'} = $self->{Bugzilla_cookie_list};
+ }
+
+ # Add Strict-Transport-Security (STS) header if this response
+ # is over SSL and the strict_transport_security param is turned on.
+ if ( $self->https
+ && !$self->url_is_attachment_base
+ && Bugzilla->params->{'strict_transport_security'} ne 'off')
+ {
+ my $sts_opts = 'max-age=' . MAX_STS_AGE;
+ if (Bugzilla->params->{'strict_transport_security'} eq 'include_subdomains') {
+ $sts_opts .= '; includeSubDomains';
+ }
+
+ $headers{'-strict_transport_security'} = $sts_opts;
+ }
+
+ # Add X-Frame-Options & CSP headers to prevent framing and subsequent
+ # possible clickjacking problems.
+ unless ($self->url_is_attachment_base) {
+ $headers{'-x_frame_options'} = 'SAMEORIGIN';
+ $headers{'-content_security_policy'} = "frame-ancestors 'self'";
+ }
+
+ # Add X-XSS-Protection header to prevent simple XSS attacks
+ # and enforce the blocking (rather than the rewriting) mode.
+ $headers{'-x_xss_protection'} = '1; mode=block';
+
+ # Add X-Content-Type-Options header to prevent browsers sniffing
+ # the MIME type away from the declared Content-Type.
+ $headers{'-x_content_type_options'} = 'nosniff';
+
+ Bugzilla::Hook::process('cgi_headers', {cgi => $self, headers => \%headers});
+ $self->{_header_done} = 1;
+
+ return $self->SUPER::header(%headers) || "";
+}
- # Add Strict-Transport-Security (STS) header if this response
- # is over SSL and the strict_transport_security param is turned on.
- if ($self->https && !$self->url_is_attachment_base
- && Bugzilla->params->{'strict_transport_security'} ne 'off')
+sub param {
+ my $self = shift;
+ local $CGI::LIST_CONTEXT_WARN = 0;
+
+ # When we are just requesting the value of a parameter...
+ if (scalar(@_) == 1) {
+ my @result = $self->SUPER::param(@_);
+
+ # Also look at the URL parameters, after we look at the POST
+ # parameters. This is to allow things like login-form submissions
+ # with URL parameters in the form's "target" attribute.
+ if ( !scalar(@result)
+ && $self->request_method
+ && $self->request_method eq 'POST')
{
- my $sts_opts = 'max-age=' . MAX_STS_AGE;
- if (Bugzilla->params->{'strict_transport_security'}
- eq 'include_subdomains')
- {
- $sts_opts .= '; includeSubDomains';
- }
-
- $headers{'-strict_transport_security'} = $sts_opts;
+ @result = $self->url_param(@_);
}
- # Add X-Frame-Options & CSP headers to prevent framing and subsequent
- # possible clickjacking problems.
- unless ($self->url_is_attachment_base) {
- $headers{'-x_frame_options'} = 'SAMEORIGIN';
- $headers{'-content_security_policy'} = "frame-ancestors 'self'";
+ # Fix UTF-8-ness of input parameters.
+ if (Bugzilla->params->{'utf8'}) {
+ @result = map { _fix_utf8($_) } @result;
}
- # Add X-XSS-Protection header to prevent simple XSS attacks
- # and enforce the blocking (rather than the rewriting) mode.
- $headers{'-x_xss_protection'} = '1; mode=block';
-
- # Add X-Content-Type-Options header to prevent browsers sniffing
- # the MIME type away from the declared Content-Type.
- $headers{'-x_content_type_options'} = 'nosniff';
-
- Bugzilla::Hook::process('cgi_headers',
- { cgi => $self, headers => \%headers }
- );
- $self->{_header_done} = 1;
-
- return $self->SUPER::header(%headers) || "";
-}
+ return wantarray ? @result : $result[0];
+ }
-sub param {
- my $self = shift;
- local $CGI::LIST_CONTEXT_WARN = 0;
-
- # When we are just requesting the value of a parameter...
- if (scalar(@_) == 1) {
- my @result = $self->SUPER::param(@_);
-
- # Also look at the URL parameters, after we look at the POST
- # parameters. This is to allow things like login-form submissions
- # with URL parameters in the form's "target" attribute.
- if (!scalar(@result)
- && $self->request_method && $self->request_method eq 'POST')
- {
- @result = $self->url_param(@_);
- }
+ # And for various other functions in CGI.pm, we need to correctly
+ # return the URL parameters in addition to the POST parameters when
+ # asked for the list of parameters.
+ elsif (!scalar(@_) && $self->request_method && $self->request_method eq 'POST')
+ {
+ my @post_params = $self->SUPER::param;
+ my @url_params = $self->url_param;
+ my %params = map { $_ => 1 } (@post_params, @url_params);
+ return keys %params;
+ }
- # Fix UTF-8-ness of input parameters.
- if (Bugzilla->params->{'utf8'}) {
- @result = map { _fix_utf8($_) } @result;
- }
-
- return wantarray ? @result : $result[0];
- }
- # And for various other functions in CGI.pm, we need to correctly
- # return the URL parameters in addition to the POST parameters when
- # asked for the list of parameters.
- elsif (!scalar(@_) && $self->request_method
- && $self->request_method eq 'POST')
- {
- my @post_params = $self->SUPER::param;
- my @url_params = $self->url_param;
- my %params = map { $_ => 1 } (@post_params, @url_params);
- return keys %params;
- }
-
- return $self->SUPER::param(@_);
+ return $self->SUPER::param(@_);
}
sub url_param {
- my $self = shift;
- # Some servers fail to set the QUERY_STRING parameter, which
- # causes undef issues
- $ENV{'QUERY_STRING'} //= '';
- return $self->SUPER::url_param(@_);
+ my $self = shift;
+
+ # Some servers fail to set the QUERY_STRING parameter, which
+ # causes undef issues
+ $ENV{'QUERY_STRING'} //= '';
+ return $self->SUPER::url_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 && !ref $input && !utf8::is_utf8($input);
- return $input;
+ my $input = shift;
+
+ # The is_utf8 is here in case CGI gets smart about utf8 someday.
+ utf8::decode($input) if defined $input && !ref $input && !utf8::is_utf8($input);
+ return $input;
}
sub should_set {
- my ($self, $param) = @_;
- my $set = (defined $self->param($param)
- or defined $self->param("defined_$param"))
- ? 1 : 0;
- return $set;
+ my ($self, $param) = @_;
+ my $set
+ = (defined $self->param($param) or defined $self->param("defined_$param"))
+ ? 1
+ : 0;
+ return $set;
}
# 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));
+ 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');
+ 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'
+ );
}
# This helps implement Bugzilla::Search::Recent, and also shortens search
# URLs that get POSTed to buglist.cgi.
sub redirect_search_url {
- my $self = shift;
-
- # If there is no parameter, there is nothing to do.
- return unless $self->param;
-
- # If we're retreiving an old list, we never need to redirect or
- # do anything related to Bugzilla::Search::Recent.
- return if $self->param('regetlastlist');
-
- my $user = Bugzilla->user;
-
- if ($user->id) {
- # There are two conditions that could happen here--we could get a URL
- # with no list id, and we could get a URL with a list_id that isn't
- # ours.
- my $list_id = $self->param('list_id');
- if ($list_id) {
- # If we have a valid list_id, no need to redirect or clean.
- return if Bugzilla::Search::Recent->check_quietly(
- { id => $list_id });
- }
- }
- elsif ($self->request_method ne 'POST') {
- # Logged-out users who do a GET don't get a list_id, don't get
- # their URLs cleaned, and don't get redirected.
- return;
- }
+ my $self = shift;
- my $no_redirect = $self->param('no_redirect');
- $self->clean_search_url();
-
- # Make sure we still have params still after cleaning otherwise we
- # do not want to store a list_id for an empty search.
- if ($user->id && $self->param) {
- # Insert a placeholder Bugzilla::Search::Recent, so that we know what
- # the id of the resulting search will be. This is then pulled out
- # of the Referer header when viewing show_bug.cgi to know what
- # bug list we came from.
- my $recent_search = Bugzilla::Search::Recent->create_placeholder;
- $self->param('list_id', $recent_search->id);
- }
+ # If there is no parameter, there is nothing to do.
+ return unless $self->param;
+
+ # If we're retreiving an old list, we never need to redirect or
+ # do anything related to Bugzilla::Search::Recent.
+ return if $self->param('regetlastlist');
+
+ my $user = Bugzilla->user;
- # Browsers which support history.replaceState do not need to be
- # redirected. We can fix the URL on the fly.
- return if $no_redirect;
+ if ($user->id) {
- # GET requests that lacked a list_id are always redirected. POST requests
- # are only redirected if they're under the CGI_URI_LIMIT though.
- my $self_url = $self->self_url();
- if ($self->request_method() ne 'POST' or length($self_url) < CGI_URI_LIMIT) {
- print $self->redirect(-url => $self_url);
- exit;
+ # There are two conditions that could happen here--we could get a URL
+ # with no list id, and we could get a URL with a list_id that isn't
+ # ours.
+ my $list_id = $self->param('list_id');
+ if ($list_id) {
+
+ # If we have a valid list_id, no need to redirect or clean.
+ return if Bugzilla::Search::Recent->check_quietly({id => $list_id});
}
+ }
+ elsif ($self->request_method ne 'POST') {
+
+ # Logged-out users who do a GET don't get a list_id, don't get
+ # their URLs cleaned, and don't get redirected.
+ return;
+ }
+
+ my $no_redirect = $self->param('no_redirect');
+ $self->clean_search_url();
+
+ # Make sure we still have params still after cleaning otherwise we
+ # do not want to store a list_id for an empty search.
+ if ($user->id && $self->param) {
+
+ # Insert a placeholder Bugzilla::Search::Recent, so that we know what
+ # the id of the resulting search will be. This is then pulled out
+ # of the Referer header when viewing show_bug.cgi to know what
+ # bug list we came from.
+ my $recent_search = Bugzilla::Search::Recent->create_placeholder;
+ $self->param('list_id', $recent_search->id);
+ }
+
+ # Browsers which support history.replaceState do not need to be
+ # redirected. We can fix the URL on the fly.
+ return if $no_redirect;
+
+ # GET requests that lacked a list_id are always redirected. POST requests
+ # are only redirected if they're under the CGI_URI_LIMIT though.
+ my $self_url = $self->self_url();
+ if ($self->request_method() ne 'POST' or length($self_url) < CGI_URI_LIMIT) {
+ print $self->redirect(-url => $self_url);
+ exit;
+ }
}
sub redirect_to_https {
- my $self = shift;
- my $sslbase = Bugzilla->params->{'sslbase'};
- # If this is a POST, we don't want ?POSTDATA in the query string.
- # We expect the client to re-POST, which may be a violation of
- # the HTTP spec, but the only time we're expecting it often is
- # in the WebService, and WebService clients usually handle this
- # correctly.
- $self->delete('POSTDATA');
- my $url = $sslbase . $self->url('-path_info' => 1, '-query' => 1,
- '-relative' => 1);
-
- # XML-RPC clients (SOAP::Lite at least) require a 301 to redirect properly
- # and do not work with 302. Our redirect really is permanent anyhow, so
- # it doesn't hurt to make it a 301.
- print $self->redirect(-location => $url, -status => 301);
-
- # When using XML-RPC with mod_perl, we need the headers sent immediately.
- $self->r->rflush if $ENV{MOD_PERL};
- exit;
+ my $self = shift;
+ my $sslbase = Bugzilla->params->{'sslbase'};
+
+ # If this is a POST, we don't want ?POSTDATA in the query string.
+ # We expect the client to re-POST, which may be a violation of
+ # the HTTP spec, but the only time we're expecting it often is
+ # in the WebService, and WebService clients usually handle this
+ # correctly.
+ $self->delete('POSTDATA');
+ my $url
+ = $sslbase . $self->url('-path_info' => 1, '-query' => 1, '-relative' => 1);
+
+ # XML-RPC clients (SOAP::Lite at least) require a 301 to redirect properly
+ # and do not work with 302. Our redirect really is permanent anyhow, so
+ # it doesn't hurt to make it a 301.
+ print $self->redirect(-location => $url, -status => 301);
+
+ # When using XML-RPC with mod_perl, we need the headers sent immediately.
+ $self->r->rflush if $ENV{MOD_PERL};
+ exit;
}
# Redirect to the urlbase version of the current URL.
sub redirect_to_urlbase {
- my $self = shift;
- my $path = $self->url('-path_info' => 1, '-query' => 1, '-relative' => 1);
- print $self->redirect('-location' => correct_urlbase() . $path);
- exit;
+ my $self = shift;
+ my $path = $self->url('-path_info' => 1, '-query' => 1, '-relative' => 1);
+ print $self->redirect('-location' => correct_urlbase() . $path);
+ exit;
}
sub url_is_attachment_base {
- my ($self, $id) = @_;
- return 0 if !use_attachbase() or !i_am_cgi();
- my $attach_base = Bugzilla->params->{'attachment_base'};
- # If we're passed an id, we only want one specific attachment base
- # for a particular bug. If we're not passed an ID, we just want to
- # know if our current URL matches the attachment_base *pattern*.
- my $regex;
- if ($id) {
- $attach_base =~ s/\%bugid\%/$id/;
- $regex = quotemeta($attach_base);
- }
- else {
- # In this circumstance we run quotemeta first because we need to
- # insert an active regex meta-character afterward.
- $regex = quotemeta($attach_base);
- $regex =~ s/\\\%bugid\\\%/\\d+/;
- }
- $regex = "^$regex";
- return ($self->url =~ $regex) ? 1 : 0;
+ my ($self, $id) = @_;
+ return 0 if !use_attachbase() or !i_am_cgi();
+ my $attach_base = Bugzilla->params->{'attachment_base'};
+
+ # If we're passed an id, we only want one specific attachment base
+ # for a particular bug. If we're not passed an ID, we just want to
+ # know if our current URL matches the attachment_base *pattern*.
+ my $regex;
+ if ($id) {
+ $attach_base =~ s/\%bugid\%/$id/;
+ $regex = quotemeta($attach_base);
+ }
+ else {
+ # In this circumstance we run quotemeta first because we need to
+ # insert an active regex meta-character afterward.
+ $regex = quotemeta($attach_base);
+ $regex =~ s/\\\%bugid\\\%/\\d+/;
+ }
+ $regex = "^$regex";
+ return ($self->url =~ $regex) ? 1 : 0;
}
sub set_dated_content_disp {
- my ($self, $type, $prefix, $ext) = @_;
+ my ($self, $type, $prefix, $ext) = @_;
- my @time = localtime(time());
- my $date = sprintf "%04d-%02d-%02d", 1900+$time[5], $time[4]+1, $time[3];
- my $filename = "$prefix-$date.$ext";
+ my @time = localtime(time());
+ my $date = sprintf "%04d-%02d-%02d", 1900 + $time[5], $time[4] + 1, $time[3];
+ my $filename = "$prefix-$date.$ext";
- $filename =~ s/\s/_/g; # Remove whitespace to avoid HTTP header tampering
- $filename =~ s/\\/_/g; # Remove backslashes as well
- $filename =~ s/"/\\"/g; # escape quotes
+ $filename =~ s/\s/_/g; # Remove whitespace to avoid HTTP header tampering
+ $filename =~ s/\\/_/g; # Remove backslashes as well
+ $filename =~ s/"/\\"/g; # escape quotes
- my $disposition = "$type; filename=\"$filename\"";
+ my $disposition = "$type; filename=\"$filename\"";
- $self->{'_content_disp'} = $disposition;
+ $self->{'_content_disp'} = $disposition;
}
##########################
# Vars TIEHASH Interface #
##########################
-# Fix the TIEHASH interface (scalar $cgi->Vars) to return and accept
+# Fix the TIEHASH interface (scalar $cgi->Vars) to return and accept
# arrayrefs.
sub STORE {
- my $self = shift;
- my ($param, $value) = @_;
- if (defined $value and ref $value eq 'ARRAY') {
- return $self->param(-name => $param, -value => $value);
- }
- return $self->SUPER::STORE(@_);
+ my $self = shift;
+ my ($param, $value) = @_;
+ if (defined $value and ref $value eq 'ARRAY') {
+ return $self->param(-name => $param, -value => $value);
+ }
+ return $self->SUPER::STORE(@_);
}
sub FETCH {
- my ($self, $param) = @_;
- return $self if $param eq 'CGI'; # CGI.pm did this, so we do too.
- my @result = $self->param($param);
- return undef if !scalar(@result);
- return $result[0] if scalar(@result) == 1;
- return \@result;
+ my ($self, $param) = @_;
+ return $self if $param eq 'CGI'; # CGI.pm did this, so we do too.
+ my @result = $self->param($param);
+ return undef if !scalar(@result);
+ return $result[0] if scalar(@result) == 1;
+ return \@result;
}
-# For the Vars TIEHASH interface: the normal CGI.pm DELETE doesn't return
+# For the Vars TIEHASH interface: the normal CGI.pm DELETE doesn't return
# the value deleted, but Perl's "delete" expects that value.
sub DELETE {
- my ($self, $param) = @_;
- my $value = $self->FETCH($param);
- $self->delete($param);
- return $value;
+ my ($self, $param) = @_;
+ my $value = $self->FETCH($param);
+ $self->delete($param);
+ return $value;
}
1;
diff --git a/Bugzilla/Chart.pm b/Bugzilla/Chart.pm
index 3c69006aa..1abf3dbe4 100644
--- a/Bugzilla/Chart.pm
+++ b/Bugzilla/Chart.pm
@@ -26,405 +26,424 @@ 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 \($#_\)($_)");
- }
+ 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) {
- return $self;
+ # 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+/+CONFIRMED&label1=...
- # &select0=1&select3=1...
- # &cumulate=1&datefrom=2002-02-03&dateto=2002-04-04&ctype=html...
- # &gt=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}});
- }
+ 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+/+CONFIRMED&label1=...
+ # &select0=1&select3=1...
+ # &cumulate=1&datefrom=2002-02-03&dateto=2002-04-04&ctype=html...
+ # &gt=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;
+ }
}
- # datefrom can't be after dateto
- if ($self->{'datefrom'} && $self->{'dateto'} &&
- $self->{'datefrom'} > $self->{'dateto'})
- {
- ThrowUserError('misarranged_dates', { 'datefrom' => scalar $cgi->param('datefrom'),
- 'dateto' => scalar $cgi->param('dateto') });
+ # 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' => scalar $cgi->param('datefrom'),
+ 'dateto' => scalar $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++;
- }
+ 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;
- }
+ }
+
+ # 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]);
- }
+ 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];
- }
+ 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);
+ $self->remove(@line_ids);
- push(@{$self->{'lines'}}, \@series);
- push(@{$self->{'labels'}}, $label);
+ push(@{$self->{'lines'}}, \@series);
+ push(@{$self->{'labels'}}, $label);
}
sub data {
- my $self = shift;
- $self->{'_data'} ||= $self->readData();
- return $self->{'_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];
- }
- }
- }
+ 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];
}
- # We are done with the series making up this line, go to the next one
- $line_index++;
- }
+ $datediff_total[$datediff] += $value;
- # 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);
+ # 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];
+ }
}
- $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);
+ # We are done with the series making up this line, go to the next one
+ $line_index++;
+ }
- if ($self->{'gt'}) {
- # Add Grand Total to label list
- push(@{$self->{'labels'}}, $self->{'labelgt'});
+ # calculate maximum y value
+ if ($self->{'cumulate'}) {
- $data[$gt_index] ||= [];
+ # 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);
}
-
- return \@data;
+ $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;
+ my $self = shift;
+ my @series_ids;
- foreach my $line (@{$self->{'lines'}}) {
- foreach my $series (@$line) {
- push(@series_ids, $series->{'series_id'});
- }
+ foreach my $line (@{$self->{'lines'}}) {
+ foreach my $series (@$line) {
+ push(@series_ids, $series->{'series_id'});
}
+ }
- return @series_ids;
+ return @series_ids;
}
# Class method to get the data necessary to populate the "select series"
# widgets on various pages.
sub getVisibleSeries {
- my %cats;
-
- my $grouplist = Bugzilla->user->groups_as_string;
-
- # 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 = ? OR (is_public = 1 AND cgm.category_id IS NULL) " .
- $dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
- 'series.name'),
- undef, Bugzilla->user->id);
- foreach my $series (@$serieses) {
- my ($cat, $subcat, $name, $series_id) = @$series;
- $cats{$cat}{$subcat}{$name} = $series_id;
- }
-
- return \%cats;
+ my %cats;
+
+ my $grouplist = Bugzilla->user->groups_as_string;
+
+ # 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 = ? OR (is_public = 1 AND cgm.category_id IS NULL) "
+ . $dbh->sql_group_by(
+ 'series.series_id', 'cc1.name, cc2.name, ' . 'series.name'
+ ),
+ undef,
+ Bugzilla->user->id
+ );
+ 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;
- }
+ 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;
- return \@progression;
+ 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;
- say "<pre>Bugzilla::Chart object:";
- print html_quote(Data::Dumper::Dumper($self));
- print "</pre>";
+ my $self = shift;
+
+ # Make sure we've read in our data
+ my $data = $self->data;
+
+ require Data::Dumper;
+ say "<pre>Bugzilla::Chart object:";
+ print html_quote(Data::Dumper::Dumper($self));
+ print "</pre>";
}
1;
diff --git a/Bugzilla/Classification.pm b/Bugzilla/Classification.pm
index 09f71baaf..1ea86f592 100644
--- a/Bugzilla/Classification.pm
+++ b/Bugzilla/Classification.pm
@@ -26,26 +26,26 @@ use parent qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object Exporter);
use constant IS_CONFIG => 1;
-use constant DB_TABLE => 'classifications';
+use constant DB_TABLE => 'classifications';
use constant LIST_ORDER => 'sortkey, name';
use constant DB_COLUMNS => qw(
- id
- name
- description
- sortkey
+ id
+ name
+ description
+ sortkey
);
use constant UPDATE_COLUMNS => qw(
- name
- description
- sortkey
+ name
+ description
+ sortkey
);
use constant VALIDATORS => {
- name => \&_check_name,
- description => \&_check_description,
- sortkey => \&_check_sortkey,
+ name => \&_check_name,
+ description => \&_check_description,
+ sortkey => \&_check_sortkey,
};
###############################
@@ -53,29 +53,31 @@ use constant VALIDATORS => {
###############################
sub remove_from_db {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- ThrowUserError("classification_not_deletable") if ($self->id == 1);
+ ThrowUserError("classification_not_deletable") if ($self->id == 1);
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- # Reclassify products to the default classification, if needed.
- my $product_ids = $dbh->selectcol_arrayref(
- 'SELECT id FROM products WHERE classification_id = ?', undef, $self->id);
-
- if (@$product_ids) {
- $dbh->do('UPDATE products SET classification_id = 1 WHERE '
- . $dbh->sql_in('id', $product_ids));
- foreach my $id (@$product_ids) {
- Bugzilla->memcached->clear({ table => 'products', id => $id });
- }
- Bugzilla->memcached->clear_config();
+ # Reclassify products to the default classification, if needed.
+ my $product_ids
+ = $dbh->selectcol_arrayref(
+ 'SELECT id FROM products WHERE classification_id = ?',
+ undef, $self->id);
+
+ if (@$product_ids) {
+ $dbh->do('UPDATE products SET classification_id = 1 WHERE '
+ . $dbh->sql_in('id', $product_ids));
+ foreach my $id (@$product_ids) {
+ Bugzilla->memcached->clear({table => 'products', id => $id});
}
+ Bugzilla->memcached->clear_config();
+ }
- $self->SUPER::remove_from_db();
+ $self->SUPER::remove_from_db();
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
}
@@ -84,38 +86,41 @@ sub remove_from_db {
###############################
sub _check_name {
- my ($invocant, $name) = @_;
-
- $name = trim($name);
- $name || ThrowUserError('classification_not_specified');
-
- if (length($name) > MAX_CLASSIFICATION_SIZE) {
- ThrowUserError('classification_name_too_long', {'name' => $name});
- }
-
- my $classification = new Bugzilla::Classification({name => $name});
- if ($classification && (!ref $invocant || $classification->id != $invocant->id)) {
- ThrowUserError("classification_already_exists", { name => $classification->name });
- }
- return $name;
+ my ($invocant, $name) = @_;
+
+ $name = trim($name);
+ $name || ThrowUserError('classification_not_specified');
+
+ if (length($name) > MAX_CLASSIFICATION_SIZE) {
+ ThrowUserError('classification_name_too_long', {'name' => $name});
+ }
+
+ my $classification = new Bugzilla::Classification({name => $name});
+ if ($classification && (!ref $invocant || $classification->id != $invocant->id))
+ {
+ ThrowUserError("classification_already_exists",
+ {name => $classification->name});
+ }
+ return $name;
}
sub _check_description {
- my ($invocant, $description) = @_;
+ my ($invocant, $description) = @_;
- $description = trim($description || '');
- return $description;
+ $description = trim($description || '');
+ return $description;
}
sub _check_sortkey {
- my ($invocant, $sortkey) = @_;
-
- $sortkey ||= 0;
- my $stored_sortkey = $sortkey;
- if (!detaint_natural($sortkey) || $sortkey > MAX_SMALLINT) {
- ThrowUserError('classification_invalid_sortkey', { 'sortkey' => $stored_sortkey });
- }
- return $sortkey;
+ my ($invocant, $sortkey) = @_;
+
+ $sortkey ||= 0;
+ my $stored_sortkey = $sortkey;
+ if (!detaint_natural($sortkey) || $sortkey > MAX_SMALLINT) {
+ ThrowUserError('classification_invalid_sortkey',
+ {'sortkey' => $stored_sortkey});
+ }
+ return $sortkey;
}
#####################################
@@ -124,41 +129,45 @@ sub _check_sortkey {
use constant FIELD_NAME => 'classification';
use constant is_default => 0;
-use constant is_active => 1;
+use constant is_active => 1;
###############################
#### Methods ####
###############################
-sub set_name { $_[0]->set('name', $_[1]); }
+sub set_name { $_[0]->set('name', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); }
-sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
sub product_count {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!defined $self->{'product_count'}) {
- $self->{'product_count'} = $dbh->selectrow_array(q{
+ 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'};
+ WHERE classification_id = ?}, undef, $self->id
+ ) || 0;
+ }
+ return $self->{'product_count'};
}
sub products {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!$self->{'products'}) {
- my $product_ids = $dbh->selectcol_arrayref(q{
+ if (!$self->{'products'}) {
+ my $product_ids = $dbh->selectcol_arrayref(
+ q{
SELECT id FROM products
WHERE classification_id = ?
- ORDER BY name}, undef, $self->id);
+ ORDER BY name}, undef, $self->id
+ );
- $self->{'products'} = Bugzilla::Product->new_from_list($product_ids);
- }
- return $self->{'products'};
+ $self->{'products'} = Bugzilla::Product->new_from_list($product_ids);
+ }
+ return $self->{'products'};
}
###############################
@@ -166,7 +175,7 @@ sub products {
###############################
sub description { return $_[0]->{'description'}; }
-sub sortkey { return $_[0]->{'sortkey'}; }
+sub sortkey { return $_[0]->{'sortkey'}; }
###############################
@@ -177,27 +186,32 @@ sub sortkey { return $_[0]->{'sortkey'}; }
# in global/choose-product.html.tmpl.
sub sort_products_by_classification {
- my $products = shift;
- my $list;
-
- if (Bugzilla->params->{'useclassification'}) {
- my $class = {};
- # Get all classifications with at least one product.
- foreach my $product (@$products) {
- $class->{$product->classification_id}->{'object'} ||=
- new Bugzilla::Classification($product->classification_id);
- # Nice way to group products per classification, without querying
- # the DB again.
- push(@{$class->{$product->classification_id}->{'products'}}, $product);
- }
- $list = [sort {$a->{'object'}->sortkey <=> $b->{'object'}->sortkey
- || lc($a->{'object'}->name) cmp lc($b->{'object'}->name)}
- (values %$class)];
- }
- else {
- $list = [{object => undef, products => $products}];
+ my $products = shift;
+ my $list;
+
+ if (Bugzilla->params->{'useclassification'}) {
+ my $class = {};
+
+ # Get all classifications with at least one product.
+ foreach my $product (@$products) {
+ $class->{$product->classification_id}->{'object'}
+ ||= new Bugzilla::Classification($product->classification_id);
+
+ # Nice way to group products per classification, without querying
+ # the DB again.
+ push(@{$class->{$product->classification_id}->{'products'}}, $product);
}
- return $list;
+ $list = [
+ sort {
+ $a->{'object'}->sortkey <=> $b->{'object'}->sortkey
+ || lc($a->{'object'}->name) cmp lc($b->{'object'}->name)
+ } (values %$class)
+ ];
+ }
+ else {
+ $list = [{object => undef, products => $products}];
+ }
+ return $list;
}
1;
diff --git a/Bugzilla/Comment.pm b/Bugzilla/Comment.pm
index b036907d7..575703b4b 100644
--- a/Bugzilla/Comment.pm
+++ b/Bugzilla/Comment.pm
@@ -33,47 +33,48 @@ use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant DB_COLUMNS => qw(
- comment_id
- bug_id
- who
- bug_when
- work_time
- thetext
- isprivate
- already_wrapped
- type
- extra_data
+ comment_id
+ bug_id
+ who
+ bug_when
+ work_time
+ thetext
+ isprivate
+ already_wrapped
+ type
+ extra_data
);
use constant UPDATE_COLUMNS => qw(
- isprivate
- type
- extra_data
+ isprivate
+ type
+ extra_data
);
use constant DB_TABLE => 'longdescs';
use constant ID_FIELD => 'comment_id';
+
# In some rare cases, two comments can have identical timestamps. If
# this happens, we want to be sure that the comment added later shows up
# later in the sequence.
use constant LIST_ORDER => 'bug_when, comment_id';
use constant VALIDATORS => {
- bug_id => \&_check_bug_id,
- who => \&_check_who,
- bug_when => \&_check_bug_when,
- work_time => \&_check_work_time,
- thetext => \&_check_thetext,
- isprivate => \&_check_isprivate,
- extra_data => \&_check_extra_data,
- type => \&_check_type,
+ bug_id => \&_check_bug_id,
+ who => \&_check_who,
+ bug_when => \&_check_bug_when,
+ work_time => \&_check_work_time,
+ thetext => \&_check_thetext,
+ isprivate => \&_check_isprivate,
+ extra_data => \&_check_extra_data,
+ type => \&_check_type,
};
use constant VALIDATOR_DEPENDENCIES => {
- extra_data => ['type'],
- bug_id => ['who'],
- work_time => ['who', 'bug_id'],
- isprivate => ['who'],
+ extra_data => ['type'],
+ bug_id => ['who'],
+ work_time => ['who', 'bug_id'],
+ isprivate => ['who'],
};
#########################
@@ -81,95 +82,100 @@ use constant VALIDATOR_DEPENDENCIES => {
#########################
sub update {
- my $self = shift;
- my ($changes, $old_comment) = $self->SUPER::update(@_);
-
- if (exists $changes->{'thetext'} || exists $changes->{'isprivate'}) {
- $self->bug->_sync_fulltext( update_comments => 1);
- }
-
- my @old_tags = @{ $old_comment->tags };
- my @new_tags = @{ $self->tags };
- my ($removed_tags, $added_tags) = diff_arrays(\@old_tags, \@new_tags);
-
- if (@$removed_tags || @$added_tags) {
- my $dbh = Bugzilla->dbh;
- my $when = $dbh->selectrow_array("SELECT LOCALTIMESTAMP(0)");
- my $sth_delete = $dbh->prepare(
- "DELETE FROM longdescs_tags WHERE comment_id = ? AND tag = ?"
- );
- my $sth_insert = $dbh->prepare(
- "INSERT INTO longdescs_tags(comment_id, tag) VALUES (?, ?)"
- );
- my $sth_activity = $dbh->prepare(
- "INSERT INTO longdescs_tags_activity
+ my $self = shift;
+ my ($changes, $old_comment) = $self->SUPER::update(@_);
+
+ if (exists $changes->{'thetext'} || exists $changes->{'isprivate'}) {
+ $self->bug->_sync_fulltext(update_comments => 1);
+ }
+
+ my @old_tags = @{$old_comment->tags};
+ my @new_tags = @{$self->tags};
+ my ($removed_tags, $added_tags) = diff_arrays(\@old_tags, \@new_tags);
+
+ if (@$removed_tags || @$added_tags) {
+ my $dbh = Bugzilla->dbh;
+ my $when = $dbh->selectrow_array("SELECT LOCALTIMESTAMP(0)");
+ my $sth_delete = $dbh->prepare(
+ "DELETE FROM longdescs_tags WHERE comment_id = ? AND tag = ?");
+ my $sth_insert
+ = $dbh->prepare("INSERT INTO longdescs_tags(comment_id, tag) VALUES (?, ?)");
+ my $sth_activity = $dbh->prepare(
+ "INSERT INTO longdescs_tags_activity
(bug_id, comment_id, who, bug_when, added, removed)
VALUES (?, ?, ?, ?, ?, ?)"
- );
-
- foreach my $tag (@$removed_tags) {
- my $weighted = Bugzilla::Comment::TagWeights->new({ name => $tag });
- if ($weighted) {
- if ($weighted->weight == 1) {
- $weighted->remove_from_db();
- } else {
- $weighted->set_weight($weighted->weight - 1);
- $weighted->update();
- }
- }
- trick_taint($tag);
- $sth_delete->execute($self->id, $tag);
- $sth_activity->execute(
- $self->bug_id, $self->id, Bugzilla->user->id, $when, '', $tag);
- }
+ );
- foreach my $tag (@$added_tags) {
- my $weighted = Bugzilla::Comment::TagWeights->new({ name => $tag });
- if ($weighted) {
- $weighted->set_weight($weighted->weight + 1);
- $weighted->update();
- } else {
- Bugzilla::Comment::TagWeights->create({ tag => $tag, weight => 1 });
- }
- trick_taint($tag);
- $sth_insert->execute($self->id, $tag);
- $sth_activity->execute(
- $self->bug_id, $self->id, Bugzilla->user->id, $when, $tag, '');
+ foreach my $tag (@$removed_tags) {
+ my $weighted = Bugzilla::Comment::TagWeights->new({name => $tag});
+ if ($weighted) {
+ if ($weighted->weight == 1) {
+ $weighted->remove_from_db();
}
+ else {
+ $weighted->set_weight($weighted->weight - 1);
+ $weighted->update();
+ }
+ }
+ trick_taint($tag);
+ $sth_delete->execute($self->id, $tag);
+ $sth_activity->execute($self->bug_id, $self->id, Bugzilla->user->id, $when, '',
+ $tag);
}
- return $changes;
+ foreach my $tag (@$added_tags) {
+ my $weighted = Bugzilla::Comment::TagWeights->new({name => $tag});
+ if ($weighted) {
+ $weighted->set_weight($weighted->weight + 1);
+ $weighted->update();
+ }
+ else {
+ Bugzilla::Comment::TagWeights->create({tag => $tag, weight => 1});
+ }
+ trick_taint($tag);
+ $sth_insert->execute($self->id, $tag);
+ $sth_activity->execute($self->bug_id, $self->id, Bugzilla->user->id, $when,
+ $tag, '');
+ }
+ }
+
+ return $changes;
}
# Speeds up displays of comment lists by loading all author objects and tags at
# once for a whole list.
sub preload {
- my ($class, $comments) = @_;
- # Author
- my %user_ids = map { $_->{who} => 1 } @$comments;
- my $users = Bugzilla::User->new_from_list([keys %user_ids]);
- my %user_map = map { $_->id => $_ } @$users;
- foreach my $comment (@$comments) {
- $comment->{author} = $user_map{$comment->{who}};
- }
- # Tags
- if (Bugzilla->params->{'comment_taggers_group'}) {
- my $dbh = Bugzilla->dbh;
- my @comment_ids = map { $_->id } @$comments;
- my %comment_map = map { $_->id => $_ } @$comments;
- my $rows = $dbh->selectall_arrayref(
- "SELECT comment_id, " . $dbh->sql_group_concat('tag', "','") . "
+ my ($class, $comments) = @_;
+
+ # Author
+ my %user_ids = map { $_->{who} => 1 } @$comments;
+ my $users = Bugzilla::User->new_from_list([keys %user_ids]);
+ my %user_map = map { $_->id => $_ } @$users;
+ foreach my $comment (@$comments) {
+ $comment->{author} = $user_map{$comment->{who}};
+ }
+
+ # Tags
+ if (Bugzilla->params->{'comment_taggers_group'}) {
+ my $dbh = Bugzilla->dbh;
+ my @comment_ids = map { $_->id } @$comments;
+ my %comment_map = map { $_->id => $_ } @$comments;
+ my $rows = $dbh->selectall_arrayref(
+ "SELECT comment_id, " . $dbh->sql_group_concat('tag', "','") . "
FROM longdescs_tags
- WHERE " . $dbh->sql_in('comment_id', \@comment_ids) . ' ' .
- $dbh->sql_group_by('comment_id'));
- foreach my $row (@$rows) {
- $comment_map{$row->[0]}->{tags} = [ split(/,/, $row->[1]) ];
- }
- # Also sets the 'tags' attribute for comments which have no entry
- # in the longdescs_tags table, else calling $comment->tags will
- # trigger another SQL query again.
- $comment_map{$_}->{tags} ||= [] foreach @comment_ids;
+ WHERE "
+ . $dbh->sql_in('comment_id', \@comment_ids) . ' '
+ . $dbh->sql_group_by('comment_id')
+ );
+ foreach my $row (@$rows) {
+ $comment_map{$row->[0]}->{tags} = [split(/,/, $row->[1])];
}
+
+ # Also sets the 'tags' attribute for comments which have no entry
+ # in the longdescs_tags table, else calling $comment->tags will
+ # trigger another SQL query again.
+ $comment_map{$_}->{tags} ||= [] foreach @comment_ids;
+ }
}
###############################
@@ -177,130 +183,132 @@ sub preload {
###############################
sub already_wrapped { return $_[0]->{'already_wrapped'}; }
-sub body { return $_[0]->{'thetext'}; }
-sub bug_id { return $_[0]->{'bug_id'}; }
-sub creation_ts { return $_[0]->{'bug_when'}; }
-sub is_private { return $_[0]->{'isprivate'}; }
-sub work_time {
- # Work time is returned as a string (see bug 607909)
- return 0 if $_[0]->{'work_time'} + 0 == 0;
- return $_[0]->{'work_time'};
+sub body { return $_[0]->{'thetext'}; }
+sub bug_id { return $_[0]->{'bug_id'}; }
+sub creation_ts { return $_[0]->{'bug_when'}; }
+sub is_private { return $_[0]->{'isprivate'}; }
+
+sub work_time {
+
+ # Work time is returned as a string (see bug 607909)
+ return 0 if $_[0]->{'work_time'} + 0 == 0;
+ return $_[0]->{'work_time'};
}
-sub type { return $_[0]->{'type'}; }
-sub extra_data { return $_[0]->{'extra_data'} }
+sub type { return $_[0]->{'type'}; }
+sub extra_data { return $_[0]->{'extra_data'} }
sub tags {
- my ($self) = @_;
- state $comment_taggers_group = Bugzilla->params->{'comment_taggers_group'};
- return [] unless $comment_taggers_group;
- $self->{'tags'} ||= Bugzilla->dbh->selectcol_arrayref(
- "SELECT tag
+ my ($self) = @_;
+ state $comment_taggers_group = Bugzilla->params->{'comment_taggers_group'};
+ return [] unless $comment_taggers_group;
+ $self->{'tags'} ||= Bugzilla->dbh->selectcol_arrayref(
+ "SELECT tag
FROM longdescs_tags
WHERE comment_id = ?
- ORDER BY tag",
- undef, $self->id);
- return $self->{'tags'};
+ ORDER BY tag", undef, $self->id
+ );
+ return $self->{'tags'};
}
sub collapsed {
- my ($self) = @_;
- state $comment_taggers_group = Bugzilla->params->{'comment_taggers_group'};
- return 0 unless $comment_taggers_group;
- return $self->{collapsed} if exists $self->{collapsed};
-
- state $collapsed_comment_tags = Bugzilla->params->{'collapsed_comment_tags'};
- $self->{collapsed} = 0;
- Bugzilla->request_cache->{comment_tags_collapsed}
- ||= [ split(/\s*,\s*/, $collapsed_comment_tags) ];
- my @collapsed_tags = @{ Bugzilla->request_cache->{comment_tags_collapsed} };
- foreach my $my_tag (@{ $self->tags }) {
- $my_tag = lc($my_tag);
- foreach my $collapsed_tag (@collapsed_tags) {
- if ($my_tag eq lc($collapsed_tag)) {
- $self->{collapsed} = 1;
- last;
- }
- }
- last if $self->{collapsed};
+ my ($self) = @_;
+ state $comment_taggers_group = Bugzilla->params->{'comment_taggers_group'};
+ return 0 unless $comment_taggers_group;
+ return $self->{collapsed} if exists $self->{collapsed};
+
+ state $collapsed_comment_tags = Bugzilla->params->{'collapsed_comment_tags'};
+ $self->{collapsed} = 0;
+ Bugzilla->request_cache->{comment_tags_collapsed}
+ ||= [split(/\s*,\s*/, $collapsed_comment_tags)];
+ my @collapsed_tags = @{Bugzilla->request_cache->{comment_tags_collapsed}};
+ foreach my $my_tag (@{$self->tags}) {
+ $my_tag = lc($my_tag);
+ foreach my $collapsed_tag (@collapsed_tags) {
+ if ($my_tag eq lc($collapsed_tag)) {
+ $self->{collapsed} = 1;
+ last;
+ }
}
- return $self->{collapsed};
+ last if $self->{collapsed};
+ }
+ return $self->{collapsed};
}
sub bug {
- my $self = shift;
- require Bugzilla::Bug;
- $self->{bug} ||= new Bugzilla::Bug($self->bug_id);
- return $self->{bug};
+ my $self = shift;
+ require Bugzilla::Bug;
+ $self->{bug} ||= new Bugzilla::Bug($self->bug_id);
+ return $self->{bug};
}
sub is_about_attachment {
- my ($self) = @_;
- return 1 if ($self->type == CMT_ATTACHMENT_CREATED
- or $self->type == CMT_ATTACHMENT_UPDATED);
- return 0;
+ my ($self) = @_;
+ return 1
+ if ($self->type == CMT_ATTACHMENT_CREATED
+ or $self->type == CMT_ATTACHMENT_UPDATED);
+ return 0;
}
sub attachment {
- my ($self) = @_;
- return undef if not $self->is_about_attachment;
- $self->{attachment} ||=
- new Bugzilla::Attachment({ id => $self->extra_data, cache => 1 });
- return $self->{attachment};
+ my ($self) = @_;
+ return undef if not $self->is_about_attachment;
+ $self->{attachment}
+ ||= new Bugzilla::Attachment({id => $self->extra_data, cache => 1});
+ return $self->{attachment};
}
-sub author {
- my $self = shift;
- $self->{'author'}
- ||= new Bugzilla::User({ id => $self->{'who'}, cache => 1 });
- return $self->{'author'};
+sub author {
+ my $self = shift;
+ $self->{'author'} ||= new Bugzilla::User({id => $self->{'who'}, cache => 1});
+ return $self->{'author'};
}
sub body_full {
- my ($self, $params) = @_;
- $params ||= {};
- my $template = Bugzilla->template_inner;
- my $body;
- if ($self->type) {
- $template->process("bug/format_comment.txt.tmpl",
- { comment => $self, %$params }, \$body)
- || ThrowTemplateError($template->error());
- $body =~ s/^X//;
- }
- else {
- $body = $self->body;
- }
- if ($params->{wrap} and !$self->already_wrapped) {
- $body = wrap_comment($body);
- }
- return $body;
+ my ($self, $params) = @_;
+ $params ||= {};
+ my $template = Bugzilla->template_inner;
+ my $body;
+ if ($self->type) {
+ $template->process("bug/format_comment.txt.tmpl", {comment => $self, %$params},
+ \$body)
+ || ThrowTemplateError($template->error());
+ $body =~ s/^X//;
+ }
+ else {
+ $body = $self->body;
+ }
+ if ($params->{wrap} and !$self->already_wrapped) {
+ $body = wrap_comment($body);
+ }
+ return $body;
}
############
# Mutators #
############
-sub set_is_private { $_[0]->set('isprivate', $_[1]); }
-sub set_type { $_[0]->set('type', $_[1]); }
-sub set_extra_data { $_[0]->set('extra_data', $_[1]); }
+sub set_is_private { $_[0]->set('isprivate', $_[1]); }
+sub set_type { $_[0]->set('type', $_[1]); }
+sub set_extra_data { $_[0]->set('extra_data', $_[1]); }
sub add_tag {
- my ($self, $tag) = @_;
- $tag = $self->_check_tag($tag);
+ my ($self, $tag) = @_;
+ $tag = $self->_check_tag($tag);
- my $tags = $self->tags;
- return if grep { lc($tag) eq lc($_) } @$tags;
- push @$tags, $tag;
- $self->{'tags'} = [ sort @$tags ];
+ my $tags = $self->tags;
+ return if grep { lc($tag) eq lc($_) } @$tags;
+ push @$tags, $tag;
+ $self->{'tags'} = [sort @$tags];
}
sub remove_tag {
- my ($self, $tag) = @_;
- $tag = $self->_check_tag($tag);
+ my ($self, $tag) = @_;
+ $tag = $self->_check_tag($tag);
- my $tags = $self->tags;
- my $index = first { lc($tags->[$_]) eq lc($tag) } 0..scalar(@$tags) - 1;
- return unless defined $index;
- splice(@$tags, $index, 1);
+ my $tags = $self->tags;
+ my $index = first { lc($tags->[$_]) eq lc($tag) } 0 .. scalar(@$tags) - 1;
+ return unless defined $index;
+ splice(@$tags, $index, 1);
}
##############
@@ -308,180 +316,180 @@ sub remove_tag {
##############
sub run_create_validators {
- my $self = shift;
- my $params = $self->SUPER::run_create_validators(@_);
- # Sometimes this run_create_validators is called with parameters that
- # skip bug_id validation, so it might not exist in the resulting hash.
- if (defined $params->{bug_id}) {
- $params->{bug_id} = $params->{bug_id}->id;
- }
- return $params;
+ my $self = shift;
+ my $params = $self->SUPER::run_create_validators(@_);
+
+ # Sometimes this run_create_validators is called with parameters that
+ # skip bug_id validation, so it might not exist in the resulting hash.
+ if (defined $params->{bug_id}) {
+ $params->{bug_id} = $params->{bug_id}->id;
+ }
+ return $params;
}
sub _check_extra_data {
- my ($invocant, $extra_data, undef, $params) = @_;
- my $type = blessed($invocant) ? $invocant->type : $params->{type};
+ my ($invocant, $extra_data, undef, $params) = @_;
+ my $type = blessed($invocant) ? $invocant->type : $params->{type};
- if ($type == CMT_NORMAL) {
- if (defined $extra_data) {
- ThrowCodeError('comment_extra_data_not_allowed',
- { type => $type, extra_data => $extra_data });
- }
+ if ($type == CMT_NORMAL) {
+ if (defined $extra_data) {
+ ThrowCodeError('comment_extra_data_not_allowed',
+ {type => $type, extra_data => $extra_data});
+ }
+ }
+ else {
+ if (!defined $extra_data) {
+ ThrowCodeError('comment_extra_data_required', {type => $type});
+ }
+ elsif ($type == CMT_ATTACHMENT_CREATED or $type == CMT_ATTACHMENT_UPDATED) {
+ my $attachment = Bugzilla::Attachment->check({id => $extra_data});
+ $extra_data = $attachment->id;
}
else {
- if (!defined $extra_data) {
- ThrowCodeError('comment_extra_data_required', { type => $type });
- }
- elsif ($type == CMT_ATTACHMENT_CREATED
- or $type == CMT_ATTACHMENT_UPDATED)
- {
- my $attachment = Bugzilla::Attachment->check({
- id => $extra_data });
- $extra_data = $attachment->id;
- }
- else {
- my $original = $extra_data;
- detaint_natural($extra_data)
- or ThrowCodeError('comment_extra_data_not_numeric',
- { type => $type, extra_data => $original });
- }
+ my $original = $extra_data;
+ detaint_natural($extra_data)
+ or ThrowCodeError('comment_extra_data_not_numeric',
+ {type => $type, extra_data => $original});
}
+ }
- return $extra_data;
+ return $extra_data;
}
sub _check_type {
- my ($invocant, $type) = @_;
- $type ||= CMT_NORMAL;
- my $original = $type;
- detaint_natural($type)
- or ThrowCodeError('comment_type_invalid', { type => $original });
- return $type;
+ my ($invocant, $type) = @_;
+ $type ||= CMT_NORMAL;
+ my $original = $type;
+ detaint_natural($type)
+ or ThrowCodeError('comment_type_invalid', {type => $original});
+ return $type;
}
sub _check_bug_id {
- my ($invocant, $bug_id) = @_;
-
- ThrowCodeError('param_required', {function => 'Bugzilla::Comment->create',
- param => 'bug_id'}) unless $bug_id;
-
- my $bug;
- if (blessed $bug_id) {
- # We got a bug object passed in, use it
- $bug = $bug_id;
- $bug->check_is_visible;
- }
- else {
- # We got a bug id passed in, check it and get the bug object
- $bug = Bugzilla::Bug->check({ id => $bug_id });
- }
-
- # Make sure the user can edit the product
- Bugzilla->user->can_edit_product($bug->{product_id});
-
- # Make sure the user can comment
- my $privs;
- $bug->check_can_change_field('longdesc', 0, 1, \$privs)
- || ThrowUserError('illegal_change',
- { field => 'longdesc', privs => $privs });
- return $bug;
+ my ($invocant, $bug_id) = @_;
+
+ ThrowCodeError('param_required',
+ {function => 'Bugzilla::Comment->create', param => 'bug_id'})
+ unless $bug_id;
+
+ my $bug;
+ if (blessed $bug_id) {
+
+ # We got a bug object passed in, use it
+ $bug = $bug_id;
+ $bug->check_is_visible;
+ }
+ else {
+ # We got a bug id passed in, check it and get the bug object
+ $bug = Bugzilla::Bug->check({id => $bug_id});
+ }
+
+ # Make sure the user can edit the product
+ Bugzilla->user->can_edit_product($bug->{product_id});
+
+ # Make sure the user can comment
+ my $privs;
+ $bug->check_can_change_field('longdesc', 0, 1, \$privs)
+ || ThrowUserError('illegal_change', {field => 'longdesc', privs => $privs});
+ return $bug;
}
sub _check_who {
- my ($invocant, $who) = @_;
- Bugzilla->login(LOGIN_REQUIRED);
- return Bugzilla->user->id;
+ my ($invocant, $who) = @_;
+ Bugzilla->login(LOGIN_REQUIRED);
+ return Bugzilla->user->id;
}
sub _check_bug_when {
- my ($invocant, $when) = @_;
+ my ($invocant, $when) = @_;
- # Make sure the timestamp is defined, default to a timestamp from the db
- if (!defined $when) {
- $when = Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- }
+ # Make sure the timestamp is defined, default to a timestamp from the db
+ if (!defined $when) {
+ $when = Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ }
- # Make sure the timestamp parses
- if (!datetime_from($when)) {
- ThrowCodeError('invalid_timestamp', { timestamp => $when });
- }
+ # Make sure the timestamp parses
+ if (!datetime_from($when)) {
+ ThrowCodeError('invalid_timestamp', {timestamp => $when});
+ }
- return $when;
+ return $when;
}
sub _check_work_time {
- my ($invocant, $value_in, $field, $params) = @_;
-
- # Call down to Bugzilla::Object, letting it know negative
- # values are ok
- my $time = $invocant->check_time($value_in, $field, $params, 1);
- my $privs;
- $params->{bug_id}->check_can_change_field('work_time', 0, $time, \$privs)
- || ThrowUserError('illegal_change',
- { field => 'work_time', privs => $privs });
- return $time;
+ my ($invocant, $value_in, $field, $params) = @_;
+
+ # Call down to Bugzilla::Object, letting it know negative
+ # values are ok
+ my $time = $invocant->check_time($value_in, $field, $params, 1);
+ my $privs;
+ $params->{bug_id}->check_can_change_field('work_time', 0, $time, \$privs)
+ || ThrowUserError('illegal_change', {field => 'work_time', privs => $privs});
+ return $time;
}
sub _check_thetext {
- my ($invocant, $thetext) = @_;
-
- ThrowCodeError('param_required',{function => 'Bugzilla::Comment->create',
- param => 'thetext'}) unless defined $thetext;
-
- # Remove any trailing whitespace. Leading whitespace could be
- # a valid part of the comment.
- $thetext =~ s/\s*$//s;
- $thetext =~ s/\r\n?/\n/g; # Get rid of \r.
-
- # Characters above U+FFFF cannot be stored by MySQL older than 5.5.3 as they
- # require the new utf8mb4 character set. Other DB servers are handling them
- # without any problem. So we need to replace these characters if we use MySQL,
- # else the comment is truncated.
- # XXX - Once we use utf8mb4 for comments, this hack for MySQL can go away.
- state $is_mysql = Bugzilla->dbh->isa('Bugzilla::DB::Mysql') ? 1 : 0;
- if ($is_mysql) {
- # Perl 5.13.8 and older complain about non-characters.
- no warnings 'utf8';
- $thetext =~ s/([\x{10000}-\x{10FFFF}])/"\x{FDD0}[" . uc(sprintf('U+%04x', ord($1))) . "]\x{FDD1}"/eg;
- }
-
- ThrowUserError('comment_too_long') if length($thetext) > MAX_COMMENT_LENGTH;
- return $thetext;
+ my ($invocant, $thetext) = @_;
+
+ ThrowCodeError('param_required',
+ {function => 'Bugzilla::Comment->create', param => 'thetext'})
+ unless defined $thetext;
+
+ # Remove any trailing whitespace. Leading whitespace could be
+ # a valid part of the comment.
+ $thetext =~ s/\s*$//s;
+ $thetext =~ s/\r\n?/\n/g; # Get rid of \r.
+
+ # Characters above U+FFFF cannot be stored by MySQL older than 5.5.3 as they
+ # require the new utf8mb4 character set. Other DB servers are handling them
+ # without any problem. So we need to replace these characters if we use MySQL,
+ # else the comment is truncated.
+ # XXX - Once we use utf8mb4 for comments, this hack for MySQL can go away.
+ state $is_mysql = Bugzilla->dbh->isa('Bugzilla::DB::Mysql') ? 1 : 0;
+ if ($is_mysql) {
+
+ # Perl 5.13.8 and older complain about non-characters.
+ no warnings 'utf8';
+ $thetext
+ =~ s/([\x{10000}-\x{10FFFF}])/"\x{FDD0}[" . uc(sprintf('U+%04x', ord($1))) . "]\x{FDD1}"/eg;
+ }
+
+ ThrowUserError('comment_too_long') if length($thetext) > MAX_COMMENT_LENGTH;
+ return $thetext;
}
sub _check_isprivate {
- my ($invocant, $isprivate) = @_;
- if ($isprivate && !Bugzilla->user->is_insider) {
- ThrowUserError('user_not_insider');
- }
- return $isprivate ? 1 : 0;
+ my ($invocant, $isprivate) = @_;
+ if ($isprivate && !Bugzilla->user->is_insider) {
+ ThrowUserError('user_not_insider');
+ }
+ return $isprivate ? 1 : 0;
}
sub _check_tag {
- my ($invocant, $tag) = @_;
- length($tag) < MIN_COMMENT_TAG_LENGTH
- and ThrowUserError('comment_tag_too_short', { tag => $tag });
- length($tag) > MAX_COMMENT_TAG_LENGTH
- and ThrowUserError('comment_tag_too_long', { tag => $tag });
- $tag =~ /^[\w\d\._-]+$/
- or ThrowUserError('comment_tag_invalid', { tag => $tag });
- return $tag;
+ my ($invocant, $tag) = @_;
+ length($tag) < MIN_COMMENT_TAG_LENGTH
+ and ThrowUserError('comment_tag_too_short', {tag => $tag});
+ length($tag) > MAX_COMMENT_TAG_LENGTH
+ and ThrowUserError('comment_tag_too_long', {tag => $tag});
+ $tag =~ /^[\w\d\._-]+$/ or ThrowUserError('comment_tag_invalid', {tag => $tag});
+ return $tag;
}
sub count {
- my ($self) = @_;
+ my ($self) = @_;
- return $self->{'count'} if defined $self->{'count'};
+ return $self->{'count'} if defined $self->{'count'};
- my $dbh = Bugzilla->dbh;
- ($self->{'count'}) = $dbh->selectrow_array(
- "SELECT COUNT(*)
+ my $dbh = Bugzilla->dbh;
+ ($self->{'count'}) = $dbh->selectrow_array(
+ "SELECT COUNT(*)
FROM longdescs
WHERE bug_id = ?
- AND bug_when <= ?",
- undef, $self->bug_id, $self->creation_ts);
+ AND bug_when <= ?", undef, $self->bug_id, $self->creation_ts
+ );
- return --$self->{'count'};
+ return --$self->{'count'};
}
1;
diff --git a/Bugzilla/Comment/TagWeights.pm b/Bugzilla/Comment/TagWeights.pm
index 7dba53e34..5355cad7f 100644
--- a/Bugzilla/Comment/TagWeights.pm
+++ b/Bugzilla/Comment/TagWeights.pm
@@ -21,20 +21,20 @@ use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
- id
- tag
- weight
+ id
+ tag
+ weight
);
use constant UPDATE_COLUMNS => qw(
- weight
+ weight
);
use constant DB_TABLE => 'longdescs_tags_weights';
use constant ID_FIELD => 'id';
use constant NAME_FIELD => 'tag';
use constant LIST_ORDER => 'weight DESC';
-use constant VALIDATORS => { };
+use constant VALIDATORS => {};
# There's no gain to caching these objects
use constant USE_MEMCACHED => 0;
diff --git a/Bugzilla/Component.pm b/Bugzilla/Component.pm
index d5a6ece5d..ef8180eb0 100644
--- a/Bugzilla/Component.pm
+++ b/Bugzilla/Component.pm
@@ -27,150 +27,147 @@ use Scalar::Util qw(blessed);
###############################
use constant DB_TABLE => 'components';
+
# This is mostly for the editfields.cgi case where ->get_all is called.
use constant LIST_ORDER => 'product_id, name';
use constant DB_COLUMNS => qw(
- id
- name
- product_id
- initialowner
- initialqacontact
- description
- isactive
+ id
+ name
+ product_id
+ initialowner
+ initialqacontact
+ description
+ isactive
);
use constant UPDATE_COLUMNS => qw(
- name
- initialowner
- initialqacontact
- description
- isactive
+ name
+ initialowner
+ initialqacontact
+ description
+ isactive
);
-use constant REQUIRED_FIELD_MAP => {
- product_id => 'product',
-};
+use constant REQUIRED_FIELD_MAP => {product_id => 'product',};
use constant VALIDATORS => {
- create_series => \&Bugzilla::Object::check_boolean,
- product => \&_check_product,
- initialowner => \&_check_initialowner,
- initialqacontact => \&_check_initialqacontact,
- description => \&_check_description,
- initial_cc => \&_check_cc_list,
- name => \&_check_name,
- isactive => \&Bugzilla::Object::check_boolean,
+ create_series => \&Bugzilla::Object::check_boolean,
+ product => \&_check_product,
+ initialowner => \&_check_initialowner,
+ initialqacontact => \&_check_initialqacontact,
+ description => \&_check_description,
+ initial_cc => \&_check_cc_list,
+ name => \&_check_name,
+ isactive => \&Bugzilla::Object::check_boolean,
};
-use constant VALIDATOR_DEPENDENCIES => {
- name => ['product'],
-};
+use constant VALIDATOR_DEPENDENCIES => {name => ['product'],};
###############################
sub new {
- my $class = shift;
- my $param = shift;
- my $dbh = Bugzilla->dbh;
-
- my $product;
- if (ref $param and !defined $param->{id}) {
- $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 };
+ my $class = shift;
+ my $param = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $product;
+ if (ref $param and !defined $param->{id}) {
+ $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;
+ 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;
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- $class->check_required_create_fields(@_);
- my $params = $class->run_create_validators(@_);
- my $cc_list = delete $params->{initial_cc};
- my $create_series = delete $params->{create_series};
- my $product = delete $params->{product};
- $params->{product_id} = $product->id;
+ $class->check_required_create_fields(@_);
+ my $params = $class->run_create_validators(@_);
+ my $cc_list = delete $params->{initial_cc};
+ my $create_series = delete $params->{create_series};
+ my $product = delete $params->{product};
+ $params->{product_id} = $product->id;
- my $component = $class->insert_create_data($params);
- $component->{product} = $product;
+ my $component = $class->insert_create_data($params);
+ $component->{product} = $product;
- # We still have to fill the component_cc table.
- $component->_update_cc_list($cc_list) if $cc_list;
+ # We still have to fill the component_cc table.
+ $component->_update_cc_list($cc_list) if $cc_list;
- # Create series for the new component.
- $component->_create_series() if $create_series;
+ # Create series for the new component.
+ $component->_create_series() if $create_series;
- $dbh->bz_commit_transaction();
- return $component;
+ $dbh->bz_commit_transaction();
+ return $component;
}
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;
+ 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;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- $self->_check_if_controller(); # From ChoiceInterface
+ $self->_check_if_controller(); # From ChoiceInterface
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- # Products must have at least one component.
- my @components = @{ $self->product->components };
- if (scalar(@components) == 1) {
- ThrowUserError('component_is_last', { comp => $self });
- }
+ # Products must have at least one component.
+ my @components = @{$self->product->components};
+ if (scalar(@components) == 1) {
+ ThrowUserError('component_is_last', {comp => $self});
+ }
+
+ if ($self->bug_count) {
+ if (Bugzilla->params->{'allowbugdeletion'}) {
+ require Bugzilla::Bug;
+ foreach my $bug_id (@{$self->bug_ids}) {
- 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});
- }
+ # 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();
+ }
}
- # Update the list of components in the product object.
- $self->product->{components} = [grep { $_->id != $self->id } @components];
- $self->SUPER::remove_from_db();
+ else {
+ ThrowUserError('component_has_bugs', {nb => $self->bug_count});
+ }
+ }
+
+ # Update the list of components in the product object.
+ $self->product->{components} = [grep { $_->id != $self->id } @components];
+ $self->SUPER::remove_from_db();
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
}
################################
@@ -178,69 +175,70 @@ sub remove_from_db {
################################
sub _check_name {
- my ($invocant, $name, undef, $params) = @_;
- my $product = blessed($invocant) ? $invocant->product : $params->{product};
-
- $name = trim($name);
- $name || ThrowUserError('component_blank_name');
-
- if (length($name) > MAX_COMPONENT_SIZE) {
- ThrowUserError('component_name_too_long', {'name' => $name});
- }
-
- 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;
+ my ($invocant, $name, undef, $params) = @_;
+ my $product = blessed($invocant) ? $invocant->product : $params->{product};
+
+ $name = trim($name);
+ $name || ThrowUserError('component_blank_name');
+
+ if (length($name) > MAX_COMPONENT_SIZE) {
+ ThrowUserError('component_name_too_long', {'name' => $name});
+ }
+
+ 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) = @_;
+ my ($invocant, $description) = @_;
- $description = trim($description);
- $description || ThrowUserError('component_blank_description');
- return $description;
+ $description = trim($description);
+ $description || ThrowUserError('component_blank_description');
+ return $description;
}
sub _check_initialowner {
- my ($invocant, $owner) = @_;
+ my ($invocant, $owner) = @_;
- $owner || ThrowUserError('component_need_initialowner');
- my $owner_id = Bugzilla::User->check($owner)->id;
- return $owner_id;
+ $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;
+ 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) = @_;
- $product || ThrowCodeError('param_required',
- { function => "$invocant->create", param => 'product' });
- return Bugzilla->user->check_can_admin_product($product->name);
+ my ($invocant, $product) = @_;
+ $product
+ || ThrowCodeError('param_required',
+ {function => "$invocant->create", param => '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];
+ 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];
}
###############################
@@ -248,156 +246,176 @@ sub _check_cc_list {
###############################
sub _update_cc_list {
- my ($self, $cc_list) = @_;
- my $dbh = Bugzilla->dbh;
+ 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 $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)];
- }
+ 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);
+ $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);
+ my $sth = $dbh->prepare(
+ 'INSERT INTO component_cc
+ (user_id, component_id) VALUES (?, ?)'
+ );
+ $sth->execute($_, $self->id) foreach (@$cc_list);
- return $diff;
+ 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();
- }
+ 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_name { $_[0]->set('name', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); }
-sub set_is_active { $_[0]->set('isactive', $_[1]); }
+sub set_is_active { $_[0]->set('isactive', $_[1]); }
+
sub set_default_assignee {
- my ($self, $owner) = @_;
+ my ($self, $owner) = @_;
+
+ $self->set('initialowner', $owner);
- $self->set('initialowner', $owner);
- # Reset the default owner object.
- delete $self->{default_assignee};
+ # Reset the default owner object.
+ delete $self->{default_assignee};
}
+
sub set_default_qa_contact {
- my ($self, $qa_contact) = @_;
+ my ($self, $qa_contact) = @_;
+
+ $self->set('initialqacontact', $qa_contact);
- $self->set('initialqacontact', $qa_contact);
- # Reset the default QA contact object.
- delete $self->{default_qa_contact};
+ # Reset the default QA contact object.
+ delete $self->{default_qa_contact};
}
+
sub set_cc_list {
- my ($self, $cc_list) = @_;
+ my ($self, $cc_list) = @_;
+
+ $self->{cc_ids} = $self->_check_cc_list($cc_list);
- $self->{cc_ids} = $self->_check_cc_list($cc_list);
- # Reset the list of CC user objects.
- delete $self->{initial_cc};
+ # Reset the list of CC user objects.
+ delete $self->{initial_cc};
}
sub bug_count {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!defined $self->{'bug_count'}) {
- $self->{'bug_count'} = $dbh->selectrow_array(q{
+ 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'};
+ WHERE component_id = ?}, undef, $self->id
+ ) || 0;
+ }
+ return $self->{'bug_count'};
}
sub bug_ids {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!defined $self->{'bugs_ids'}) {
- $self->{'bugs_ids'} = $dbh->selectcol_arrayref(q{
+ 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'};
+ WHERE component_id = ?}, undef, $self->id
+ );
+ }
+ return $self->{'bugs_ids'};
}
sub default_assignee {
- my $self = shift;
+ my $self = shift;
- return $self->{'default_assignee'}
- ||= new Bugzilla::User({ id => $self->{'initialowner'}, cache => 1 });
+ return $self->{'default_assignee'}
+ ||= new Bugzilla::User({id => $self->{'initialowner'}, cache => 1});
}
sub default_qa_contact {
- my $self = shift;
+ my $self = shift;
- return unless $self->{'initialqacontact'};
- return $self->{'default_qa_contact'}
- ||= new Bugzilla::User({id => $self->{'initialqacontact'}, cache => 1 });
+ return unless $self->{'initialqacontact'};
+ return $self->{'default_qa_contact'}
+ ||= new Bugzilla::User({id => $self->{'initialqacontact'}, cache => 1});
}
sub flag_types {
- my $self = shift;
-
- if (!defined $self->{'flag_types'}) {
- my $flagtypes = Bugzilla::FlagType::match({ product_id => $self->product_id,
- component_id => $self->id });
-
- $self->{'flag_types'} = {};
- $self->{'flag_types'}->{'bug'} =
- [grep { $_->target_type eq 'bug' } @$flagtypes];
- $self->{'flag_types'}->{'attachment'} =
- [grep { $_->target_type eq 'attachment' } @$flagtypes];
- }
- return $self->{'flag_types'};
+ my $self = shift;
+
+ if (!defined $self->{'flag_types'}) {
+ my $flagtypes = Bugzilla::FlagType::match(
+ {product_id => $self->product_id, component_id => $self->id});
+
+ $self->{'flag_types'} = {};
+ $self->{'flag_types'}->{'bug'}
+ = [grep { $_->target_type eq 'bug' } @$flagtypes];
+ $self->{'flag_types'}->{'attachment'}
+ = [grep { $_->target_type eq 'attachment' } @$flagtypes];
+ }
+ 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'};
+ 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'};
+ 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'};
}
###############################
@@ -405,8 +423,8 @@ sub product {
###############################
sub description { return $_[0]->{'description'}; }
-sub product_id { return $_[0]->{'product_id'}; }
-sub is_active { return $_[0]->{'isactive'}; }
+sub product_id { return $_[0]->{'product_id'}; }
+sub is_active { return $_[0]->{'isactive'}; }
##############################################
# Implement Bugzilla::Field::ChoiceInterface #
@@ -416,11 +434,11 @@ use constant FIELD_NAME => 'component';
use constant is_default => 0;
sub is_set_on_bug {
- my ($self, $bug) = @_;
- my $value = blessed($bug) ? $bug->component_id : $bug->{component};
- $value = $value->id if blessed($value);
- return 0 unless $value;
- return $value == $self->id ? 1 : 0;
+ my ($self, $bug) = @_;
+ my $value = blessed($bug) ? $bug->component_id : $bug->{component};
+ $value = $value->id if blessed($value);
+ return 0 unless $value;
+ return $value == $self->id ? 1 : 0;
}
###############################
diff --git a/Bugzilla/Config.pm b/Bugzilla/Config.pm
index 458616701..1aa944985 100644
--- a/Bugzilla/Config.pm
+++ b/Bugzilla/Config.pm
@@ -25,316 +25,323 @@ use File::Basename;
# 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)],
- );
+%Bugzilla::Config::EXPORT_TAGS
+ = (admin => [qw(update_params SetParam write_params)],);
Exporter::export_ok_tags('admin');
# 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();
- my %hook_panels;
- foreach my $panel (keys %$panels) {
- my $module = $panels->{$panel};
- eval("require $module") || die $@;
- my @new_param_list = $module->get_param_list();
- $hook_panels{lc($panel)} = { params => \@new_param_list };
- }
- # This hook is also called in editparams.cgi. This call here is required
- # to make SetParam work.
- Bugzilla::Hook::process('config_modify_panels',
- { panels => \%hook_panels });
-
- foreach my $panel (keys %hook_panels) {
- foreach my $item (@{$hook_panels{$panel}->{params}}) {
- $params{$item->{'name'}} = $item;
- }
+ my $panels = param_panels();
+ my %hook_panels;
+ foreach my $panel (keys %$panels) {
+ my $module = $panels->{$panel};
+ eval("require $module") || die $@;
+ my @new_param_list = $module->get_param_list();
+ $hook_panels{lc($panel)} = {params => \@new_param_list};
+ }
+
+ # This hook is also called in editparams.cgi. This call here is required
+ # to make SetParam work.
+ Bugzilla::Hook::process('config_modify_panels', {panels => \%hook_panels});
+
+ foreach my $panel (keys %hook_panels) {
+ foreach my $item (@{$hook_panels{$panel}->{params}}) {
+ $params{$item->{'name'}} = $item;
}
+ }
}
+
# 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_add_panels',
- { panel_modules => $param_panels });
- return $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_add_panels', {panel_modules => $param_panels});
+ return $param_panels;
}
sub SetParam {
- my ($name, $value) = @_;
+ my ($name, $value) = @_;
- _load_params unless %params;
- die "Unknown param $name" unless (exists $params{$name});
+ _load_params unless %params;
+ die "Unknown param $name" unless (exists $params{$name});
- my $entry = $params{$name};
+ my $entry = $params{$name};
- # sanity check the value
+ # 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 '';
- }
+ # 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;
+ Bugzilla->params->{$name} = $value;
}
sub update_params {
- my ($params) = @_;
- my $answer = Bugzilla->installation_answers;
- my $datadir = bz_locations()->{'datadir'};
- my $param;
-
- # If the old data/params file using Data::Dumper output still exists,
- # read it. It will be deleted once the parameters are stored in the new
- # data/params.json file.
- my $old_file = "$datadir/params";
-
- if (-e $old_file) {
- require Safe;
- my $s = new Safe;
-
- $s->rdo($old_file);
- die "Error reading $old_file: $!" if $!;
- die "Error evaluating $old_file: $@" if $@;
-
- # Now read the param back out from the sandbox.
- $param = \%{ $s->varglob('param') };
+ my ($params) = @_;
+ my $answer = Bugzilla->installation_answers;
+ my $datadir = bz_locations()->{'datadir'};
+ my $param;
+
+ # If the old data/params file using Data::Dumper output still exists,
+ # read it. It will be deleted once the parameters are stored in the new
+ # data/params.json file.
+ my $old_file = "$datadir/params";
+
+ if (-e $old_file) {
+ require Safe;
+ my $s = new Safe;
+
+ $s->rdo($old_file);
+ die "Error reading $old_file: $!" if $!;
+ die "Error evaluating $old_file: $@" if $@;
+
+ # Now read the param back out from the sandbox.
+ $param = \%{$s->varglob('param')};
+ }
+ else {
+ # Rename params.js to params.json if checksetup.pl
+ # was executed with an earlier version of this change
+ rename "$old_file.js", "$old_file.json"
+ if -e "$old_file.js" && !-e "$old_file.json";
+
+ # Read the new data/params.json file.
+ $param = read_param_file();
+ }
+
+ my %new_params;
+
+ # If we didn't return any param values, then this is a new installation.
+ my $new_install = !(keys %$param);
+
+ # --- UPDATE OLD PARAMS ---
+
+ # Change from usebrowserinfo to defaultplatform/defaultopsys combo
+ if (exists $param->{'usebrowserinfo'}) {
+ if (!$param->{'usebrowserinfo'}) {
+ if (!exists $param->{'defaultplatform'}) {
+ $new_params{'defaultplatform'} = 'Other';
+ }
+ if (!exists $param->{'defaultopsys'}) {
+ $new_params{'defaultopsys'} = 'Other';
+ }
}
- else {
- # Rename params.js to params.json if checksetup.pl
- # was executed with an earlier version of this change
- rename "$old_file.js", "$old_file.json"
- if -e "$old_file.js" && !-e "$old_file.json";
-
- # Read the new data/params.json file.
- $param = read_param_file();
+ }
+
+ # Change from a boolean for quips to multi-state
+ if (exists $param->{'usequip'} && !exists $param->{'enablequips'}) {
+ $new_params{'enablequips'} = $param->{'usequip'} ? 'on' : 'off';
+ }
+
+ # 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'}) {
+ $new_params{'makeproductgroups'} = $param->{'usebuggroups'};
+ }
+
+ # Modularise auth code
+ if (exists $param->{'useLDAP'} && !exists $param->{'loginmethod'}) {
+ $new_params{'loginmethod'} = $param->{'useLDAP'} ? "LDAP" : "DB";
+ }
+
+ # set verify method to whatever loginmethod was
+ if (exists $param->{'loginmethod'} && !exists $param->{'user_verify_class'}) {
+ $new_params{'user_verify_class'} = $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'; };
+ $new_params{'quip_list_entry_control'} = $new_value;
+ }
+
+ # Old mail_delivery_method choices contained no uppercase characters
+ my $mta = $param->{'mail_delivery_method'};
+ if ($mta) {
+ if ($mta !~ /[A-Z]/) {
+ my %translation = (
+ 'sendmail' => 'Sendmail',
+ 'smtp' => 'SMTP',
+ 'qmail' => 'Qmail',
+ 'testfile' => 'Test',
+ 'none' => 'None'
+ );
+ $param->{'mail_delivery_method'} = $translation{$mta};
}
- my %new_params;
-
- # If we didn't return any param values, then this is a new installation.
- my $new_install = !(keys %$param);
-
- # --- UPDATE OLD PARAMS ---
-
- # Change from usebrowserinfo to defaultplatform/defaultopsys combo
- if (exists $param->{'usebrowserinfo'}) {
- if (!$param->{'usebrowserinfo'}) {
- if (!exists $param->{'defaultplatform'}) {
- $new_params{'defaultplatform'} = 'Other';
- }
- if (!exists $param->{'defaultopsys'}) {
- $new_params{'defaultopsys'} = 'Other';
- }
- }
+ # This will force the parameter to be reset to its default value.
+ delete $param->{'mail_delivery_method'}
+ if $param->{'mail_delivery_method'} eq 'Qmail';
+ }
+
+ # Convert the old "ssl" parameter to the new "ssl_redirect" parameter.
+ # Both "authenticated sessions" and "always" turn on "ssl_redirect"
+ # when upgrading.
+ if (exists $param->{'ssl'} and $param->{'ssl'} ne 'never') {
+ $new_params{'ssl_redirect'} = 1;
+ }
+
+# "specific_search_allow_empty_words" has been renamed to "search_allow_no_criteria".
+ if (exists $param->{'specific_search_allow_empty_words'}) {
+ $new_params{'search_allow_no_criteria'}
+ = $param->{'specific_search_allow_empty_words'};
+ }
+
+ # --- DEFAULTS FOR NEW PARAMS ---
+
+ _load_params unless %params;
+ foreach my $name (keys %params) {
+ my $item = $params{$name};
+ unless (exists $param->{$name}) {
+ print "New parameter: $name\n" unless $new_install;
+ if (exists $new_params{$name}) {
+ $param->{$name} = $new_params{$name};
+ }
+ elsif (exists $answer->{$name}) {
+ $param->{$name} = $answer->{$name};
+ }
+ else {
+ $param->{$name} = $item->{'default'};
+ }
}
+ }
- # Change from a boolean for quips to multi-state
- if (exists $param->{'usequip'} && !exists $param->{'enablequips'}) {
- $new_params{'enablequips'} = $param->{'usequip'} ? 'on' : 'off';
- }
+ $param->{'utf8'} = 1 if $new_install;
- # 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'})
- {
- $new_params{'makeproductgroups'} = $param->{'usebuggroups'};
- }
+ # Bug 452525: OR based groups are on by default for new installations
+ $param->{'or_groups'} = 1 if $new_install;
- # Modularise auth code
- if (exists $param->{'useLDAP'} && !exists $param->{'loginmethod'}) {
- $new_params{'loginmethod'} = $param->{'useLDAP'} ? "LDAP" : "DB";
- }
+ # --- REMOVE OLD PARAMS ---
- # set verify method to whatever loginmethod was
- if (exists $param->{'loginmethod'}
- && !exists $param->{'user_verify_class'})
- {
- $new_params{'user_verify_class'} = $param->{'loginmethod'};
- }
+ my %oldparams;
- # 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';};
- $new_params{'quip_list_entry_control'} = $new_value;
+ # Remove any old params
+ foreach my $item (keys %$param) {
+ if (!exists $params{$item}) {
+ $oldparams{$item} = delete $param->{$item};
}
-
- # Old mail_delivery_method choices contained no uppercase characters
- my $mta = $param->{'mail_delivery_method'};
- if ($mta) {
- if ($mta !~ /[A-Z]/) {
- my %translation = (
- 'sendmail' => 'Sendmail',
- 'smtp' => 'SMTP',
- 'qmail' => 'Qmail',
- 'testfile' => 'Test',
- 'none' => 'None');
- $param->{'mail_delivery_method'} = $translation{$mta};
- }
- # This will force the parameter to be reset to its default value.
- delete $param->{'mail_delivery_method'} if $param->{'mail_delivery_method'} eq 'Qmail';
+ }
+
+ # Write any old parameters to old-params.txt
+ my $old_param_file = "$datadir/old-params.txt";
+ if (scalar(keys %oldparams)) {
+ my $op_file = new IO::File($old_param_file, '>>', 0600)
+ || die "Couldn't create $old_param_file: $!";
+
+ print "The following parameters are no longer used in Bugzilla,",
+ " and so have been\nmoved from your parameters file into",
+ " $old_param_file:\n";
+
+ my $comma = "";
+ foreach my $item (keys %oldparams) {
+ print $op_file "\n\n$item:\n" . $oldparams{$item} . "\n";
+ print "${comma}$item";
+ $comma = ", ";
}
+ print "\n";
+ $op_file->close;
+ }
- # Convert the old "ssl" parameter to the new "ssl_redirect" parameter.
- # Both "authenticated sessions" and "always" turn on "ssl_redirect"
- # when upgrading.
- if (exists $param->{'ssl'} and $param->{'ssl'} ne 'never') {
- $new_params{'ssl_redirect'} = 1;
- }
+ write_params($param);
- # "specific_search_allow_empty_words" has been renamed to "search_allow_no_criteria".
- if (exists $param->{'specific_search_allow_empty_words'}) {
- $new_params{'search_allow_no_criteria'} = $param->{'specific_search_allow_empty_words'};
- }
+ if (-e $old_file) {
+ unlink $old_file;
+ say "$old_file has been converted into $old_file.json, using the JSON format.";
+ }
- # --- DEFAULTS FOR NEW PARAMS ---
-
- _load_params unless %params;
- foreach my $name (keys %params) {
- my $item = $params{$name};
- unless (exists $param->{$name}) {
- print "New parameter: $name\n" unless $new_install;
- if (exists $new_params{$name}) {
- $param->{$name} = $new_params{$name};
- }
- elsif (exists $answer->{$name}) {
- $param->{$name} = $answer->{$name};
- }
- else {
- $param->{$name} = $item->{'default'};
- }
- }
- }
-
- $param->{'utf8'} = 1 if $new_install;
-
- # Bug 452525: OR based groups are on by default for new installations
- $param->{'or_groups'} = 1 if $new_install;
-
- # --- REMOVE OLD PARAMS ---
-
- my %oldparams;
- # Remove any old params
- foreach my $item (keys %$param) {
- if (!exists $params{$item}) {
- $oldparams{$item} = delete $param->{$item};
- }
- }
-
- # Write any old parameters to old-params.txt
- my $old_param_file = "$datadir/old-params.txt";
- if (scalar(keys %oldparams)) {
- my $op_file = new IO::File($old_param_file, '>>', 0600)
- || die "Couldn't create $old_param_file: $!";
-
- print "The following parameters are no longer used in Bugzilla,",
- " and so have been\nmoved from your parameters file into",
- " $old_param_file:\n";
-
- my $comma = "";
- foreach my $item (keys %oldparams) {
- print $op_file "\n\n$item:\n" . $oldparams{$item} . "\n";
- print "${comma}$item";
- $comma = ", ";
- }
- print "\n";
- $op_file->close;
- }
-
- write_params($param);
-
- if (-e $old_file) {
- unlink $old_file;
- say "$old_file has been converted into $old_file.json, using the JSON format.";
- }
-
- # Return deleted params and values so that checksetup.pl has a chance
- # to convert old params to new data.
- return %oldparams;
+ # 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 $param_file = bz_locations()->{'datadir'} . '/params.json';
+ my ($param_data) = @_;
+ $param_data ||= Bugzilla->params;
+ my $param_file = bz_locations()->{'datadir'} . '/params.json';
- my $json_data = JSON::XS->new->canonical->pretty->encode($param_data);
- write_text($param_file, $json_data);
+ my $json_data = JSON::XS->new->canonical->pretty->encode($param_data);
+ write_text($param_file, $json_data);
- # It's not common to edit parameters and loading
- # Bugzilla::Install::Filesystem is slow.
- require Bugzilla::Install::Filesystem;
- Bugzilla::Install::Filesystem::fix_file_permissions($param_file);
+ # It's not common to edit parameters and loading
+ # Bugzilla::Install::Filesystem is slow.
+ require Bugzilla::Install::Filesystem;
+ Bugzilla::Install::Filesystem::fix_file_permissions($param_file);
- # And now we have to reset the params cache so that Bugzilla will re-read
- # them.
- delete Bugzilla->request_cache->{params};
+ # And now we have to reset the params cache so that Bugzilla will re-read
+ # them.
+ delete Bugzilla->request_cache->{params};
}
sub read_param_file {
- my %params;
- my $file = bz_locations()->{'datadir'} . '/params.json';
-
- if (-e $file) {
- my $data = read_text($file);
- trick_taint($data);
-
- # If params.json has been manually edited and e.g. some quotes are
- # missing, we don't want JSON::XS to leak the content of the file
- # to all users in its error message, so we have to eval'uate it.
- %params = eval { %{JSON::XS->new->decode($data)} };
- if ($@) {
- my $error_msg = (basename($0) eq 'checksetup.pl') ?
- $@ : 'run checksetup.pl to see the details.';
- die "Error parsing $file: $error_msg";
- }
- # JSON::XS doesn't detaint data for us.
- foreach my $key (keys %params) {
- if (ref($params{$key}) eq "ARRAY") {
- foreach my $item (@{$params{$key}}) {
- trick_taint($item);
- }
- } else {
- trick_taint($params{$key}) if defined $params{$key};
- }
- }
+ my %params;
+ my $file = bz_locations()->{'datadir'} . '/params.json';
+
+ if (-e $file) {
+ my $data = read_text($file);
+ trick_taint($data);
+
+ # If params.json has been manually edited and e.g. some quotes are
+ # missing, we don't want JSON::XS to leak the content of the file
+ # to all users in its error message, so we have to eval'uate it.
+ %params = eval { %{JSON::XS->new->decode($data)} };
+ if ($@) {
+ my $error_msg
+ = (basename($0) eq 'checksetup.pl')
+ ? $@
+ : 'run checksetup.pl to see the details.';
+ die "Error parsing $file: $error_msg";
}
- 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 $file file does not exist."
- . ' You probably need to run checksetup.pl.',
+
+ # JSON::XS doesn't detaint data for us.
+ foreach my $key (keys %params) {
+ if (ref($params{$key}) eq "ARRAY") {
+ foreach my $item (@{$params{$key}}) {
+ trick_taint($item);
+ }
+ }
+ else {
+ trick_taint($params{$key}) if defined $params{$key};
+ }
}
- return \%params;
+ }
+ 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 $file file does not exist."
+ . ' You probably need to run checksetup.pl.',;
+ }
+ return \%params;
}
1;
diff --git a/Bugzilla/Config/Admin.pm b/Bugzilla/Config/Admin.pm
index 41d929298..fe19d7cf0 100644
--- a/Bugzilla/Config/Admin.pm
+++ b/Bugzilla/Config/Admin.pm
@@ -16,32 +16,21 @@ use Bugzilla::Config::Common;
our $sortkey = 200;
sub get_param_list {
- my $class = shift;
+ my $class = shift;
my @param_list = (
- {
- name => 'allowbugdeletion',
- type => 'b',
- default => 0
- },
-
- {
- name => 'allowemailchange',
- type => 'b',
- default => 1
- },
-
- {
- name => 'allowuserdeletion',
- type => 'b',
- default => 0
- },
-
- {
- name => 'last_visit_keep_days',
- type => 't',
- default => 10,
- checker => \&check_numeric
- });
+ {name => 'allowbugdeletion', type => 'b', default => 0},
+
+ {name => 'allowemailchange', type => 'b', default => 1},
+
+ {name => 'allowuserdeletion', type => 'b', default => 0},
+
+ {
+ name => 'last_visit_keep_days',
+ type => 't',
+ default => 10,
+ checker => \&check_numeric
+ }
+ );
return @param_list;
}
diff --git a/Bugzilla/Config/Advanced.pm b/Bugzilla/Config/Advanced.pm
index 8356c3361..043a892d7 100644
--- a/Bugzilla/Config/Advanced.pm
+++ b/Bugzilla/Config/Advanced.pm
@@ -16,31 +16,18 @@ use Bugzilla::Config::Common;
our $sortkey = 1700;
use constant get_param_list => (
- {
- name => 'cookiedomain',
- type => 't',
- default => ''
- },
+ {name => 'cookiedomain', type => 't', default => ''},
- {
- name => 'inbound_proxies',
- type => 't',
- default => '',
- checker => \&check_ip
- },
+ {name => 'inbound_proxies', type => 't', default => '', checker => \&check_ip},
- {
- name => 'proxy_url',
- type => 't',
- default => ''
- },
+ {name => 'proxy_url', type => 't', default => ''},
{
- name => 'strict_transport_security',
- type => 's',
- choices => ['off', 'this_domain_only', 'include_subdomains'],
- default => 'off',
- checker => \&check_multi
+ name => 'strict_transport_security',
+ type => 's',
+ choices => ['off', 'this_domain_only', 'include_subdomains'],
+ default => 'off',
+ checker => \&check_multi
},
);
diff --git a/Bugzilla/Config/Attachment.pm b/Bugzilla/Config/Attachment.pm
index 580ec46d9..0cf4b768a 100644
--- a/Bugzilla/Config/Attachment.pm
+++ b/Bugzilla/Config/Attachment.pm
@@ -16,48 +16,41 @@ use Bugzilla::Config::Common;
our $sortkey = 400;
sub get_param_list {
- my $class = shift;
+ my $class = shift;
my @param_list = (
- {
- name => 'allow_attachment_display',
- type => 'b',
- default => 0
- },
-
- {
- name => 'attachment_base',
- type => 't',
- default => '',
- checker => \&check_urlbase
- },
-
- {
- name => 'allow_attachment_deletion',
- type => 'b',
- default => 0
- },
-
- {
- name => 'maxattachmentsize',
- type => 't',
- default => '1000',
- checker => \&check_maxattachmentsize
- },
-
- # 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 => 'allow_attachment_display', type => 'b', default => 0},
+
+ {
+ name => 'attachment_base',
+ type => 't',
+ default => '',
+ checker => \&check_urlbase
+ },
+
+ {name => 'allow_attachment_deletion', type => 'b', default => 0},
+
+ {
+ name => 'maxattachmentsize',
+ type => 't',
+ default => '1000',
+ checker => \&check_maxattachmentsize
+ },
+
+ # 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
+ }
+ );
return @param_list;
}
diff --git a/Bugzilla/Config/Auth.pm b/Bugzilla/Config/Auth.pm
index 78d719b15..09e81339f 100644
--- a/Bugzilla/Config/Auth.pm
+++ b/Bugzilla/Config/Auth.pm
@@ -16,111 +16,85 @@ use Bugzilla::Config::Common;
our $sortkey = 300;
sub get_param_list {
- my $class = shift;
+ 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 => 'requirelogin',
- type => 'b',
- default => '0'
- },
-
- {
- name => 'webservice_email_filter',
- 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
- },
-
- {
- name => 'password_complexity',
- type => 's',
- choices => [ 'no_constraints', 'mixed_letters', 'letters_numbers',
- 'letters_numbers_specialchars' ],
- default => 'no_constraints',
- checker => \&check_multi
- },
-
- {
- name => 'password_check_on_login',
- type => 'b',
- default => '1'
- },
+ {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 => 'requirelogin', type => 'b', default => '0'},
+
+ {name => 'webservice_email_filter', 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
+ },
+
+ {
+ name => 'password_complexity',
+ type => 's',
+ choices => [
+ 'no_constraints', 'mixed_letters',
+ 'letters_numbers', 'letters_numbers_specialchars'
+ ],
+ default => 'no_constraints',
+ checker => \&check_multi
+ },
+
+ {name => 'password_check_on_login', type => 'b', default => '1'},
);
return @param_list;
}
diff --git a/Bugzilla/Config/BugChange.pm b/Bugzilla/Config/BugChange.pm
index 0acdc0ce4..ad1cafefc 100644
--- a/Bugzilla/Config/BugChange.pm
+++ b/Bugzilla/Config/BugChange.pm
@@ -26,55 +26,33 @@ sub get_param_list {
# 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 @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 => 'commentonchange_resolution',
- type => 'b',
- default => 0
- },
-
- {
- name => 'commentonduplicate',
- type => 'b',
- default => 0
- },
-
- {
- name => 'noresolveonopenblockers',
- type => 'b',
- default => 0,
- } );
+ {
+ 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 => 'commentonchange_resolution', type => 'b', default => 0},
+
+ {name => 'commentonduplicate', type => 'b', default => 0},
+
+ {name => 'noresolveonopenblockers', type => 'b', default => 0,}
+ );
return @param_list;
}
diff --git a/Bugzilla/Config/BugFields.pm b/Bugzilla/Config/BugFields.pm
index ef2faa64b..1659dc66a 100644
--- a/Bugzilla/Config/BugFields.pm
+++ b/Bugzilla/Config/BugFields.pm
@@ -25,73 +25,50 @@ sub get_param_list {
my @legal_OS = @{get_legal_field_values('op_sys')};
my @param_list = (
- {
- name => 'useclassification',
- type => 'b',
- default => 0
- },
-
- {
- name => 'usetargetmilestone',
- type => 'b',
- default => 0
- },
-
- {
- name => 'useqacontact',
- type => 'b',
- default => 0
- },
-
- {
- name => 'usestatuswhiteboard',
- type => 'b',
- default => 0
- },
-
- {
- name => 'use_see_also',
- type => 'b',
- default => 1
- },
-
- {
- 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
- },
-
- {
- name => 'collapsed_comment_tags',
- type => 't',
- default => 'obsolete, spam',
- });
+ {name => 'useclassification', type => 'b', default => 0},
+
+ {name => 'usetargetmilestone', type => 'b', default => 0},
+
+ {name => 'useqacontact', type => 'b', default => 0},
+
+ {name => 'usestatuswhiteboard', type => 'b', default => 0},
+
+ {name => 'use_see_also', type => 'b', default => 1},
+
+ {
+ 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
+ },
+
+ {name => 'collapsed_comment_tags', type => 't', default => 'obsolete, spam',}
+ );
return @param_list;
}
diff --git a/Bugzilla/Config/Common.pm b/Bugzilla/Config/Common.pm
index bd9b0bf84..a0077b5b2 100644
--- a/Bugzilla/Config/Common.pm
+++ b/Bugzilla/Config/Common.pm
@@ -21,392 +21,406 @@ use Bugzilla::Group;
use Bugzilla::Status;
use parent 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_user_verify_class check_ip check_font_file
- check_mail_delivery_method check_notification check_utf8
- check_bug_status check_smtp_auth check_theschwartz_available
- check_maxattachmentsize check_email check_smtp_ssl
- check_comment_taggers_group check_smtp_server
+@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_user_verify_class check_ip check_font_file
+ check_mail_delivery_method check_notification check_utf8
+ check_bug_status check_smtp_auth check_theschwartz_available
+ check_maxattachmentsize check_email check_smtp_ssl
+ check_comment_taggers_group check_smtp_server
);
# Checking functions for the various values
sub check_multi {
- my ($value, $param) = (@_);
+ 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 "";
+ if ($param->{'type'} eq "s") {
+ unless (scalar(grep { $_ eq $value } (@{$param->{'choices'}}))) {
+ return
+ "Invalid choice '$value' for single-select list param '$param->{'name'}'";
}
- elsif ($param->{'type'} eq 'm' || $param->{'type'} eq 'o') {
- if (ref($value) ne "ARRAY") {
- $value = [split(',', $value)]
- }
- foreach my $chkParam (@$value) {
- unless (scalar(grep {$_ eq $chkParam} (@{$param->{'choices'}}))) {
- return "Invalid choice '$chkParam' for multi-select list param '$param->{'name'}'";
- }
- }
-
- return "";
+
+ return "";
+ }
+ elsif ($param->{'type'} eq 'm' || $param->{'type'} eq 'o') {
+ if (ref($value) ne "ARRAY") {
+ $value = [split(',', $value)];
}
- else {
- return "Invalid param type '$param->{'type'}' for check_multi(); " .
- "contact your Bugzilla administrator";
+ foreach my $chkParam (@$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 "";
+ my ($value) = (@_);
+ if ($value !~ /^[0-9]+$/) {
+ return "must be a numeric value";
+ }
+ return "";
}
sub check_regexp {
- my ($value) = (@_);
- eval { qr/$value/ };
- return $@;
+ my ($value) = (@_);
+ eval {qr/$value/};
+ return $@;
}
sub check_email {
- my ($value) = @_;
- if ($value !~ $Email::Address::mailbox) {
- return "must be a valid email address.";
- }
- return "";
+ my ($value) = @_;
+ if ($value !~ $Email::Address::mailbox) {
+ return "must be a valid email address.";
+ }
+ 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 "";
+ 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_ip {
- my $inbound_proxies = shift;
- my @proxies = split(/[\s,]+/, $inbound_proxies);
- foreach my $proxy (@proxies) {
- validate_ip($proxy) || return "$proxy is not a valid IPv4 or IPv6 address";
- }
- return "";
+ my $inbound_proxies = shift;
+ my @proxies = split(/[\s,]+/, $inbound_proxies);
+ foreach my $proxy (@proxies) {
+ validate_ip($proxy) || return "$proxy is not a valid IPv4 or IPv6 address";
+ }
+ 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 "";
+ 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 (!grep($_ eq $value, @$legal_priorities)) {
- return "Must be a legal priority value: one of " .
- join(", ", @$legal_priorities);
- }
- return "";
+ my ($value) = (@_);
+ my $legal_priorities = get_legal_field_values('priority');
+ if (!grep($_ eq $value, @$legal_priorities)) {
+ 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 (!grep($_ eq $value, @$legal_severities)) {
- return "Must be a legal severity value: one of " .
- join(", ", @$legal_severities);
- }
- return "";
+ my ($value) = (@_);
+ my $legal_severities = get_legal_field_values('bug_severity');
+ if (!grep($_ eq $value, @$legal_severities)) {
+ 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 (!grep($_ eq $value, '', @$legal_platforms)) {
- return "Must be empty or a legal platform value: one of " .
- join(", ", @$legal_platforms);
- }
- return "";
+ my ($value) = (@_);
+ my $legal_platforms = get_legal_field_values('rep_platform');
+ if (!grep($_ eq $value, '', @$legal_platforms)) {
+ 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 (!grep($_ eq $value, '', @$legal_OS)) {
- return "Must be empty or a legal operating system value: one of " .
- join(", ", @$legal_OS);
- }
- return "";
+ my ($value) = (@_);
+ my $legal_OS = get_legal_field_values('op_sys');
+ if (!grep($_ eq $value, '', @$legal_OS)) {
+ 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 (!grep($_ eq $bug_status, @closed_bug_statuses)) {
- return "Must be a valid closed status: one of " . join(', ', @closed_bug_statuses);
- }
- return "";
+ my $bug_status = shift;
+ my @closed_bug_statuses = map { $_->name } closed_bug_statuses();
+ if (!grep($_ eq $bug_status, @closed_bug_statuses)) {
+ 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 "";
+ 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 "";
- }
+ 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";
- }
+ 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 "";
+ # 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 "";
+ 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 '';
+ 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;
- }
- }
+ 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_font_file {
- my ($font) = @_;
- $font = trim($font);
- return '' unless $font;
-
- if ($font !~ /\.(ttf|otf)$/) {
- return "The file must point to a TrueType or OpenType font file (its extension must be .ttf or .otf)"
- }
- if (! -f $font) {
- return "The file '$font' cannot be found. Make sure you typed the full path to the file"
- }
- return '';
+ my ($font) = @_;
+ $font = trim($font);
+ return '' unless $font;
+
+ if ($font !~ /\.(ttf|otf)$/) {
+ return
+ "The file must point to a TrueType or OpenType font file (its extension must be .ttf or .otf)";
+ }
+ if (!-f $font) {
+ return
+ "The file '$font' cannot be found. Make sure you typed the full path to the file";
+ }
+ 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 $params = Bugzilla->params;
- 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 'RADIUS') {
- if (!Bugzilla->feature('auth_radius')) {
- return "RADIUS support is not available. Run checksetup.pl"
- . " for more details";
- }
- return "RADIUS servername (RADIUS_server) is missing"
- if !$params->{"RADIUS_server"};
- return "RADIUS_secret is empty" if !$params->{"RADIUS_secret"};
- }
- elsif ($class eq 'LDAP') {
- if (!Bugzilla->feature('auth_ldap')) {
- return "LDAP support is not available. Run checksetup.pl"
- . " for more details";
- }
- return "LDAP servername (LDAPserver) is missing"
- if !$params->{"LDAPserver"};
- return "LDAPBaseDN is empty" if !$params->{"LDAPBaseDN"};
- }
- }
- return "";
+
+ # 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 $params = Bugzilla->params;
+ 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 'RADIUS') {
+ if (!Bugzilla->feature('auth_radius')) {
+ return "RADIUS support is not available. Run checksetup.pl"
+ . " for more details";
+ }
+ return "RADIUS servername (RADIUS_server) is missing"
+ if !$params->{"RADIUS_server"};
+ return "RADIUS_secret is empty" if !$params->{"RADIUS_secret"};
+ }
+ elsif ($class eq 'LDAP') {
+ if (!Bugzilla->feature('auth_ldap')) {
+ return "LDAP support is not available. Run checksetup.pl" . " for more details";
+ }
+ return "LDAP servername (LDAPserver) is missing" if !$params->{"LDAPserver"};
+ return "LDAPBaseDN is empty" if !$params->{"LDAPBaseDN"};
+ }
+ }
+ return "";
}
sub check_mail_delivery_method {
- my $check = check_multi(@_);
- return $check if $check;
- my $mailer = shift;
- if ($mailer eq 'Sendmail' and ON_WINDOWS) {
- # look for sendmail.exe
- return "Failed to locate " . SENDMAIL_EXE
- unless -e SENDMAIL_EXE;
- }
- return "";
+ my $check = check_multi(@_);
+ return $check if $check;
+ my $mailer = shift;
+ if ($mailer eq 'Sendmail' and ON_WINDOWS) {
+
+ # look for sendmail.exe
+ return "Failed to locate " . SENDMAIL_EXE unless -e SENDMAIL_EXE;
+ }
+ return "";
}
sub check_maxattachmentsize {
- my $check = check_numeric(@_);
- return $check if $check;
- my $size = shift;
- my $dbh = Bugzilla->dbh;
- if ($dbh->isa('Bugzilla::DB::Mysql')) {
- my (undef, $max_packet) = $dbh->selectrow_array(
- q{SHOW VARIABLES LIKE 'max\_allowed\_packet'});
- my $byte_size = $size * 1024;
- if ($max_packet < $byte_size) {
- return "You asked for a maxattachmentsize of $byte_size bytes,"
- . " but the max_allowed_packet setting in MySQL currently"
- . " only allows packets up to $max_packet bytes";
- }
- }
- return "";
+ my $check = check_numeric(@_);
+ return $check if $check;
+ my $size = shift;
+ my $dbh = Bugzilla->dbh;
+ if ($dbh->isa('Bugzilla::DB::Mysql')) {
+ my (undef, $max_packet)
+ = $dbh->selectrow_array(q{SHOW VARIABLES LIKE 'max\_allowed\_packet'});
+ my $byte_size = $size * 1024;
+ if ($max_packet < $byte_size) {
+ return
+ "You asked for a maxattachmentsize of $byte_size bytes,"
+ . " but the max_allowed_packet setting in MySQL currently"
+ . " only allows packets up to $max_packet bytes";
+ }
+ }
+ 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";
- }
- if ($option ne 'disabled' && !Bugzilla->feature('updates')) {
- return "Some Perl modules are missing to get notifications about " .
- "new releases. See the output of checksetup.pl for more information";
- }
- return "";
+ 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";
+ }
+ if ($option ne 'disabled' && !Bugzilla->feature('updates')) {
+ return "Some Perl modules are missing to get notifications about "
+ . "new releases. See the output of checksetup.pl for more information";
+ }
+ return "";
}
sub check_smtp_server {
- my $host = shift;
- my $port;
-
- return '' unless $host;
-
- if ($host =~ /:/) {
- ($host, $port) = split(/:/, $host, 2);
- unless ($port && detaint_natural($port)) {
- return "Invalid port. It must be an integer (typically 25, 465 or 587)";
- }
- }
- trick_taint($host);
- # Let's first try to connect using SSL. If this fails, we fall back to
- # an unencrypted connection.
- foreach my $method (['Net::SMTP::SSL', 465], ['Net::SMTP', 25]) {
- my ($class, $default_port) = @$method;
- next if $class eq 'Net::SMTP::SSL' && !Bugzilla->feature('smtp_ssl');
- eval "require $class";
- my $smtp = $class->new($host, Port => $port || $default_port, Timeout => 5);
- if ($smtp) {
- # The connection works!
- $smtp->quit;
- return '';
- }
- }
- return "Cannot connect to $host" . ($port ? " using port $port" : "");
+ my $host = shift;
+ my $port;
+
+ return '' unless $host;
+
+ if ($host =~ /:/) {
+ ($host, $port) = split(/:/, $host, 2);
+ unless ($port && detaint_natural($port)) {
+ return "Invalid port. It must be an integer (typically 25, 465 or 587)";
+ }
+ }
+ trick_taint($host);
+
+ # Let's first try to connect using SSL. If this fails, we fall back to
+ # an unencrypted connection.
+ foreach my $method (['Net::SMTP::SSL', 465], ['Net::SMTP', 25]) {
+ my ($class, $default_port) = @$method;
+ next if $class eq 'Net::SMTP::SSL' && !Bugzilla->feature('smtp_ssl');
+ eval "require $class";
+ my $smtp = $class->new($host, Port => $port || $default_port, Timeout => 5);
+ if ($smtp) {
+
+ # The connection works!
+ $smtp->quit;
+ return '';
+ }
+ }
+ return "Cannot connect to $host" . ($port ? " using port $port" : "");
}
sub check_smtp_auth {
- my $username = shift;
- if ($username and !Bugzilla->feature('smtp_auth')) {
- return "SMTP Authentication is not available. Run checksetup.pl for"
- . " more details";
- }
- return "";
+ my $username = shift;
+ if ($username and !Bugzilla->feature('smtp_auth')) {
+ return "SMTP Authentication is not available. Run checksetup.pl for"
+ . " more details";
+ }
+ return "";
}
sub check_smtp_ssl {
- my $use_ssl = shift;
- if ($use_ssl && !Bugzilla->feature('smtp_ssl')) {
- return "SSL support is not available. Run checksetup.pl for more details";
- }
- return "";
+ my $use_ssl = shift;
+ if ($use_ssl && !Bugzilla->feature('smtp_ssl')) {
+ return "SSL support is not available. Run checksetup.pl for more details";
+ }
+ return "";
}
sub check_theschwartz_available {
- my $use_queue = shift;
- if ($use_queue && !Bugzilla->feature('jobqueue')) {
- return "Using the job queue requires that you have certain Perl"
- . " modules installed. See the output of checksetup.pl"
- . " for more information";
- }
- return "";
+ my $use_queue = shift;
+ if ($use_queue && !Bugzilla->feature('jobqueue')) {
+ return
+ "Using the job queue requires that you have certain Perl"
+ . " modules installed. See the output of checksetup.pl"
+ . " for more information";
+ }
+ return "";
}
sub check_comment_taggers_group {
- my $group_name = shift;
- if ($group_name && !Bugzilla->feature('jsonrpc')) {
- return "Comment tagging requires installation of the JSONRPC feature";
- }
- return check_group($group_name);
+ my $group_name = shift;
+ if ($group_name && !Bugzilla->feature('jsonrpc')) {
+ return "Comment tagging requires installation of the JSONRPC feature";
+ }
+ return check_group($group_name);
}
# OK, here are the parameter definitions themselves.
@@ -467,13 +481,13 @@ sub check_comment_taggers_group {
# }
#
# Here, 'b' is the default option, and 'a' and 'c' are other possible
-# options, but only one at a time!
+# 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;
+ return;
}
1;
diff --git a/Bugzilla/Config/Core.pm b/Bugzilla/Config/Core.pm
index 654e569ba..50af9a077 100644
--- a/Bugzilla/Config/Core.pm
+++ b/Bugzilla/Config/Core.pm
@@ -16,31 +16,13 @@ use Bugzilla::Config::Common;
our $sortkey = 100;
use constant get_param_list => (
- {
- name => 'urlbase',
- type => 't',
- default => '',
- checker => \&check_urlbase
- },
-
- {
- name => 'ssl_redirect',
- type => 'b',
- default => 0
- },
-
- {
- name => 'sslbase',
- type => 't',
- default => '',
- checker => \&check_sslbase
- },
-
- {
- name => 'cookiepath',
- type => 't',
- default => '/'
- },
+ {name => 'urlbase', type => 't', default => '', checker => \&check_urlbase},
+
+ {name => 'ssl_redirect', type => 'b', default => 0},
+
+ {name => 'sslbase', type => 't', default => '', checker => \&check_sslbase},
+
+ {name => 'cookiepath', type => 't', default => '/'},
);
1;
diff --git a/Bugzilla/Config/DependencyGraph.pm b/Bugzilla/Config/DependencyGraph.pm
index c815822f3..27bc9938d 100644
--- a/Bugzilla/Config/DependencyGraph.pm
+++ b/Bugzilla/Config/DependencyGraph.pm
@@ -16,21 +16,17 @@ use Bugzilla::Config::Common;
our $sortkey = 800;
sub get_param_list {
- my $class = shift;
+ my $class = shift;
my @param_list = (
- {
- name => 'webdotbase',
- type => 't',
- default => '',
- checker => \&check_webdotbase
- },
+ {
+ name => 'webdotbase',
+ type => 't',
+ default => '',
+ checker => \&check_webdotbase
+ },
- {
- name => 'font_file',
- type => 't',
- default => '',
- checker => \&check_font_file
- });
+ {name => 'font_file', type => 't', default => '', checker => \&check_font_file}
+ );
return @param_list;
}
diff --git a/Bugzilla/Config/General.pm b/Bugzilla/Config/General.pm
index 380680590..322275aa0 100644
--- a/Bugzilla/Config/General.pm
+++ b/Bugzilla/Config/General.pm
@@ -17,39 +17,28 @@ our $sortkey = 150;
use constant get_param_list => (
{
- name => 'maintainer',
- type => 't',
- no_reset => '1',
- default => '',
- checker => \&check_email
+ name => 'maintainer',
+ type => 't',
+ no_reset => '1',
+ default => '',
+ checker => \&check_email
},
- {
- name => 'utf8',
- type => 'b',
- default => '0',
- checker => \&check_utf8
- },
+ {name => 'utf8', type => 'b', default => '0', checker => \&check_utf8},
- {
- name => 'shutdownhtml',
- type => 'l',
- default => ''
- },
+ {name => 'shutdownhtml', type => 'l', default => ''},
- {
- name => 'announcehtml',
- type => 'l',
- default => ''
- },
+ {name => 'announcehtml', type => 'l', default => ''},
{
- name => 'upgrade_notification',
- type => 's',
- choices => ['development_snapshot', 'latest_stable_release',
- 'stable_branch_release', 'disabled'],
- default => 'latest_stable_release',
- checker => \&check_notification
+ name => 'upgrade_notification',
+ type => 's',
+ choices => [
+ 'development_snapshot', 'latest_stable_release',
+ 'stable_branch_release', 'disabled'
+ ],
+ default => 'latest_stable_release',
+ checker => \&check_notification
},
);
diff --git a/Bugzilla/Config/GroupSecurity.pm b/Bugzilla/Config/GroupSecurity.pm
index e827834a0..6602cdfea 100644
--- a/Bugzilla/Config/GroupSecurity.pm
+++ b/Bugzilla/Config/GroupSecurity.pm
@@ -20,84 +20,69 @@ sub get_param_list {
my $class = shift;
my @param_list = (
- {
- name => 'makeproductgroups',
- 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 => 'comment_taggers_group',
- type => 's',
- choices => \&_get_all_group_names,
- default => 'editbugs',
- checker => \&check_comment_taggers_group
- },
-
- {
- name => 'debug_group',
- type => 's',
- choices => \&_get_all_group_names,
- default => 'admin',
- checker => \&check_group
- },
-
- {
- name => 'usevisibilitygroups',
- type => 'b',
- default => 0
- },
-
- {
- name => 'strict_isolation',
- type => 'b',
- default => 0
- },
-
- {
- name => 'or_groups',
- type => 'b',
- default => 0
- } );
+ {name => 'makeproductgroups', 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 => 'comment_taggers_group',
+ type => 's',
+ choices => \&_get_all_group_names,
+ default => 'editbugs',
+ checker => \&check_comment_taggers_group
+ },
+
+ {
+ name => 'debug_group',
+ type => 's',
+ choices => \&_get_all_group_names,
+ default => 'admin',
+ checker => \&check_group
+ },
+
+ {name => 'usevisibilitygroups', type => 'b', default => 0},
+
+ {name => 'strict_isolation', type => 'b', default => 0},
+
+ {name => 'or_groups', 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;
+ my @group_names = map { $_->name } Bugzilla::Group->get_all;
+ unshift(@group_names, '');
+ return \@group_names;
}
1;
diff --git a/Bugzilla/Config/LDAP.pm b/Bugzilla/Config/LDAP.pm
index 0bc8240df..75f58e141 100644
--- a/Bugzilla/Config/LDAP.pm
+++ b/Bugzilla/Config/LDAP.pm
@@ -16,49 +16,22 @@ use Bugzilla::Config::Common;
our $sortkey = 1000;
sub get_param_list {
- my $class = shift;
+ my $class = shift;
my @param_list = (
- {
- name => 'LDAPserver',
- type => 't',
- default => ''
- },
+ {name => 'LDAPserver', type => 't', default => ''},
- {
- name => 'LDAPstarttls',
- type => 'b',
- default => 0
- },
+ {name => 'LDAPstarttls', type => 'b', default => 0},
- {
- name => 'LDAPbinddn',
- type => 't',
- default => ''
- },
+ {name => 'LDAPbinddn', type => 't', default => ''},
- {
- name => 'LDAPBaseDN',
- type => 't',
- default => ''
- },
+ {name => 'LDAPBaseDN', type => 't', default => ''},
- {
- name => 'LDAPuidattribute',
- type => 't',
- default => 'uid'
- },
+ {name => 'LDAPuidattribute', type => 't', default => 'uid'},
- {
- name => 'LDAPmailattribute',
- type => 't',
- default => 'mail'
- },
+ {name => 'LDAPmailattribute', type => 't', default => 'mail'},
- {
- name => 'LDAPfilter',
- type => 't',
- default => '',
- } );
+ {name => 'LDAPfilter', type => 't', default => '',}
+ );
return @param_list;
}
diff --git a/Bugzilla/Config/MTA.pm b/Bugzilla/Config/MTA.pm
index 467bdab3f..c7f8e5057 100644
--- a/Bugzilla/Config/MTA.pm
+++ b/Bugzilla/Config/MTA.pm
@@ -16,68 +16,43 @@ use Bugzilla::Config::Common;
our $sortkey = 1200;
sub get_param_list {
- my $class = shift;
+ my $class = shift;
my @param_list = (
- {
- name => 'mail_delivery_method',
- type => 's',
- choices => ['Sendmail', 'SMTP', 'Test', 'None'],
- default => 'Sendmail',
- checker => \&check_mail_delivery_method
- },
-
- {
- name => 'mailfrom',
- type => 't',
- default => 'bugzilla-daemon'
- },
-
- {
- name => 'use_mailer_queue',
- type => 'b',
- default => 0,
- checker => \&check_theschwartz_available,
- },
-
- {
- name => 'smtpserver',
- type => 't',
- default => 'localhost',
- checker => \&check_smtp_server
- },
- {
- name => 'smtp_username',
- type => 't',
- default => '',
- checker => \&check_smtp_auth
- },
- {
- name => 'smtp_password',
- type => 'p',
- default => ''
- },
- {
- name => 'smtp_ssl',
- type => 'b',
- default => 0,
- checker => \&check_smtp_ssl
- },
- {
- name => 'smtp_debug',
- type => 'b',
- default => 0
- },
- {
- name => 'whinedays',
- type => 't',
- default => 7,
- checker => \&check_numeric
- },
- {
- name => 'globalwatchers',
- type => 't',
- default => '',
- }, );
+ {
+ name => 'mail_delivery_method',
+ type => 's',
+ choices => ['Sendmail', 'SMTP', 'Test', 'None'],
+ default => 'Sendmail',
+ checker => \&check_mail_delivery_method
+ },
+
+ {name => 'mailfrom', type => 't', default => 'bugzilla-daemon'},
+
+ {
+ name => 'use_mailer_queue',
+ type => 'b',
+ default => 0,
+ checker => \&check_theschwartz_available,
+ },
+
+ {
+ name => 'smtpserver',
+ type => 't',
+ default => 'localhost',
+ checker => \&check_smtp_server
+ },
+ {
+ name => 'smtp_username',
+ type => 't',
+ default => '',
+ checker => \&check_smtp_auth
+ },
+ {name => 'smtp_password', type => 'p', default => ''},
+ {name => 'smtp_ssl', type => 'b', default => 0, checker => \&check_smtp_ssl},
+ {name => 'smtp_debug', type => 'b', default => 0},
+ {name => 'whinedays', type => 't', default => 7, checker => \&check_numeric},
+ {name => 'globalwatchers', type => 't', default => '',},
+ );
return @param_list;
}
diff --git a/Bugzilla/Config/Memcached.pm b/Bugzilla/Config/Memcached.pm
index 292803d86..5ab3364f9 100644
--- a/Bugzilla/Config/Memcached.pm
+++ b/Bugzilla/Config/Memcached.pm
@@ -17,16 +17,8 @@ our $sortkey = 1550;
sub get_param_list {
return (
- {
- name => 'memcached_servers',
- type => 't',
- default => ''
- },
- {
- name => 'memcached_namespace',
- type => 't',
- default => 'bugzilla:',
- },
+ {name => 'memcached_servers', type => 't', default => ''},
+ {name => 'memcached_namespace', type => 't', default => 'bugzilla:',},
);
}
diff --git a/Bugzilla/Config/Query.pm b/Bugzilla/Config/Query.pm
index f18bb90df..adfb4eaf4 100644
--- a/Bugzilla/Config/Query.pm
+++ b/Bugzilla/Config/Query.pm
@@ -16,47 +16,45 @@ use Bugzilla::Config::Common;
our $sortkey = 1400;
sub get_param_list {
- my $class = shift;
+ my $class = shift;
my @param_list = (
- {
- name => 'quip_list_entry_control',
- type => 's',
- choices => ['open', 'moderated', 'closed'],
- default => 'open',
- checker => \&check_multi
- },
-
- {
- name => 'mybugstemplate',
- type => 't',
- default => 'buglist.cgi?resolution=---&amp;emailassigned_to1=1&amp;emailreporter1=1&amp;emailtype1=exact&amp;email1=%userid%'
- },
-
- {
- name => 'defaultquery',
- type => 't',
- default => 'resolution=---&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&emaillongdesc3=1&order=Importance&long_desc_type=substring'
- },
-
- {
- name => 'search_allow_no_criteria',
- type => 'b',
- default => 1
- },
-
- {
- name => 'default_search_limit',
- type => 't',
- default => '500',
- checker => \&check_numeric
- },
-
- {
- name => 'max_search_results',
- type => 't',
- default => '10000',
- checker => \&check_numeric
- },
+ {
+ name => 'quip_list_entry_control',
+ type => 's',
+ choices => ['open', 'moderated', 'closed'],
+ default => 'open',
+ checker => \&check_multi
+ },
+
+ {
+ name => 'mybugstemplate',
+ type => 't',
+ default =>
+ 'buglist.cgi?resolution=---&amp;emailassigned_to1=1&amp;emailreporter1=1&amp;emailtype1=exact&amp;email1=%userid%'
+ },
+
+ {
+ name => 'defaultquery',
+ type => 't',
+ default =>
+ 'resolution=---&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&emaillongdesc3=1&order=Importance&long_desc_type=substring'
+ },
+
+ {name => 'search_allow_no_criteria', type => 'b', default => 1},
+
+ {
+ name => 'default_search_limit',
+ type => 't',
+ default => '500',
+ checker => \&check_numeric
+ },
+
+ {
+ name => 'max_search_results',
+ type => 't',
+ default => '10000',
+ checker => \&check_numeric
+ },
);
return @param_list;
}
diff --git a/Bugzilla/Config/RADIUS.pm b/Bugzilla/Config/RADIUS.pm
index 8e30b07a9..b0a5ddbf5 100644
--- a/Bugzilla/Config/RADIUS.pm
+++ b/Bugzilla/Config/RADIUS.pm
@@ -16,31 +16,15 @@ use Bugzilla::Config::Common;
our $sortkey = 1100;
sub get_param_list {
- my $class = shift;
+ 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 => ''
- },
+ {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;
}
diff --git a/Bugzilla/Config/ShadowDB.pm b/Bugzilla/Config/ShadowDB.pm
index 5dbbb5202..101e4678f 100644
--- a/Bugzilla/Config/ShadowDB.pm
+++ b/Bugzilla/Config/ShadowDB.pm
@@ -16,35 +16,23 @@ use Bugzilla::Config::Common;
our $sortkey = 1500;
sub get_param_list {
- my $class = shift;
+ 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
- } );
+ {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;
}
diff --git a/Bugzilla/Config/UserMatch.pm b/Bugzilla/Config/UserMatch.pm
index 3f74a7c44..a1f8a3eb2 100644
--- a/Bugzilla/Config/UserMatch.pm
+++ b/Bugzilla/Config/UserMatch.pm
@@ -16,32 +16,21 @@ use Bugzilla::Config::Common;
our $sortkey = 1600;
sub get_param_list {
- my $class = shift;
+ my $class = shift;
my @param_list = (
- {
- name => 'usemenuforusers',
- type => 'b',
- default => '0'
- },
-
- {
- name => 'ajax_user_autocompletion',
- type => 'b',
- default => '1',
- },
-
- {
- name => 'maxusermatches',
- type => 't',
- default => '1000',
- checker => \&check_numeric
- },
-
- {
- name => 'confirmuniqueusermatch',
- type => 'b',
- default => 1,
- } );
+ {name => 'usemenuforusers', type => 'b', default => '0'},
+
+ {name => 'ajax_user_autocompletion', type => 'b', default => '1',},
+
+ {
+ name => 'maxusermatches',
+ type => 't',
+ default => '1000',
+ checker => \&check_numeric
+ },
+
+ {name => 'confirmuniqueusermatch', type => 'b', default => 1,}
+ );
return @param_list;
}
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index a6516774b..764c24e0e 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -18,181 +18,181 @@ use File::Basename;
use Memoize;
@Bugzilla::Constants::EXPORT = qw(
- BUGZILLA_VERSION
- REST_DOC
-
- REMOTE_FILE
- LOCAL_FILE
+ BUGZILLA_VERSION
+ REST_DOC
- bz_locations
-
- CONCATENATE_ASSETS
-
- IS_NULL
- NOT_NULL
-
- CONTROLMAPNA
- CONTROLMAPSHOWN
- CONTROLMAPDEFAULT
- CONTROLMAPMANDATORY
-
- AUTH_OK
- AUTH_NODATA
- AUTH_ERROR
- AUTH_LOGINFAILED
- AUTH_DISABLED
- AUTH_NO_SUCH_USER
- AUTH_LOCKOUT
-
- USER_PASSWORD_MIN_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
- DEFAULT_MILESTONE
-
- SAVE_NUM_SEARCHES
-
- COMMENT_COLS
- MAX_COMMENT_LENGTH
-
- MIN_COMMENT_TAG_LENGTH
- MAX_COMMENT_TAG_LENGTH
-
- CMT_NORMAL
- CMT_DUPE_OF
- CMT_HAS_DUPE
- CMT_ATTACHMENT_CREATED
- CMT_ATTACHMENT_UPDATED
-
- THROW_ERROR
-
- RELATIONSHIPS
- REL_ASSIGNEE REL_QA REL_REPORTER REL_CC 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
- EVT_BUG_CREATED EVT_COMPONENT
-
- NEG_EVENTS
- EVT_UNCONFIRMED EVT_CHANGED_BY_ME
-
- GLOBAL_EVENTS
- EVT_FLAG_REQUESTED EVT_REQUESTED_FLAG
-
- 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
- FIELD_TYPE_DATE
- FIELD_TYPE_BUG_ID
- FIELD_TYPE_BUG_URLS
- FIELD_TYPE_KEYWORDS
- FIELD_TYPE_INTEGER
- FIELD_TYPE_HIGHEST_PLUS_ONE
-
- EMPTY_DATETIME_REGEX
-
- ABNORMAL_SELECTS
-
- TIMETRACKING_FIELDS
-
- USAGE_MODE_BROWSER
- USAGE_MODE_CMDLINE
- USAGE_MODE_XMLRPC
- USAGE_MODE_EMAIL
- USAGE_MODE_JSON
- USAGE_MODE_TEST
- USAGE_MODE_REST
-
- ERROR_MODE_WEBPAGE
- ERROR_MODE_DIE
- ERROR_MODE_DIE_SOAP_FAULT
- ERROR_MODE_JSON_RPC
- ERROR_MODE_TEST
- ERROR_MODE_REST
-
- COLOR_ERROR
- COLOR_SUCCESS
-
- INSTALLATION_MODE_INTERACTIVE
- INSTALLATION_MODE_NON_INTERACTIVE
-
- DB_MODULE
- ROOT_USER
- ON_WINDOWS
- ON_ACTIVESTATE
-
- MAX_TOKEN_AGE
- MAX_LOGINCOOKIE_AGE
- MAX_SUDO_TOKEN_AGE
- MAX_LOGIN_ATTEMPTS
- LOGIN_LOCKOUT_INTERVAL
- ACCOUNT_CHANGE_INTERVAL
- MAX_STS_AGE
-
- SAFE_PROTOCOLS
- LEGAL_CONTENT_TYPES
-
- MIN_SMALLINT
- MAX_SMALLINT
- MAX_INT_32
-
- MAX_LEN_QUERY_NAME
- MAX_CLASSIFICATION_SIZE
- MAX_PRODUCT_SIZE
- MAX_MILESTONE_SIZE
- MAX_COMPONENT_SIZE
- MAX_FIELD_VALUE_SIZE
- MAX_FIELD_LONG_DESC_LENGTH
- MAX_FREETEXT_LENGTH
- MAX_BUG_URL_LENGTH
- MAX_POSSIBLE_DUPLICATES
- MAX_ATTACH_FILENAME_LENGTH
- MAX_QUIP_LENGTH
- MAX_WEBDOT_BUGS
-
- PASSWORD_DIGEST_ALGORITHM
- PASSWORD_SALT_LENGTH
-
- CGI_URI_LIMIT
-
- PRIVILEGES_REQUIRED_NONE
- PRIVILEGES_REQUIRED_REPORTER
- PRIVILEGES_REQUIRED_ASSIGNEE
- PRIVILEGES_REQUIRED_EMPOWERED
-
- AUDIT_CREATE
- AUDIT_REMOVE
-
- MOST_FREQUENT_THRESHOLD
+ REMOTE_FILE
+ LOCAL_FILE
+
+ bz_locations
+
+ CONCATENATE_ASSETS
+
+ IS_NULL
+ NOT_NULL
+
+ CONTROLMAPNA
+ CONTROLMAPSHOWN
+ CONTROLMAPDEFAULT
+ CONTROLMAPMANDATORY
+
+ AUTH_OK
+ AUTH_NODATA
+ AUTH_ERROR
+ AUTH_LOGINFAILED
+ AUTH_DISABLED
+ AUTH_NO_SUCH_USER
+ AUTH_LOCKOUT
+
+ USER_PASSWORD_MIN_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
+ DEFAULT_MILESTONE
+
+ SAVE_NUM_SEARCHES
+
+ COMMENT_COLS
+ MAX_COMMENT_LENGTH
+
+ MIN_COMMENT_TAG_LENGTH
+ MAX_COMMENT_TAG_LENGTH
+
+ CMT_NORMAL
+ CMT_DUPE_OF
+ CMT_HAS_DUPE
+ CMT_ATTACHMENT_CREATED
+ CMT_ATTACHMENT_UPDATED
+
+ THROW_ERROR
+
+ RELATIONSHIPS
+ REL_ASSIGNEE REL_QA REL_REPORTER REL_CC 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
+ EVT_BUG_CREATED EVT_COMPONENT
+
+ NEG_EVENTS
+ EVT_UNCONFIRMED EVT_CHANGED_BY_ME
+
+ GLOBAL_EVENTS
+ EVT_FLAG_REQUESTED EVT_REQUESTED_FLAG
+
+ 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
+ FIELD_TYPE_DATE
+ FIELD_TYPE_BUG_ID
+ FIELD_TYPE_BUG_URLS
+ FIELD_TYPE_KEYWORDS
+ FIELD_TYPE_INTEGER
+ FIELD_TYPE_HIGHEST_PLUS_ONE
+
+ EMPTY_DATETIME_REGEX
+
+ ABNORMAL_SELECTS
+
+ TIMETRACKING_FIELDS
+
+ USAGE_MODE_BROWSER
+ USAGE_MODE_CMDLINE
+ USAGE_MODE_XMLRPC
+ USAGE_MODE_EMAIL
+ USAGE_MODE_JSON
+ USAGE_MODE_TEST
+ USAGE_MODE_REST
+
+ ERROR_MODE_WEBPAGE
+ ERROR_MODE_DIE
+ ERROR_MODE_DIE_SOAP_FAULT
+ ERROR_MODE_JSON_RPC
+ ERROR_MODE_TEST
+ ERROR_MODE_REST
+
+ COLOR_ERROR
+ COLOR_SUCCESS
+
+ INSTALLATION_MODE_INTERACTIVE
+ INSTALLATION_MODE_NON_INTERACTIVE
+
+ DB_MODULE
+ ROOT_USER
+ ON_WINDOWS
+ ON_ACTIVESTATE
+
+ MAX_TOKEN_AGE
+ MAX_LOGINCOOKIE_AGE
+ MAX_SUDO_TOKEN_AGE
+ MAX_LOGIN_ATTEMPTS
+ LOGIN_LOCKOUT_INTERVAL
+ ACCOUNT_CHANGE_INTERVAL
+ MAX_STS_AGE
+
+ SAFE_PROTOCOLS
+ LEGAL_CONTENT_TYPES
+
+ MIN_SMALLINT
+ MAX_SMALLINT
+ MAX_INT_32
+
+ MAX_LEN_QUERY_NAME
+ MAX_CLASSIFICATION_SIZE
+ MAX_PRODUCT_SIZE
+ MAX_MILESTONE_SIZE
+ MAX_COMPONENT_SIZE
+ MAX_FIELD_VALUE_SIZE
+ MAX_FIELD_LONG_DESC_LENGTH
+ MAX_FREETEXT_LENGTH
+ MAX_BUG_URL_LENGTH
+ MAX_POSSIBLE_DUPLICATES
+ MAX_ATTACH_FILENAME_LENGTH
+ MAX_QUIP_LENGTH
+ MAX_WEBDOT_BUGS
+
+ PASSWORD_DIGEST_ALGORITHM
+ PASSWORD_SALT_LENGTH
+
+ CGI_URI_LIMIT
+
+ PRIVILEGES_REQUIRED_NONE
+ PRIVILEGES_REQUIRED_REPORTER
+ PRIVILEGES_REQUIRED_ASSIGNEE
+ PRIVILEGES_REQUIRED_EMPOWERED
+
+ AUDIT_CREATE
+ AUDIT_REMOVE
+
+ MOST_FREQUENT_THRESHOLD
);
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
@@ -200,7 +200,7 @@ use Memoize;
# CONSTANTS
#
# Bugzilla version
-use constant BUGZILLA_VERSION => "5.0.4";
+use constant BUGZILLA_VERSION => "5.0.6";
# A base link to the current REST Documentation. We place it here
# as it will need to be updated to whatever the current release is.
@@ -208,7 +208,7 @@ use constant REST_DOC => 'https://bugzilla.readthedocs.org/en/5.0/api/';
# Location of the remote and local XML files to track new releases.
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
-use constant LOCAL_FILE => 'bugzilla-update.xml'; # Relative to datadir.
+use constant LOCAL_FILE => 'bugzilla-update.xml'; # Relative to datadir.
# When true CSS and JavaScript assets will be concatanted and minified at
# run-time, to reduce the number of requests required to render a page.
@@ -229,9 +229,9 @@ 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
+# Na:Na => Bugs in this product may not be restricted to this
# group.
-# Shown:Na => Members of the group may restrict bugs
+# 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.
@@ -253,46 +253,46 @@ use constant NOT_NULL => ' __NOT_NULL__ ';
# 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 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;
-use constant AUTH_LOCKOUT => 6;
+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;
+use constant AUTH_LOCKOUT => 6;
# The minimum length a password must have.
use constant USER_PASSWORD_MIN_LENGTH => 6;
use constant LOGIN_OPTIONAL => 0;
-use constant LOGIN_NORMAL => 1;
+use constant LOGIN_NORMAL => 1;
use constant LOGIN_REQUIRED => 2;
-use constant LOGOUT_ALL => 0;
-use constant LOGOUT_CURRENT => 1;
+use constant LOGOUT_ALL => 0;
+use constant LOGOUT_CURRENT => 1;
use constant LOGOUT_KEEP_CURRENT => 2;
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 GROUP_BLESS => 1;
+use constant GROUP_VISIBLE => 2;
-use constant MAILTO_USER => 0;
+use constant MAILTO_USER => 0;
use constant MAILTO_GROUP => 1;
# The default list of columns for buglist.cgi
use constant DEFAULT_COLUMN_LIST => (
- "product", "component", "assigned_to",
- "bug_status", "resolution", "short_desc", "changeddate"
+ "product", "component", "assigned_to", "bug_status",
+ "resolution", "short_desc", "changeddate"
);
# Used by query.cgi and buglist.cgi as the named-query name
@@ -307,6 +307,7 @@ use constant SAVE_NUM_SEARCHES => 10;
# The column width for comment textareas and comments in bugmails.
use constant COMMENT_COLS => 80;
+
# Used in _check_comment(). Gives the max length allowed for a comment.
use constant MAX_COMMENT_LENGTH => 16384;
@@ -319,9 +320,10 @@ use constant MIN_COMMENT_TAG_LENGTH => 3;
use constant MAX_COMMENT_TAG_LENGTH => 24;
# The type of bug comments.
-use constant CMT_NORMAL => 0;
-use constant CMT_DUPE_OF => 1;
+use constant CMT_NORMAL => 0;
+use constant CMT_DUPE_OF => 1;
use constant CMT_HAS_DUPE => 2;
+
# Type 3 was CMT_POPULAR_VOTES, which moved to the Voting extension.
# Type 4 was CMT_MOVED_TO, which moved to the OldBugMove extension.
use constant CMT_ATTACHMENT_CREATED => 5;
@@ -331,27 +333,26 @@ use constant CMT_ATTACHMENT_UPDATED => 6;
# 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_ASSIGNEE => 0;
+use constant REL_QA => 1;
+use constant REL_REPORTER => 2;
+use constant REL_CC => 3;
+
# REL 4 was REL_VOTER, before it was moved ino an extension.
-use constant REL_GLOBAL_WATCHER => 5;
+use constant REL_GLOBAL_WATCHER => 5;
# We need these strings for the X-Bugzilla-Reasons header
# Note: this hash uses "," rather than "=>" to avoid auto-quoting of the LHS.
# This should be accessed through Bugzilla::BugMail::relationships() instead
# of being accessed directly.
use constant RELATIONSHIPS => {
- REL_ASSIGNEE , "AssignedTo",
- REL_REPORTER , "Reporter",
- REL_QA , "QAcontact",
- REL_CC , "CC",
- REL_GLOBAL_WATCHER, "GlobalWatcher"
+ REL_ASSIGNEE, "AssignedTo", REL_REPORTER, "Reporter",
+ REL_QA, "QAcontact", REL_CC, "CC",
+ REL_GLOBAL_WATCHER, "GlobalWatcher"
};
-
+
# Used for global events like EVT_FLAG_REQUESTED
-use constant REL_ANY => 100;
+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
@@ -359,34 +360,34 @@ use constant REL_ANY => 100;
#
# 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 EVT_BUG_CREATED => 10;
-use constant EVT_COMPONENT => 11;
-
-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, EVT_BUG_CREATED,
- EVT_COMPONENT;
-
-use constant EVT_UNCONFIRMED => 50;
-use constant EVT_CHANGED_BY_ME => 51;
+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 EVT_BUG_CREATED => 10;
+use constant EVT_COMPONENT => 11;
+
+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, EVT_BUG_CREATED, EVT_COMPONENT;
+
+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 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;
@@ -394,10 +395,12 @@ use constant GLOBAL_EVENTS => EVT_FLAG_REQUESTED, EVT_REQUESTED_FLAG;
use constant ADMIN_GROUP_NAME => 'admin';
# Privileges which can be per-product.
-use constant PER_PRODUCT_PRIVILEGES => ('editcomponents', 'editbugs', 'canconfirm');
+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';
@@ -408,45 +411,46 @@ use constant SENDMAIL_PATH => '/usr/lib:/usr/sbin:/usr/ucblib';
# 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_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;
-use constant FIELD_TYPE_BUG_ID => 6;
-use constant FIELD_TYPE_BUG_URLS => 7;
-use constant FIELD_TYPE_KEYWORDS => 8;
-use constant FIELD_TYPE_DATE => 9;
-use constant FIELD_TYPE_INTEGER => 10;
+use constant FIELD_TYPE_MULTI_SELECT => 3;
+use constant FIELD_TYPE_TEXTAREA => 4;
+use constant FIELD_TYPE_DATETIME => 5;
+use constant FIELD_TYPE_BUG_ID => 6;
+use constant FIELD_TYPE_BUG_URLS => 7;
+use constant FIELD_TYPE_KEYWORDS => 8;
+use constant FIELD_TYPE_DATE => 9;
+use constant FIELD_TYPE_INTEGER => 10;
+
# Add new field types above this line, and change the below value in the
# obvious fashion
use constant FIELD_TYPE_HIGHEST_PLUS_ONE => 11;
-use constant EMPTY_DATETIME_REGEX => qr/^[0\-:\sA-Za-z]+$/;
+use constant EMPTY_DATETIME_REGEX => qr/^[0\-:\sA-Za-z]+$/;
# See the POD for Bugzilla::Field/is_abnormal to see why these are listed
# here.
-use constant ABNORMAL_SELECTS => {
- classification => 1,
- component => 1,
- product => 1,
-};
+use constant ABNORMAL_SELECTS =>
+ {classification => 1, component => 1, product => 1,};
# The fields from fielddefs that are blocked from non-timetracking users.
# work_time is sometimes called actual_time.
use constant TIMETRACKING_FIELDS =>
- qw(estimated_time remaining_time work_time actual_time percentage_complete);
+ qw(estimated_time remaining_time work_time actual_time percentage_complete);
# The maximum number of days a token will remain valid.
use constant MAX_TOKEN_AGE => 3;
+
# How many days a logincookie will remain valid if not used.
use constant MAX_LOGINCOOKIE_AGE => 30;
+
# How many seconds (default is 6 hours) a sudo cookie remains valid.
use constant MAX_SUDO_TOKEN_AGE => 21600;
# Maximum failed logins to lock account for this IP
use constant MAX_LOGIN_ATTEMPTS => 5;
+
# If the maximum login attempts occur during this many minutes, the
# account is locked.
use constant LOGIN_LOCKOUT_INTERVAL => 30;
@@ -460,36 +464,39 @@ use constant ACCOUNT_CHANGE_INTERVAL => 10;
use constant MAX_STS_AGE => 15768000;
# Protocols which are considered as safe.
-use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
- 'irc', 'ircs', 'mid', 'news', 'nntp', 'prospero',
- 'telnet', 'view-source', 'wais');
+use constant SAFE_PROTOCOLS => (
+ 'afs', 'cid', 'ftp', 'gopher', 'http', 'https',
+ 'irc', 'ircs', 'mid', 'news', 'nntp', 'prospero',
+ 'telnet', 'view-source', 'wais'
+);
# Valid MIME types for attachments.
-use constant LEGAL_CONTENT_TYPES => ('application', 'audio', 'image', 'message',
- 'model', 'multipart', 'text', 'video');
-
-use constant contenttypes =>
- {
- "html" => "text/html" ,
- "rdf" => "application/rdf+xml" ,
- "atom" => "application/atom+xml" ,
- "xml" => "application/xml" ,
- "dtd" => "application/xml-dtd" ,
- "js" => "application/x-javascript" ,
- "json" => "application/json" ,
- "csv" => "text/csv" ,
- "png" => "image/png" ,
- "ics" => "text/calendar" ,
- };
+use constant LEGAL_CONTENT_TYPES => (
+ 'application', 'audio', 'image', 'message',
+ 'model', 'multipart', 'text', 'video'
+);
+
+use constant contenttypes => {
+ "html" => "text/html",
+ "rdf" => "application/rdf+xml",
+ "atom" => "application/atom+xml",
+ "xml" => "application/xml",
+ "dtd" => "application/xml-dtd",
+ "js" => "application/x-javascript",
+ "json" => "application/json",
+ "csv" => "text/csv",
+ "png" => "image/png",
+ "ics" => "text/calendar",
+};
# 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_XMLRPC => 2;
-use constant USAGE_MODE_EMAIL => 3;
-use constant USAGE_MODE_JSON => 4;
-use constant USAGE_MODE_TEST => 5;
-use constant USAGE_MODE_REST => 6;
+use constant USAGE_MODE_BROWSER => 0;
+use constant USAGE_MODE_CMDLINE => 1;
+use constant USAGE_MODE_XMLRPC => 2;
+use constant USAGE_MODE_EMAIL => 3;
+use constant USAGE_MODE_JSON => 4;
+use constant USAGE_MODE_TEST => 5;
+use constant USAGE_MODE_REST => 6;
# Error modes. Default set by Bugzilla->usage_mode (so ERROR_MODE_WEBPAGE
# usually). Use with Bugzilla->error_mode.
@@ -501,60 +508,76 @@ use constant ERROR_MODE_TEST => 4;
use constant ERROR_MODE_REST => 5;
# The ANSI colors of messages that command-line scripts use
-use constant COLOR_ERROR => 'red';
+use constant COLOR_ERROR => 'red';
use constant COLOR_SUCCESS => 'green';
# The various modes that checksetup.pl can run in.
-use constant INSTALLATION_MODE_INTERACTIVE => 0;
+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 5.0.15 was the first production 5.0.x release.
- 'mysql' => {db => 'Bugzilla::DB::Mysql', db_version => '5.0.15',
- dbd => {
- package => 'DBD-mysql',
- module => 'DBD::mysql',
- # Disallow development versions
- blacklist => ['_'],
- # For UTF-8 support. 4.001 makes sure that blobs aren't
- # marked as UTF-8.
- version => '4.001',
- },
- name => 'MySQL'},
- # Also see Bugzilla::DB::Pg::bz_check_server_version, which has special
- # code to require DBD::Pg 2.17.2 for PostgreSQL 9 and above.
- 'pg' => {db => 'Bugzilla::DB::Pg', db_version => '8.03.0000',
- dbd => {
- package => 'DBD-Pg',
- module => 'DBD::Pg',
- # 2.7.0 fixes a problem with quoting strings
- # containing backslashes in them.
- version => '2.7.0',
- },
- name => 'PostgreSQL'},
- 'oracle'=> {db => 'Bugzilla::DB::Oracle', db_version => '10.02.0',
- dbd => {
- package => 'DBD-Oracle',
- module => 'DBD::Oracle',
- version => '1.19',
- },
- name => 'Oracle'},
- # SQLite 3.6.22 fixes a WHERE clause problem that may affect us.
- sqlite => {db => 'Bugzilla::DB::Sqlite', db_version => '3.6.22',
- dbd => {
- package => 'DBD-SQLite',
- module => 'DBD::SQLite',
- # 1.29 is the version that contains 3.6.22.
- version => '1.29',
- },
- name => 'SQLite'},
+
+ # MySQL 5.0.15 was the first production 5.0.x release.
+ 'mysql' => {
+ db => 'Bugzilla::DB::Mysql',
+ db_version => '5.0.15',
+ dbd => {
+ package => 'DBD-mysql',
+ module => 'DBD::mysql',
+
+ # Disallow development versions
+ blacklist => ['_'],
+
+ # For UTF-8 support. 4.001 makes sure that blobs aren't
+ # marked as UTF-8.
+ version => '4.001',
+ },
+ name => 'MySQL'
+ },
+
+ # Also see Bugzilla::DB::Pg::bz_check_server_version, which has special
+ # code to require DBD::Pg 2.17.2 for PostgreSQL 9 and above.
+ 'pg' => {
+ db => 'Bugzilla::DB::Pg',
+ db_version => '8.03.0000',
+ dbd => {
+ package => 'DBD-Pg',
+ module => 'DBD::Pg',
+
+ # 2.7.0 fixes a problem with quoting strings
+ # containing backslashes in them.
+ version => '2.7.0',
+ },
+ name => 'PostgreSQL'
+ },
+ 'oracle' => {
+ db => 'Bugzilla::DB::Oracle',
+ db_version => '10.02.0',
+ dbd => {package => 'DBD-Oracle', module => 'DBD::Oracle', version => '1.19',},
+ name => 'Oracle'
+ },
+
+ # SQLite 3.6.22 fixes a WHERE clause problem that may affect us.
+ sqlite => {
+ db => 'Bugzilla::DB::Sqlite',
+ db_version => '3.6.22',
+ dbd => {
+ package => 'DBD-SQLite',
+ module => 'DBD::SQLite',
+
+ # 1.29 is the version that contains 3.6.22.
+ version => '1.29',
+ },
+ name => 'SQLite'
+ },
};
# True if we're on Win32.
use constant ON_WINDOWS => ($^O =~ /MSWin32/i) ? 1 : 0;
+
# True if we're using ActiveState Perl (as opposed to Strawberry) on Windows.
-use constant ON_ACTIVESTATE => eval { &Win32::BuildNumber };
+use constant ON_ACTIVESTATE => eval {&Win32::BuildNumber};
# The user who should be considered "root" when we're giving
# instructions to Bugzilla administrators.
@@ -562,7 +585,7 @@ use constant ROOT_USER => ON_WINDOWS ? 'Administrator' : 'root';
use constant MIN_SMALLINT => -32768;
use constant MAX_SMALLINT => 32767;
-use constant MAX_INT_32 => 2147483647;
+use constant MAX_INT_32 => 2147483647;
# The longest that a saved search name can be.
use constant MAX_LEN_QUERY_NAME => 64;
@@ -611,6 +634,7 @@ use constant MAX_WEBDOT_BUGS => 2000;
# Perl's "Digest" module. Note that if you change this, it won't take
# effect until a user logs in or changes their password.
use constant PASSWORD_DIGEST_ALGORITHM => 'SHA-256';
+
# How long of a salt should we use? Note that if you change this, it
# won't take effect until a user logs in or changes their password.
use constant PASSWORD_SALT_LENGTH => 8;
@@ -619,7 +643,9 @@ use constant PASSWORD_SALT_LENGTH => 8;
# via POST such as buglist.cgi. This value determines whether the redirect
# can be safely done or not based on the web server's URI length setting.
# See http://support.microsoft.com/kb/208427 for why MSIE is different
-use constant CGI_URI_LIMIT => ($ENV{'HTTP_USER_AGENT'} || '') =~ /MSIE/ ? 2083 : 8000;
+use constant CGI_URI_LIMIT => ($ENV{'HTTP_USER_AGENT'} || '') =~ /MSIE/
+ ? 2083
+ : 8000;
# If the user isn't allowed to change a field, we must tell them who can.
# We store the required permission set into the $PrivilegesRequired
@@ -640,72 +666,79 @@ use constant AUDIT_REMOVE => '__remove__';
use constant MOST_FREQUENT_THRESHOLD => 2;
sub bz_locations {
- # Force memoize() to re-compute data per project, to avoid
- # sharing the same data across different installations.
- return _bz_locations($ENV{'PROJECT'});
+
+ # Force memoize() to re-compute data per project, to avoid
+ # sharing the same data across different installations.
+ return _bz_locations($ENV{'PROJECT'});
}
sub _bz_locations {
- my $project = shift;
- # 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 ($localconfig, $datadir);
- if ($project && $project =~ /^(\w+)$/) {
- $project = $1;
- $localconfig = "localconfig.$project";
- $datadir = "data/$project";
- } else {
- $project = undef;
- $localconfig = "localconfig";
- $datadir = "data";
- }
-
- $datadir = "$libpath/$datadir";
- # 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",
- 'template_cache' => "$datadir/template",
- 'project' => $project,
- 'localconfig' => "$libpath/$localconfig",
- 'datadir' => $datadir,
- 'attachdir' => "$datadir/attachments",
- 'skinsdir' => "$libpath/skins",
- 'graphsdir' => "$libpath/graphs",
- # $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' => "$datadir/webdot",
- 'extensionsdir' => "$libpath/extensions",
- 'assetsdir' => "$datadir/assets",
- };
+ my $project = shift;
+
+ # 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 ($localconfig, $datadir);
+ if ($project && $project =~ /^(\w+)$/) {
+ $project = $1;
+ $localconfig = "localconfig.$project";
+ $datadir = "data/$project";
+ }
+ else {
+ $project = undef;
+ $localconfig = "localconfig";
+ $datadir = "data";
+ }
+
+ $datadir = "$libpath/$datadir";
+
+ # 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",
+ 'template_cache' => "$datadir/template",
+ 'project' => $project,
+ 'localconfig' => "$libpath/$localconfig",
+ 'datadir' => $datadir,
+ 'attachdir' => "$datadir/attachments",
+ 'skinsdir' => "$libpath/skins",
+ 'graphsdir' => "$libpath/graphs",
+
+ # $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' => "$datadir/webdot",
+ 'extensionsdir' => "$libpath/extensions",
+ 'assetsdir' => "$datadir/assets",
+ };
}
# This makes us not re-compute all the bz_locations data every time it's
# called.
-BEGIN { memoize('_bz_locations') };
+BEGIN { memoize('_bz_locations') }
1;
diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm
index 5bc83f9d6..c528e77d1 100644
--- a/Bugzilla/DB.pm
+++ b/Bugzilla/DB.pm
@@ -33,7 +33,7 @@ use Storable qw(dclone);
# Constants
#####################################################################
-use constant BLOB_TYPE => DBI::SQL_BLOB;
+use constant BLOB_TYPE => DBI::SQL_BLOB;
use constant ISOLATION_LEVEL => 'REPEATABLE READ';
# Set default values for what used to be the enum types. These values
@@ -46,14 +46,14 @@ use constant ISOLATION_LEVEL => 'REPEATABLE READ';
# Bugzilla with enums. After that, they are either controlled through
# the Bugzilla UI or through the DB.
use constant ENUM_DEFAULTS => {
- bug_severity => ['blocker', 'critical', 'major', 'normal',
- 'minor', 'trivial', 'enhancement'],
- priority => ["Highest", "High", "Normal", "Low", "Lowest", "---"],
- op_sys => ["All","Windows","Mac OS","Linux","Other"],
- rep_platform => ["All","PC","Macintosh","Other"],
- bug_status => ["UNCONFIRMED","CONFIRMED","IN_PROGRESS","RESOLVED",
- "VERIFIED"],
- resolution => ["","FIXED","INVALID","WONTFIX", "DUPLICATE","WORKSFORME"],
+ bug_severity =>
+ ['blocker', 'critical', 'major', 'normal', 'minor', 'trivial', 'enhancement'],
+ priority => ["Highest", "High", "Normal", "Low", "Lowest", "---"],
+ op_sys => ["All", "Windows", "Mac OS", "Linux", "Other"],
+ rep_platform => ["All", "PC", "Macintosh", "Other"],
+ bug_status =>
+ ["UNCONFIRMED", "CONFIRMED", "IN_PROGRESS", "RESOLVED", "VERIFIED"],
+ resolution => ["", "FIXED", "INVALID", "WONTFIX", "DUPLICATE", "WORKSFORME"],
};
# The character that means "OR" in a boolean fulltext search. If empty,
@@ -83,14 +83,14 @@ use constant WORD_END => '($|[^[:alnum:]])';
use constant INDEX_DROPS_REQUIRE_FK_DROPS => 1;
#####################################################################
-# Overridden Superclass Methods
+# Overridden Superclass Methods
#####################################################################
sub quote {
- my $self = shift;
- my $retval = $self->SUPER::quote(@_);
- trick_taint($retval) if defined $retval;
- return $retval;
+ my $self = shift;
+ my $retval = $self->SUPER::quote(@_);
+ trick_taint($retval) if defined $retval;
+ return $retval;
}
#####################################################################
@@ -98,95 +98,94 @@ sub quote {
#####################################################################
sub connect_shadow {
- my $params = Bugzilla->params;
- die "Tried to connect to non-existent shadowdb"
- unless $params->{'shadowdb'};
+ my $params = Bugzilla->params;
+ die "Tried to connect to non-existent shadowdb" unless $params->{'shadowdb'};
- # Instead of just passing in a new hashref, we locally modify the
- # values of "localconfig", because some drivers access it while
- # connecting.
- my %connect_params = %{ Bugzilla->localconfig };
- $connect_params{db_host} = $params->{'shadowdbhost'};
- $connect_params{db_name} = $params->{'shadowdb'};
- $connect_params{db_port} = $params->{'shadowdbport'};
- $connect_params{db_sock} = $params->{'shadowdbsock'};
+ # Instead of just passing in a new hashref, we locally modify the
+ # values of "localconfig", because some drivers access it while
+ # connecting.
+ my %connect_params = %{Bugzilla->localconfig};
+ $connect_params{db_host} = $params->{'shadowdbhost'};
+ $connect_params{db_name} = $params->{'shadowdb'};
+ $connect_params{db_port} = $params->{'shadowdbport'};
+ $connect_params{db_sock} = $params->{'shadowdbsock'};
- return _connect(\%connect_params);
+ return _connect(\%connect_params);
}
sub connect_main {
- my $lc = Bugzilla->localconfig;
- return _connect(Bugzilla->localconfig);
+ my $lc = Bugzilla->localconfig;
+ return _connect(Bugzilla->localconfig);
}
sub _connect {
- my ($params) = @_;
+ my ($params) = @_;
- my $driver = $params->{db_driver};
- my $pkg_module = DB_MODULE->{lc($driver)}->{db};
+ my $driver = $params->{db_driver};
+ my $pkg_module = DB_MODULE->{lc($driver)}->{db};
- # do the actual import
- eval ("require $pkg_module")
- || die ("'$driver' is not a valid choice for \$db_driver in "
- . " localconfig: " . $@);
+ # do the actual import
+ eval("require $pkg_module")
+ || die(
+ "'$driver' is not a valid choice for \$db_driver in " . " localconfig: " . $@);
- # instantiate the correct DB specific module
- my $dbh = $pkg_module->new($params);
+ # instantiate the correct DB specific module
+ my $dbh = $pkg_module->new($params);
- return $dbh;
+ return $dbh;
}
sub _handle_error {
- require Carp;
+ require Carp;
- # Cut down the error string to a reasonable size
- $_[0] = substr($_[0], 0, 2000) . ' ... ' . substr($_[0], -2000)
- if length($_[0]) > 4000;
- $_[0] = Carp::longmess($_[0]);
- return 0; # Now let DBI handle raising the error
+ # Cut down the error string to a reasonable size
+ $_[0] = substr($_[0], 0, 2000) . ' ... ' . substr($_[0], -2000)
+ if length($_[0]) > 4000;
+ $_[0] = Carp::longmess($_[0]);
+ return 0; # Now let DBI handle raising the error
}
sub bz_check_requirements {
- my ($output) = @_;
+ my ($output) = @_;
- my $lc = Bugzilla->localconfig;
- my $db = DB_MODULE->{lc($lc->{db_driver})};
+ my $lc = Bugzilla->localconfig;
+ my $db = DB_MODULE->{lc($lc->{db_driver})};
- # Only certain values are allowed for $db_driver.
- if (!defined $db) {
- die "$lc->{db_driver} is not a valid choice for \$db_driver in"
- . bz_locations()->{'localconfig'};
- }
+ # Only certain values are allowed for $db_driver.
+ if (!defined $db) {
+ die "$lc->{db_driver} is not a valid choice for \$db_driver in"
+ . bz_locations()->{'localconfig'};
+ }
- # Check the existence and version of the DBD that we need.
- my $dbd = $db->{dbd};
- _bz_check_dbd($db, $output);
+ # Check the existence and version of the DBD that we need.
+ my $dbd = $db->{dbd};
+ _bz_check_dbd($db, $output);
- # We don't try to connect to the actual database if $db_check is
- # disabled.
- unless ($lc->{db_check}) {
- print "\n" if $output;
- return;
- }
+ # We don't try to connect to the actual database if $db_check is
+ # disabled.
+ unless ($lc->{db_check}) {
+ print "\n" if $output;
+ return;
+ }
- # And now check the version of the database server itself.
- my $dbh = _get_no_db_connection();
- $dbh->bz_check_server_version($db, $output);
+ # And now check the version of the database server itself.
+ my $dbh = _get_no_db_connection();
+ $dbh->bz_check_server_version($db, $output);
- print "\n" if $output;
+ print "\n" if $output;
}
sub _bz_check_dbd {
- my ($db, $output) = @_;
+ my ($db, $output) = @_;
- my $dbd = $db->{dbd};
- unless (have_vers($dbd, $output)) {
- my $sql_server = $db->{name};
- my $command = install_command($dbd);
- my $root = ROOT_USER;
- my $dbd_mod = $dbd->{module};
- my $dbd_ver = $dbd->{version};
- die <<EOT;
+ my $dbd = $db->{dbd};
+ unless (have_vers($dbd, $output)) {
+ my $sql_server = $db->{name};
+ my $command = install_command($dbd);
+ my $root = ROOT_USER;
+ my $dbd_mod = $dbd->{module};
+ my $dbd_ver = $dbd->{version};
+ die <<EOT;
For $sql_server, Bugzilla requires that perl's $dbd_mod $dbd_ver or later be
installed. To install this module, run the following command (as $root):
@@ -194,103 +193,107 @@ installed. To install this module, run the following command (as $root):
$command
EOT
- }
+ }
}
sub bz_check_server_version {
- my ($self, $db, $output) = @_;
+ my ($self, $db, $output) = @_;
- my $sql_vers = $self->bz_server_version;
- $self->disconnect;
+ my $sql_vers = $self->bz_server_version;
+ $self->disconnect;
- my $sql_want = $db->{db_version};
- my $version_ok = vers_cmp($sql_vers, $sql_want) > -1 ? 1 : 0;
+ my $sql_want = $db->{db_version};
+ my $version_ok = vers_cmp($sql_vers, $sql_want) > -1 ? 1 : 0;
- my $sql_server = $db->{name};
- if ($output) {
- Bugzilla::Install::Requirements::_checking_for({
- package => $sql_server, wanted => $sql_want,
- found => $sql_vers, ok => $version_ok });
- }
+ my $sql_server = $db->{name};
+ if ($output) {
+ Bugzilla::Install::Requirements::_checking_for({
+ package => $sql_server,
+ wanted => $sql_want,
+ found => $sql_vers,
+ ok => $version_ok
+ });
+ }
- # Check what version of the database server is installed and let
- # the user know if the version is too old to be used with Bugzilla.
- if (!$version_ok) {
- die <<EOT;
+ # Check what version of the database server is installed and let
+ # the user know if the version is too old to be used with Bugzilla.
+ if (!$version_ok) {
+ die <<EOT;
Your $sql_server v$sql_vers is too old. Bugzilla requires version
$sql_want or later of $sql_server. Please download and install a
newer version.
EOT
- }
+ }
- # This is used by subclasses.
- return $sql_vers;
+ # This is used by subclasses.
+ return $sql_vers;
}
# Note that this function requires that localconfig exist and
# be valid.
sub bz_create_database {
- my $dbh;
- # See if we can connect to the actual Bugzilla database.
- my $conn_success = eval { $dbh = connect_main() };
- my $db_name = Bugzilla->localconfig->{db_name};
-
- if (!$conn_success) {
- $dbh = _get_no_db_connection();
- say "Creating database $db_name...";
-
- # Try to create the DB, and if we fail print a friendly error.
- my $success = eval {
- my @sql = $dbh->_bz_schema->get_create_database_sql($db_name);
- # This ends with 1 because this particular do doesn't always
- # return something.
- $dbh->do($_) foreach @sql; 1;
- };
- if (!$success) {
- my $error = $dbh->errstr || $@;
- chomp($error);
- die "The '$db_name' database could not be created.",
- " The error returned was:\n\n $error\n\n",
- _bz_connect_error_reasons();
- }
+ my $dbh;
+
+ # See if we can connect to the actual Bugzilla database.
+ my $conn_success = eval { $dbh = connect_main() };
+ my $db_name = Bugzilla->localconfig->{db_name};
+
+ if (!$conn_success) {
+ $dbh = _get_no_db_connection();
+ say "Creating database $db_name...";
+
+ # Try to create the DB, and if we fail print a friendly error.
+ my $success = eval {
+ my @sql = $dbh->_bz_schema->get_create_database_sql($db_name);
+
+ # This ends with 1 because this particular do doesn't always
+ # return something.
+ $dbh->do($_) foreach @sql;
+ 1;
+ };
+ if (!$success) {
+ my $error = $dbh->errstr || $@;
+ chomp($error);
+ die "The '$db_name' database could not be created.",
+ " The error returned was:\n\n $error\n\n", _bz_connect_error_reasons();
}
+ }
- $dbh->disconnect;
+ $dbh->disconnect;
}
# A helper for bz_create_database and bz_check_requirements.
sub _get_no_db_connection {
- my ($sql_server) = @_;
- my $dbh;
- my %connect_params = %{ Bugzilla->localconfig };
- $connect_params{db_name} = '';
- my $conn_success = eval {
- $dbh = _connect(\%connect_params);
- };
- if (!$conn_success) {
- my $driver = $connect_params{db_driver};
- my $sql_server = DB_MODULE->{lc($driver)}->{name};
- # Can't use $dbh->errstr because $dbh is undef.
- my $error = $DBI::errstr || $@;
- chomp($error);
- die "There was an error connecting to $sql_server:\n\n",
- " $error\n\n", _bz_connect_error_reasons(), "\n";
- }
- return $dbh;
+ my ($sql_server) = @_;
+ my $dbh;
+ my %connect_params = %{Bugzilla->localconfig};
+ $connect_params{db_name} = '';
+ my $conn_success = eval { $dbh = _connect(\%connect_params); };
+ if (!$conn_success) {
+ my $driver = $connect_params{db_driver};
+ my $sql_server = DB_MODULE->{lc($driver)}->{name};
+
+ # Can't use $dbh->errstr because $dbh is undef.
+ my $error = $DBI::errstr || $@;
+ chomp($error);
+ die "There was an error connecting to $sql_server:\n\n", " $error\n\n",
+ _bz_connect_error_reasons(), "\n";
+ }
+ return $dbh;
}
# Just a helper because we have to re-use this text.
# We don't use this in db_new because it gives away the database
# username, and db_new errors can show up on CGIs.
sub _bz_connect_error_reasons {
- my $lc_file = bz_locations()->{'localconfig'};
- my $lc = Bugzilla->localconfig;
- my $db = DB_MODULE->{lc($lc->{db_driver})};
- my $server = $db->{name};
+ my $lc_file = bz_locations()->{'localconfig'};
+ my $lc = Bugzilla->localconfig;
+ my $db = DB_MODULE->{lc($lc->{db_driver})};
+ my $server = $db->{name};
-return <<EOT;
+ return <<EOT;
This might have several reasons:
* $server is not running.
@@ -309,154 +312,157 @@ EOT
# List of abstract methods we are checking the derived class implements
our @_abstract_methods = qw(new sql_regexp sql_not_regexp sql_limit sql_to_days
- sql_date_format sql_date_math bz_explain
- sql_group_concat);
+ sql_date_format sql_date_math bz_explain
+ sql_group_concat);
# This overridden import method will check implementation of inherited classes
# for missing implementation of abstract methods
# See http://perlmonks.thepen.com/44265.html
sub import {
- my $pkg = shift;
-
- # do not check this module
- if ($pkg ne __PACKAGE__) {
- # make sure all abstract methods are implemented
- foreach my $meth (@_abstract_methods) {
- $pkg->can($meth)
- or die("Class $pkg does not define method $meth");
- }
+ my $pkg = shift;
+
+ # do not check this module
+ if ($pkg ne __PACKAGE__) {
+
+ # make sure all abstract methods are implemented
+ foreach my $meth (@_abstract_methods) {
+ $pkg->can($meth) or die("Class $pkg does not define method $meth");
}
+ }
- # Now we want to call our superclass implementation.
- # If our superclass is Exporter, which is using caller() to find
- # a namespace to populate, we need to adjust for this extra call.
- # All this can go when we stop using deprecated functions.
- my $is_exporter = $pkg->isa('Exporter');
- $Exporter::ExportLevel++ if $is_exporter;
- $pkg->SUPER::import(@_);
- $Exporter::ExportLevel-- if $is_exporter;
+ # Now we want to call our superclass implementation.
+ # If our superclass is Exporter, which is using caller() to find
+ # a namespace to populate, we need to adjust for this extra call.
+ # All this can go when we stop using deprecated functions.
+ my $is_exporter = $pkg->isa('Exporter');
+ $Exporter::ExportLevel++ if $is_exporter;
+ $pkg->SUPER::import(@_);
+ $Exporter::ExportLevel-- if $is_exporter;
}
sub sql_istrcmp {
- my ($self, $left, $right, $op) = @_;
- $op ||= "=";
+ my ($self, $left, $right, $op) = @_;
+ $op ||= "=";
- return $self->sql_istring($left) . " $op " . $self->sql_istring($right);
+ return $self->sql_istring($left) . " $op " . $self->sql_istring($right);
}
sub sql_istring {
- my ($self, $string) = @_;
+ my ($self, $string) = @_;
- return "LOWER($string)";
+ return "LOWER($string)";
}
sub sql_iposition {
- my ($self, $fragment, $text) = @_;
- $fragment = $self->sql_istring($fragment);
- $text = $self->sql_istring($text);
- return $self->sql_position($fragment, $text);
+ my ($self, $fragment, $text) = @_;
+ $fragment = $self->sql_istring($fragment);
+ $text = $self->sql_istring($text);
+ return $self->sql_position($fragment, $text);
}
sub sql_position {
- my ($self, $fragment, $text) = @_;
+ my ($self, $fragment, $text) = @_;
- return "POSITION($fragment IN $text)";
+ return "POSITION($fragment IN $text)";
}
sub sql_like {
- my ($self, $fragment, $column) = @_;
+ my ($self, $fragment, $column) = @_;
- my $quoted = $self->quote($fragment);
+ my $quoted = $self->quote($fragment);
- return $self->sql_position($quoted, $column) . " > 0";
+ return $self->sql_position($quoted, $column) . " > 0";
}
sub sql_ilike {
- my ($self, $fragment, $column) = @_;
+ my ($self, $fragment, $column) = @_;
- my $quoted = $self->quote($fragment);
+ my $quoted = $self->quote($fragment);
- return $self->sql_iposition($quoted, $column) . " > 0";
+ return $self->sql_iposition($quoted, $column) . " > 0";
}
sub sql_not_ilike {
- my ($self, $fragment, $column) = @_;
+ my ($self, $fragment, $column) = @_;
- my $quoted = $self->quote($fragment);
+ my $quoted = $self->quote($fragment);
- return $self->sql_iposition($quoted, $column) . " = 0";
+ return $self->sql_iposition($quoted, $column) . " = 0";
}
sub sql_group_by {
- my ($self, $needed_columns, $optional_columns) = @_;
+ my ($self, $needed_columns, $optional_columns) = @_;
- my $expression = "GROUP BY $needed_columns";
- $expression .= ", " . $optional_columns if $optional_columns;
-
- return $expression;
+ my $expression = "GROUP BY $needed_columns";
+ $expression .= ", " . $optional_columns if $optional_columns;
+
+ return $expression;
}
sub sql_string_concat {
- my ($self, @params) = @_;
-
- return '(' . join(' || ', @params) . ')';
+ my ($self, @params) = @_;
+
+ return '(' . join(' || ', @params) . ')';
}
sub sql_string_until {
- my ($self, $string, $substring) = @_;
+ my ($self, $string, $substring) = @_;
- my $position = $self->sql_position($substring, $string);
- return "CASE WHEN $position != 0"
- . " THEN SUBSTR($string, 1, $position - 1)"
- . " ELSE $string END";
+ my $position = $self->sql_position($substring, $string);
+ return
+ "CASE WHEN $position != 0"
+ . " THEN SUBSTR($string, 1, $position - 1)"
+ . " ELSE $string END";
}
sub sql_in {
- my ($self, $column_name, $in_list_ref, $negate) = @_;
- return " $column_name "
- . ($negate ? "NOT " : "")
- . "IN (" . join(',', @$in_list_ref) . ") ";
+ my ($self, $column_name, $in_list_ref, $negate) = @_;
+ return
+ " $column_name "
+ . ($negate ? "NOT " : "") . "IN ("
+ . join(',', @$in_list_ref) . ") ";
}
sub sql_fulltext_search {
- my ($self, $column, $text) = @_;
-
- # This is as close as we can get to doing full text search using
- # standard ANSI SQL, without real full text search support. DB specific
- # modules should override this, as this will be always much slower.
-
- # make the string lowercase to do case insensitive search
- my $lower_text = lc($text);
-
- # split the text we're searching for into separate words. As a hack
- # to allow quicksearch to work, if the field starts and ends with
- # a double-quote, then we don't split it into words. We can't use
- # Text::ParseWords here because it gets very confused by unbalanced
- # quotes, which breaks searches like "don't try this" (because of the
- # unbalanced single-quote in "don't").
- my @words;
- if ($lower_text =~ /^"/ and $lower_text =~ /"$/) {
- $lower_text =~ s/^"//;
- $lower_text =~ s/"$//;
- @words = ($lower_text);
- }
- else {
- @words = split(/\s+/, $lower_text);
- }
-
- # surround the words with wildcards and SQL quotes so we can use them
- # in LIKE search clauses
- @words = map($self->quote("\%$_\%"), @words);
-
- # untaint words, since they are safe to use now that we've quoted them
- trick_taint($_) foreach @words;
-
- # turn the words into a set of LIKE search clauses
- @words = map("LOWER($column) LIKE $_", @words);
-
- # search for occurrences of all specified words in the column
- return join (" AND ", @words), "CASE WHEN (" . join(" AND ", @words) . ") THEN 1 ELSE 0 END";
+ my ($self, $column, $text) = @_;
+
+ # This is as close as we can get to doing full text search using
+ # standard ANSI SQL, without real full text search support. DB specific
+ # modules should override this, as this will be always much slower.
+
+ # make the string lowercase to do case insensitive search
+ my $lower_text = lc($text);
+
+ # split the text we're searching for into separate words. As a hack
+ # to allow quicksearch to work, if the field starts and ends with
+ # a double-quote, then we don't split it into words. We can't use
+ # Text::ParseWords here because it gets very confused by unbalanced
+ # quotes, which breaks searches like "don't try this" (because of the
+ # unbalanced single-quote in "don't").
+ my @words;
+ if ($lower_text =~ /^"/ and $lower_text =~ /"$/) {
+ $lower_text =~ s/^"//;
+ $lower_text =~ s/"$//;
+ @words = ($lower_text);
+ }
+ else {
+ @words = split(/\s+/, $lower_text);
+ }
+
+ # surround the words with wildcards and SQL quotes so we can use them
+ # in LIKE search clauses
+ @words = map($self->quote("\%$_\%"), @words);
+
+ # untaint words, since they are safe to use now that we've quoted them
+ trick_taint($_) foreach @words;
+
+ # turn the words into a set of LIKE search clauses
+ @words = map("LOWER($column) LIKE $_", @words);
+
+ # search for occurrences of all specified words in the column
+ return join(" AND ", @words),
+ "CASE WHEN (" . join(" AND ", @words) . ") THEN 1 ELSE 0 END";
}
#####################################################################
@@ -465,24 +471,27 @@ sub sql_fulltext_search {
# XXX - Needs to be documented.
sub bz_server_version {
- my ($self) = @_;
- return $self->get_info(18); # SQL_DBMS_VER
+ my ($self) = @_;
+ return $self->get_info(18); # SQL_DBMS_VER
}
sub bz_last_key {
- my ($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- return $self->last_insert_id(Bugzilla->localconfig->{db_name}, undef,
- $table, $column);
+ return $self->last_insert_id(Bugzilla->localconfig->{db_name},
+ undef, $table, $column);
}
sub bz_check_regexp {
- my ($self, $pattern) = @_;
+ my ($self, $pattern) = @_;
- eval { $self->do("SELECT " . $self->sql_regexp($self->quote("a"), $pattern, 1)) };
+ eval {
+ $self->do("SELECT " . $self->sql_regexp($self->quote("a"), $pattern, 1));
+ };
- $@ && ThrowUserError('illegal_regexp',
- { value => $pattern, dberror => $self->errstr });
+ $@
+ && ThrowUserError('illegal_regexp',
+ {value => $pattern, dberror => $self->errstr});
}
#####################################################################
@@ -490,99 +499,100 @@ sub bz_check_regexp {
#####################################################################
sub bz_setup_database {
- my ($self) = @_;
-
- # If we haven't ever stored a serialized schema,
- # set up the bz_schema table and store it.
- $self->_bz_init_schema_storage();
-
- # We don't use bz_table_list here, because that uses _bz_real_schema.
- # We actually want the table list from the ABSTRACT_SCHEMA in
- # Bugzilla::DB::Schema.
- my @desired_tables = $self->_bz_schema->get_table_list();
- my $bugs_exists = $self->bz_table_info('bugs');
- if (!$bugs_exists) {
- say install_string('db_table_setup');
- }
+ my ($self) = @_;
- foreach my $table_name (@desired_tables) {
- $self->bz_add_table($table_name, { silently => !$bugs_exists });
- }
+ # If we haven't ever stored a serialized schema,
+ # set up the bz_schema table and store it.
+ $self->_bz_init_schema_storage();
+
+ # We don't use bz_table_list here, because that uses _bz_real_schema.
+ # We actually want the table list from the ABSTRACT_SCHEMA in
+ # Bugzilla::DB::Schema.
+ my @desired_tables = $self->_bz_schema->get_table_list();
+ my $bugs_exists = $self->bz_table_info('bugs');
+ if (!$bugs_exists) {
+ say install_string('db_table_setup');
+ }
+
+ foreach my $table_name (@desired_tables) {
+ $self->bz_add_table($table_name, {silently => !$bugs_exists});
+ }
}
# This really just exists to get overridden in Bugzilla::DB::Mysql.
sub bz_enum_initial_values {
- return ENUM_DEFAULTS;
+ return ENUM_DEFAULTS;
}
sub bz_populate_enum_tables {
- my ($self) = @_;
+ my ($self) = @_;
- my $any_severities = $self->selectrow_array(
- 'SELECT 1 FROM bug_severity ' . $self->sql_limit(1));
- print install_string('db_enum_setup'), "\n " if !$any_severities;
+ my $any_severities
+ = $self->selectrow_array('SELECT 1 FROM bug_severity ' . $self->sql_limit(1));
+ print install_string('db_enum_setup'), "\n " if !$any_severities;
- my $enum_values = $self->bz_enum_initial_values();
- while (my ($table, $values) = each %$enum_values) {
- $self->_bz_populate_enum_table($table, $values);
- }
+ my $enum_values = $self->bz_enum_initial_values();
+ while (my ($table, $values) = each %$enum_values) {
+ $self->_bz_populate_enum_table($table, $values);
+ }
- print "\n" if !$any_severities;
+ print "\n" if !$any_severities;
}
sub bz_setup_foreign_keys {
- my ($self) = @_;
-
- # profiles_activity was the first table to get foreign keys,
- # so if it doesn't have them, then we're setting up FKs
- # for the first time, and should be quieter about it.
- my $activity_fk = $self->bz_fk_info('profiles_activity', 'userid');
- my $any_fks = $activity_fk && $activity_fk->{created};
- if (!$any_fks) {
- say get_text('install_fk_setup');
- }
+ my ($self) = @_;
+
+ # profiles_activity was the first table to get foreign keys,
+ # so if it doesn't have them, then we're setting up FKs
+ # for the first time, and should be quieter about it.
+ my $activity_fk = $self->bz_fk_info('profiles_activity', 'userid');
+ my $any_fks = $activity_fk && $activity_fk->{created};
+ if (!$any_fks) {
+ say get_text('install_fk_setup');
+ }
+
+ my @tables = $self->bz_table_list();
+ foreach my $table (@tables) {
+ my @columns = $self->bz_table_columns($table);
+ my %add_fks;
+ foreach my $column (@columns) {
- my @tables = $self->bz_table_list();
- foreach my $table (@tables) {
- my @columns = $self->bz_table_columns($table);
- my %add_fks;
- foreach my $column (@columns) {
- # First we check for any FKs that have created => 0,
- # in the _bz_real_schema. This also picks up FKs with
- # created => 1, but bz_add_fks will ignore those.
- my $fk = $self->bz_fk_info($table, $column);
- # Then we check the abstract schema to see if there
- # should be an FK on this column, but one wasn't set in the
- # _bz_real_schema for some reason. We do this to handle
- # various problems caused by upgrading from versions
- # prior to 4.2, and also to handle problems caused
- # by enabling an extension pre-4.2, disabling it for
- # the 4.2 upgrade, and then re-enabling it later.
- unless ($fk && $fk->{created}) {
- my $standard_def =
- $self->_bz_schema->get_column_abstract($table, $column);
- if (exists $standard_def->{REFERENCES}) {
- $fk = dclone($standard_def->{REFERENCES});
- }
- }
-
- $add_fks{$column} = $fk if $fk;
+ # First we check for any FKs that have created => 0,
+ # in the _bz_real_schema. This also picks up FKs with
+ # created => 1, but bz_add_fks will ignore those.
+ my $fk = $self->bz_fk_info($table, $column);
+
+ # Then we check the abstract schema to see if there
+ # should be an FK on this column, but one wasn't set in the
+ # _bz_real_schema for some reason. We do this to handle
+ # various problems caused by upgrading from versions
+ # prior to 4.2, and also to handle problems caused
+ # by enabling an extension pre-4.2, disabling it for
+ # the 4.2 upgrade, and then re-enabling it later.
+ unless ($fk && $fk->{created}) {
+ my $standard_def = $self->_bz_schema->get_column_abstract($table, $column);
+ if (exists $standard_def->{REFERENCES}) {
+ $fk = dclone($standard_def->{REFERENCES});
}
- $self->bz_add_fks($table, \%add_fks, { silently => !$any_fks });
+ }
+
+ $add_fks{$column} = $fk if $fk;
}
+ $self->bz_add_fks($table, \%add_fks, {silently => !$any_fks});
+ }
}
# This is used by contrib/bzdbcopy.pl, mostly.
sub bz_drop_foreign_keys {
- my ($self) = @_;
+ my ($self) = @_;
- my @tables = $self->bz_table_list();
- foreach my $table (@tables) {
- my @columns = $self->bz_table_columns($table);
- foreach my $column (@columns) {
- $self->bz_drop_fk($table, $column);
- }
+ my @tables = $self->bz_table_list();
+ foreach my $table (@tables) {
+ my @columns = $self->bz_table_columns($table);
+ foreach my $column (@columns) {
+ $self->bz_drop_fk($table, $column);
}
+ }
}
#####################################################################
@@ -590,119 +600,121 @@ sub bz_drop_foreign_keys {
#####################################################################
sub bz_add_column {
- my ($self, $table, $name, $new_def, $init_value) = @_;
-
- # You can't add a NOT NULL column to a table with
- # no DEFAULT statement, unless you have an init_value.
- # SERIAL types are an exception, though, because they can
- # auto-populate.
- if ( $new_def->{NOTNULL} && !exists $new_def->{DEFAULT}
- && !defined $init_value && $new_def->{TYPE} !~ /SERIAL/)
- {
- ThrowCodeError('column_not_null_without_default',
- { name => "$table.$name" });
+ my ($self, $table, $name, $new_def, $init_value) = @_;
+
+ # You can't add a NOT NULL column to a table with
+ # no DEFAULT statement, unless you have an init_value.
+ # SERIAL types are an exception, though, because they can
+ # auto-populate.
+ if ( $new_def->{NOTNULL}
+ && !exists $new_def->{DEFAULT}
+ && !defined $init_value
+ && $new_def->{TYPE} !~ /SERIAL/)
+ {
+ ThrowCodeError('column_not_null_without_default', {name => "$table.$name"});
+ }
+
+ my $current_def = $self->bz_column_info($table, $name);
+
+ if (!$current_def) {
+
+ # REFERENCES need to happen later and not be created right away
+ my $trimmed_def = dclone($new_def);
+ delete $trimmed_def->{REFERENCES};
+ my @statements
+ = $self->_bz_real_schema->get_add_column_ddl($table, $name, $trimmed_def,
+ defined $init_value ? $self->quote($init_value) : undef);
+ print get_text('install_column_add', {column => $name, table => $table}) . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ foreach my $sql (@statements) {
+ $self->do($sql);
}
- my $current_def = $self->bz_column_info($table, $name);
-
- if (!$current_def) {
- # REFERENCES need to happen later and not be created right away
- my $trimmed_def = dclone($new_def);
- delete $trimmed_def->{REFERENCES};
- my @statements = $self->_bz_real_schema->get_add_column_ddl(
- $table, $name, $trimmed_def,
- defined $init_value ? $self->quote($init_value) : undef);
- print get_text('install_column_add',
- { column => $name, table => $table }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- foreach my $sql (@statements) {
- $self->do($sql);
- }
-
- # To make things easier for callers, if they don't specify
- # a REFERENCES item, we pull it from the _bz_schema if the
- # column exists there and has a REFERENCES item.
- # bz_setup_foreign_keys will then add this FK at the end of
- # Install::DB.
- my $col_abstract =
- $self->_bz_schema->get_column_abstract($table, $name);
- if (exists $col_abstract->{REFERENCES}) {
- my $new_fk = dclone($col_abstract->{REFERENCES});
- $new_fk->{created} = 0;
- $new_def->{REFERENCES} = $new_fk;
- }
-
- $self->_bz_real_schema->set_column($table, $name, $new_def);
- $self->_bz_store_real_schema;
+ # To make things easier for callers, if they don't specify
+ # a REFERENCES item, we pull it from the _bz_schema if the
+ # column exists there and has a REFERENCES item.
+ # bz_setup_foreign_keys will then add this FK at the end of
+ # Install::DB.
+ my $col_abstract = $self->_bz_schema->get_column_abstract($table, $name);
+ if (exists $col_abstract->{REFERENCES}) {
+ my $new_fk = dclone($col_abstract->{REFERENCES});
+ $new_fk->{created} = 0;
+ $new_def->{REFERENCES} = $new_fk;
}
+
+ $self->_bz_real_schema->set_column($table, $name, $new_def);
+ $self->_bz_store_real_schema;
+ }
}
sub bz_add_fk {
- my ($self, $table, $column, $def) = @_;
- $self->bz_add_fks($table, { $column => $def });
+ my ($self, $table, $column, $def) = @_;
+ $self->bz_add_fks($table, {$column => $def});
}
sub bz_add_fks {
- my ($self, $table, $column_fks, $options) = @_;
-
- my %add_these;
- foreach my $column (keys %$column_fks) {
- my $current_fk = $self->bz_fk_info($table, $column);
- next if ($current_fk and $current_fk->{created});
- my $new_fk = $column_fks->{$column};
- $self->_check_references($table, $column, $new_fk);
- $add_these{$column} = $new_fk;
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE
- and !$options->{silently})
- {
- print get_text('install_fk_add',
- { table => $table, column => $column,
- fk => $new_fk }), "\n";
- }
+ my ($self, $table, $column_fks, $options) = @_;
+
+ my %add_these;
+ foreach my $column (keys %$column_fks) {
+ my $current_fk = $self->bz_fk_info($table, $column);
+ next if ($current_fk and $current_fk->{created});
+ my $new_fk = $column_fks->{$column};
+ $self->_check_references($table, $column, $new_fk);
+ $add_these{$column} = $new_fk;
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE and !$options->{silently}) {
+ print get_text(
+ 'install_fk_add', {table => $table, column => $column, fk => $new_fk}
+ ),
+ "\n";
}
+ }
- return if !scalar(keys %add_these);
+ return if !scalar(keys %add_these);
- my @sql = $self->_bz_real_schema->get_add_fks_sql($table, \%add_these);
- $self->do($_) foreach @sql;
+ my @sql = $self->_bz_real_schema->get_add_fks_sql($table, \%add_these);
+ $self->do($_) foreach @sql;
- foreach my $column (keys %add_these) {
- my $fk_def = $add_these{$column};
- $fk_def->{created} = 1;
- $self->_bz_real_schema->set_fk($table, $column, $fk_def);
- }
+ foreach my $column (keys %add_these) {
+ my $fk_def = $add_these{$column};
+ $fk_def->{created} = 1;
+ $self->_bz_real_schema->set_fk($table, $column, $fk_def);
+ }
- $self->_bz_store_real_schema();
+ $self->_bz_store_real_schema();
}
sub bz_alter_column {
- my ($self, $table, $name, $new_def, $set_nulls_to) = @_;
+ my ($self, $table, $name, $new_def, $set_nulls_to) = @_;
- my $current_def = $self->bz_column_info($table, $name);
+ my $current_def = $self->bz_column_info($table, $name);
- if (!$self->_bz_schema->columns_equal($current_def, $new_def)) {
- # You can't change a column to be NOT NULL if you have no DEFAULT
- # and no value for $set_nulls_to, if there are any NULL values
- # in that column.
- if ($new_def->{NOTNULL} &&
- !exists $new_def->{DEFAULT} && !defined $set_nulls_to)
- {
- # Check for NULLs
- my $any_nulls = $self->selectrow_array(
- "SELECT 1 FROM $table WHERE $name IS NULL");
- ThrowCodeError('column_not_null_no_default_alter',
- { name => "$table.$name" }) if ($any_nulls);
- }
- # Preserve foreign key definitions in the Schema object when altering
- # types.
- if (my $fk = $self->bz_fk_info($table, $name)) {
- $new_def->{REFERENCES} = $fk;
- }
- $self->bz_alter_column_raw($table, $name, $new_def, $current_def,
- $set_nulls_to);
- $self->_bz_real_schema->set_column($table, $name, $new_def);
- $self->_bz_store_real_schema;
+ if (!$self->_bz_schema->columns_equal($current_def, $new_def)) {
+
+ # You can't change a column to be NOT NULL if you have no DEFAULT
+ # and no value for $set_nulls_to, if there are any NULL values
+ # in that column.
+ if ( $new_def->{NOTNULL}
+ && !exists $new_def->{DEFAULT}
+ && !defined $set_nulls_to)
+ {
+ # Check for NULLs
+ my $any_nulls
+ = $self->selectrow_array("SELECT 1 FROM $table WHERE $name IS NULL");
+ ThrowCodeError('column_not_null_no_default_alter', {name => "$table.$name"})
+ if ($any_nulls);
+ }
+
+ # Preserve foreign key definitions in the Schema object when altering
+ # types.
+ if (my $fk = $self->bz_fk_info($table, $name)) {
+ $new_def->{REFERENCES} = $fk;
}
+ $self->bz_alter_column_raw($table, $name, $new_def, $current_def,
+ $set_nulls_to);
+ $self->_bz_real_schema->set_column($table, $name, $new_def);
+ $self->_bz_store_real_schema;
+ }
}
@@ -728,39 +740,40 @@ sub bz_alter_column {
# Returns: nothing
#
sub bz_alter_column_raw {
- my ($self, $table, $name, $new_def, $current_def, $set_nulls_to) = @_;
- my @statements = $self->_bz_real_schema->get_alter_column_ddl(
- $table, $name, $new_def,
- defined $set_nulls_to ? $self->quote($set_nulls_to) : undef);
- my $new_ddl = $self->_bz_schema->get_type_ddl($new_def);
- say "Updating column $name in table $table ...";
- if (defined $current_def) {
- my $old_ddl = $self->_bz_schema->get_type_ddl($current_def);
- say "Old: $old_ddl";
- }
- say "New: $new_ddl";
- $self->do($_) foreach (@statements);
+ my ($self, $table, $name, $new_def, $current_def, $set_nulls_to) = @_;
+ my @statements
+ = $self->_bz_real_schema->get_alter_column_ddl($table, $name, $new_def,
+ defined $set_nulls_to ? $self->quote($set_nulls_to) : undef);
+ my $new_ddl = $self->_bz_schema->get_type_ddl($new_def);
+ say "Updating column $name in table $table ...";
+ if (defined $current_def) {
+ my $old_ddl = $self->_bz_schema->get_type_ddl($current_def);
+ say "Old: $old_ddl";
+ }
+ say "New: $new_ddl";
+ $self->do($_) foreach (@statements);
}
sub bz_alter_fk {
- my ($self, $table, $column, $fk_def) = @_;
- my $current_fk = $self->bz_fk_info($table, $column);
- ThrowCodeError('column_alter_nonexistent_fk',
- { table => $table, column => $column }) if !$current_fk;
- $self->bz_drop_fk($table, $column);
- $self->bz_add_fk($table, $column, $fk_def);
+ my ($self, $table, $column, $fk_def) = @_;
+ my $current_fk = $self->bz_fk_info($table, $column);
+ ThrowCodeError('column_alter_nonexistent_fk',
+ {table => $table, column => $column})
+ if !$current_fk;
+ $self->bz_drop_fk($table, $column);
+ $self->bz_add_fk($table, $column, $fk_def);
}
sub bz_add_index {
- my ($self, $table, $name, $definition) = @_;
+ my ($self, $table, $name, $definition) = @_;
- my $index_exists = $self->bz_index_info($table, $name);
+ my $index_exists = $self->bz_index_info($table, $name);
- if (!$index_exists) {
- $self->bz_add_index_raw($table, $name, $definition);
- $self->_bz_real_schema->set_index($table, $name, $definition);
- $self->_bz_store_real_schema;
- }
+ if (!$index_exists) {
+ $self->bz_add_index_raw($table, $name, $definition);
+ $self->_bz_real_schema->set_index($table, $name, $definition);
+ $self->_bz_store_real_schema;
+ }
}
# bz_add_index_raw($table, $name, $silent)
@@ -780,36 +793,36 @@ sub bz_add_index {
# Returns: nothing
#
sub bz_add_index_raw {
- my ($self, $table, $name, $definition, $silent) = @_;
- my @statements = $self->_bz_schema->get_add_index_ddl(
- $table, $name, $definition);
- print "Adding new index '$name' to the $table table ...\n" unless $silent;
- $self->do($_) foreach (@statements);
+ my ($self, $table, $name, $definition, $silent) = @_;
+ my @statements
+ = $self->_bz_schema->get_add_index_ddl($table, $name, $definition);
+ print "Adding new index '$name' to the $table table ...\n" unless $silent;
+ $self->do($_) foreach (@statements);
}
sub bz_add_table {
- my ($self, $name, $options) = @_;
-
- my $table_exists = $self->bz_table_info($name);
-
- if (!$table_exists) {
- $self->_bz_add_table_raw($name, $options);
- my $table_def = dclone($self->_bz_schema->get_table_abstract($name));
-
- my %fields = @{$table_def->{FIELDS}};
- foreach my $col (keys %fields) {
- # Foreign Key references have to be added by Install::DB after
- # initial table creation, because column names have changed
- # over history and it's impossible to keep track of that info
- # in ABSTRACT_SCHEMA.
- next unless exists $fields{$col}->{REFERENCES};
- $fields{$col}->{REFERENCES}->{created} =
- $self->_bz_real_schema->FK_ON_CREATE;
- }
-
- $self->_bz_real_schema->add_table($name, $table_def);
- $self->_bz_store_real_schema;
+ my ($self, $name, $options) = @_;
+
+ my $table_exists = $self->bz_table_info($name);
+
+ if (!$table_exists) {
+ $self->_bz_add_table_raw($name, $options);
+ my $table_def = dclone($self->_bz_schema->get_table_abstract($name));
+
+ my %fields = @{$table_def->{FIELDS}};
+ foreach my $col (keys %fields) {
+
+ # Foreign Key references have to be added by Install::DB after
+ # initial table creation, because column names have changed
+ # over history and it's impossible to keep track of that info
+ # in ABSTRACT_SCHEMA.
+ next unless exists $fields{$col}->{REFERENCES};
+ $fields{$col}->{REFERENCES}->{created} = $self->_bz_real_schema->FK_ON_CREATE;
}
+
+ $self->_bz_real_schema->add_table($name, $table_def);
+ $self->_bz_store_real_schema;
+ }
}
# _bz_add_table_raw($name) - Private
@@ -821,164 +834,174 @@ sub bz_add_table {
# _bz_init_schema_storage. Used when you don't
# yet have a Schema object but you need to
# add a table, for some reason.
-# Params: $name - The name of the table you're creating.
-# The definition for the table is pulled from
+# Params: $name - The name of the table you're creating.
+# The definition for the table is pulled from
# _bz_schema.
# Returns: nothing
#
sub _bz_add_table_raw {
- my ($self, $name, $options) = @_;
- my @statements = $self->_bz_schema->get_table_ddl($name);
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE
- and !$options->{silently})
- {
- say install_string('db_table_new', { table => $name });
- }
- $self->do($_) foreach (@statements);
+ my ($self, $name, $options) = @_;
+ my @statements = $self->_bz_schema->get_table_ddl($name);
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE and !$options->{silently}) {
+ say install_string('db_table_new', {table => $name});
+ }
+ $self->do($_) foreach (@statements);
}
sub _bz_add_field_table {
- my ($self, $name, $schema_ref) = @_;
- # We do nothing if the table already exists.
- return if $self->bz_table_info($name);
-
- # Copy this so that we're not modifying the passed reference.
- # (This avoids modifying a constant in Bugzilla::DB::Schema.)
- my %table_schema = %$schema_ref;
- my %indexes = @{ $table_schema{INDEXES} };
- my %fixed_indexes;
- foreach my $key (keys %indexes) {
- $fixed_indexes{$name . "_" . $key} = $indexes{$key};
- }
- # INDEXES is supposed to be an arrayref, so we have to convert back.
- my @indexes_array = %fixed_indexes;
- $table_schema{INDEXES} = \@indexes_array;
- # We add this to the abstract schema so that bz_add_table can find it.
- $self->_bz_schema->add_table($name, \%table_schema);
- $self->bz_add_table($name);
+ my ($self, $name, $schema_ref) = @_;
+
+ # We do nothing if the table already exists.
+ return if $self->bz_table_info($name);
+
+ # Copy this so that we're not modifying the passed reference.
+ # (This avoids modifying a constant in Bugzilla::DB::Schema.)
+ my %table_schema = %$schema_ref;
+ my %indexes = @{$table_schema{INDEXES}};
+ my %fixed_indexes;
+ foreach my $key (keys %indexes) {
+ $fixed_indexes{$name . "_" . $key} = $indexes{$key};
+ }
+
+ # INDEXES is supposed to be an arrayref, so we have to convert back.
+ my @indexes_array = %fixed_indexes;
+ $table_schema{INDEXES} = \@indexes_array;
+
+ # We add this to the abstract schema so that bz_add_table can find it.
+ $self->_bz_schema->add_table($name, \%table_schema);
+ $self->bz_add_table($name);
}
sub bz_add_field_tables {
- my ($self, $field) = @_;
-
- $self->_bz_add_field_table($field->name,
- $self->_bz_schema->FIELD_TABLE_SCHEMA, $field->type);
- if ($field->type == FIELD_TYPE_MULTI_SELECT) {
- my $ms_table = "bug_" . $field->name;
- $self->_bz_add_field_table($ms_table,
- $self->_bz_schema->MULTI_SELECT_VALUE_TABLE);
-
- $self->bz_add_fks($ms_table,
- { bug_id => {TABLE => 'bugs', COLUMN => 'bug_id',
- DELETE => 'CASCADE'},
-
- value => {TABLE => $field->name, COLUMN => 'value'} });
- }
+ my ($self, $field) = @_;
+
+ $self->_bz_add_field_table($field->name, $self->_bz_schema->FIELD_TABLE_SCHEMA,
+ $field->type);
+ if ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ my $ms_table = "bug_" . $field->name;
+ $self->_bz_add_field_table($ms_table,
+ $self->_bz_schema->MULTI_SELECT_VALUE_TABLE);
+
+ $self->bz_add_fks(
+ $ms_table,
+ {
+ bug_id => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'},
+
+ value => {TABLE => $field->name, COLUMN => 'value'}
+ }
+ );
+ }
}
sub bz_drop_field_tables {
- my ($self, $field) = @_;
- if ($field->type == FIELD_TYPE_MULTI_SELECT) {
- $self->bz_drop_table('bug_' . $field->name);
- }
- $self->bz_drop_table($field->name);
+ my ($self, $field) = @_;
+ if ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ $self->bz_drop_table('bug_' . $field->name);
+ }
+ $self->bz_drop_table($field->name);
}
sub bz_drop_column {
- my ($self, $table, $column) = @_;
-
- my $current_def = $self->bz_column_info($table, $column);
-
- if ($current_def) {
- my @statements = $self->_bz_real_schema->get_drop_column_ddl(
- $table, $column);
- print get_text('install_column_drop',
- { table => $table, column => $column }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- foreach my $sql (@statements) {
- # Because this is a deletion, we don't want to die hard if
- # we fail because of some local customization. If something
- # is already gone, that's fine with us!
- eval { $self->do($sql); } or warn "Failed SQL: [$sql] Error: $@";
- }
- $self->_bz_real_schema->delete_column($table, $column);
- $self->_bz_store_real_schema;
+ my ($self, $table, $column) = @_;
+
+ my $current_def = $self->bz_column_info($table, $column);
+
+ if ($current_def) {
+ my @statements = $self->_bz_real_schema->get_drop_column_ddl($table, $column);
+ print get_text('install_column_drop', {table => $table, column => $column})
+ . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ foreach my $sql (@statements) {
+
+ # Because this is a deletion, we don't want to die hard if
+ # we fail because of some local customization. If something
+ # is already gone, that's fine with us!
+ eval { $self->do($sql); } or warn "Failed SQL: [$sql] Error: $@";
}
+ $self->_bz_real_schema->delete_column($table, $column);
+ $self->_bz_store_real_schema;
+ }
}
sub bz_drop_fk {
- my ($self, $table, $column) = @_;
-
- my $fk_def = $self->bz_fk_info($table, $column);
- if ($fk_def and $fk_def->{created}) {
- print get_text('install_fk_drop',
- { table => $table, column => $column, fk => $fk_def })
- . "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- my @statements =
- $self->_bz_real_schema->get_drop_fk_sql($table, $column, $fk_def);
- foreach my $sql (@statements) {
- # Because this is a deletion, we don't want to die hard if
- # we fail because of some local customization. If something
- # is already gone, that's fine with us!
- eval { $self->do($sql); } or warn "Failed SQL: [$sql] Error: $@";
- }
- # Under normal circumstances, we don't permanently drop the fk--
- # we want checksetup to re-create it again later. The only
- # time that FKs get permanently dropped is if the column gets
- # dropped.
- $fk_def->{created} = 0;
- $self->_bz_real_schema->set_fk($table, $column, $fk_def);
- $self->_bz_store_real_schema;
+ my ($self, $table, $column) = @_;
+
+ my $fk_def = $self->bz_fk_info($table, $column);
+ if ($fk_def and $fk_def->{created}) {
+ print get_text('install_fk_drop',
+ {table => $table, column => $column, fk => $fk_def})
+ . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ my @statements
+ = $self->_bz_real_schema->get_drop_fk_sql($table, $column, $fk_def);
+ foreach my $sql (@statements) {
+
+ # Because this is a deletion, we don't want to die hard if
+ # we fail because of some local customization. If something
+ # is already gone, that's fine with us!
+ eval { $self->do($sql); } or warn "Failed SQL: [$sql] Error: $@";
}
+ # Under normal circumstances, we don't permanently drop the fk--
+ # we want checksetup to re-create it again later. The only
+ # time that FKs get permanently dropped is if the column gets
+ # dropped.
+ $fk_def->{created} = 0;
+ $self->_bz_real_schema->set_fk($table, $column, $fk_def);
+ $self->_bz_store_real_schema;
+ }
+
}
sub bz_get_related_fks {
- my ($self, $table, $column) = @_;
- my @tables = $self->_bz_real_schema->get_table_list();
- my @related;
- foreach my $check_table (@tables) {
- my @columns = $self->bz_table_columns($check_table);
- foreach my $check_column (@columns) {
- my $fk = $self->bz_fk_info($check_table, $check_column);
- if ($fk
- and (($fk->{TABLE} eq $table and $fk->{COLUMN} eq $column)
- or ($check_column eq $column and $check_table eq $table)))
- {
- push(@related, [$check_table, $check_column, $fk]);
- }
- } # foreach $column
- } # foreach $table
-
- return \@related;
+ my ($self, $table, $column) = @_;
+ my @tables = $self->_bz_real_schema->get_table_list();
+ my @related;
+ foreach my $check_table (@tables) {
+ my @columns = $self->bz_table_columns($check_table);
+ foreach my $check_column (@columns) {
+ my $fk = $self->bz_fk_info($check_table, $check_column);
+ if (
+ $fk
+ and (($fk->{TABLE} eq $table and $fk->{COLUMN} eq $column)
+ or ($check_column eq $column and $check_table eq $table))
+ )
+ {
+ push(@related, [$check_table, $check_column, $fk]);
+ }
+ } # foreach $column
+ } # foreach $table
+
+ return \@related;
}
sub bz_drop_related_fks {
- my $self = shift;
- my $related = $self->bz_get_related_fks(@_);
- foreach my $item (@$related) {
- my ($table, $column) = @$item;
- $self->bz_drop_fk($table, $column);
- }
- return $related;
+ my $self = shift;
+ my $related = $self->bz_get_related_fks(@_);
+ foreach my $item (@$related) {
+ my ($table, $column) = @$item;
+ $self->bz_drop_fk($table, $column);
+ }
+ return $related;
}
sub bz_drop_index {
- my ($self, $table, $name) = @_;
+ my ($self, $table, $name) = @_;
- my $index_exists = $self->bz_index_info($table, $name);
+ my $index_exists = $self->bz_index_info($table, $name);
- if ($index_exists) {
- if ($self->INDEX_DROPS_REQUIRE_FK_DROPS) {
- # We cannot delete an index used by a FK.
- foreach my $column (@{$index_exists->{FIELDS}}) {
- $self->bz_drop_related_fks($table, $column);
- }
- }
- $self->bz_drop_index_raw($table, $name);
- $self->_bz_real_schema->delete_index($table, $name);
- $self->_bz_store_real_schema;
+ if ($index_exists) {
+ if ($self->INDEX_DROPS_REQUIRE_FK_DROPS) {
+
+ # We cannot delete an index used by a FK.
+ foreach my $column (@{$index_exists->{FIELDS}}) {
+ $self->bz_drop_related_fks($table, $column);
+ }
}
+ $self->bz_drop_index_raw($table, $name);
+ $self->_bz_real_schema->delete_index($table, $name);
+ $self->_bz_store_real_schema;
+ }
}
# bz_drop_index_raw($table, $name, $silent)
@@ -987,7 +1010,7 @@ sub bz_drop_index {
# Drops an index from the database
# without updating any Schema object. Generally
# should only be called by bz_drop_index.
-# Used when either: (1) You don't yet have a Schema
+# Used when either: (1) You don't yet have a Schema
# object but you need to drop an index, for some reason.
# (2) You need to drop an index that somehow got into the
# database but doesn't exist in Schema.
@@ -998,108 +1021,111 @@ sub bz_drop_index {
# Returns: nothing
#
sub bz_drop_index_raw {
- my ($self, $table, $name, $silent) = @_;
- my @statements = $self->_bz_schema->get_drop_index_ddl(
- $table, $name);
- print "Removing index '$name' from the $table table...\n" unless $silent;
- foreach my $sql (@statements) {
- # Because this is a deletion, we don't want to die hard if
- # we fail because of some local customization. If something
- # is already gone, that's fine with us!
- eval { $self->do($sql) } or warn "Failed SQL: [$sql] Error: $@";
- }
+ my ($self, $table, $name, $silent) = @_;
+ my @statements = $self->_bz_schema->get_drop_index_ddl($table, $name);
+ print "Removing index '$name' from the $table table...\n" unless $silent;
+ foreach my $sql (@statements) {
+
+ # Because this is a deletion, we don't want to die hard if
+ # we fail because of some local customization. If something
+ # is already gone, that's fine with us!
+ eval { $self->do($sql) } or warn "Failed SQL: [$sql] Error: $@";
+ }
}
sub bz_drop_table {
- my ($self, $name) = @_;
-
- my $table_exists = $self->bz_table_info($name);
-
- if ($table_exists) {
- my @statements = $self->_bz_schema->get_drop_table_ddl($name);
- print get_text('install_table_drop', { name => $name }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- foreach my $sql (@statements) {
- # Because this is a deletion, we don't want to die hard if
- # we fail because of some local customization. If something
- # is already gone, that's fine with us!
- eval { $self->do($sql); } or warn "Failed SQL: [$sql] Error: $@";
- }
- $self->_bz_real_schema->delete_table($name);
- $self->_bz_store_real_schema;
+ my ($self, $name) = @_;
+
+ my $table_exists = $self->bz_table_info($name);
+
+ if ($table_exists) {
+ my @statements = $self->_bz_schema->get_drop_table_ddl($name);
+ print get_text('install_table_drop', {name => $name}) . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ foreach my $sql (@statements) {
+
+ # Because this is a deletion, we don't want to die hard if
+ # we fail because of some local customization. If something
+ # is already gone, that's fine with us!
+ eval { $self->do($sql); } or warn "Failed SQL: [$sql] Error: $@";
}
+ $self->_bz_real_schema->delete_table($name);
+ $self->_bz_store_real_schema;
+ }
}
sub bz_fk_info {
- my ($self, $table, $column) = @_;
- my $col_info = $self->bz_column_info($table, $column);
- return undef if !$col_info;
- my $fk = $col_info->{REFERENCES};
- return $fk;
+ my ($self, $table, $column) = @_;
+ my $col_info = $self->bz_column_info($table, $column);
+ return undef if !$col_info;
+ my $fk = $col_info->{REFERENCES};
+ return $fk;
}
sub bz_rename_column {
- my ($self, $table, $old_name, $new_name) = @_;
+ my ($self, $table, $old_name, $new_name) = @_;
- my $old_col_exists = $self->bz_column_info($table, $old_name);
+ my $old_col_exists = $self->bz_column_info($table, $old_name);
- if ($old_col_exists) {
- my $already_renamed = $self->bz_column_info($table, $new_name);
- ThrowCodeError('db_rename_conflict',
- { old => "$table.$old_name",
- new => "$table.$new_name" }) if $already_renamed;
- my @statements = $self->_bz_real_schema->get_rename_column_ddl(
- $table, $old_name, $new_name);
+ if ($old_col_exists) {
+ my $already_renamed = $self->bz_column_info($table, $new_name);
+ ThrowCodeError('db_rename_conflict',
+ {old => "$table.$old_name", new => "$table.$new_name"})
+ if $already_renamed;
+ my @statements
+ = $self->_bz_real_schema->get_rename_column_ddl($table, $old_name, $new_name);
- print get_text('install_column_rename',
- { old => "$table.$old_name", new => "$table.$new_name" })
- . "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ print get_text('install_column_rename',
+ {old => "$table.$old_name", new => "$table.$new_name"})
+ . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- foreach my $sql (@statements) {
- $self->do($sql);
- }
- $self->_bz_real_schema->rename_column($table, $old_name, $new_name);
- $self->_bz_store_real_schema;
+ foreach my $sql (@statements) {
+ $self->do($sql);
}
+ $self->_bz_real_schema->rename_column($table, $old_name, $new_name);
+ $self->_bz_store_real_schema;
+ }
}
sub bz_rename_table {
- my ($self, $old_name, $new_name) = @_;
- my $old_table = $self->bz_table_info($old_name);
- return if !$old_table;
-
- my $new = $self->bz_table_info($new_name);
- ThrowCodeError('db_rename_conflict', { old => $old_name,
- new => $new_name }) if $new;
-
- # FKs will all have the wrong names unless we drop and then let them
- # be re-created later. Under normal circumstances, checksetup.pl will
- # automatically re-create these dropped FKs at the end of its DB upgrade
- # run, so we don't need to re-create them in this method.
- my @columns = $self->bz_table_columns($old_name);
- foreach my $column (@columns) {
- # these just return silently if there's no FK to drop
- $self->bz_drop_fk($old_name, $column);
- $self->bz_drop_related_fks($old_name, $column);
- }
-
- my @sql = $self->_bz_real_schema->get_rename_table_sql($old_name, $new_name);
- print get_text('install_table_rename',
- { old => $old_name, new => $new_name }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- $self->do($_) foreach @sql;
- $self->_bz_real_schema->rename_table($old_name, $new_name);
- $self->_bz_store_real_schema;
+ my ($self, $old_name, $new_name) = @_;
+ my $old_table = $self->bz_table_info($old_name);
+ return if !$old_table;
+
+ my $new = $self->bz_table_info($new_name);
+ ThrowCodeError('db_rename_conflict', {old => $old_name, new => $new_name})
+ if $new;
+
+ # FKs will all have the wrong names unless we drop and then let them
+ # be re-created later. Under normal circumstances, checksetup.pl will
+ # automatically re-create these dropped FKs at the end of its DB upgrade
+ # run, so we don't need to re-create them in this method.
+ my @columns = $self->bz_table_columns($old_name);
+ foreach my $column (@columns) {
+
+ # these just return silently if there's no FK to drop
+ $self->bz_drop_fk($old_name, $column);
+ $self->bz_drop_related_fks($old_name, $column);
+ }
+
+ my @sql = $self->_bz_real_schema->get_rename_table_sql($old_name, $new_name);
+ print get_text('install_table_rename', {old => $old_name, new => $new_name})
+ . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ $self->do($_) foreach @sql;
+ $self->_bz_real_schema->rename_table($old_name, $new_name);
+ $self->_bz_store_real_schema;
}
sub bz_set_next_serial_value {
- my ($self, $table, $column, $value) = @_;
- if (!$value) {
- $value = $self->selectrow_array("SELECT MAX($column) FROM $table") || 0;
- $value++;
- }
- my @sql = $self->_bz_real_schema->get_set_serial_sql($table, $column, $value);
- $self->do($_) foreach @sql;
+ my ($self, $table, $column, $value) = @_;
+ if (!$value) {
+ $value = $self->selectrow_array("SELECT MAX($column) FROM $table") || 0;
+ $value++;
+ }
+ my @sql = $self->_bz_real_schema->get_set_serial_sql($table, $column, $value);
+ $self->do($_) foreach @sql;
}
#####################################################################
@@ -1107,12 +1133,12 @@ sub bz_set_next_serial_value {
#####################################################################
sub _bz_schema {
- my ($self) = @_;
- return $self->{private_bz_schema} if exists $self->{private_bz_schema};
- my @module_parts = split('::', ref $self);
- my $module_name = pop @module_parts;
- $self->{private_bz_schema} = Bugzilla::DB::Schema->new($module_name);
- return $self->{private_bz_schema};
+ my ($self) = @_;
+ return $self->{private_bz_schema} if exists $self->{private_bz_schema};
+ my @module_parts = split('::', ref $self);
+ my $module_name = pop @module_parts;
+ $self->{private_bz_schema} = Bugzilla::DB::Schema->new($module_name);
+ return $self->{private_bz_schema};
}
# _bz_get_initial_schema()
@@ -1126,53 +1152,54 @@ sub _bz_schema {
# Returns: A Schema object that can be serialized and written to disk
# for _bz_init_schema_storage.
sub _bz_get_initial_schema {
- my ($self) = @_;
- return $self->_bz_schema->get_empty_schema();
+ my ($self) = @_;
+ return $self->_bz_schema->get_empty_schema();
}
sub bz_column_info {
- my ($self, $table, $column) = @_;
- my $def = $self->_bz_real_schema->get_column_abstract($table, $column);
- # We dclone it so callers can't modify the Schema.
- $def = dclone($def) if defined $def;
- return $def;
+ my ($self, $table, $column) = @_;
+ my $def = $self->_bz_real_schema->get_column_abstract($table, $column);
+
+ # We dclone it so callers can't modify the Schema.
+ $def = dclone($def) if defined $def;
+ return $def;
}
sub bz_index_info {
- my ($self, $table, $index) = @_;
- my $index_def =
- $self->_bz_real_schema->get_index_abstract($table, $index);
- if (ref($index_def) eq 'ARRAY') {
- $index_def = {FIELDS => $index_def, TYPE => ''};
- }
- return $index_def;
+ my ($self, $table, $index) = @_;
+ my $index_def = $self->_bz_real_schema->get_index_abstract($table, $index);
+ if (ref($index_def) eq 'ARRAY') {
+ $index_def = {FIELDS => $index_def, TYPE => ''};
+ }
+ return $index_def;
}
sub bz_table_info {
- my ($self, $table) = @_;
- return $self->_bz_real_schema->get_table_abstract($table);
+ my ($self, $table) = @_;
+ return $self->_bz_real_schema->get_table_abstract($table);
}
sub bz_table_columns {
- my ($self, $table) = @_;
- return $self->_bz_real_schema->get_table_columns($table);
+ my ($self, $table) = @_;
+ return $self->_bz_real_schema->get_table_columns($table);
}
sub bz_table_indexes {
- my ($self, $table) = @_;
- my $indexes = $self->_bz_real_schema->get_table_indexes_abstract($table);
- my %return_indexes;
- # We do this so that they're always hashes.
- foreach my $name (keys %$indexes) {
- $return_indexes{$name} = $self->bz_index_info($table, $name);
- }
- return \%return_indexes;
+ my ($self, $table) = @_;
+ my $indexes = $self->_bz_real_schema->get_table_indexes_abstract($table);
+ my %return_indexes;
+
+ # We do this so that they're always hashes.
+ foreach my $name (keys %$indexes) {
+ $return_indexes{$name} = $self->bz_index_info($table, $name);
+ }
+ return \%return_indexes;
}
sub bz_table_list {
- my ($self) = @_;
- return $self->_bz_real_schema->get_table_list();
+ my ($self) = @_;
+ return $self->_bz_real_schema->get_table_list();
}
#####################################################################
@@ -1191,9 +1218,9 @@ sub bz_table_list {
# Returns: An array of column names.
#
sub bz_table_columns_real {
- my ($self, $table) = @_;
- my $sth = $self->column_info(undef, undef, $table, '%');
- return @{ $self->selectcol_arrayref($sth, {Columns => [4]}) };
+ my ($self, $table) = @_;
+ my $sth = $self->column_info(undef, undef, $table, '%');
+ return @{$self->selectcol_arrayref($sth, {Columns => [4]})};
}
# bz_table_list_real()
@@ -1203,9 +1230,9 @@ sub bz_table_columns_real {
# Params: none
# Returns: An array containing table names.
sub bz_table_list_real {
- my ($self) = @_;
- my $table_sth = $self->table_info(undef, undef, undef, "TABLE");
- return @{$self->selectcol_arrayref($table_sth, { Columns => [3] })};
+ my ($self) = @_;
+ my $table_sth = $self->table_info(undef, undef, undef, "TABLE");
+ return @{$self->selectcol_arrayref($table_sth, {Columns => [3]})};
}
#####################################################################
@@ -1213,55 +1240,59 @@ sub bz_table_list_real {
#####################################################################
sub bz_in_transaction {
- return $_[0]->{private_bz_transaction_count} ? 1 : 0;
+ return $_[0]->{private_bz_transaction_count} ? 1 : 0;
}
sub bz_start_transaction {
- my ($self) = @_;
-
- if ($self->bz_in_transaction) {
- $self->{private_bz_transaction_count}++;
- } else {
- # Turn AutoCommit off and start a new transaction
- $self->begin_work();
- # REPEATABLE READ means "We work on a snapshot of the DB that
- # is created when we execute our first SQL statement." It's
- # what we need in Bugzilla to be safe, for what we do.
- # Different DBs have different defaults for their isolation
- # level, so we just set it here manually.
- if ($self->ISOLATION_LEVEL) {
- $self->do('SET TRANSACTION ISOLATION LEVEL '
- . $self->ISOLATION_LEVEL);
- }
- $self->{private_bz_transaction_count} = 1;
+ my ($self) = @_;
+
+ if ($self->bz_in_transaction) {
+ $self->{private_bz_transaction_count}++;
+ }
+ else {
+ # Turn AutoCommit off and start a new transaction
+ $self->begin_work();
+
+ # REPEATABLE READ means "We work on a snapshot of the DB that
+ # is created when we execute our first SQL statement." It's
+ # what we need in Bugzilla to be safe, for what we do.
+ # Different DBs have different defaults for their isolation
+ # level, so we just set it here manually.
+ if ($self->ISOLATION_LEVEL) {
+ $self->do('SET TRANSACTION ISOLATION LEVEL ' . $self->ISOLATION_LEVEL);
}
+ $self->{private_bz_transaction_count} = 1;
+ }
}
sub bz_commit_transaction {
- my ($self) = @_;
-
- if ($self->{private_bz_transaction_count} > 1) {
- $self->{private_bz_transaction_count}--;
- } elsif ($self->bz_in_transaction) {
- $self->commit();
- $self->{private_bz_transaction_count} = 0;
- Bugzilla::Mailer->send_staged_mail();
- } else {
- ThrowCodeError('not_in_transaction');
- }
+ my ($self) = @_;
+
+ if ($self->{private_bz_transaction_count} > 1) {
+ $self->{private_bz_transaction_count}--;
+ }
+ elsif ($self->bz_in_transaction) {
+ $self->commit();
+ $self->{private_bz_transaction_count} = 0;
+ Bugzilla::Mailer->send_staged_mail();
+ }
+ else {
+ ThrowCodeError('not_in_transaction');
+ }
}
sub bz_rollback_transaction {
- my ($self) = @_;
-
- # Unlike start and commit, if we rollback at any point it happens
- # instantly, even if we're in a nested transaction.
- if (!$self->bz_in_transaction) {
- ThrowCodeError("not_in_transaction");
- } else {
- $self->rollback();
- $self->{private_bz_transaction_count} = 0;
- }
+ my ($self) = @_;
+
+ # Unlike start and commit, if we rollback at any point it happens
+ # instantly, even if we're in a nested transaction.
+ if (!$self->bz_in_transaction) {
+ ThrowCodeError("not_in_transaction");
+ }
+ else {
+ $self->rollback();
+ $self->{private_bz_transaction_count} = 0;
+ }
}
#####################################################################
@@ -1269,42 +1300,43 @@ sub bz_rollback_transaction {
#####################################################################
sub db_new {
- my ($class, $params) = @_;
- my ($dsn, $user, $pass, $override_attrs) =
- @$params{qw(dsn user pass attrs)};
-
- # set up default attributes used to connect to the database
- # (may be overridden by DB driver implementations)
- my $attributes = { RaiseError => 0,
- AutoCommit => 1,
- PrintError => 0,
- ShowErrorStatement => 1,
- HandleError => \&_handle_error,
- TaintIn => 1,
- # See https://rt.perl.org/rt3/Public/Bug/Display.html?id=30933
- # for the reason to use NAME instead of NAME_lc (bug 253696).
- FetchHashKeyName => 'NAME',
- };
-
- if ($override_attrs) {
- foreach my $key (keys %$override_attrs) {
- $attributes->{$key} = $override_attrs->{$key};
- }
+ my ($class, $params) = @_;
+ my ($dsn, $user, $pass, $override_attrs) = @$params{qw(dsn user pass attrs)};
+
+ # set up default attributes used to connect to the database
+ # (may be overridden by DB driver implementations)
+ my $attributes = {
+ RaiseError => 0,
+ AutoCommit => 1,
+ PrintError => 0,
+ ShowErrorStatement => 1,
+ HandleError => \&_handle_error,
+ TaintIn => 1,
+
+ # See https://rt.perl.org/rt3/Public/Bug/Display.html?id=30933
+ # for the reason to use NAME instead of NAME_lc (bug 253696).
+ FetchHashKeyName => 'NAME',
+ };
+
+ if ($override_attrs) {
+ foreach my $key (keys %$override_attrs) {
+ $attributes->{$key} = $override_attrs->{$key};
}
+ }
- # connect using our known info to the specified db
- my $self = DBI->connect($dsn, $user, $pass, $attributes)
- or die "\nCan't connect to the database.\nError: $DBI::errstr\n"
- . " Is your database installed and up and running?\n Do you have"
- . " the correct username and password selected in localconfig?\n\n";
+ # connect using our known info to the specified db
+ my $self = DBI->connect($dsn, $user, $pass, $attributes)
+ or die "\nCan't connect to the database.\nError: $DBI::errstr\n"
+ . " Is your database installed and up and running?\n Do you have"
+ . " the correct username and password selected in localconfig?\n\n";
- # RaiseError was only set to 0 so that we could catch the
- # above "die" condition.
- $self->{RaiseError} = 1;
+ # RaiseError was only set to 0 so that we could catch the
+ # above "die" condition.
+ $self->{RaiseError} = 1;
- bless ($self, $class);
+ bless($self, $class);
- return $self;
+ return $self;
}
#####################################################################
@@ -1328,55 +1360,54 @@ These methods really are private. Do not override them in subclasses.
=cut
sub _bz_init_schema_storage {
- my ($self) = @_;
-
- my $table_size;
- eval {
- $table_size =
- $self->selectrow_array("SELECT COUNT(*) FROM bz_schema");
- };
+ my ($self) = @_;
+
+ my $table_size;
+ eval { $table_size = $self->selectrow_array("SELECT COUNT(*) FROM bz_schema"); };
+
+ if (!$table_size) {
+ my $init_schema = $self->_bz_get_initial_schema;
+ my $store_me = $init_schema->serialize_abstract();
+ my $schema_version = $init_schema->SCHEMA_VERSION;
+
+ # If table_size is not defined, then we hit an error reading the
+ # bz_schema table, which means it probably doesn't exist yet. So,
+ # we have to create it. If we failed above for some other reason,
+ # we'll see the failure here.
+ # However, we must create the table after we do get_initial_schema,
+ # because some versions of get_initial_schema read that the table
+ # exists and then add it to the Schema, where other versions don't.
+ if (!defined $table_size) {
+ $self->_bz_add_table_raw('bz_schema');
+ }
- if (!$table_size) {
- my $init_schema = $self->_bz_get_initial_schema;
- my $store_me = $init_schema->serialize_abstract();
- my $schema_version = $init_schema->SCHEMA_VERSION;
-
- # If table_size is not defined, then we hit an error reading the
- # bz_schema table, which means it probably doesn't exist yet. So,
- # we have to create it. If we failed above for some other reason,
- # we'll see the failure here.
- # However, we must create the table after we do get_initial_schema,
- # because some versions of get_initial_schema read that the table
- # exists and then add it to the Schema, where other versions don't.
- if (!defined $table_size) {
- $self->_bz_add_table_raw('bz_schema');
- }
+ say install_string('db_schema_init');
+ my $sth = $self->prepare(
+ "INSERT INTO bz_schema " . " (schema_data, version) VALUES (?,?)");
+ $sth->bind_param(1, $store_me, $self->BLOB_TYPE);
+ $sth->bind_param(2, $schema_version);
+ $sth->execute();
- say install_string('db_schema_init');
- my $sth = $self->prepare("INSERT INTO bz_schema "
- ." (schema_data, version) VALUES (?,?)");
- $sth->bind_param(1, $store_me, $self->BLOB_TYPE);
- $sth->bind_param(2, $schema_version);
- $sth->execute();
-
- # And now we have to update the on-disk schema to hold the bz_schema
- # table, if the bz_schema table didn't exist when we were called.
- if (!defined $table_size) {
- $self->_bz_real_schema->add_table('bz_schema',
- $self->_bz_schema->get_table_abstract('bz_schema'));
- $self->_bz_store_real_schema;
- }
- }
- # Sanity check
- elsif ($table_size > 1) {
- # We tell them to delete the newer one. Better to have checksetup
- # run migration code too many times than to have it not run the
- # correct migration code at all.
- die "Attempted to initialize the schema but there are already "
- . " $table_size copies of it stored.\nThis should never happen.\n"
- . " Compare the rows of the bz_schema table and delete the "
- . "newer one(s).";
+ # And now we have to update the on-disk schema to hold the bz_schema
+ # table, if the bz_schema table didn't exist when we were called.
+ if (!defined $table_size) {
+ $self->_bz_real_schema->add_table('bz_schema',
+ $self->_bz_schema->get_table_abstract('bz_schema'));
+ $self->_bz_store_real_schema;
}
+ }
+
+ # Sanity check
+ elsif ($table_size > 1) {
+
+ # We tell them to delete the newer one. Better to have checksetup
+ # run migration code too many times than to have it not run the
+ # correct migration code at all.
+ die "Attempted to initialize the schema but there are already "
+ . " $table_size copies of it stored.\nThis should never happen.\n"
+ . " Compare the rows of the bz_schema table and delete the "
+ . "newer one(s).";
+ }
}
=item C<_bz_real_schema()>
@@ -1390,24 +1421,23 @@ sub _bz_init_schema_storage {
=cut
sub _bz_real_schema {
- my ($self) = @_;
- return $self->{private_real_schema} if exists $self->{private_real_schema};
-
- my $bz_schema;
- unless ($bz_schema = Bugzilla->memcached->get({ key => 'bz_schema' })) {
- $bz_schema = $self->selectrow_arrayref(
- "SELECT schema_data, version FROM bz_schema"
- );
- Bugzilla->memcached->set({ key => 'bz_schema', value => $bz_schema });
- }
+ my ($self) = @_;
+ return $self->{private_real_schema} if exists $self->{private_real_schema};
- (die "_bz_real_schema tried to read the bz_schema table but it's empty!")
- if !$bz_schema;
+ my $bz_schema;
+ unless ($bz_schema = Bugzilla->memcached->get({key => 'bz_schema'})) {
+ $bz_schema
+ = $self->selectrow_arrayref("SELECT schema_data, version FROM bz_schema");
+ Bugzilla->memcached->set({key => 'bz_schema', value => $bz_schema});
+ }
- $self->{private_real_schema} =
- $self->_bz_schema->deserialize_abstract($bz_schema->[0], $bz_schema->[1]);
+ (die "_bz_real_schema tried to read the bz_schema table but it's empty!")
+ if !$bz_schema;
- return $self->{private_real_schema};
+ $self->{private_real_schema}
+ = $self->_bz_schema->deserialize_abstract($bz_schema->[0], $bz_schema->[1]);
+
+ return $self->{private_real_schema};
}
=item C<_bz_store_real_schema()>
@@ -1427,106 +1457,135 @@ sub _bz_real_schema {
=cut
sub _bz_store_real_schema {
- my ($self) = @_;
-
- # Make sure that there's a schema to update
- my $table_size = $self->selectrow_array("SELECT COUNT(*) FROM bz_schema");
-
- die "Attempted to update the bz_schema table but there's nothing "
- . "there to update. Run checksetup." unless $table_size;
-
- # We want to store the current object, not one
- # that we read from the database. So we use the actual hash
- # member instead of the subroutine call. If the hash
- # member is not defined, we will (and should) fail.
- my $update_schema = $self->{private_real_schema};
- my $store_me = $update_schema->serialize_abstract();
- my $schema_version = $update_schema->SCHEMA_VERSION;
- my $sth = $self->prepare("UPDATE bz_schema
- SET schema_data = ?, version = ?");
- $sth->bind_param(1, $store_me, $self->BLOB_TYPE);
- $sth->bind_param(2, $schema_version);
- $sth->execute();
+ my ($self) = @_;
+
+ # Make sure that there's a schema to update
+ my $table_size = $self->selectrow_array("SELECT COUNT(*) FROM bz_schema");
- Bugzilla->memcached->clear({ key => 'bz_schema' });
+ die "Attempted to update the bz_schema table but there's nothing "
+ . "there to update. Run checksetup."
+ unless $table_size;
+
+ # We want to store the current object, not one
+ # that we read from the database. So we use the actual hash
+ # member instead of the subroutine call. If the hash
+ # member is not defined, we will (and should) fail.
+ my $update_schema = $self->{private_real_schema};
+ my $store_me = $update_schema->serialize_abstract();
+ my $schema_version = $update_schema->SCHEMA_VERSION;
+ my $sth = $self->prepare(
+ "UPDATE bz_schema
+ SET schema_data = ?, version = ?"
+ );
+ $sth->bind_param(1, $store_me, $self->BLOB_TYPE);
+ $sth->bind_param(2, $schema_version);
+ $sth->execute();
+
+ Bugzilla->memcached->clear({key => 'bz_schema'});
}
# For bz_populate_enum_tables
sub _bz_populate_enum_table {
- my ($self, $table, $valuelist) = @_;
-
- my $sql_table = $self->quote_identifier($table);
-
- # Check if there are any table entries
- my $table_size = $self->selectrow_array("SELECT COUNT(*) FROM $sql_table");
-
- # If the table is empty...
- if (!$table_size) {
- print " $table";
- my $insert = $self->prepare(
- "INSERT INTO $sql_table (value,sortkey) VALUES (?,?)");
- my $sortorder = 0;
- my $maxlen = max(map(length($_), @$valuelist)) + 2;
- foreach my $value (@$valuelist) {
- $sortorder += 100;
- $insert->execute($value, $sortorder);
- }
+ my ($self, $table, $valuelist) = @_;
+
+ my $sql_table = $self->quote_identifier($table);
+
+ # Check if there are any table entries
+ my $table_size = $self->selectrow_array("SELECT COUNT(*) FROM $sql_table");
+
+ # If the table is empty...
+ if (!$table_size) {
+ print " $table";
+ my $insert
+ = $self->prepare("INSERT INTO $sql_table (value,sortkey) VALUES (?,?)");
+ my $sortorder = 0;
+ my $maxlen = max(map(length($_), @$valuelist)) + 2;
+ foreach my $value (@$valuelist) {
+ $sortorder += 100;
+ $insert->execute($value, $sortorder);
}
+ }
}
# This is used before adding a foreign key to a column, to make sure
# that the database won't fail adding the key.
sub _check_references {
- my ($self, $table, $column, $fk) = @_;
- my $foreign_table = $fk->{TABLE};
- my $foreign_column = $fk->{COLUMN};
-
- # We use table aliases because sometimes we join a table to itself,
- # and we can't use the same table name on both sides of the join.
- # We also can't use the words "table" or "foreign" because those are
- # reserved words.
- my $bad_values = $self->selectcol_arrayref(
- "SELECT DISTINCT tabl.$column
+ my ($self, $table, $column, $fk) = @_;
+ my $foreign_table = $fk->{TABLE};
+ my $foreign_column = $fk->{COLUMN};
+
+ # We use table aliases because sometimes we join a table to itself,
+ # and we can't use the same table name on both sides of the join.
+ # We also can't use the words "table" or "foreign" because those are
+ # reserved words.
+ my $bad_values = $self->selectcol_arrayref(
+ "SELECT DISTINCT tabl.$column
FROM $table AS tabl LEFT JOIN $foreign_table AS forn
ON tabl.$column = forn.$foreign_column
WHERE forn.$foreign_column IS NULL
- AND tabl.$column IS NOT NULL");
-
- if (@$bad_values) {
- my $delete_action = $fk->{DELETE} || '';
- if ($delete_action eq 'CASCADE') {
- $self->do("DELETE FROM $table WHERE $column IN ("
- . join(',', ('?') x @$bad_values) . ")",
- undef, @$bad_values);
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- print "\n", get_text('install_fk_invalid_fixed',
- { table => $table, column => $column,
- foreign_table => $foreign_table,
- foreign_column => $foreign_column,
- 'values' => $bad_values, action => 'delete' }), "\n";
- }
- }
- elsif ($delete_action eq 'SET NULL') {
- $self->do("UPDATE $table SET $column = NULL
+ AND tabl.$column IS NOT NULL"
+ );
+
+ if (@$bad_values) {
+ my $delete_action = $fk->{DELETE} || '';
+ if ($delete_action eq 'CASCADE') {
+ $self->do(
+ "DELETE FROM $table WHERE $column IN (" . join(',', ('?') x @$bad_values) . ")",
+ undef, @$bad_values
+ );
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ print "\n",
+ get_text(
+ 'install_fk_invalid_fixed',
+ {
+ table => $table,
+ column => $column,
+ foreign_table => $foreign_table,
+ foreign_column => $foreign_column,
+ 'values' => $bad_values,
+ action => 'delete'
+ }
+ ),
+ "\n";
+ }
+ }
+ elsif ($delete_action eq 'SET NULL') {
+ $self->do(
+ "UPDATE $table SET $column = NULL
WHERE $column IN ("
- . join(',', ('?') x @$bad_values) . ")",
- undef, @$bad_values);
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- print "\n", get_text('install_fk_invalid_fixed',
- { table => $table, column => $column,
- foreign_table => $foreign_table,
- foreign_column => $foreign_column,
- 'values' => $bad_values, action => 'null' }), "\n";
- }
- }
- else {
- die "\n", get_text('install_fk_invalid',
- { table => $table, column => $column,
- foreign_table => $foreign_table,
- foreign_column => $foreign_column,
- 'values' => $bad_values }), "\n";
+ . join(',', ('?') x @$bad_values) . ")", undef, @$bad_values
+ );
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ print "\n",
+ get_text(
+ 'install_fk_invalid_fixed',
+ {
+ table => $table,
+ column => $column,
+ foreign_table => $foreign_table,
+ foreign_column => $foreign_column,
+ 'values' => $bad_values,
+ action => 'null'
+ }
+ ),
+ "\n";
+ }
+ }
+ else {
+ die "\n",
+ get_text(
+ 'install_fk_invalid',
+ {
+ table => $table,
+ column => $column,
+ foreign_table => $foreign_table,
+ foreign_column => $foreign_column,
+ 'values' => $bad_values
}
+ ),
+ "\n";
}
+ }
}
1;
diff --git a/Bugzilla/DB/Mysql.pm b/Bugzilla/DB/Mysql.pm
index d0915f1e6..a58d88df4 100644
--- a/Bugzilla/DB/Mysql.pm
+++ b/Bugzilla/DB/Mysql.pm
@@ -37,258 +37,265 @@ use List::Util qw(max);
use Text::ParseWords;
# 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
+# In reality, you could have a LOT more comments than this, because
# MAX_COMMENT_LENGTH is big.
use constant MAX_COMMENTS => 50;
use constant FULLTEXT_OR => '|';
sub new {
- my ($class, $params) = @_;
- my ($user, $pass, $host, $dbname, $port, $sock) =
- @$params{qw(db_user db_pass db_host db_name db_port db_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'},
- # Needs to be explicitly specified for command-line processes.
- mysql_auto_reconnect => 1,
- );
-
- # MySQL SSL options
- my ($ssl_ca_file, $ssl_ca_path, $ssl_cert, $ssl_key) =
- @$params{qw(db_mysql_ssl_ca_file db_mysql_ssl_ca_path
- db_mysql_ssl_client_cert db_mysql_ssl_client_key)};
- if ($ssl_ca_file || $ssl_ca_path || $ssl_cert || $ssl_key) {
- $attrs{'mysql_ssl'} = 1;
- $attrs{'mysql_ssl_ca_file'} = $ssl_ca_file if $ssl_ca_file;
- $attrs{'mysql_ssl_ca_path'} = $ssl_ca_path if $ssl_ca_path;
- $attrs{'mysql_ssl_client_cert'} = $ssl_cert if $ssl_cert;
- $attrs{'mysql_ssl_client_key'} = $ssl_key if $ssl_key;
+ my ($class, $params) = @_;
+ my ($user, $pass, $host, $dbname, $port, $sock)
+ = @$params{qw(db_user db_pass db_host db_name db_port db_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'},
+
+ # Needs to be explicitly specified for command-line processes.
+ mysql_auto_reconnect => 1,
+ );
+
+ # MySQL SSL options
+ my ($ssl_ca_file, $ssl_ca_path, $ssl_cert, $ssl_key) = @$params{
+ qw(db_mysql_ssl_ca_file db_mysql_ssl_ca_path
+ db_mysql_ssl_client_cert db_mysql_ssl_client_key)
+ };
+ if ($ssl_ca_file || $ssl_ca_path || $ssl_cert || $ssl_key) {
+ $attrs{'mysql_ssl'} = 1;
+ $attrs{'mysql_ssl_ca_file'} = $ssl_ca_file if $ssl_ca_file;
+ $attrs{'mysql_ssl_ca_path'} = $ssl_ca_path if $ssl_ca_path;
+ $attrs{'mysql_ssl_client_cert'} = $ssl_cert if $ssl_cert;
+ $attrs{'mysql_ssl_client_key'} = $ssl_key if $ssl_key;
+ }
+
+ my $self = $class->db_new(
+ {dsn => $dsn, user => $user, pass => $pass, attrs => \%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} = "";
+
+ # Needed by TheSchwartz
+ $self->{private_bz_dsn} = $dsn;
+
+ bless($self, $class);
+
+ # Check for MySQL modes.
+ my ($var, $sql_mode)
+ = $self->selectrow_array("SHOW VARIABLES LIKE 'sql\\_mode'");
+
+ # Disable ANSI and strict modes, else Bugzilla will crash.
+ 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 { $_ !~ /^(?:ANSI|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);
}
+ }
- my $self = $class->db_new({ dsn => $dsn, user => $user,
- pass => $pass, attrs => \%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} = "";
-
- # Needed by TheSchwartz
- $self->{private_bz_dsn} = $dsn;
+ # Allow large GROUP_CONCATs (largely for inserting comments
+ # into bugs_fulltext).
+ $self->do('SET SESSION group_concat_max_len = 128000000');
- bless ($self, $class);
+ # MySQL 5.5.2 and older have this variable set to true, which causes
+ # trouble, see bug 870369.
+ $self->do('SET SESSION sql_auto_is_null = 0');
- # Check for MySQL modes.
- my ($var, $sql_mode) = $self->selectrow_array(
- "SHOW VARIABLES LIKE 'sql\\_mode'");
-
- # Disable ANSI and strict modes, else Bugzilla will crash.
- 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 {$_ !~ /^(?:ANSI|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);
- }
- }
-
- # Allow large GROUP_CONCATs (largely for inserting comments
- # into bugs_fulltext).
- $self->do('SET SESSION group_concat_max_len = 128000000');
-
- # MySQL 5.5.2 and older have this variable set to true, which causes
- # trouble, see bug 870369.
- $self->do('SET SESSION sql_auto_is_null = 0');
-
- return $self;
+ 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 ($self) = @_;
- my ($last_insert_id) = $self->selectrow_array('SELECT LAST_INSERT_ID()');
+ my ($last_insert_id) = $self->selectrow_array('SELECT LAST_INSERT_ID()');
- return $last_insert_id;
+ return $last_insert_id;
}
sub sql_group_concat {
- my ($self, $column, $separator, $sort, $order_by) = @_;
- $separator = $self->quote(', ') if !defined $separator;
- $sort = 1 if !defined $sort;
- if ($order_by) {
- $column .= " ORDER BY $order_by";
- }
- elsif ($sort) {
- my $sort_order = $column;
- $sort_order =~ s/^DISTINCT\s+//i;
- $column = "$column ORDER BY $sort_order";
- }
- return "GROUP_CONCAT($column SEPARATOR $separator)";
+ my ($self, $column, $separator, $sort, $order_by) = @_;
+ $separator = $self->quote(', ') if !defined $separator;
+ $sort = 1 if !defined $sort;
+ if ($order_by) {
+ $column .= " ORDER BY $order_by";
+ }
+ elsif ($sort) {
+ my $sort_order = $column;
+ $sort_order =~ s/^DISTINCT\s+//i;
+ $column = "$column ORDER BY $sort_order";
+ }
+ return "GROUP_CONCAT($column SEPARATOR $separator)";
}
sub sql_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "$expr REGEXP $pattern";
+ return "$expr REGEXP $pattern";
}
sub sql_not_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "$expr NOT REGEXP $pattern";
+ return "$expr NOT REGEXP $pattern";
}
sub sql_limit {
- my ($self, $limit, $offset) = @_;
-
- if (defined($offset)) {
- return "LIMIT $offset, $limit";
- } else {
- return "LIMIT $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) . ')';
+ 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 at the start or end of a word.
- my $mode = '';
- if ($text =~ /(?:^|\W)[+\-<>~"()]/ || $text =~ /[()"*](?:$|\W)/) {
- $mode = 'IN BOOLEAN MODE';
-
- my @terms = split(quotemeta(FULLTEXT_OR), $text);
- foreach my $term (@terms) {
- # quote un-quoted compound words
- my @words = quotewords('[\s()]+', 'delimiters', $term);
- foreach my $word (@words) {
- # match words that have non-word chars in the middle of them
- if ($word =~ /\w\W+\w/ && $word !~ m/"/) {
- $word = '"' . $word . '"';
- }
- }
- $term = join('', @words);
+ my ($self, $column, $text) = @_;
+
+ # Add the boolean mode modifier if the search string contains
+ # boolean operators at the start or end of a word.
+ my $mode = '';
+ if ($text =~ /(?:^|\W)[+\-<>~"()]/ || $text =~ /[()"*](?:$|\W)/) {
+ $mode = 'IN BOOLEAN MODE';
+
+ my @terms = split(quotemeta(FULLTEXT_OR), $text);
+ foreach my $term (@terms) {
+
+ # quote un-quoted compound words
+ my @words = quotewords('[\s()]+', 'delimiters', $term);
+ foreach my $word (@words) {
+
+ # match words that have non-word chars in the middle of them
+ if ($word =~ /\w\W+\w/ && $word !~ m/"/) {
+ $word = '"' . $word . '"';
}
- $text = join(FULLTEXT_OR, @terms);
+ }
+ $term = join('', @words);
}
+ $text = join(FULLTEXT_OR, @terms);
+ }
- # quote the text for use in the MATCH AGAINST expression
- $text = $self->quote($text);
+ # 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);
+ # untaint the text, since it's safe to use now that we've quoted it
+ trick_taint($text);
- return "MATCH($column) AGAINST($text $mode)";
+ return "MATCH($column) AGAINST($text $mode)";
}
sub sql_istring {
- my ($self, $string) = @_;
-
- return $string;
+ my ($self, $string) = @_;
+
+ return $string;
}
sub sql_from_days {
- my ($self, $days) = @_;
+ my ($self, $days) = @_;
- return "FROM_DAYS($days)";
+ return "FROM_DAYS($days)";
}
sub sql_to_days {
- my ($self, $date) = @_;
+ my ($self, $date) = @_;
- return "TO_DAYS($date)";
+ return "TO_DAYS($date)";
}
sub sql_date_format {
- my ($self, $date, $format) = @_;
+ my ($self, $date, $format) = @_;
+
+ $format = "%Y.%m.%d %H:%i:%s" if !$format;
- $format = "%Y.%m.%d %H:%i:%s" if !$format;
-
- return "DATE_FORMAT($date, " . $self->quote($format) . ")";
+ return "DATE_FORMAT($date, " . $self->quote($format) . ")";
}
sub sql_date_math {
- my ($self, $date, $operator, $interval, $units) = @_;
-
- return "$date $operator INTERVAL $interval $units";
+ my ($self, $date, $operator, $interval, $units) = @_;
+
+ return "$date $operator INTERVAL $interval $units";
}
sub sql_iposition {
- my ($self, $fragment, $text) = @_;
- return "INSTR($text, $fragment)";
+ my ($self, $fragment, $text) = @_;
+ return "INSTR($text, $fragment)";
}
sub sql_position {
- my ($self, $fragment, $text) = @_;
+ my ($self, $fragment, $text) = @_;
- return "INSTR(CAST($text AS BINARY), CAST($fragment AS BINARY))";
+ return "INSTR(CAST($text AS BINARY), CAST($fragment AS BINARY))";
}
sub sql_group_by {
- my ($self, $needed_columns, $optional_columns) = @_;
+ 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";
+ # 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_explain {
- my ($self, $sql) = @_;
- my $sth = $self->prepare("EXPLAIN $sql");
- $sth->execute();
- my $columns = $sth->{'NAME'};
- my $lengths = $sth->{'mysql_max_length'};
- my $format_string = '|';
- my $i = 0;
- foreach my $column (@$columns) {
- # Sometimes the column name is longer than the contents.
- my $length = max($lengths->[$i], length($column));
- $format_string .= ' %-' . $length . 's |';
- $i++;
- }
-
- my $first_row = sprintf($format_string, @$columns);
- my @explain_rows = ($first_row, '-' x length($first_row));
- while (my $row = $sth->fetchrow_arrayref) {
- my @fixed = map { defined $_ ? $_ : 'NULL' } @$row;
- push(@explain_rows, sprintf($format_string, @fixed));
- }
-
- return join("\n", @explain_rows);
+ my ($self, $sql) = @_;
+ my $sth = $self->prepare("EXPLAIN $sql");
+ $sth->execute();
+ my $columns = $sth->{'NAME'};
+ my $lengths = $sth->{'mysql_max_length'};
+ my $format_string = '|';
+ my $i = 0;
+ foreach my $column (@$columns) {
+
+ # Sometimes the column name is longer than the contents.
+ my $length = max($lengths->[$i], length($column));
+ $format_string .= ' %-' . $length . 's |';
+ $i++;
+ }
+
+ my $first_row = sprintf($format_string, @$columns);
+ my @explain_rows = ($first_row, '-' x length($first_row));
+ while (my $row = $sth->fetchrow_arrayref) {
+ my @fixed = map { defined $_ ? $_ : 'NULL' } @$row;
+ push(@explain_rows, sprintf($format_string, @fixed));
+ }
+
+ return join("\n", @explain_rows);
}
sub _bz_get_initial_schema {
- my ($self) = @_;
- return $self->_bz_build_schema_from_disk();
+ my ($self) = @_;
+ return $self->_bz_build_schema_from_disk();
}
#####################################################################
@@ -296,493 +303,503 @@ sub _bz_get_initial_schema {
#####################################################################
sub bz_check_server_version {
- my $self = shift;
+ my $self = shift;
- my $lc = Bugzilla->localconfig;
- if (lc(Bugzilla->localconfig->{db_name}) eq 'mysql') {
- die "It is not safe to run Bugzilla inside a database named 'mysql'.\n"
- . " Please pick a different value for \$db_name in localconfig.\n";
- }
+ my $lc = Bugzilla->localconfig;
+ if (lc(Bugzilla->localconfig->{db_name}) eq 'mysql') {
+ die "It is not safe to run Bugzilla inside a database named 'mysql'.\n"
+ . " Please pick a different value for \$db_name in localconfig.\n";
+ }
- $self->SUPER::bz_check_server_version(@_);
+ $self->SUPER::bz_check_server_version(@_);
}
sub bz_setup_database {
- my ($self) = @_;
-
- # 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. So, we
- # warn the user here if max_allowed_packet is too small.
- my $min_max_allowed = MAX_COMMENTS * MAX_COMMENT_LENGTH;
- my (undef, $current_max_allowed) = $self->selectrow_array(
- q{SHOW VARIABLES LIKE 'max\_allowed\_packet'});
- # This parameter is not yet defined when the DB is being built for
- # the very first time. The code below still works properly, however,
- # because the default maxattachmentsize is smaller than $min_max_allowed.
- my $max_attachment = (Bugzilla->params->{'maxattachmentsize'} || 0) * 1024;
- my $needed_max_allowed = max($min_max_allowed, $max_attachment);
- if ($current_max_allowed < $needed_max_allowed) {
- warn install_string('max_allowed_packet',
- { current => $current_max_allowed,
- needed => $needed_max_allowed }) . "\n";
+ my ($self) = @_;
+
+ # 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. So, we
+ # warn the user here if max_allowed_packet is too small.
+ my $min_max_allowed = MAX_COMMENTS * MAX_COMMENT_LENGTH;
+ my (undef, $current_max_allowed)
+ = $self->selectrow_array(q{SHOW VARIABLES LIKE 'max\_allowed\_packet'});
+
+ # This parameter is not yet defined when the DB is being built for
+ # the very first time. The code below still works properly, however,
+ # because the default maxattachmentsize is smaller than $min_max_allowed.
+ my $max_attachment = (Bugzilla->params->{'maxattachmentsize'} || 0) * 1024;
+ my $needed_max_allowed = max($min_max_allowed, $max_attachment);
+ if ($current_max_allowed < $needed_max_allowed) {
+ warn install_string('max_allowed_packet',
+ {current => $current_max_allowed, needed => $needed_max_allowed})
+ . "\n";
+ }
+
+ # 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 %engines = @{$self->selectcol_arrayref('SHOW ENGINES', {Columns => [1, 2]})};
+ if (!$engines{InnoDB} || $engines{InnoDB} !~ /^(YES|DEFAULT)$/) {
+ die install_string('mysql_innodb_disabled');
+ }
+
+
+ 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) and !grep($_ eq 'bugs_fulltext', @tables)) {
+ if ($self->bz_index_info_real('bugs', 'short_desc')) {
+ $self->bz_drop_index_raw('bugs', 'short_desc');
}
-
- # 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 %engines = @{$self->selectcol_arrayref('SHOW ENGINES', {Columns => [1,2]})};
- if (!$engines{InnoDB} || $engines{InnoDB} !~ /^(YES|DEFAULT)$/) {
- die install_string('mysql_innodb_disabled');
+ 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.
}
-
-
- 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)
- and !grep($_ eq 'bugs_fulltext', @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) and !grep($_ eq 'bugs_fulltext', @tables))
+ {
+ if ($self->bz_index_info_real('longdescs', 'thetext')) {
+ $self->bz_drop_index_raw('longdescs', 'thetext');
}
- if (grep($_ eq 'longdescs', @tables)
- and !grep($_ eq 'bugs_fulltext', @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.
- }
+ 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 $db_name = Bugzilla->localconfig->{db_name};
- my $myisam_tables = $self->selectcol_arrayref(
- 'SELECT TABLE_NAME FROM information_schema.TABLES
- WHERE TABLE_SCHEMA = ? AND ENGINE = ?',
- undef, $db_name, 'MyISAM');
- foreach my $should_be_myisam (Bugzilla::DB::Schema::Mysql::MYISAM_TABLES) {
- @$myisam_tables = grep { $_ ne $should_be_myisam } @$myisam_tables;
+ }
+
+ # Upgrade tables from MyISAM to InnoDB
+ my $db_name = Bugzilla->localconfig->{db_name};
+ my $myisam_tables = $self->selectcol_arrayref(
+ 'SELECT TABLE_NAME FROM information_schema.TABLES
+ WHERE TABLE_SCHEMA = ? AND ENGINE = ?', undef, $db_name, 'MyISAM'
+ );
+ foreach my $should_be_myisam (Bugzilla::DB::Schema::Mysql::MYISAM_TABLES) {
+ @$myisam_tables = grep { $_ ne $should_be_myisam } @$myisam_tables;
+ }
+
+ 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 ENGINE = InnoDB");
+ print "done.\n";
}
-
- 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 ENGINE = InnoDB");
- print "done.\n";
- }
+ }
+
+ # 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 install_string('bz_schema_exists_before_220');
}
-
- # 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 install_string('bz_schema_exists_before_220');
- }
+ my $bug_count = $self->selectrow_array("SELECT COUNT(*) FROM bugs");
- 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 "\n", install_string('mysql_index_renaming',
- { minutes => $rename_time });
- # 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;
- }
+ # 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;
- # 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 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);
- # And now we create the tables and the Schema object.
- $self->SUPER::bz_setup_database();
+ # 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 "\n", install_string('mysql_index_renaming', {minutes => $rename_time});
- if ($sd_index_deleted) {
- $self->_bz_real_schema->delete_index('bugs', 'bugs_short_desc_idx');
- $self->_bz_store_real_schema;
+ # Wait 45 seconds for them to respond.
+ sleep(45) unless Bugzilla->installation_answers->{NO_PAUSE};
}
- if ($longdescs_index_deleted) {
- $self->_bz_real_schema->delete_index('longdescs',
- 'longdescs_thetext_idx');
- $self->_bz_store_real_schema;
+ 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 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";
+ # 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');
+ }
- my $sth = $self->prepare("SELECT COUNT(attach_id) FROM attachments");
- $sth->execute();
- my ($attach_count) = $sth->fetchrow_array();
+ # Go through all the tables.
+ foreach my $table (@tables) {
- 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";
+ # 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();
- $self->bz_alter_column("attachments", "creation_ts",
- {TYPE => 'DATETIME', NOTNULL => 1});
+ if ($attach_count > 1000) {
+ print "This may take a while...\n";
}
+ my $i = 0;
- # 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});
- }
+ # 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();
- # 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});
+ # 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);
}
-
- # 2005-09-24 - bugreport@peshkin.net, bug 307602
- # Make sure that default 4G table limit is overridden
- my $attach_data_create = $self->selectrow_array(
- 'SELECT CREATE_OPTIONS FROM information_schema.TABLES
- WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?',
- undef, $db_name, 'attach_data');
- if ($attach_data_create !~ /MAX_ROWS/i) {
- print "Converting attach_data maximum size to 100G...\n";
- $self->do("ALTER TABLE attach_data
+ 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 $attach_data_create = $self->selectrow_array(
+ 'SELECT CREATE_OPTIONS FROM information_schema.TABLES
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?', undef, $db_name, 'attach_data'
+ );
+ if ($attach_data_create !~ /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.
- #
- # TABLE_COLLATION IS NOT NULL prevents us from trying to convert views.
- my $non_utf8_tables = $self->selectrow_array(
- "SELECT 1 FROM information_schema.TABLES
+ 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.
+ #
+ # TABLE_COLLATION IS NOT NULL prevents us from trying to convert views.
+ my $non_utf8_tables = $self->selectrow_array(
+ "SELECT 1 FROM information_schema.TABLES
WHERE TABLE_SCHEMA = ? AND TABLE_COLLATION IS NOT NULL
AND TABLE_COLLATION NOT LIKE 'utf8%'
- LIMIT 1", undef, $db_name);
-
- if (Bugzilla->params->{'utf8'} && $non_utf8_tables) {
- print "\n", install_string('mysql_utf8_conversion');
-
- if (!Bugzilla->installation_answers->{NO_PAUSE}) {
- if (Bugzilla->installation_mode ==
- INSTALLATION_MODE_NON_INTERACTIVE)
- {
- die install_string('continue_without_answers'), "\n";
- }
- else {
- print "\n " . install_string('enter_or_ctrl_c');
- 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();
- my (@binary_sql, @utf8_sql);
- 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};
-
- print "$table.$name needs to be converted to UTF-8...\n";
-
- # These will be automatically re-created at the end
- # of checksetup.
- $self->bz_drop_related_fks($table, $name);
-
- 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/;
- push(@binary_sql, "MODIFY COLUMN $name $binary");
- push(@utf8_sql, "MODIFY COLUMN $name $utf8");
- }
- } # foreach column
-
- if (@binary_sql) {
- my %indexes = %{ $self->bz_table_indexes($table) };
- foreach my $index_name (keys %indexes) {
- my $index = $indexes{$index_name};
- if ($index->{TYPE} and $index->{TYPE} eq 'FULLTEXT') {
- $self->bz_drop_index($table, $index_name);
- }
- else {
- delete $indexes{$index_name};
- }
- }
-
- print "Converting the $table table to UTF-8...\n";
- my $bin = "ALTER TABLE $table " . join(', ', @binary_sql);
- my $utf = "ALTER TABLE $table " . join(', ', @utf8_sql,
- 'DEFAULT CHARACTER SET utf8');
- $self->do($bin);
- $self->do($utf);
-
- # Re-add any removed FULLTEXT indexes.
- foreach my $index (keys %indexes) {
- $self->bz_add_index($table, $index, $indexes{$index});
- }
- }
- else {
- $self->do("ALTER TABLE $table DEFAULT CHARACTER SET utf8");
- }
-
- } # foreach my $table (@tables)
+ LIMIT 1", undef, $db_name
+ );
+
+ if (Bugzilla->params->{'utf8'} && $non_utf8_tables) {
+ print "\n", install_string('mysql_utf8_conversion');
+
+ if (!Bugzilla->installation_answers->{NO_PAUSE}) {
+ if (Bugzilla->installation_mode == INSTALLATION_MODE_NON_INTERACTIVE) {
+ die install_string('continue_without_answers'), "\n";
+ }
+ else {
+ print "\n " . install_string('enter_or_ctrl_c');
+ getc;
+ }
}
- # 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();
- }
+ 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();
+ my (@binary_sql, @utf8_sql);
+ 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};
- $self->_fix_defaults();
+ print "$table.$name needs to be converted to UTF-8...\n";
- # Bug 451735 highlighted a bug in bz_drop_index() which didn't
- # check for FKs before trying to delete an index. Consequently,
- # the series_creator_idx index was considered to be deleted
- # despite it was still present in the DB. That's why we have to
- # force the deletion, bypassing the DB schema.
- if (!$self->bz_index_info('series', 'series_category_idx')) {
- if (!$self->bz_index_info('series', 'series_creator_idx')
- && $self->bz_index_info_real('series', 'series_creator_idx'))
- {
- foreach my $column (qw(creator category subcategory name)) {
- $self->bz_drop_related_fks('series', $column);
- }
- $self->bz_drop_index_raw('series', 'series_creator_idx');
+ # These will be automatically re-created at the end
+ # of checksetup.
+ $self->bz_drop_related_fks($table, $name);
+
+ 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/;
+ push(@binary_sql, "MODIFY COLUMN $name $binary");
+ push(@utf8_sql, "MODIFY COLUMN $name $utf8");
}
+ } # foreach column
+
+ if (@binary_sql) {
+ my %indexes = %{$self->bz_table_indexes($table)};
+ foreach my $index_name (keys %indexes) {
+ my $index = $indexes{$index_name};
+ if ($index->{TYPE} and $index->{TYPE} eq 'FULLTEXT') {
+ $self->bz_drop_index($table, $index_name);
+ }
+ else {
+ delete $indexes{$index_name};
+ }
+ }
+
+ print "Converting the $table table to UTF-8...\n";
+ my $bin = "ALTER TABLE $table " . join(', ', @binary_sql);
+ my $utf
+ = "ALTER TABLE $table " . join(', ', @utf8_sql, 'DEFAULT CHARACTER SET utf8');
+ $self->do($bin);
+ $self->do($utf);
+
+ # Re-add any removed FULLTEXT indexes.
+ foreach my $index (keys %indexes) {
+ $self->bz_add_index($table, $index, $indexes{$index});
+ }
+ }
+ else {
+ $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();
+ }
+
+ $self->_fix_defaults();
+
+ # Bug 451735 highlighted a bug in bz_drop_index() which didn't
+ # check for FKs before trying to delete an index. Consequently,
+ # the series_creator_idx index was considered to be deleted
+ # despite it was still present in the DB. That's why we have to
+ # force the deletion, bypassing the DB schema.
+ if (!$self->bz_index_info('series', 'series_category_idx')) {
+ if (!$self->bz_index_info('series', 'series_creator_idx')
+ && $self->bz_index_info_real('series', 'series_creator_idx'))
+ {
+ foreach my $column (qw(creator category subcategory name)) {
+ $self->bz_drop_related_fks('series', $column);
+ }
+ $self->bz_drop_index_raw('series', 'series_creator_idx');
}
+ }
}
# When you import a MySQL 3/4 mysqldump into MySQL 5, columns that
@@ -792,100 +809,109 @@ sub bz_setup_database {
# looks like. So we remove defaults from columns that aren't supposed
# to have them
sub _fix_defaults {
- my $self = shift;
- my $maj_version = substr($self->bz_server_version, 0, 1);
- return if $maj_version < 5;
-
- # The oldest column that could have this problem is bugs.assigned_to,
- # so if it doesn't have the problem, we just skip doing this entirely.
- my $assi_def = $self->_bz_raw_column_info('bugs', 'assigned_to');
- my $assi_default = $assi_def->{COLUMN_DEF};
- # This "ne ''" thing is necessary because _raw_column_info seems to
- # return COLUMN_DEF as an empty string for columns that don't have
- # a default.
- return unless (defined $assi_default && $assi_default ne '');
-
- my %fix_columns;
- foreach my $table ($self->_bz_real_schema->get_table_list()) {
- foreach my $column ($self->bz_table_columns($table)) {
- my $abs_def = $self->bz_column_info($table, $column);
- # BLOB/TEXT columns never have defaults
- next if $abs_def->{TYPE} =~ /BLOB|TEXT/i;
- if (!defined $abs_def->{DEFAULT}) {
- # Get the exact default from the database without any
- # "fixing" by bz_column_info_real.
- my $raw_info = $self->_bz_raw_column_info($table, $column);
- my $raw_default = $raw_info->{COLUMN_DEF};
- if (defined $raw_default) {
- if ($raw_default eq '') {
- # Only (var)char columns can have empty strings as
- # defaults, so if we got an empty string for some
- # other default type, then it's bogus.
- next unless $abs_def->{TYPE} =~ /char/i;
- $raw_default = "''";
- }
- $fix_columns{$table} ||= [];
- push(@{ $fix_columns{$table} }, $column);
- print "$table.$column has incorrect DB default: $raw_default\n";
- }
- }
- } # foreach $column
- } # foreach $table
-
- print "Fixing defaults...\n";
- foreach my $table (reverse sort keys %fix_columns) {
- my @alters = map("ALTER COLUMN $_ DROP DEFAULT",
- @{ $fix_columns{$table} });
- my $sql = "ALTER TABLE $table " . join(',', @alters);
- $self->do($sql);
- }
+ my $self = shift;
+ my $maj_version = substr($self->bz_server_version, 0, 1);
+ return if $maj_version < 5;
+
+ # The oldest column that could have this problem is bugs.assigned_to,
+ # so if it doesn't have the problem, we just skip doing this entirely.
+ my $assi_def = $self->_bz_raw_column_info('bugs', 'assigned_to');
+ my $assi_default = $assi_def->{COLUMN_DEF};
+
+ # This "ne ''" thing is necessary because _raw_column_info seems to
+ # return COLUMN_DEF as an empty string for columns that don't have
+ # a default.
+ return unless (defined $assi_default && $assi_default ne '');
+
+ my %fix_columns;
+ foreach my $table ($self->_bz_real_schema->get_table_list()) {
+ foreach my $column ($self->bz_table_columns($table)) {
+ my $abs_def = $self->bz_column_info($table, $column);
+
+ # BLOB/TEXT columns never have defaults
+ next if $abs_def->{TYPE} =~ /BLOB|TEXT/i;
+ if (!defined $abs_def->{DEFAULT}) {
+
+ # Get the exact default from the database without any
+ # "fixing" by bz_column_info_real.
+ my $raw_info = $self->_bz_raw_column_info($table, $column);
+ my $raw_default = $raw_info->{COLUMN_DEF};
+ if (defined $raw_default) {
+ if ($raw_default eq '') {
+
+ # Only (var)char columns can have empty strings as
+ # defaults, so if we got an empty string for some
+ # other default type, then it's bogus.
+ next unless $abs_def->{TYPE} =~ /char/i;
+ $raw_default = "''";
+ }
+ $fix_columns{$table} ||= [];
+ push(@{$fix_columns{$table}}, $column);
+ print "$table.$column has incorrect DB default: $raw_default\n";
+ }
+ }
+ } # foreach $column
+ } # foreach $table
+
+ print "Fixing defaults...\n";
+ foreach my $table (reverse sort keys %fix_columns) {
+ my @alters = map("ALTER COLUMN $_ DROP DEFAULT", @{$fix_columns{$table}});
+ my $sql = "ALTER TABLE $table " . join(',', @alters);
+ $self->do($sql);
+ }
}
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");
+ 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;
+ 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;
- }
+ 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;
+ return \%enum_values;
}
#####################################################################
@@ -916,29 +942,29 @@ backwards-compatibility anyway, for versions of Bugzilla before 2.20.
=cut
sub bz_column_info_real {
- my ($self, $table, $column) = @_;
- my $col_data = $self->_bz_raw_column_info($table, $column);
- return $self->_bz_schema->column_info_to_column($col_data);
+ my ($self, $table, $column) = @_;
+ my $col_data = $self->_bz_raw_column_info($table, $column);
+ return $self->_bz_schema->column_info_to_column($col_data);
}
sub _bz_raw_column_info {
- 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 $col_data;
+ 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 $col_data;
}
=item C<bz_index_info_real($table, $index)>
@@ -952,42 +978,43 @@ sub _bz_raw_column_info {
=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 ($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;
+ my $retval;
+ if (scalar(@fields)) {
+ $retval = {FIELDS => \@fields, TYPE => $index_type};
+ }
+ return $retval;
}
=item C<bz_index_list_real($table)>
@@ -1000,10 +1027,11 @@ sub bz_index_info_real {
=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]}) };
+ 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]})};
}
#####################################################################
@@ -1027,34 +1055,33 @@ this code does.
# 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) = @_;
-
- my $schema = $self->_bz_schema->get_empty_schema();
-
- my @tables = $self->bz_table_list_real();
- if (@tables) {
- print "Building Schema object from database...\n";
+ my ($self) = @_;
+
+ my $schema = $self->_bz_schema->get_empty_schema();
+
+ my @tables = $self->bz_table_list_real();
+ if (@tables) {
+ print "Building Schema object from database...\n";
+ }
+ 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);
}
- 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);
- }
- }
+ 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;
+ return $schema;
}
1;
diff --git a/Bugzilla/DB/Oracle.pm b/Bugzilla/DB/Oracle.pm
index 7424019ac..930270ccc 100644
--- a/Bugzilla/DB/Oracle.pm
+++ b/Bugzilla/DB/Oracle.pm
@@ -38,461 +38,473 @@ use Bugzilla::Util;
#####################################################################
# Constants
#####################################################################
-use constant EMPTY_STRING => '__BZ_EMPTY_STR__';
+use constant EMPTY_STRING => '__BZ_EMPTY_STR__';
use constant ISOLATION_LEVEL => 'READ COMMITTED';
-use constant BLOB_TYPE => { ora_type => ORA_BLOB };
+use constant BLOB_TYPE => {ora_type => ORA_BLOB};
+
# The max size allowed for LOB fields, in kilobytes.
use constant MIN_LONG_READ_LEN => 32 * 1024;
-use constant FULLTEXT_OR => ' OR ';
+use constant FULLTEXT_OR => ' OR ';
sub new {
- my ($class, $params) = @_;
- my ($user, $pass, $host, $dbname, $port) =
- @$params{qw(db_user db_pass db_host db_name db_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 => max(Bugzilla->params->{'maxattachmentsize'} || 0,
- MIN_LONG_READ_LEN) * 1024,
- };
- my $self = $class->db_new({ dsn => $dsn, user => $user,
- pass => $pass, attrs => $attrs });
- # Needed by TheSchwartz
- $self->{private_bz_dsn} = $dsn;
-
- 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;
+ my ($class, $params) = @_;
+ my ($user, $pass, $host, $dbname, $port)
+ = @$params{qw(db_user db_pass db_host db_name db_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 =>
+ max(Bugzilla->params->{'maxattachmentsize'} || 0, MIN_LONG_READ_LEN) * 1024,
+ };
+ my $self = $class->db_new(
+ {dsn => $dsn, user => $user, pass => $pass, attrs => $attrs});
+
+ # Needed by TheSchwartz
+ $self->{private_bz_dsn} = $dsn;
+
+ 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 ($self, $table, $column) = @_;
- my $seq = $table . "_" . $column . "_SEQ";
- my ($last_insert_id) = $self->selectrow_array("SELECT $seq.CURRVAL "
- . " FROM DUAL");
- return $last_insert_id;
+ my $seq = $table . "_" . $column . "_SEQ";
+ my ($last_insert_id)
+ = $self->selectrow_array("SELECT $seq.CURRVAL " . " FROM DUAL");
+ return $last_insert_id;
}
sub bz_check_regexp {
- my ($self, $pattern) = @_;
+ my ($self, $pattern) = @_;
- eval { $self->do("SELECT 1 FROM DUAL WHERE "
- . $self->sql_regexp($self->quote("a"), $pattern, 1)) };
+ eval {
+ $self->do("SELECT 1 FROM DUAL WHERE "
+ . $self->sql_regexp($self->quote("a"), $pattern, 1));
+ };
- $@ && ThrowUserError('illegal_regexp',
- { value => $pattern, dberror => $self->errstr });
+ $@
+ && ThrowUserError('illegal_regexp',
+ {value => $pattern, dberror => $self->errstr});
}
-sub bz_explain {
- my ($self, $sql) = @_;
- my $sth = $self->prepare("EXPLAIN PLAN FOR $sql");
- $sth->execute();
- my $explain = $self->selectcol_arrayref(
- "SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY)");
- return join("\n", @$explain);
-}
+sub bz_explain {
+ my ($self, $sql) = @_;
+ my $sth = $self->prepare("EXPLAIN PLAN FOR $sql");
+ $sth->execute();
+ my $explain = $self->selectcol_arrayref(
+ "SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY)");
+ return join("\n", @$explain);
+}
sub sql_group_concat {
- my ($self, $text, $separator) = @_;
- $separator = $self->quote(', ') if !defined $separator;
- my ($distinct, $rest) = $text =~/^(\s*DISTINCT\s|)(.+)$/i;
- return "group_concat($distinct T_CLOB_DELIM(NVL($rest, ' '), $separator))";
+ my ($self, $text, $separator) = @_;
+ $separator = $self->quote(', ') if !defined $separator;
+ my ($distinct, $rest) = $text =~ /^(\s*DISTINCT\s|)(.+)$/i;
+ return "group_concat($distinct T_CLOB_DELIM(NVL($rest, ' '), $separator))";
}
sub sql_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "REGEXP_LIKE($expr, $pattern)";
+ return "REGEXP_LIKE($expr, $pattern)";
}
sub sql_not_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "NOT REGEXP_LIKE($expr, $pattern)"
+ return "NOT REGEXP_LIKE($expr, $pattern)";
}
sub sql_limit {
- my ($self, $limit, $offset) = @_;
+ my ($self, $limit, $offset) = @_;
- if(defined $offset) {
- return "/* LIMIT $limit $offset */";
- }
- return "/* LIMIT $limit */";
+ if (defined $offset) {
+ return "/* LIMIT $limit $offset */";
+ }
+ return "/* LIMIT $limit */";
}
sub sql_string_concat {
- my ($self, @params) = @_;
+ my ($self, @params) = @_;
- return 'CONCAT(' . join(', ', @params) . ')';
+ return 'CONCAT(' . join(', ', @params) . ')';
}
sub sql_to_days {
- my ($self, $date) = @_;
+ my ($self, $date) = @_;
- return " TO_CHAR(TO_DATE($date),'J') ";
+ return " TO_CHAR(TO_DATE($date),'J') ";
}
-sub sql_from_days{
- my ($self, $date) = @_;
- return " TO_DATE($date,'J') ";
+sub sql_from_days {
+ my ($self, $date) = @_;
+
+ return " TO_DATE($date,'J') ";
}
sub sql_fulltext_search {
- my ($self, $column, $text) = @_;
- state $label = 0;
- $text = $self->quote($text);
- trick_taint($text);
- $label++;
- return "CONTAINS($column,$text,$label) > 0", "SCORE($label)";
+ my ($self, $column, $text) = @_;
+ state $label = 0;
+ $text = $self->quote($text);
+ trick_taint($text);
+ $label++;
+ return "CONTAINS($column,$text,$label) > 0", "SCORE($label)";
}
sub sql_date_format {
- my ($self, $date, $format) = @_;
-
- $format = "%Y.%m.%d %H:%i:%s" if !$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;
+ $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) . ")";
+ return "TO_CHAR($date, " . $self->quote($format) . ")";
}
sub sql_date_math {
- my ($self, $date, $operator, $interval, $units) = @_;
- my $time_sql;
- if ($units =~ /YEAR|MONTH/i) {
- $time_sql = "NUMTOYMINTERVAL($interval,'$units')";
- } else{
- $time_sql = "NUMTODSINTERVAL($interval,'$units')";
- }
- return "$date $operator $time_sql";
+ my ($self, $date, $operator, $interval, $units) = @_;
+ my $time_sql;
+ if ($units =~ /YEAR|MONTH/i) {
+ $time_sql = "NUMTOYMINTERVAL($interval,'$units')";
+ }
+ else {
+ $time_sql = "NUMTODSINTERVAL($interval,'$units')";
+ }
+ return "$date $operator $time_sql";
}
sub sql_position {
- my ($self, $fragment, $text) = @_;
- return "INSTR($text, $fragment)";
+ my ($self, $fragment, $text) = @_;
+ return "INSTR($text, $fragment)";
}
sub sql_in {
- my ($self, $column_name, $in_list_ref, $negate) = @_;
- my @in_list = @$in_list_ref;
- return $self->SUPER::sql_in($column_name, $in_list_ref, $negate) 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, $negate));
- }
- return "( " . join(" OR ", @in_str) . " )";
+ my ($self, $column_name, $in_list_ref, $negate) = @_;
+ my @in_list = @$in_list_ref;
+ return $self->SUPER::sql_in($column_name, $in_list_ref, $negate)
+ 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, $negate));
+ }
+ return "( " . join(" OR ", @in_str) . " )";
}
sub _bz_add_field_table {
- my ($self, $name, $schema_ref, $type) = @_;
- $self->SUPER::_bz_add_field_table($name, $schema_ref);
- if (defined($type) && $type == FIELD_TYPE_MULTI_SELECT) {
- my $uk_name = "UK_" . $self->_bz_schema->_hash_identifier($name . '_value');
- $self->do("ALTER TABLE $name ADD CONSTRAINT $uk_name UNIQUE(value)");
- }
+ my ($self, $name, $schema_ref, $type) = @_;
+ $self->SUPER::_bz_add_field_table($name, $schema_ref);
+ if (defined($type) && $type == FIELD_TYPE_MULTI_SELECT) {
+ my $uk_name = "UK_" . $self->_bz_schema->_hash_identifier($name . '_value');
+ $self->do("ALTER TABLE $name ADD CONSTRAINT $uk_name UNIQUE(value)");
+ }
}
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);
- }
+ 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.
+# Dropping all FKs for a specified table.
sub _bz_drop_fks {
- my ($self, $table) = @_;
- my @columns = $self->bz_table_columns($table);
- foreach my $column (@columns) {
- $self->bz_drop_fk($table, $column);
- }
+ my ($self, $table) = @_;
+ my @columns = $self->bz_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;
+ 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;
+ 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;
+ 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) = @_;
-
- if ($sql =~ /^CREATE OR REPLACE.*/i){
- return $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;
+ my ($sql) = @_;
+
+ if ($sql =~ /^CREATE OR REPLACE.*/i) {
+ return $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 includes the time in CURRENT_DATE.
+ $part =~ s/\bCURRENT_DATE\b/TRUNC(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;
}
- # 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;
+ # 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 includes the time in CURRENT_DATE.
- $part =~ s/\bCURRENT_DATE\b/TRUNC(CURRENT_DATE)/io;
+ $nonstring =~ s/\bCURRENT_DATE\b/TRUNC(CURRENT_DATE)/io;
# Oracle use SUBSTR instead of SUBSTRING
- $part =~ s/\bSUBSTRING\b/SUBSTR/io;
-
+ $nonstring =~ 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;
- }
+ $nonstring =~ s/\bAS\b//ig;
- # 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);
+ # Look for a LIMIT clause
+ ($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o);
- # Oracle includes the time in CURRENT_DATE.
- $nonstring =~ s/\bCURRENT_DATE\b/TRUNC(CURRENT_DATE)/io;
+ if (!length($string)) {
+ push @result, EMPTY_STRING;
+ push @result, $nonstring;
+ }
+ else {
+ push @result, $string;
+ push @result, $nonstring;
+ }
+ }
- # Oracle use SUBSTR instead of SUBSTRING
- $nonstring =~ s/\bSUBSTRING\b/SUBSTR/io;
+ my $new_sql = join "'", @result;
- # Oracle need no 'AS'
- $nonstring =~ s/\bAS\b//ig;
+ # Append "FROM dual" if this is a SELECT without a FROM clause
+ $new_sql .= " FROM DUAL" if ($is_select and !$has_from);
- # Look for a LIMIT clause
- ($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o);
+ # Wrap the query with a "WHERE rownum <= ..." if we found LIMIT
- if(!length($string)){
- push @result, EMPTY_STRING;
- push @result, $nonstring;
- } else {
- push @result, $string;
- push @result, $nonstring;
- }
+ if (defined($limit)) {
+ if ($new_sql !~ /\bWHERE\b/) {
+ $new_sql = $new_sql . " WHERE 1=1";
}
-
- 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, 2);
- if (defined($offset)) {
- my ($before_from, $after_from) = split(/\bFROM\b/i, $new_sql, 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;
+ my ($before_where, $after_where) = split(/\bWHERE\b/i, $new_sql, 2);
+ if (defined($offset)) {
+ my ($before_from, $after_from) = split(/\bFROM\b/i, $new_sql, 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;
}
- return $new_sql;
+ $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(@_);
+ 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;
- }
+ 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;
+ 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;
+ _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;
+ 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;
+ _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);
- }
+ 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;
+ 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;
+ 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;
+ 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';
+ 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';
+ 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;
+ my ($self, $id) = @_;
+ return $id;
}
#####################################################################
@@ -500,20 +512,22 @@ sub quote_identifier {
#####################################################################
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;
+ 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;
+ 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;
}
#####################################################################
@@ -521,32 +535,37 @@ sub bz_table_list_real {
#####################################################################
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 types for group_concat
- my $type_exists = $self->selectrow_array("SELECT 1 FROM user_types
- WHERE type_name = 'T_GROUP_CONCAT'");
- $self->do("DROP TYPE T_GROUP_CONCAT") if $type_exists;
- $self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
- . "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256)"
- . ", MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2"
- . ");");
- $self->do("CREATE OR REPLACE TYPE BODY T_CLOB_DELIM IS
+ 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 types for group_concat
+ my $type_exists = $self->selectrow_array(
+ "SELECT 1 FROM user_types
+ WHERE type_name = 'T_GROUP_CONCAT'"
+ );
+ $self->do("DROP TYPE T_GROUP_CONCAT") if $type_exists;
+ $self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
+ . "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256)"
+ . ", MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2"
+ . ");");
+ $self->do(
+ "CREATE OR REPLACE TYPE BODY T_CLOB_DELIM IS
MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2 is
BEGIN
RETURN p_CONTENT;
END;
- END;");
+ END;"
+ );
- $self->do("CREATE OR REPLACE TYPE T_GROUP_CONCAT AS OBJECT
+ $self->do(
+ "CREATE OR REPLACE TYPE T_GROUP_CONCAT AS OBJECT
( CLOB_CONTENT CLOB,
DELIMITER VARCHAR2(256),
STATIC FUNCTION ODCIAGGREGATEINITIALIZE(
@@ -564,9 +583,11 @@ sub bz_setup_database {
MEMBER FUNCTION ODCIAGGREGATEMERGE(
SELF IN OUT NOCOPY T_GROUP_CONCAT,
CTX2 IN T_GROUP_CONCAT)
- RETURN NUMBER);");
+ RETURN NUMBER);"
+ );
- $self->do("CREATE OR REPLACE TYPE BODY T_GROUP_CONCAT IS
+ $self->do(
+ "CREATE OR REPLACE TYPE BODY T_GROUP_CONCAT IS
STATIC FUNCTION ODCIAGGREGATEINITIALIZE(
SCTX IN OUT NOCOPY T_GROUP_CONCAT)
RETURN NUMBER IS
@@ -610,110 +631,117 @@ sub bz_setup_database {
DBMS_LOB.APPEND(SELF.CLOB_CONTENT, CTX2.CLOB_CONTENT);
RETURN ODCICONST.SUCCESS;
END;
- END;");
+ END;"
+ );
- # Create user-defined aggregate function group_concat
- $self->do("CREATE OR REPLACE FUNCTION GROUP_CONCAT(P_INPUT T_CLOB_DELIM)
+ # Create user-defined aggregate function group_concat
+ $self->do(
+ "CREATE OR REPLACE FUNCTION GROUP_CONCAT(P_INPUT T_CLOB_DELIM)
RETURN CLOB
- DETERMINISTIC PARALLEL_ENABLE AGGREGATE USING T_GROUP_CONCAT;");
-
- # 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 $sth = $self->prepare("SELECT OBJECT_NAME FROM USER_OBJECTS WHERE OBJECT_NAME = ?");
- 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);
- # bz_add_column() before Bugzilla 4.2.3 didn't handle primary keys
- # correctly (bug 731156). We have to add missing sequences and
- # triggers ourselves.
- if ($def->{TYPE} =~ /SERIAL/i) {
- my $sequence = "${table}_${column}_SEQ";
- my $exists = $self->selectrow_array($sth, undef, $sequence);
- if (!$exists) {
- my @sql = $self->_get_create_seq_ddl($table, $column);
- $self->do($_) foreach @sql;
- }
- }
-
- 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);
- # bz_rename_table didn't rename the trigger correctly.
- if ($table eq 'bug_tag' && $to_table eq 'tags') {
- $to_table = 'tag';
- }
- if ( $update =~ /CASCADE/i ){
- my $trigger_name = uc($fk_name . "_UC");
- my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
- if(@$exist_trigger) {
- $self->do("DROP TRIGGER $trigger_name");
- }
-
- my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
- . " AFTER UPDATE OF $to_column 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);
- }
- }
+ DETERMINISTIC PARALLEL_ENABLE AGGREGATE USING T_GROUP_CONCAT;"
+ );
+
+ # 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 $sth = $self->prepare(
+ "SELECT OBJECT_NAME FROM USER_OBJECTS WHERE OBJECT_NAME = ?");
+ 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);
+
+ # bz_add_column() before Bugzilla 4.2.3 didn't handle primary keys
+ # correctly (bug 731156). We have to add missing sequences and
+ # triggers ourselves.
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ my $sequence = "${table}_${column}_SEQ";
+ my $exists = $self->selectrow_array($sth, undef, $sequence);
+ if (!$exists) {
+ my @sql = $self->_get_create_seq_ddl($table, $column);
+ $self->do($_) foreach @sql;
}
+ }
+
+ 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);
+
+ # bz_rename_table didn't rename the trigger correctly.
+ if ($table eq 'bug_tag' && $to_table eq 'tags') {
+ $to_table = 'tag';
+ }
+ if ($update =~ /CASCADE/i) {
+ my $trigger_name = uc($fk_name . "_UC");
+ my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
+ if (@$exist_trigger) {
+ $self->do("DROP TRIGGER $trigger_name");
+ }
+
+ my $tr_str
+ = "CREATE OR REPLACE TRIGGER $trigger_name"
+ . " AFTER UPDATE OF $to_column 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);
+ }
+ }
}
+ }
- # Drop the trigger which causes bug 541553
- my $trigger_name = "PRODUCTS_MILESTONEURL";
- my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
- if(@$exist_trigger) {
- $self->do("DROP TRIGGER $trigger_name");
- }
+ # Drop the trigger which causes bug 541553
+ my $trigger_name = "PRODUCTS_MILESTONEURL";
+ my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
+ if (@$exist_trigger) {
+ $self->do("DROP TRIGGER $trigger_name");
+ }
}
# These two methods have been copied from Bugzilla::DB::Schema::Oracle.
sub _get_create_seq_ddl {
- my ($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- my $seq_name = "${table}_${column}_SEQ";
- my $seq_sql = "CREATE SEQUENCE $seq_name INCREMENT BY 1 START WITH 1 " .
- "NOMAXVALUE NOCYCLE NOCACHE";
- my $trigger_sql = $self->_get_create_trigger_ddl($table, $column, $seq_name);
- return ($seq_sql, $trigger_sql);
+ my $seq_name = "${table}_${column}_SEQ";
+ my $seq_sql = "CREATE SEQUENCE $seq_name INCREMENT BY 1 START WITH 1 "
+ . "NOMAXVALUE NOCYCLE NOCACHE";
+ my $trigger_sql = $self->_get_create_trigger_ddl($table, $column, $seq_name);
+ return ($seq_sql, $trigger_sql);
}
sub _get_create_trigger_ddl {
- my ($self, $table, $column, $seq_name) = @_;
+ my ($self, $table, $column, $seq_name) = @_;
- my $trigger_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;";
- return $trigger_sql;
+ my $trigger_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;";
+ return $trigger_sql;
}
############################################################################
@@ -725,68 +753,69 @@ use strict;
use warnings;
use parent -norequire, 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;
+ 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;
- }
+ 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;
+ 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);
- }
+ 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);
}
- return $ref;
+ 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;
+ 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;
+ my $self = shift;
+ my $row = $self->SUPER::fetch(@_);
+ if ($row) {
+ Bugzilla::DB::Oracle::_fix_arrayref($row);
+ }
+ return $row;
}
1;
diff --git a/Bugzilla/DB/Pg.pm b/Bugzilla/DB/Pg.pm
index cbf8d7af1..15a268381 100644
--- a/Bugzilla/DB/Pg.pm
+++ b/Bugzilla/DB/Pg.pm
@@ -32,215 +32,227 @@ use DBD::Pg;
# This module extends the DB interface via inheritance
use parent qw(Bugzilla::DB);
-use constant BLOB_TYPE => { pg_type => DBD::Pg::PG_BYTEA };
+use constant BLOB_TYPE => {pg_type => DBD::Pg::PG_BYTEA};
sub new {
- my ($class, $params) = @_;
- my ($user, $pass, $host, $dbname, $port) =
- @$params{qw(db_user db_pass db_host db_name db_port)};
+ my ($class, $params) = @_;
+ my ($user, $pass, $host, $dbname, $port)
+ = @$params{qw(db_user db_pass db_host db_name db_port)};
- # The default database name for PostgreSQL. We have
- # to connect to SOME database, even if we have
- # no $dbname parameter.
- $dbname ||= 'template1';
+ # 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;
+ # 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'";
+ # 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 $attrs = {pg_enable_utf8 => Bugzilla->params->{'utf8'}};
- my $self = $class->db_new({ dsn => $dsn, user => $user,
- pass => $pass, attrs => $attrs });
+ my $self = $class->db_new(
+ {dsn => $dsn, user => $user, pass => $pass, attrs => $attrs});
- # all class local variables stored in DBI derived class needs to have
- # a prefix 'private_'. See DBI documentation.
- $self->{private_bz_tables_locked} = "";
- # Needed by TheSchwartz
- $self->{private_bz_dsn} = $dsn;
+ # 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);
+ # Needed by TheSchwartz
+ $self->{private_bz_dsn} = $dsn;
- return $self;
+ 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 ($self, $table, $column) = @_;
- my $seq = $table . "_" . $column . "_seq";
- my ($last_insert_id) = $self->selectrow_array("SELECT CURRVAL('$seq')");
+ my $seq = $table . "_" . $column . "_seq";
+ my ($last_insert_id) = $self->selectrow_array("SELECT CURRVAL('$seq')");
- return $last_insert_id;
+ return $last_insert_id;
}
sub sql_group_concat {
- my ($self, $text, $separator, $sort, $order_by) = @_;
- $sort = 1 if !defined $sort;
- $separator = $self->quote(', ') if !defined $separator;
-
- # PostgreSQL 8.x doesn't support STRING_AGG
- if (vers_cmp($self->bz_server_version, 9) < 0) {
- my $sql = "ARRAY_ACCUM($text)";
- if ($sort) {
- $sql = "ARRAY_SORT($sql)";
- }
- return "ARRAY_TO_STRING($sql, $separator)";
- }
-
- if ($order_by && $text =~ /^DISTINCT\s*(.+)$/i) {
- # Since Postgres (quite rightly) doesn't support "SELECT DISTINCT x
- # ORDER BY y", we need to sort the list, and then get the unique
- # values
- return "ARRAY_TO_STRING(ANYARRAY_UNIQ(ARRAY_AGG($1 ORDER BY $order_by)), $separator)";
- }
-
- # Determine the ORDER BY clause (if any)
- if ($order_by) {
- $order_by = " ORDER BY $order_by";
- }
- elsif ($sort) {
- # We don't include the DISTINCT keyword in an order by
- $text =~ /^(?:DISTINCT\s*)?(.+)$/i;
- $order_by = " ORDER BY $1";
+ my ($self, $text, $separator, $sort, $order_by) = @_;
+ $sort = 1 if !defined $sort;
+ $separator = $self->quote(', ') if !defined $separator;
+
+ # PostgreSQL 8.x doesn't support STRING_AGG
+ if (vers_cmp($self->bz_server_version, 9) < 0) {
+ my $sql = "ARRAY_ACCUM($text)";
+ if ($sort) {
+ $sql = "ARRAY_SORT($sql)";
}
-
- return "STRING_AGG(${text}::text, $separator${order_by}::text)"
+ return "ARRAY_TO_STRING($sql, $separator)";
+ }
+
+ if ($order_by && $text =~ /^DISTINCT\s*(.+)$/i) {
+
+ # Since Postgres (quite rightly) doesn't support "SELECT DISTINCT x
+ # ORDER BY y", we need to sort the list, and then get the unique
+ # values
+ return
+ "ARRAY_TO_STRING(ANYARRAY_UNIQ(ARRAY_AGG($1 ORDER BY $order_by)), $separator)";
+ }
+
+ # Determine the ORDER BY clause (if any)
+ if ($order_by) {
+ $order_by = " ORDER BY $order_by";
+ }
+ elsif ($sort) {
+
+ # We don't include the DISTINCT keyword in an order by
+ $text =~ /^(?:DISTINCT\s*)?(.+)$/i;
+ $order_by = " ORDER BY $1";
+ }
+
+ return "STRING_AGG(${text}::text, $separator${order_by}::text)";
}
sub sql_istring {
- my ($self, $string) = @_;
+ my ($self, $string) = @_;
- return "LOWER(${string}::text)";
+ return "LOWER(${string}::text)";
}
sub sql_position {
- my ($self, $fragment, $text) = @_;
+ my ($self, $fragment, $text) = @_;
- return "POSITION(${fragment}::text IN ${text}::text)";
+ return "POSITION(${fragment}::text IN ${text}::text)";
}
sub sql_like {
- my ($self, $fragment, $column, $not) = @_;
- $not //= '';
+ my ($self, $fragment, $column, $not) = @_;
+ $not //= '';
- return "${column}::text $not LIKE " . $self->sql_like_escape($fragment) . " ESCAPE '|'";
+ return
+ "${column}::text $not LIKE "
+ . $self->sql_like_escape($fragment)
+ . " ESCAPE '|'";
}
sub sql_ilike {
- my ($self, $fragment, $column, $not) = @_;
- $not //= '';
+ my ($self, $fragment, $column, $not) = @_;
+ $not //= '';
- return "${column}::text $not ILIKE " . $self->sql_like_escape($fragment) . " ESCAPE '|'";
+ return
+ "${column}::text $not ILIKE "
+ . $self->sql_like_escape($fragment)
+ . " ESCAPE '|'";
}
sub sql_not_ilike {
- return shift->sql_ilike(@_, 'NOT');
+ return shift->sql_ilike(@_, 'NOT');
}
# Escapes any % or _ characters which are special in a LIKE match.
# Also performs a $dbh->quote to escape any quote characters.
sub sql_like_escape {
- my ($self, $fragment) = @_;
+ my ($self, $fragment) = @_;
- $fragment =~ s/\|/\|\|/g; # escape the escape character if it appears
- $fragment =~ s/%/\|%/g; # percent and underscore are the special match
- $fragment =~ s/_/\|_/g; # characters in SQL.
+ $fragment =~ s/\|/\|\|/g; # escape the escape character if it appears
+ $fragment =~ s/%/\|%/g; # percent and underscore are the special match
+ $fragment =~ s/_/\|_/g; # characters in SQL.
- return $self->quote("%$fragment%");
+ return $self->quote("%$fragment%");
}
sub sql_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "${expr}::text ~* $pattern";
+ return "${expr}::text ~* $pattern";
}
sub sql_not_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "${expr}::text !~* $pattern"
+ return "${expr}::text !~* $pattern";
}
sub sql_limit {
- my ($self, $limit, $offset) = @_;
-
- if (defined($offset)) {
- return "LIMIT $limit OFFSET $offset";
- } else {
- return "LIMIT $limit";
- }
+ my ($self, $limit, $offset) = @_;
+
+ if (defined($offset)) {
+ return "LIMIT $limit OFFSET $offset";
+ }
+ else {
+ return "LIMIT $limit";
+ }
}
sub sql_from_days {
- my ($self, $days) = @_;
+ my ($self, $days) = @_;
- return "TO_TIMESTAMP('$days', 'J')::date";
+ return "TO_TIMESTAMP('$days', 'J')::date";
}
sub sql_to_days {
- my ($self, $date) = @_;
+ my ($self, $date) = @_;
- return "TO_CHAR(${date}::date, 'J')::int";
+ 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) . ")";
+ 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_date_math {
- my ($self, $date, $operator, $interval, $units) = @_;
-
- return "$date $operator $interval * INTERVAL '1 $units'";
+ my ($self, $date, $operator, $interval, $units) = @_;
+
+ return "$date $operator $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))';
+ 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;
+ my ($self, $seq_name) = @_;
+ my $exists
+ = $self->selectrow_array(
+ 'SELECT 1 FROM pg_statio_user_sequences WHERE relname = ?',
+ undef, $seq_name);
+ return $exists || 0;
}
sub bz_explain {
- my ($self, $sql) = @_;
- my $explain = $self->selectcol_arrayref("EXPLAIN ANALYZE $sql");
- return join("\n", @$explain);
+ my ($self, $sql) = @_;
+ my $explain = $self->selectcol_arrayref("EXPLAIN ANALYZE $sql");
+ return join("\n", @$explain);
}
#####################################################################
@@ -248,42 +260,49 @@ sub bz_explain {
#####################################################################
sub bz_check_server_version {
- my $self = shift;
- my ($db) = @_;
- my $server_version = $self->SUPER::bz_check_server_version(@_);
- my ($major_version, $minor_version) = $server_version =~ /^0*(\d+)\.0*(\d+)/;
- # Pg 9.0 requires DBD::Pg 2.17.2 in order to properly read bytea values.
- # Pg 9.2 requires DBD::Pg 2.19.3 as spclocation no longer exists.
- if ($major_version >= 9) {
- local $db->{dbd}->{version} = ($minor_version >= 2) ? '2.19.3' : '2.17.2';
- local $db->{name} = $db->{name} . " ${major_version}.$minor_version";
- Bugzilla::DB::_bz_check_dbd(@_);
- }
+ my $self = shift;
+ my ($db) = @_;
+ my $server_version = $self->SUPER::bz_check_server_version(@_);
+ my ($major_version, $minor_version) = $server_version =~ /^0*(\d+)\.0*(\d+)/;
+
+ # Pg 9.0 requires DBD::Pg 2.17.2 in order to properly read bytea values.
+ # Pg 9.2 requires DBD::Pg 2.19.3 as spclocation no longer exists.
+ if ($major_version >= 9) {
+ local $db->{dbd}->{version} = ($minor_version >= 2) ? '2.19.3' : '2.17.2';
+ local $db->{name} = $db->{name} . " ${major_version}.$minor_version";
+ Bugzilla::DB::_bz_check_dbd(@_);
+ }
}
sub bz_setup_database {
- my $self = shift;
- $self->SUPER::bz_setup_database(@_);
-
- my ($has_plpgsql) = $self->selectrow_array("SELECT COUNT(*) FROM pg_language WHERE lanname = 'plpgsql'");
- $self->do('CREATE LANGUAGE plpgsql') unless $has_plpgsql;
-
- if (vers_cmp($self->bz_server_version, 9) < 0) {
- # Custom Functions for Postgres 8
- my $function = 'array_accum';
- my $array_accum = $self->selectrow_array(
- 'SELECT 1 FROM pg_proc WHERE proname = ?', undef, $function);
- if (!$array_accum) {
- print "Creating function $function...\n";
- $self->do("CREATE AGGREGATE array_accum (
+ my $self = shift;
+ $self->SUPER::bz_setup_database(@_);
+
+ my ($has_plpgsql)
+ = $self->selectrow_array(
+ "SELECT COUNT(*) FROM pg_language WHERE lanname = 'plpgsql'");
+ $self->do('CREATE LANGUAGE plpgsql') unless $has_plpgsql;
+
+ if (vers_cmp($self->bz_server_version, 9) < 0) {
+
+ # Custom Functions for Postgres 8
+ my $function = 'array_accum';
+ my $array_accum
+ = $self->selectrow_array('SELECT 1 FROM pg_proc WHERE proname = ?',
+ undef, $function);
+ if (!$array_accum) {
+ print "Creating function $function...\n";
+ $self->do(
+ "CREATE AGGREGATE array_accum (
SFUNC = array_append,
BASETYPE = anyelement,
STYPE = anyarray,
INITCOND = '{}'
- )");
- }
+ )"
+ );
+ }
- $self->do(<<'END');
+ $self->do(<<'END');
CREATE OR REPLACE FUNCTION array_sort(ANYARRAY)
RETURNS ANYARRAY LANGUAGE SQL
IMMUTABLE STRICT
@@ -296,31 +315,32 @@ SELECT ARRAY(
);
$$;
END
- }
- else {
- # Custom functions for Postgres 9.0+
-
- # -Copyright © 2013 Joshua D. Burns (JDBurnZ) and Message In Action LLC
- # JDBurnZ: https://github.com/JDBurnZ
- # Message In Action: https://www.messageinaction.com
- #
- #Permission is hereby granted, free of charge, to any person obtaining a copy of
- #this software and associated documentation files (the "Software"), to deal in
- #the Software without restriction, including without limitation the rights to
- #use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
- #the Software, and to permit persons to whom the Software is furnished to do so,
- #subject to the following conditions:
- #
- #The above copyright notice and this permission notice shall be included in all
- #copies or substantial portions of the Software.
- #
- #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
- #FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
- #COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
- #IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- #CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- $self->do(q|
+ }
+ else {
+ # Custom functions for Postgres 9.0+
+
+ # -Copyright © 2013 Joshua D. Burns (JDBurnZ) and Message In Action LLC
+ # JDBurnZ: https://github.com/JDBurnZ
+ # Message In Action: https://www.messageinaction.com
+ #
+ #Permission is hereby granted, free of charge, to any person obtaining a copy of
+ #this software and associated documentation files (the "Software"), to deal in
+ #the Software without restriction, including without limitation the rights to
+ #use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ #the Software, and to permit persons to whom the Software is furnished to do so,
+ #subject to the following conditions:
+ #
+ #The above copyright notice and this permission notice shall be included in all
+ #copies or substantial portions of the Software.
+ #
+ #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ #FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ #COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ #IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ #CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ $self->do(
+ q|
DROP FUNCTION IF EXISTS anyarray_uniq(anyarray);
CREATE OR REPLACE FUNCTION anyarray_uniq(with_array anyarray)
RETURNS anyarray AS $BODY$
@@ -345,135 +365,152 @@ END
RETURN return_array;
END;
$BODY$ LANGUAGE plpgsql;
- |);
+ |
+ );
+ }
+
+ # 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 and bz_rename_table didn't correctly rename
+ # the sequence.
+ $self->_fix_bad_sequence('fielddefs', 'id', 'fielddefs_fieldid_seq',
+ 'fielddefs_id_seq');
+
+ # If the 'tags' table still exists, then bz_rename_table()
+ # will fix the sequence for us.
+ if (!$self->bz_table_info('tags')) {
+ my $res = $self->_fix_bad_sequence('tag', 'id', 'tags_id_seq', 'tag_id_seq');
+
+ # If $res is true, then the sequence has been renamed, meaning that
+ # the primary key must be renamed too.
+ if ($res) {
+ $self->do('ALTER INDEX tags_pkey RENAME TO tag_pkey');
}
-
- # 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 and bz_rename_table didn't correctly rename
- # the sequence.
- $self->_fix_bad_sequence('fielddefs', 'id', 'fielddefs_fieldid_seq', 'fielddefs_id_seq');
- # If the 'tags' table still exists, then bz_rename_table()
- # will fix the sequence for us.
- if (!$self->bz_table_info('tags')) {
- my $res = $self->_fix_bad_sequence('tag', 'id', 'tags_id_seq', 'tag_id_seq');
- # If $res is true, then the sequence has been renamed, meaning that
- # the primary key must be renamed too.
- if ($res) {
- $self->do('ALTER INDEX tags_pkey RENAME TO tag_pkey');
- }
- }
-
- # Certain sequences got upgraded before we required Pg 8.3, and
- # so they were not properly associated with their columns.
- my @tables = $self->bz_table_list_real;
- foreach my $table (@tables) {
- my @columns = $self->bz_table_columns_real($table);
- foreach my $column (@columns) {
- # All our SERIAL pks have "id" in their name at the end.
- next unless $column =~ /id$/;
- my $sequence = "${table}_${column}_seq";
- if ($self->bz_sequence_exists($sequence)) {
- my $is_associated = $self->selectrow_array(
- 'SELECT pg_get_serial_sequence(?,?)',
- undef, $table, $column);
- next if $is_associated;
- print "Fixing $sequence to be associated"
- . " with $table.$column...\n";
- $self->do("ALTER SEQUENCE $sequence OWNED BY $table.$column");
- # In order to produce an exactly identical schema to what
- # a brand-new checksetup.pl run would produce, we also need
- # to re-set the default on this column.
- $self->do("ALTER TABLE $table
+ }
+
+ # Certain sequences got upgraded before we required Pg 8.3, and
+ # so they were not properly associated with their columns.
+ my @tables = $self->bz_table_list_real;
+ foreach my $table (@tables) {
+ my @columns = $self->bz_table_columns_real($table);
+ foreach my $column (@columns) {
+
+ # All our SERIAL pks have "id" in their name at the end.
+ next unless $column =~ /id$/;
+ my $sequence = "${table}_${column}_seq";
+ if ($self->bz_sequence_exists($sequence)) {
+ my $is_associated = $self->selectrow_array('SELECT pg_get_serial_sequence(?,?)',
+ undef, $table, $column);
+ next if $is_associated;
+ print "Fixing $sequence to be associated" . " with $table.$column...\n";
+ $self->do("ALTER SEQUENCE $sequence OWNED BY $table.$column");
+
+ # In order to produce an exactly identical schema to what
+ # a brand-new checksetup.pl run would produce, we also need
+ # to re-set the default on this column.
+ $self->do(
+ "ALTER TABLE $table
ALTER COLUMN $column
- SET DEFAULT nextval('$sequence')");
- }
- }
+ SET DEFAULT nextval('$sequence')"
+ );
+ }
}
+ }
}
sub _fix_bad_sequence {
- my ($self, $table, $column, $old_seq, $new_seq) = @_;
- if ($self->bz_column_info($table, $column)
- && $self->bz_sequence_exists($old_seq))
- {
- print "Fixing $old_seq sequence...\n";
- $self->do("ALTER SEQUENCE $old_seq RENAME TO $new_seq");
- $self->do("ALTER TABLE $table ALTER COLUMN $column
- SET DEFAULT NEXTVAL('$new_seq')");
- return 1;
- }
- return 0;
+ my ($self, $table, $column, $old_seq, $new_seq) = @_;
+ if ( $self->bz_column_info($table, $column)
+ && $self->bz_sequence_exists($old_seq))
+ {
+ print "Fixing $old_seq sequence...\n";
+ $self->do("ALTER SEQUENCE $old_seq RENAME TO $new_seq");
+ $self->do(
+ "ALTER TABLE $table ALTER COLUMN $column
+ SET DEFAULT NEXTVAL('$new_seq')"
+ );
+ return 1;
+ }
+ return 0;
}
# 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);
- }
+ 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
+# 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 $self = shift;
+
+ my @full_table_list = $self->SUPER::bz_table_list_real(@_);
- 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;
+ # All PostgreSQL system tables start with "pg_" or "sql_"
+ my @table_list = grep(!/(^pg_)|(^sql_)/, @full_table_list);
+ return @table_list;
}
1;
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index d1c1dc7e9..94b8734f3 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -29,6 +29,7 @@ use Digest::MD5 qw(md5_hex);
use Hash::Util qw(lock_value unlock_hash lock_keys unlock_keys);
use List::MoreUtils qw(firstidx natatime);
use Safe;
+
# Historical, needed for SCHEMA_VERSION = '1.00'
use Storable qw(dclone freeze thaw);
@@ -197,1596 +198,1544 @@ update this column in this table."
=cut
-use constant SCHEMA_VERSION => 3;
-use constant ADD_COLUMN => 'ADD COLUMN';
+use constant SCHEMA_VERSION => 3;
+use constant ADD_COLUMN => 'ADD COLUMN';
+
# Multiple FKs can be added using ALTER TABLE ADD CONSTRAINT in one
# SQL statement. This isn't true for all databases.
use constant MULTIPLE_FKS_IN_ALTER => 1;
+
# This is a reasonable default that's true for both PostgreSQL and MySQL.
use constant MAX_IDENTIFIER_LEN => 63;
use constant FIELD_TABLE_SCHEMA => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ value => {TYPE => 'varchar(64)', NOTNULL => 1},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ visibility_value_id => {TYPE => 'INT2'},
+ ],
+
+ # Note that bz_add_field_table should prepend the table name
+ # to these index names.
+ INDEXES => [
+ value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ sortkey_idx => ['sortkey', 'value'],
+ visibility_value_id_idx => ['visibility_value_id'],
+ ],
+};
+
+use constant ABSTRACT_SCHEMA => {
+
+ # BUG-RELATED TABLES
+ # ------------------
+
+ # General Bug Information
+ # -----------------------
+ bugs => {
FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- visibility_value_id => {TYPE => 'INT2'},
+ bug_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ assigned_to => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ bug_file_loc => {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"},
+ bug_severity => {TYPE => 'varchar(64)', NOTNULL => 1},
+ bug_status => {TYPE => 'varchar(64)', NOTNULL => 1},
+ creation_ts => {TYPE => 'DATETIME'},
+ delta_ts => {TYPE => 'DATETIME', NOTNULL => 1},
+ short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
+ op_sys => {TYPE => 'varchar(64)', NOTNULL => 1},
+ priority => {TYPE => 'varchar(64)', NOTNULL => 1},
+ product_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'products', COLUMN => 'id'}
+ },
+ rep_platform => {TYPE => 'varchar(64)', NOTNULL => 1},
+ reporter => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ version => {TYPE => 'varchar(64)', NOTNULL => 1},
+ component_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'components', COLUMN => 'id'}
+ },
+ resolution => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"},
+ target_milestone => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "'---'"},
+ qa_contact =>
+ {TYPE => 'INT3', REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}},
+ status_whiteboard => {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"},
+ lastdiffed => {TYPE => 'DATETIME'},
+ everconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1},
+ reporter_accessible => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ cclist_accessible => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ estimated_time => {TYPE => 'decimal(7,2)', NOTNULL => 1, DEFAULT => '0'},
+ remaining_time => {TYPE => 'decimal(7,2)', NOTNULL => 1, DEFAULT => '0'},
+ deadline => {TYPE => 'DATETIME'},
],
- # Note that bz_add_field_table should prepend the table name
- # to these index names.
INDEXES => [
- value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
- sortkey_idx => ['sortkey', 'value'],
- visibility_value_id_idx => ['visibility_value_id'],
+ bugs_assigned_to_idx => ['assigned_to'],
+ bugs_creation_ts_idx => ['creation_ts'],
+ bugs_delta_ts_idx => ['delta_ts'],
+ bugs_bug_severity_idx => ['bug_severity'],
+ bugs_bug_status_idx => ['bug_status'],
+ bugs_op_sys_idx => ['op_sys'],
+ bugs_priority_idx => ['priority'],
+ bugs_product_id_idx => ['product_id'],
+ bugs_reporter_idx => ['reporter'],
+ bugs_version_idx => ['version'],
+ bugs_component_id_idx => ['component_id'],
+ bugs_resolution_idx => ['resolution'],
+ bugs_target_milestone_idx => ['target_milestone'],
+ bugs_qa_contact_idx => ['qa_contact'],
],
-};
+ },
-use constant ABSTRACT_SCHEMA => {
+ bugs_fulltext => {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ PRIMARYKEY => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
+
+ # Comments are stored all together in one column for searching.
+ # This allows us to examine all comments together when deciding
+ # the relevance of a bug in fulltext search.
+ comments => {TYPE => 'LONGTEXT'},
+ comments_noprivate => {TYPE => 'LONGTEXT'},
+ ],
+ INDEXES => [
+ bugs_fulltext_short_desc_idx => {FIELDS => ['short_desc'], TYPE => 'FULLTEXT'},
+ bugs_fulltext_comments_idx => {FIELDS => ['comments'], TYPE => 'FULLTEXT'},
+ bugs_fulltext_comments_noprivate_idx =>
+ {FIELDS => ['comments_noprivate'], TYPE => 'FULLTEXT'},
+ ],
+ },
- # BUG-RELATED TABLES
- # ------------------
-
- # General Bug Information
- # -----------------------
- bugs => {
- FIELDS => [
- bug_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- assigned_to => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- bug_file_loc => {TYPE => 'MEDIUMTEXT',
- NOTNULL => 1, DEFAULT => "''"},
- bug_severity => {TYPE => 'varchar(64)', NOTNULL => 1},
- bug_status => {TYPE => 'varchar(64)', NOTNULL => 1},
- creation_ts => {TYPE => 'DATETIME'},
- delta_ts => {TYPE => 'DATETIME', NOTNULL => 1},
- short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
- op_sys => {TYPE => 'varchar(64)', NOTNULL => 1},
- priority => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id'}},
- rep_platform => {TYPE => 'varchar(64)', NOTNULL => 1},
- reporter => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- version => {TYPE => 'varchar(64)', NOTNULL => 1},
- component_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'components',
- COLUMN => 'id'}},
- resolution => {TYPE => 'varchar(64)',
- NOTNULL => 1, DEFAULT => "''"},
- target_milestone => {TYPE => 'varchar(64)',
- NOTNULL => 1, DEFAULT => "'---'"},
- qa_contact => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- status_whiteboard => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- lastdiffed => {TYPE => 'DATETIME'},
- everconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1},
- reporter_accessible => {TYPE => 'BOOLEAN',
- NOTNULL => 1, DEFAULT => 'TRUE'},
- cclist_accessible => {TYPE => 'BOOLEAN',
- NOTNULL => 1, DEFAULT => 'TRUE'},
- estimated_time => {TYPE => 'decimal(7,2)',
- NOTNULL => 1, DEFAULT => '0'},
- remaining_time => {TYPE => 'decimal(7,2)',
- NOTNULL => 1, DEFAULT => '0'},
- deadline => {TYPE => 'DATETIME'},
- ],
- INDEXES => [
- bugs_assigned_to_idx => ['assigned_to'],
- bugs_creation_ts_idx => ['creation_ts'],
- bugs_delta_ts_idx => ['delta_ts'],
- bugs_bug_severity_idx => ['bug_severity'],
- bugs_bug_status_idx => ['bug_status'],
- bugs_op_sys_idx => ['op_sys'],
- bugs_priority_idx => ['priority'],
- bugs_product_id_idx => ['product_id'],
- bugs_reporter_idx => ['reporter'],
- bugs_version_idx => ['version'],
- bugs_component_id_idx => ['component_id'],
- bugs_resolution_idx => ['resolution'],
- bugs_target_milestone_idx => ['target_milestone'],
- bugs_qa_contact_idx => ['qa_contact'],
- ],
- },
-
- bugs_fulltext => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1, PRIMARYKEY => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
- # Comments are stored all together in one column for searching.
- # This allows us to examine all comments together when deciding
- # the relevance of a bug in fulltext search.
- comments => {TYPE => 'LONGTEXT'},
- comments_noprivate => {TYPE => 'LONGTEXT'},
- ],
- INDEXES => [
- bugs_fulltext_short_desc_idx => {FIELDS => ['short_desc'],
- TYPE => 'FULLTEXT'},
- bugs_fulltext_comments_idx => {FIELDS => ['comments'],
- TYPE => 'FULLTEXT'},
- bugs_fulltext_comments_noprivate_idx => {
- FIELDS => ['comments_noprivate'], TYPE => 'FULLTEXT'},
- ],
- },
-
- bugs_activity => {
- FIELDS => [
- id => {TYPE => 'INTSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- attach_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'attachments',
- COLUMN => 'attach_id',
- DELETE => 'CASCADE'}},
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
- fieldid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'fielddefs',
- COLUMN => 'id'}},
- added => {TYPE => 'varchar(255)'},
- removed => {TYPE => 'varchar(255)'},
- comment_id => {TYPE => 'INT4',
- REFERENCES => { TABLE => 'longdescs',
- COLUMN => 'comment_id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- bugs_activity_bug_id_idx => ['bug_id'],
- bugs_activity_who_idx => ['who'],
- bugs_activity_bug_when_idx => ['bug_when'],
- bugs_activity_fieldid_idx => ['fieldid'],
- bugs_activity_added_idx => ['added'],
- bugs_activity_removed_idx => ['removed'],
- ],
- },
-
- bugs_aliases => {
- FIELDS => [
- alias => {TYPE => 'varchar(40)', NOTNULL => 1},
- bug_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- bugs_aliases_bug_id_idx => ['bug_id'],
- bugs_aliases_alias_idx => {FIELDS => ['alias'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- cc => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- cc_bug_id_idx => {FIELDS => [qw(bug_id who)],
- TYPE => 'UNIQUE'},
- cc_who_idx => ['who'],
- ],
- },
-
- longdescs => {
- FIELDS => [
- comment_id => {TYPE => 'INTSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
- work_time => {TYPE => 'decimal(7,2)', NOTNULL => 1,
- DEFAULT => '0'},
- thetext => {TYPE => 'LONGTEXT', NOTNULL => 1},
- isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- already_wrapped => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- type => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '0'},
- extra_data => {TYPE => 'varchar(255)'}
- ],
- INDEXES => [
- longdescs_bug_id_idx => [qw(bug_id work_time)],
- longdescs_who_idx => [qw(who bug_id)],
- longdescs_bug_when_idx => ['bug_when'],
- ],
- },
-
- longdescs_tags => {
- FIELDS => [
- id => { TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1 },
- comment_id => { TYPE => 'INT4',
- REFERENCES => { TABLE => 'longdescs',
- COLUMN => 'comment_id',
- DELETE => 'CASCADE' }},
- tag => { TYPE => 'varchar(24)', NOTNULL => 1 },
- ],
- INDEXES => [
- longdescs_tags_idx => { FIELDS => ['comment_id', 'tag'], TYPE => 'UNIQUE' },
- ],
- },
-
- longdescs_tags_weights => {
- FIELDS => [
- id => { TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1 },
- tag => { TYPE => 'varchar(24)', NOTNULL => 1 },
- weight => { TYPE => 'INT3', NOTNULL => 1 },
- ],
- INDEXES => [
- longdescs_tags_weights_tag_idx => { FIELDS => ['tag'], TYPE => 'UNIQUE' },
- ],
- },
-
- longdescs_tags_activity => {
- FIELDS => [
- id => { TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1 },
- bug_id => { TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => { TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE' }},
- comment_id => { TYPE => 'INT4',
- REFERENCES => { TABLE => 'longdescs',
- COLUMN => 'comment_id',
- DELETE => 'CASCADE' }},
- who => { TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => { TABLE => 'profiles',
- COLUMN => 'userid' }},
- bug_when => { TYPE => 'DATETIME', NOTNULL => 1 },
- added => { TYPE => 'varchar(24)' },
- removed => { TYPE => 'varchar(24)' },
- ],
- INDEXES => [
- longdescs_tags_activity_bug_id_idx => ['bug_id'],
- ],
- },
-
- dependencies => {
- FIELDS => [
- blocked => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- dependson => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- dependencies_blocked_idx => {FIELDS => [qw(blocked dependson)],
- TYPE => 'UNIQUE'},
- dependencies_dependson_idx => ['dependson'],
- ],
- },
-
- attachments => {
- FIELDS => [
- attach_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- creation_ts => {TYPE => 'DATETIME', NOTNULL => 1},
- modification_time => {TYPE => 'DATETIME', NOTNULL => 1},
- description => {TYPE => 'TINYTEXT', NOTNULL => 1},
- mimetype => {TYPE => 'TINYTEXT', NOTNULL => 1},
- ispatch => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- filename => {TYPE => 'varchar(255)', NOTNULL => 1},
- submitter_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- isobsolete => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- INDEXES => [
- attachments_bug_id_idx => ['bug_id'],
- attachments_creation_ts_idx => ['creation_ts'],
- attachments_modification_time_idx => ['modification_time'],
- attachments_submitter_id_idx => ['submitter_id', 'bug_id'],
- ],
- },
- attach_data => {
- FIELDS => [
- id => {TYPE => 'INT3', NOTNULL => 1,
- PRIMARYKEY => 1,
- REFERENCES => {TABLE => 'attachments',
- COLUMN => 'attach_id',
- DELETE => 'CASCADE'}},
- thedata => {TYPE => 'LONGBLOB', NOTNULL => 1},
- ],
- },
-
- duplicates => {
- FIELDS => [
- dupe_of => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- dupe => {TYPE => 'INT3', NOTNULL => 1,
- PRIMARYKEY => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- ],
- },
-
- bug_see_also => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- value => {TYPE => 'varchar(255)', NOTNULL => 1},
- class => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"},
- ],
- INDEXES => [
- bug_see_also_bug_id_idx => {FIELDS => [qw(bug_id value)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # Auditing
- # --------
-
- audit_log => {
- FIELDS => [
- user_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'SET NULL'}},
- class => {TYPE => 'varchar(255)', NOTNULL => 1},
- object_id => {TYPE => 'INT4', NOTNULL => 1},
- field => {TYPE => 'varchar(64)', NOTNULL => 1},
- removed => {TYPE => 'MEDIUMTEXT'},
- added => {TYPE => 'MEDIUMTEXT'},
- at_time => {TYPE => 'DATETIME', NOTNULL => 1},
- ],
- INDEXES => [
- audit_log_class_idx => ['class', 'at_time'],
- ],
- },
-
- # Keywords
- # --------
-
- keyworddefs => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- ],
- INDEXES => [
- keyworddefs_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- keywords => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- keywordid => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'keyworddefs',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
-
- ],
- INDEXES => [
- keywords_bug_id_idx => {FIELDS => [qw(bug_id keywordid)],
- TYPE => 'UNIQUE'},
- keywords_keywordid_idx => ['keywordid'],
- ],
- },
-
- # Flags
- # -----
-
- # "flags" stores one record for each flag on each bug/attachment.
- flags => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- type_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'flagtypes',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- status => {TYPE => 'char(1)', NOTNULL => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- attach_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'attachments',
- COLUMN => 'attach_id',
- DELETE => 'CASCADE'}},
- creation_date => {TYPE => 'DATETIME', NOTNULL => 1},
- modification_date => {TYPE => 'DATETIME'},
- setter_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- requestee_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- ],
- INDEXES => [
- flags_bug_id_idx => [qw(bug_id attach_id)],
- flags_setter_id_idx => ['setter_id'],
- flags_requestee_id_idx => ['requestee_id'],
- flags_type_id_idx => ['type_id'],
- ],
- },
-
- # "flagtypes" defines the types of flags that can be set.
- flagtypes => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(50)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- cc_list => {TYPE => 'varchar(200)'},
- target_type => {TYPE => 'char(1)', NOTNULL => 1,
- DEFAULT => "'b'"},
- is_active => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- is_requestable => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- is_requesteeble => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- is_multiplicable => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- sortkey => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '0'},
- grant_group_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'SET NULL'}},
- request_group_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'SET NULL'}},
- ],
- },
-
- # "flaginclusions" and "flagexclusions" specify the products/components
- # a bug/attachment must belong to in order for flags of a given type
- # to be set for them.
- flaginclusions => {
- FIELDS => [
- type_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'flagtypes',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- product_id => {TYPE => 'INT2',
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- component_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'components',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- flaginclusions_type_id_idx => { FIELDS => [qw(type_id product_id component_id)],
- TYPE => 'UNIQUE' },
- ],
- },
-
- flagexclusions => {
- FIELDS => [
- type_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'flagtypes',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- product_id => {TYPE => 'INT2',
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- component_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'components',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- flagexclusions_type_id_idx => { FIELDS => [qw(type_id product_id component_id)],
- TYPE => 'UNIQUE' },
- ],
- },
-
- # General Field Information
- # -------------------------
-
- fielddefs => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- type => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => FIELD_TYPE_UNKNOWN},
- custom => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- description => {TYPE => 'TINYTEXT', NOTNULL => 1},
- long_desc => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"},
- mailhead => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- sortkey => {TYPE => 'INT2', NOTNULL => 1},
- obsolete => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- enter_bug => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- buglist => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- visibility_field_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'fielddefs',
- COLUMN => 'id'}},
- value_field_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'fielddefs',
- COLUMN => 'id'}},
- reverse_desc => {TYPE => 'TINYTEXT'},
- is_mandatory => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- is_numeric => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- INDEXES => [
- fielddefs_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- fielddefs_sortkey_idx => ['sortkey'],
- fielddefs_value_field_id_idx => ['value_field_id'],
- fielddefs_is_mandatory_idx => ['is_mandatory'],
- ],
- },
-
- # Field Visibility Information
- # -------------------------
-
- field_visibility => {
- FIELDS => [
- field_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'fielddefs',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- value_id => {TYPE => 'INT2', NOTNULL => 1}
- ],
- INDEXES => [
- field_visibility_field_id_idx => {
- FIELDS => [qw(field_id value_id)],
- TYPE => 'UNIQUE'
- },
- ],
- },
-
- # Per-product Field Values
- # ------------------------
-
- versions => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- versions_product_id_idx => {FIELDS => [qw(product_id value)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- milestones => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- milestones_product_id_idx => {FIELDS => [qw(product_id value)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # Global Field Values
- # -------------------
-
- bug_status => {
- FIELDS => [
- @{ dclone(FIELD_TABLE_SCHEMA->{FIELDS}) },
- is_open => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
-
- ],
- INDEXES => [
- bug_status_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- bug_status_sortkey_idx => ['sortkey', 'value'],
- bug_status_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- resolution => {
- FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
- INDEXES => [
- resolution_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- resolution_sortkey_idx => ['sortkey', 'value'],
- resolution_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- bug_severity => {
- FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
- INDEXES => [
- bug_severity_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- bug_severity_sortkey_idx => ['sortkey', 'value'],
- bug_severity_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- priority => {
- FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
- INDEXES => [
- priority_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- priority_sortkey_idx => ['sortkey', 'value'],
- priority_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- rep_platform => {
- FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
- INDEXES => [
- rep_platform_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- rep_platform_sortkey_idx => ['sortkey', 'value'],
- rep_platform_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- op_sys => {
- FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
- INDEXES => [
- op_sys_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- op_sys_sortkey_idx => ['sortkey', 'value'],
- op_sys_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- status_workflow => {
- FIELDS => [
- # On bug creation, there is no old value.
- old_status => {TYPE => 'INT2',
- REFERENCES => {TABLE => 'bug_status',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- new_status => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'bug_status',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- require_comment => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => 0},
- ],
- INDEXES => [
- status_workflow_idx => {FIELDS => ['old_status', 'new_status'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # USER INFO
- # ---------
-
- # General User Information
- # ------------------------
-
- profiles => {
- FIELDS => [
- userid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- login_name => {TYPE => 'varchar(255)', NOTNULL => 1},
- cryptpassword => {TYPE => 'varchar(128)'},
- realname => {TYPE => 'varchar(255)', NOTNULL => 1,
- DEFAULT => "''"},
- disabledtext => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- disable_mail => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- mybugslink => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- extern_id => {TYPE => 'varchar(64)'},
- is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- last_seen_date => {TYPE => 'DATETIME'},
- ],
- INDEXES => [
- profiles_login_name_idx => {FIELDS => ['login_name'],
- TYPE => 'UNIQUE'},
- profiles_extern_id_idx => {FIELDS => ['extern_id'],
- TYPE => 'UNIQUE'}
- ],
- },
-
- profile_search => {
- FIELDS => [
- id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- bug_list => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- list_order => {TYPE => 'MEDIUMTEXT'},
- ],
- INDEXES => [
- profile_search_user_id_idx => [qw(user_id)],
- ],
- },
-
- profiles_activity => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- profiles_when => {TYPE => 'DATETIME', NOTNULL => 1},
- fieldid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'fielddefs',
- COLUMN => 'id'}},
- oldvalue => {TYPE => 'TINYTEXT'},
- newvalue => {TYPE => 'TINYTEXT'},
- ],
- INDEXES => [
- profiles_activity_userid_idx => ['userid'],
- profiles_activity_profiles_when_idx => ['profiles_when'],
- profiles_activity_fieldid_idx => ['fieldid'],
- ],
- },
-
- email_setting => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- relationship => {TYPE => 'INT1', NOTNULL => 1},
- event => {TYPE => 'INT1', NOTNULL => 1},
- ],
- INDEXES => [
- email_setting_user_id_idx =>
- {FIELDS => [qw(user_id relationship event)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- email_bug_ignore => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- email_bug_ignore_user_id_idx => {FIELDS => [qw(user_id bug_id)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- watch => {
- FIELDS => [
- watcher => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- watched => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- watch_watcher_idx => {FIELDS => [qw(watcher watched)],
- TYPE => 'UNIQUE'},
- watch_watched_idx => ['watched'],
- ],
- },
-
- namedqueries => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- query => {TYPE => 'LONGTEXT', NOTNULL => 1},
- ],
- INDEXES => [
- namedqueries_userid_idx => {FIELDS => [qw(userid name)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- namedqueries_link_in_footer => {
- FIELDS => [
- namedquery_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'namedqueries',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- namedqueries_link_in_footer_id_idx => {FIELDS => [qw(namedquery_id user_id)],
- TYPE => 'UNIQUE'},
- namedqueries_link_in_footer_userid_idx => ['user_id'],
- ],
- },
-
- tag => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- tag_user_id_idx => {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'},
- ],
- },
-
- bug_tag => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- tag_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'tag',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- bug_tag_bug_id_idx => {FIELDS => [qw(bug_id tag_id)], TYPE => 'UNIQUE'},
- ],
- },
-
- reports => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- query => {TYPE => 'LONGTEXT', NOTNULL => 1},
- ],
- INDEXES => [
- reports_user_id_idx => {FIELDS => [qw(user_id name)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- component_cc => {
-
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- component_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'components',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- component_cc_user_id_idx => {FIELDS => [qw(component_id user_id)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # Authentication
- # --------------
-
- logincookies => {
- FIELDS => [
- cookie => {TYPE => 'varchar(16)', NOTNULL => 1,
- PRIMARYKEY => 1},
- userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ipaddr => {TYPE => 'varchar(40)'},
- lastused => {TYPE => 'DATETIME', NOTNULL => 1},
- ],
- INDEXES => [
- logincookies_lastused_idx => ['lastused'],
- ],
- },
-
- login_failure => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- login_time => {TYPE => 'DATETIME', NOTNULL => 1},
- ip_addr => {TYPE => 'varchar(40)', NOTNULL => 1},
- ],
- INDEXES => [
- # We do lookups by every item in the table simultaneously, but
- # having an index with all three items would be the same size as
- # the table. So instead we have an index on just the smallest item,
- # to speed lookups.
- login_failure_user_id_idx => ['user_id'],
- ],
- },
-
-
- # "tokens" stores the tokens users receive when a password or email
- # change is requested. Tokens provide an extra measure of security
- # for these changes.
- tokens => {
- FIELDS => [
- userid => {TYPE => 'INT3', REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- issuedate => {TYPE => 'DATETIME', NOTNULL => 1} ,
- token => {TYPE => 'varchar(16)', NOTNULL => 1,
- PRIMARYKEY => 1},
- tokentype => {TYPE => 'varchar(16)', NOTNULL => 1} ,
- eventdata => {TYPE => 'TINYTEXT'},
- ],
- INDEXES => [
- tokens_userid_idx => ['userid'],
- ],
- },
-
- # GROUPS
- # ------
-
- groups => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(255)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- isbuggroup => {TYPE => 'BOOLEAN', NOTNULL => 1},
- userregexp => {TYPE => 'TINYTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- icon_url => {TYPE => 'TINYTEXT'},
- ],
- INDEXES => [
- groups_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},
- ],
- },
-
- group_control_map => {
- FIELDS => [
- group_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- product_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- entry => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- membercontrol => {TYPE => 'INT1', NOTNULL => 1,
- DEFAULT => CONTROLMAPNA},
- othercontrol => {TYPE => 'INT1', NOTNULL => 1,
- DEFAULT => CONTROLMAPNA},
- canedit => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- editcomponents => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- editbugs => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- canconfirm => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- INDEXES => [
- group_control_map_product_id_idx =>
- {FIELDS => [qw(product_id group_id)], TYPE => 'UNIQUE'},
- group_control_map_group_id_idx => ['group_id'],
- ],
- },
-
- # "user_group_map" determines the groups that a user belongs to
- # directly or due to regexp and which groups can be blessed by a user.
- #
- # grant_type:
- # if GRANT_DIRECT - record was explicitly granted
- # if GRANT_DERIVED - record was derived from expanding a group hierarchy
- # if GRANT_REGEXP - record was created by evaluating a regexp
- user_group_map => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- group_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- isbless => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- grant_type => {TYPE => 'INT1', NOTNULL => 1,
- DEFAULT => GRANT_DIRECT},
- ],
- INDEXES => [
- user_group_map_user_id_idx =>
- {FIELDS => [qw(user_id group_id grant_type isbless)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # This table determines which groups are made a member of another
- # group, given the ability to bless another group, or given
- # visibility to another groups existence and membership
- # grant_type:
- # if GROUP_MEMBERSHIP - member groups are made members of grantor
- # if GROUP_BLESS - member groups may grant membership in grantor
- # if GROUP_VISIBLE - member groups may see grantor group
- group_group_map => {
- FIELDS => [
- member_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- grantor_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- grant_type => {TYPE => 'INT1', NOTNULL => 1,
- DEFAULT => GROUP_MEMBERSHIP},
- ],
- INDEXES => [
- group_group_map_member_id_idx =>
- {FIELDS => [qw(member_id grantor_id grant_type)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # This table determines which groups a user must be a member of
- # in order to see a bug.
- bug_group_map => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- group_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- bug_group_map_bug_id_idx =>
- {FIELDS => [qw(bug_id group_id)], TYPE => 'UNIQUE'},
- bug_group_map_group_id_idx => ['group_id'],
- ],
- },
-
- # This table determines which groups a user must be a member of
- # in order to see a named query somebody else shares.
- namedquery_group_map => {
- FIELDS => [
- namedquery_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'namedqueries',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- group_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- namedquery_group_map_namedquery_id_idx =>
- {FIELDS => [qw(namedquery_id)], TYPE => 'UNIQUE'},
- namedquery_group_map_group_id_idx => ['group_id'],
- ],
- },
-
- category_group_map => {
- FIELDS => [
- category_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'series_categories',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- group_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- category_group_map_category_id_idx =>
- {FIELDS => [qw(category_id group_id)], TYPE => 'UNIQUE'},
- ],
- },
-
-
- # PRODUCTS
- # --------
-
- classifications => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT'},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
- ],
- INDEXES => [
- classifications_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- products => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- classification_id => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '1',
- REFERENCES => {TABLE => 'classifications',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 1},
- defaultmilestone => {TYPE => 'varchar(64)',
- NOTNULL => 1, DEFAULT => "'---'"},
- allows_unconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- products_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- components => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- initialowner => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- initialqacontact => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'SET NULL'}},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- components_product_id_idx => {FIELDS => [qw(product_id name)],
- TYPE => 'UNIQUE'},
- components_name_idx => ['name'],
- ],
- },
-
- # CHARTS
- # ------
-
- series => {
- FIELDS => [
- series_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- creator => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- category => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'series_categories',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- subcategory => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'series_categories',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- frequency => {TYPE => 'INT2', NOTNULL => 1},
- query => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- is_public => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- INDEXES => [
- series_creator_idx => ['creator'],
- series_category_idx => {FIELDS => [qw(category subcategory name)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- series_data => {
- FIELDS => [
- series_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'series',
- COLUMN => 'series_id',
- DELETE => 'CASCADE'}},
- series_date => {TYPE => 'DATETIME', NOTNULL => 1},
- series_value => {TYPE => 'INT3', NOTNULL => 1},
- ],
- INDEXES => [
- series_data_series_id_idx =>
- {FIELDS => [qw(series_id series_date)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- series_categories => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- ],
- INDEXES => [
- series_categories_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # WHINE SYSTEM
- # ------------
-
- whine_queries => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- eventid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'whine_events',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- query_name => {TYPE => 'varchar(64)', NOTNULL => 1,
- DEFAULT => "''"},
- sortkey => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '0'},
- onemailperbug => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- title => {TYPE => 'varchar(128)', NOTNULL => 1,
- DEFAULT => "''"},
- ],
- INDEXES => [
- whine_queries_eventid_idx => ['eventid'],
- ],
- },
-
- whine_schedules => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- eventid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'whine_events',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- run_day => {TYPE => 'varchar(32)'},
- run_time => {TYPE => 'varchar(32)'},
- run_next => {TYPE => 'DATETIME'},
- mailto => {TYPE => 'INT3', NOTNULL => 1},
- mailto_type => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
- ],
- INDEXES => [
- whine_schedules_run_next_idx => ['run_next'],
- whine_schedules_eventid_idx => ['eventid'],
- ],
- },
-
- whine_events => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- owner_userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- subject => {TYPE => 'varchar(128)'},
- body => {TYPE => 'MEDIUMTEXT'},
- mailifnobugs => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- },
-
- # QUIPS
- # -----
-
- quips => {
- FIELDS => [
- quipid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- userid => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'SET NULL'}},
- quip => {TYPE => 'varchar(512)', NOTNULL => 1},
- approved => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- },
-
- # SETTINGS
- # --------
- # setting - each global setting will have exactly one entry
- # in this table.
- # setting_value - stores the list of acceptable values for each
- # setting, and a sort index that controls the order
- # in which the values are displayed.
- # profile_setting - If a user has chosen to use a value other than the
- # global default for a given setting, it will be
- # stored in this table. Note: even if a setting is
- # later changed so is_enabled = false, the stored
- # value will remain in case it is ever enabled again.
- #
- setting => {
- FIELDS => [
- name => {TYPE => 'varchar(32)', NOTNULL => 1,
- PRIMARYKEY => 1},
- default_value => {TYPE => 'varchar(32)', NOTNULL => 1},
- is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- subclass => {TYPE => 'varchar(32)'},
- ],
- },
-
- setting_value => {
- FIELDS => [
- name => {TYPE => 'varchar(32)', NOTNULL => 1,
- REFERENCES => {TABLE => 'setting',
- COLUMN => 'name',
- DELETE => 'CASCADE'}},
- value => {TYPE => 'varchar(32)', NOTNULL => 1},
- sortindex => {TYPE => 'INT2', NOTNULL => 1},
- ],
- INDEXES => [
- setting_value_nv_unique_idx => {FIELDS => [qw(name value)],
- TYPE => 'UNIQUE'},
- setting_value_ns_unique_idx => {FIELDS => [qw(name sortindex)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- profile_setting => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- setting_name => {TYPE => 'varchar(32)', NOTNULL => 1,
- REFERENCES => {TABLE => 'setting',
- COLUMN => 'name',
- DELETE => 'CASCADE'}},
- setting_value => {TYPE => 'varchar(32)', NOTNULL => 1},
- ],
- INDEXES => [
- profile_setting_value_unique_idx => {FIELDS => [qw(user_id setting_name)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # BUGMAIL
- # -------
-
- mail_staging => {
- FIELDS => [
- id => {TYPE => 'INTSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
- message => {TYPE => 'LONGBLOB', NOTNULL => 1},
- ],
- },
-
- # THESCHWARTZ TABLES
- # ------------------
- # Note: In the standard TheSchwartz schema, most integers are unsigned,
- # but we didn't implement unsigned ints for Bugzilla schemas, so we
- # just create signed ints, which should be fine.
-
- ts_funcmap => {
- FIELDS => [
- funcid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
- funcname => {TYPE => 'varchar(255)', NOTNULL => 1},
- ],
- INDEXES => [
- ts_funcmap_funcname_idx => {FIELDS => ['funcname'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- ts_job => {
- FIELDS => [
- # In a standard TheSchwartz schema, this is a BIGINT, but we
- # don't have those and I didn't want to add them just for this.
- jobid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- funcid => {TYPE => 'INT4', NOTNULL => 1},
- # In standard TheSchwartz, this is a MEDIUMBLOB.
- arg => {TYPE => 'LONGBLOB'},
- uniqkey => {TYPE => 'varchar(255)'},
- insert_time => {TYPE => 'INT4'},
- run_after => {TYPE => 'INT4', NOTNULL => 1},
- grabbed_until => {TYPE => 'INT4', NOTNULL => 1},
- priority => {TYPE => 'INT2'},
- coalesce => {TYPE => 'varchar(255)'},
- ],
- INDEXES => [
- ts_job_funcid_idx => {FIELDS => [qw(funcid uniqkey)],
- TYPE => 'UNIQUE'},
- # In a standard TheSchewartz schema, these both go in the other
- # direction, but there's no reason to have three indexes that
- # all start with the same column, and our naming scheme doesn't
- # allow it anyhow.
- ts_job_run_after_idx => [qw(run_after funcid)],
- ts_job_coalesce_idx => [qw(coalesce funcid)],
- ],
- },
-
- ts_note => {
- FIELDS => [
- # This is a BIGINT in standard TheSchwartz schemas.
- jobid => {TYPE => 'INT4', NOTNULL => 1},
- notekey => {TYPE => 'varchar(255)'},
- value => {TYPE => 'LONGBLOB'},
- ],
- INDEXES => [
- ts_note_jobid_idx => {FIELDS => [qw(jobid notekey)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- ts_error => {
- FIELDS => [
- error_time => {TYPE => 'INT4', NOTNULL => 1},
- jobid => {TYPE => 'INT4', NOTNULL => 1},
- message => {TYPE => 'varchar(255)', NOTNULL => 1},
- funcid => {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
- ],
- INDEXES => [
- ts_error_funcid_idx => [qw(funcid error_time)],
- ts_error_error_time_idx => ['error_time'],
- ts_error_jobid_idx => ['jobid'],
- ],
- },
-
- ts_exitstatus => {
- FIELDS => [
- jobid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- funcid => {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
- status => {TYPE => 'INT2'},
- completion_time => {TYPE => 'INT4'},
- delete_after => {TYPE => 'INT4'},
- ],
- INDEXES => [
- ts_exitstatus_funcid_idx => ['funcid'],
- ts_exitstatus_delete_after_idx => ['delete_after'],
- ],
- },
-
- # SCHEMA STORAGE
- # --------------
-
- bz_schema => {
- FIELDS => [
- schema_data => {TYPE => 'LONGBLOB', NOTNULL => 1},
- version => {TYPE => 'decimal(3,2)', NOTNULL => 1},
- ],
- },
-
- bug_user_last_visit => {
- FIELDS => [
- id => {TYPE => 'INTSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- last_visit_ts => {TYPE => 'DATETIME', NOTNULL => 1},
- ],
- INDEXES => [
- bug_user_last_visit_idx => {FIELDS => ['user_id', 'bug_id'],
- TYPE => 'UNIQUE'},
- bug_user_last_visit_last_visit_ts_idx => ['last_visit_ts'],
- ],
- },
-
- user_api_keys => {
- FIELDS => [
- id => {TYPE => 'INTSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- api_key => {TYPE => 'VARCHAR(40)', NOTNULL => 1},
- description => {TYPE => 'VARCHAR(255)'},
- revoked => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- last_used => {TYPE => 'DATETIME'},
- ],
- INDEXES => [
- user_api_keys_api_key_idx => {FIELDS => ['api_key'], TYPE => 'UNIQUE'},
- user_api_keys_user_id_idx => ['user_id'],
- ],
- },
-};
+ bugs_activity => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ attach_id => {
+ TYPE => 'INT3',
+ REFERENCES =>
+ {TABLE => 'attachments', COLUMN => 'attach_id', DELETE => 'CASCADE'}
+ },
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
+ fieldid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}
+ },
+ added => {TYPE => 'varchar(255)'},
+ removed => {TYPE => 'varchar(255)'},
+ comment_id => {
+ TYPE => 'INT4',
+ REFERENCES =>
+ {TABLE => 'longdescs', COLUMN => 'comment_id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ bugs_activity_bug_id_idx => ['bug_id'],
+ bugs_activity_who_idx => ['who'],
+ bugs_activity_bug_when_idx => ['bug_when'],
+ bugs_activity_fieldid_idx => ['fieldid'],
+ bugs_activity_added_idx => ['added'],
+ bugs_activity_removed_idx => ['removed'],
+ ],
+ },
-# Foreign Keys are added in Bugzilla::DB::bz_add_field_tables
-use constant MULTI_SELECT_VALUE_TABLE => {
+ bugs_aliases => {
+ FIELDS => [
+ alias => {TYPE => 'varchar(40)', NOTNULL => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ bugs_aliases_bug_id_idx => ['bug_id'],
+ bugs_aliases_alias_idx => {FIELDS => ['alias'], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ cc => {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ cc_bug_id_idx => {FIELDS => [qw(bug_id who)], TYPE => 'UNIQUE'},
+ cc_who_idx => ['who'],
+ ],
+ },
+
+ longdescs => {
+ FIELDS => [
+ comment_id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
+ work_time => {TYPE => 'decimal(7,2)', NOTNULL => 1, DEFAULT => '0'},
+ thetext => {TYPE => 'LONGTEXT', NOTNULL => 1},
+ isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ already_wrapped => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ type => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
+ extra_data => {TYPE => 'varchar(255)'}
+ ],
+ INDEXES => [
+ longdescs_bug_id_idx => [qw(bug_id work_time)],
+ longdescs_who_idx => [qw(who bug_id)],
+ longdescs_bug_when_idx => ['bug_when'],
+ ],
+ },
+
+ longdescs_tags => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ comment_id => {
+ TYPE => 'INT4',
+ REFERENCES =>
+ {TABLE => 'longdescs', COLUMN => 'comment_id', DELETE => 'CASCADE'}
+ },
+ tag => {TYPE => 'varchar(24)', NOTNULL => 1},
+ ],
+ INDEXES =>
+ [longdescs_tags_idx => {FIELDS => ['comment_id', 'tag'], TYPE => 'UNIQUE'},],
+ },
+
+ longdescs_tags_weights => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ tag => {TYPE => 'varchar(24)', NOTNULL => 1},
+ weight => {TYPE => 'INT3', NOTNULL => 1},
+ ],
+ INDEXES =>
+ [longdescs_tags_weights_tag_idx => {FIELDS => ['tag'], TYPE => 'UNIQUE'},],
+ },
+
+ longdescs_tags_activity => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ comment_id => {
+ TYPE => 'INT4',
+ REFERENCES =>
+ {TABLE => 'longdescs', COLUMN => 'comment_id', DELETE => 'CASCADE'}
+ },
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
+ added => {TYPE => 'varchar(24)'},
+ removed => {TYPE => 'varchar(24)'},
+ ],
+ INDEXES => [longdescs_tags_activity_bug_id_idx => ['bug_id'],],
+ },
+
+ dependencies => {
+ FIELDS => [
+ blocked => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ dependson => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ dependencies_blocked_idx =>
+ {FIELDS => [qw(blocked dependson)], TYPE => 'UNIQUE'},
+ dependencies_dependson_idx => ['dependson'],
+ ],
+ },
+
+ attachments => {
+ FIELDS => [
+ attach_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ creation_ts => {TYPE => 'DATETIME', NOTNULL => 1},
+ modification_time => {TYPE => 'DATETIME', NOTNULL => 1},
+ description => {TYPE => 'TINYTEXT', NOTNULL => 1},
+ mimetype => {TYPE => 'TINYTEXT', NOTNULL => 1},
+ ispatch => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ filename => {TYPE => 'varchar(255)', NOTNULL => 1},
+ submitter_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ isobsolete => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ ],
+ INDEXES => [
+ attachments_bug_id_idx => ['bug_id'],
+ attachments_creation_ts_idx => ['creation_ts'],
+ attachments_modification_time_idx => ['modification_time'],
+ attachments_submitter_id_idx => ['submitter_id', 'bug_id'],
+ ],
+ },
+ attach_data => {
FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
+ id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ PRIMARYKEY => 1,
+ REFERENCES =>
+ {TABLE => 'attachments', COLUMN => 'attach_id', DELETE => 'CASCADE'}
+ },
+ thedata => {TYPE => 'LONGBLOB', NOTNULL => 1},
+ ],
+ },
+
+ duplicates => {
+ FIELDS => [
+ dupe_of => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ dupe => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ PRIMARYKEY => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ ],
+ },
+
+ bug_see_also => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ value => {TYPE => 'varchar(255)', NOTNULL => 1},
+ class => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"},
+ ],
+ INDEXES => [
+ bug_see_also_bug_id_idx => {FIELDS => [qw(bug_id value)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # Auditing
+ # --------
+
+ audit_log => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'SET NULL'}
+ },
+ class => {TYPE => 'varchar(255)', NOTNULL => 1},
+ object_id => {TYPE => 'INT4', NOTNULL => 1},
+ field => {TYPE => 'varchar(64)', NOTNULL => 1},
+ removed => {TYPE => 'MEDIUMTEXT'},
+ added => {TYPE => 'MEDIUMTEXT'},
+ at_time => {TYPE => 'DATETIME', NOTNULL => 1},
+ ],
+ INDEXES => [audit_log_class_idx => ['class', 'at_time'],],
+ },
+
+ # Keywords
+ # --------
+
+ keyworddefs => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ ],
+ INDEXES => [keyworddefs_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},],
+ },
+
+ keywords => {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ keywordid => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'keyworddefs', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+
+ ],
+ INDEXES => [
+ keywords_bug_id_idx => {FIELDS => [qw(bug_id keywordid)], TYPE => 'UNIQUE'},
+ keywords_keywordid_idx => ['keywordid'],
+ ],
+ },
+
+ # Flags
+ # -----
+
+ # "flags" stores one record for each flag on each bug/attachment.
+ flags => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ type_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'flagtypes', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ status => {TYPE => 'char(1)', NOTNULL => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ attach_id => {
+ TYPE => 'INT3',
+ REFERENCES =>
+ {TABLE => 'attachments', COLUMN => 'attach_id', DELETE => 'CASCADE'}
+ },
+ creation_date => {TYPE => 'DATETIME', NOTNULL => 1},
+ modification_date => {TYPE => 'DATETIME'},
+ setter_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ requestee_id =>
+ {TYPE => 'INT3', REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}},
+ ],
+ INDEXES => [
+ flags_bug_id_idx => [qw(bug_id attach_id)],
+ flags_setter_id_idx => ['setter_id'],
+ flags_requestee_id_idx => ['requestee_id'],
+ flags_type_id_idx => ['type_id'],
+ ],
+ },
+
+ # "flagtypes" defines the types of flags that can be set.
+ flagtypes => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(50)', NOTNULL => 1},
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ cc_list => {TYPE => 'varchar(200)'},
+ target_type => {TYPE => 'char(1)', NOTNULL => 1, DEFAULT => "'b'"},
+ is_active => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ is_requestable => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ is_requesteeble => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ is_multiplicable => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
+ grant_group_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'SET NULL'}
+ },
+ request_group_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'SET NULL'}
+ },
+ ],
+ },
+
+ # "flaginclusions" and "flagexclusions" specify the products/components
+ # a bug/attachment must belong to in order for flags of a given type
+ # to be set for them.
+ flaginclusions => {
+ FIELDS => [
+ type_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'flagtypes', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ product_id => {
+ TYPE => 'INT2',
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ component_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'components', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
],
INDEXES => [
- bug_id_idx => {FIELDS => [qw( bug_id value)], TYPE => 'UNIQUE'},
+ flaginclusions_type_id_idx =>
+ {FIELDS => [qw(type_id product_id component_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ flagexclusions => {
+ FIELDS => [
+ type_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'flagtypes', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ product_id => {
+ TYPE => 'INT2',
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ component_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'components', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ flagexclusions_type_id_idx =>
+ {FIELDS => [qw(type_id product_id component_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # General Field Information
+ # -------------------------
+
+ fielddefs => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ type => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => FIELD_TYPE_UNKNOWN},
+ custom => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ description => {TYPE => 'TINYTEXT', NOTNULL => 1},
+ long_desc => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"},
+ mailhead => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1},
+ obsolete => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ enter_bug => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ buglist => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ visibility_field_id =>
+ {TYPE => 'INT3', REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}},
+ value_field_id =>
+ {TYPE => 'INT3', REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}},
+ reverse_desc => {TYPE => 'TINYTEXT'},
+ is_mandatory => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ is_numeric => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ ],
+ INDEXES => [
+ fielddefs_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},
+ fielddefs_sortkey_idx => ['sortkey'],
+ fielddefs_value_field_id_idx => ['value_field_id'],
+ fielddefs_is_mandatory_idx => ['is_mandatory'],
+ ],
+ },
+
+ # Field Visibility Information
+ # -------------------------
+
+ field_visibility => {
+ FIELDS => [
+ field_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ value_id => {TYPE => 'INT2', NOTNULL => 1}
+ ],
+ INDEXES => [
+ field_visibility_field_id_idx =>
+ {FIELDS => [qw(field_id value_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # Per-product Field Values
+ # ------------------------
+
+ versions => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ value => {TYPE => 'varchar(64)', NOTNULL => 1},
+ product_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ ],
+ INDEXES => [
+ versions_product_id_idx => {FIELDS => [qw(product_id value)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ milestones => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ product_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ value => {TYPE => 'varchar(64)', NOTNULL => 1},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ ],
+ INDEXES => [
+ milestones_product_id_idx =>
+ {FIELDS => [qw(product_id value)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # Global Field Values
+ # -------------------
+
+ bug_status => {
+ FIELDS => [
+ @{dclone(FIELD_TABLE_SCHEMA->{FIELDS})},
+ is_open => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+
+ ],
+ INDEXES => [
+ bug_status_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ bug_status_sortkey_idx => ['sortkey', 'value'],
+ bug_status_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ resolution => {
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
+ INDEXES => [
+ resolution_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ resolution_sortkey_idx => ['sortkey', 'value'],
+ resolution_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ bug_severity => {
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
+ INDEXES => [
+ bug_severity_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ bug_severity_sortkey_idx => ['sortkey', 'value'],
+ bug_severity_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ priority => {
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
+ INDEXES => [
+ priority_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ priority_sortkey_idx => ['sortkey', 'value'],
+ priority_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ rep_platform => {
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
+ INDEXES => [
+ rep_platform_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ rep_platform_sortkey_idx => ['sortkey', 'value'],
+ rep_platform_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ op_sys => {
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
+ INDEXES => [
+ op_sys_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ op_sys_sortkey_idx => ['sortkey', 'value'],
+ op_sys_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ status_workflow => {
+ FIELDS => [
+
+ # On bug creation, there is no old value.
+ old_status => {
+ TYPE => 'INT2',
+ REFERENCES => {TABLE => 'bug_status', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ new_status => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bug_status', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ require_comment => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => 0},
+ ],
+ INDEXES => [
+ status_workflow_idx =>
+ {FIELDS => ['old_status', 'new_status'], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # USER INFO
+ # ---------
+
+ # General User Information
+ # ------------------------
+
+ profiles => {
+ FIELDS => [
+ userid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ login_name => {TYPE => 'varchar(255)', NOTNULL => 1},
+ cryptpassword => {TYPE => 'varchar(128)'},
+ realname => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"},
+ disabledtext => {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"},
+ disable_mail => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ mybugslink => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ extern_id => {TYPE => 'varchar(64)'},
+ is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ last_seen_date => {TYPE => 'DATETIME'},
+ ],
+ INDEXES => [
+ profiles_login_name_idx => {FIELDS => ['login_name'], TYPE => 'UNIQUE'},
+ profiles_extern_id_idx => {FIELDS => ['extern_id'], TYPE => 'UNIQUE'}
+ ],
+ },
+
+ profile_search => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ bug_list => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ list_order => {TYPE => 'MEDIUMTEXT'},
+ ],
+ INDEXES => [profile_search_user_id_idx => [qw(user_id)],],
+ },
+
+ profiles_activity => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ userid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ profiles_when => {TYPE => 'DATETIME', NOTNULL => 1},
+ fieldid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}
+ },
+ oldvalue => {TYPE => 'TINYTEXT'},
+ newvalue => {TYPE => 'TINYTEXT'},
+ ],
+ INDEXES => [
+ profiles_activity_userid_idx => ['userid'],
+ profiles_activity_profiles_when_idx => ['profiles_when'],
+ profiles_activity_fieldid_idx => ['fieldid'],
+ ],
+ },
+
+ email_setting => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ relationship => {TYPE => 'INT1', NOTNULL => 1},
+ event => {TYPE => 'INT1', NOTNULL => 1},
+ ],
+ INDEXES => [
+ email_setting_user_id_idx =>
+ {FIELDS => [qw(user_id relationship event)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ email_bug_ignore => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ email_bug_ignore_user_id_idx =>
+ {FIELDS => [qw(user_id bug_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ watch => {
+ FIELDS => [
+ watcher => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ watched => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ watch_watcher_idx => {FIELDS => [qw(watcher watched)], TYPE => 'UNIQUE'},
+ watch_watched_idx => ['watched'],
+ ],
+ },
+
+ namedqueries => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ userid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ query => {TYPE => 'LONGTEXT', NOTNULL => 1},
+ ],
+ INDEXES =>
+ [namedqueries_userid_idx => {FIELDS => [qw(userid name)], TYPE => 'UNIQUE'},],
+ },
+
+ namedqueries_link_in_footer => {
+ FIELDS => [
+ namedquery_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'namedqueries', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ namedqueries_link_in_footer_id_idx =>
+ {FIELDS => [qw(namedquery_id user_id)], TYPE => 'UNIQUE'},
+ namedqueries_link_in_footer_userid_idx => ['user_id'],
+ ],
+ },
+
+ tag => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES =>
+ [tag_user_id_idx => {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'},],
+ },
+
+ bug_tag => {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ tag_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'tag', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES =>
+ [bug_tag_bug_id_idx => {FIELDS => [qw(bug_id tag_id)], TYPE => 'UNIQUE'},],
+ },
+
+ reports => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ query => {TYPE => 'LONGTEXT', NOTNULL => 1},
+ ],
+ INDEXES =>
+ [reports_user_id_idx => {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'},],
+ },
+
+ component_cc => {
+
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ component_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'components', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ component_cc_user_id_idx =>
+ {FIELDS => [qw(component_id user_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # Authentication
+ # --------------
+
+ logincookies => {
+ FIELDS => [
+ cookie => {TYPE => 'varchar(16)', NOTNULL => 1, PRIMARYKEY => 1},
+ userid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ ipaddr => {TYPE => 'varchar(40)'},
+ lastused => {TYPE => 'DATETIME', NOTNULL => 1},
+ ],
+ INDEXES => [logincookies_lastused_idx => ['lastused'],],
+ },
+
+ login_failure => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ login_time => {TYPE => 'DATETIME', NOTNULL => 1},
+ ip_addr => {TYPE => 'varchar(40)', NOTNULL => 1},
+ ],
+ INDEXES => [
+
+ # We do lookups by every item in the table simultaneously, but
+ # having an index with all three items would be the same size as
+ # the table. So instead we have an index on just the smallest item,
+ # to speed lookups.
+ login_failure_user_id_idx => ['user_id'],
+ ],
+ },
+
+
+ # "tokens" stores the tokens users receive when a password or email
+ # change is requested. Tokens provide an extra measure of security
+ # for these changes.
+ tokens => {
+ FIELDS => [
+ userid => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ issuedate => {TYPE => 'DATETIME', NOTNULL => 1},
+ token => {TYPE => 'varchar(16)', NOTNULL => 1, PRIMARYKEY => 1},
+ tokentype => {TYPE => 'varchar(16)', NOTNULL => 1},
+ eventdata => {TYPE => 'TINYTEXT'},
+ ],
+ INDEXES => [tokens_userid_idx => ['userid'],],
+ },
+
+ # GROUPS
+ # ------
+
+ groups => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(255)', NOTNULL => 1},
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ isbuggroup => {TYPE => 'BOOLEAN', NOTNULL => 1},
+ userregexp => {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ icon_url => {TYPE => 'TINYTEXT'},
],
+ INDEXES => [groups_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},],
+ },
+
+ group_control_map => {
+ FIELDS => [
+ group_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ product_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ entry => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ membercontrol => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA},
+ othercontrol => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA},
+ canedit => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ editcomponents => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ editbugs => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ canconfirm => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ ],
+ INDEXES => [
+ group_control_map_product_id_idx =>
+ {FIELDS => [qw(product_id group_id)], TYPE => 'UNIQUE'},
+ group_control_map_group_id_idx => ['group_id'],
+ ],
+ },
+
+ # "user_group_map" determines the groups that a user belongs to
+ # directly or due to regexp and which groups can be blessed by a user.
+ #
+ # grant_type:
+ # if GRANT_DIRECT - record was explicitly granted
+ # if GRANT_DERIVED - record was derived from expanding a group hierarchy
+ # if GRANT_REGEXP - record was created by evaluating a regexp
+ user_group_map => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ group_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ isbless => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ grant_type => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => GRANT_DIRECT},
+ ],
+ INDEXES => [
+ user_group_map_user_id_idx =>
+ {FIELDS => [qw(user_id group_id grant_type isbless)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # This table determines which groups are made a member of another
+ # group, given the ability to bless another group, or given
+ # visibility to another groups existence and membership
+ # grant_type:
+ # if GROUP_MEMBERSHIP - member groups are made members of grantor
+ # if GROUP_BLESS - member groups may grant membership in grantor
+ # if GROUP_VISIBLE - member groups may see grantor group
+ group_group_map => {
+ FIELDS => [
+ member_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ grantor_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ grant_type => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => GROUP_MEMBERSHIP},
+ ],
+ INDEXES => [
+ group_group_map_member_id_idx =>
+ {FIELDS => [qw(member_id grantor_id grant_type)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # This table determines which groups a user must be a member of
+ # in order to see a bug.
+ bug_group_map => {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ group_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ bug_group_map_bug_id_idx => {FIELDS => [qw(bug_id group_id)], TYPE => 'UNIQUE'},
+ bug_group_map_group_id_idx => ['group_id'],
+ ],
+ },
+
+ # This table determines which groups a user must be a member of
+ # in order to see a named query somebody else shares.
+ namedquery_group_map => {
+ FIELDS => [
+ namedquery_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'namedqueries', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ group_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ namedquery_group_map_namedquery_id_idx =>
+ {FIELDS => [qw(namedquery_id)], TYPE => 'UNIQUE'},
+ namedquery_group_map_group_id_idx => ['group_id'],
+ ],
+ },
+
+ category_group_map => {
+ FIELDS => [
+ category_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES =>
+ {TABLE => 'series_categories', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ group_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ category_group_map_category_id_idx =>
+ {FIELDS => [qw(category_id group_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+
+ # PRODUCTS
+ # --------
+
+ classifications => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ description => {TYPE => 'MEDIUMTEXT'},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
+ ],
+ INDEXES =>
+ [classifications_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},],
+ },
+
+ products => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ classification_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ DEFAULT => '1',
+ REFERENCES => {TABLE => 'classifications', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1},
+ defaultmilestone => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "'---'"},
+ allows_unconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ ],
+ INDEXES => [products_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},],
+ },
+
+ components => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ product_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ initialowner => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ initialqacontact => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'SET NULL'}
+ },
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ ],
+ INDEXES => [
+ components_product_id_idx =>
+ {FIELDS => [qw(product_id name)], TYPE => 'UNIQUE'},
+ components_name_idx => ['name'],
+ ],
+ },
+
+ # CHARTS
+ # ------
+
+ series => {
+ FIELDS => [
+ series_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ creator => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ category => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES =>
+ {TABLE => 'series_categories', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ subcategory => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES =>
+ {TABLE => 'series_categories', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ frequency => {TYPE => 'INT2', NOTNULL => 1},
+ query => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ is_public => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ ],
+ INDEXES => [
+ series_creator_idx => ['creator'],
+ series_category_idx =>
+ {FIELDS => [qw(category subcategory name)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ series_data => {
+ FIELDS => [
+ series_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'series', COLUMN => 'series_id', DELETE => 'CASCADE'}
+ },
+ series_date => {TYPE => 'DATETIME', NOTNULL => 1},
+ series_value => {TYPE => 'INT3', NOTNULL => 1},
+ ],
+ INDEXES => [
+ series_data_series_id_idx =>
+ {FIELDS => [qw(series_id series_date)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ series_categories => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ ],
+ INDEXES =>
+ [series_categories_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},],
+ },
+
+ # WHINE SYSTEM
+ # ------------
+
+ whine_queries => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ eventid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'whine_events', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ query_name => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
+ onemailperbug => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ title => {TYPE => 'varchar(128)', NOTNULL => 1, DEFAULT => "''"},
+ ],
+ INDEXES => [whine_queries_eventid_idx => ['eventid'],],
+ },
+
+ whine_schedules => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ eventid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'whine_events', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ run_day => {TYPE => 'varchar(32)'},
+ run_time => {TYPE => 'varchar(32)'},
+ run_next => {TYPE => 'DATETIME'},
+ mailto => {TYPE => 'INT3', NOTNULL => 1},
+ mailto_type => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
+ ],
+ INDEXES => [
+ whine_schedules_run_next_idx => ['run_next'],
+ whine_schedules_eventid_idx => ['eventid'],
+ ],
+ },
+
+ whine_events => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ owner_userid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ subject => {TYPE => 'varchar(128)'},
+ body => {TYPE => 'MEDIUMTEXT'},
+ mailifnobugs => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ ],
+ },
+
+ # QUIPS
+ # -----
+
+ quips => {
+ FIELDS => [
+ quipid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ userid => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'SET NULL'}
+ },
+ quip => {TYPE => 'varchar(512)', NOTNULL => 1},
+ approved => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ ],
+ },
+
+ # SETTINGS
+ # --------
+ # setting - each global setting will have exactly one entry
+ # in this table.
+ # setting_value - stores the list of acceptable values for each
+ # setting, and a sort index that controls the order
+ # in which the values are displayed.
+ # profile_setting - If a user has chosen to use a value other than the
+ # global default for a given setting, it will be
+ # stored in this table. Note: even if a setting is
+ # later changed so is_enabled = false, the stored
+ # value will remain in case it is ever enabled again.
+ #
+ setting => {
+ FIELDS => [
+ name => {TYPE => 'varchar(32)', NOTNULL => 1, PRIMARYKEY => 1},
+ default_value => {TYPE => 'varchar(32)', NOTNULL => 1},
+ is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ subclass => {TYPE => 'varchar(32)'},
+ ],
+ },
+
+ setting_value => {
+ FIELDS => [
+ name => {
+ TYPE => 'varchar(32)',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'setting', COLUMN => 'name', DELETE => 'CASCADE'}
+ },
+ value => {TYPE => 'varchar(32)', NOTNULL => 1},
+ sortindex => {TYPE => 'INT2', NOTNULL => 1},
+ ],
+ INDEXES => [
+ setting_value_nv_unique_idx => {FIELDS => [qw(name value)], TYPE => 'UNIQUE'},
+ setting_value_ns_unique_idx =>
+ {FIELDS => [qw(name sortindex)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ profile_setting => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ setting_name => {
+ TYPE => 'varchar(32)',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'setting', COLUMN => 'name', DELETE => 'CASCADE'}
+ },
+ setting_value => {TYPE => 'varchar(32)', NOTNULL => 1},
+ ],
+ INDEXES => [
+ profile_setting_value_unique_idx =>
+ {FIELDS => [qw(user_id setting_name)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # BUGMAIL
+ # -------
+
+ mail_staging => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ message => {TYPE => 'LONGBLOB', NOTNULL => 1},
+ ],
+ },
+
+ # THESCHWARTZ TABLES
+ # ------------------
+ # Note: In the standard TheSchwartz schema, most integers are unsigned,
+ # but we didn't implement unsigned ints for Bugzilla schemas, so we
+ # just create signed ints, which should be fine.
+
+ ts_funcmap => {
+ FIELDS => [
+ funcid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ funcname => {TYPE => 'varchar(255)', NOTNULL => 1},
+ ],
+ INDEXES =>
+ [ts_funcmap_funcname_idx => {FIELDS => ['funcname'], TYPE => 'UNIQUE'},],
+ },
+
+ ts_job => {
+ FIELDS => [
+
+ # In a standard TheSchwartz schema, this is a BIGINT, but we
+ # don't have those and I didn't want to add them just for this.
+ jobid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ funcid => {TYPE => 'INT4', NOTNULL => 1},
+
+ # In standard TheSchwartz, this is a MEDIUMBLOB.
+ arg => {TYPE => 'LONGBLOB'},
+ uniqkey => {TYPE => 'varchar(255)'},
+ insert_time => {TYPE => 'INT4'},
+ run_after => {TYPE => 'INT4', NOTNULL => 1},
+ grabbed_until => {TYPE => 'INT4', NOTNULL => 1},
+ priority => {TYPE => 'INT2'},
+ coalesce => {TYPE => 'varchar(255)'},
+ ],
+ INDEXES => [
+ ts_job_funcid_idx => {FIELDS => [qw(funcid uniqkey)], TYPE => 'UNIQUE'},
+
+ # In a standard TheSchewartz schema, these both go in the other
+ # direction, but there's no reason to have three indexes that
+ # all start with the same column, and our naming scheme doesn't
+ # allow it anyhow.
+ ts_job_run_after_idx => [qw(run_after funcid)],
+ ts_job_coalesce_idx => [qw(coalesce funcid)],
+ ],
+ },
+
+ ts_note => {
+ FIELDS => [
+
+ # This is a BIGINT in standard TheSchwartz schemas.
+ jobid => {TYPE => 'INT4', NOTNULL => 1},
+ notekey => {TYPE => 'varchar(255)'},
+ value => {TYPE => 'LONGBLOB'},
+ ],
+ INDEXES =>
+ [ts_note_jobid_idx => {FIELDS => [qw(jobid notekey)], TYPE => 'UNIQUE'},],
+ },
+
+ ts_error => {
+ FIELDS => [
+ error_time => {TYPE => 'INT4', NOTNULL => 1},
+ jobid => {TYPE => 'INT4', NOTNULL => 1},
+ message => {TYPE => 'varchar(255)', NOTNULL => 1},
+ funcid => {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
+ ],
+ INDEXES => [
+ ts_error_funcid_idx => [qw(funcid error_time)],
+ ts_error_error_time_idx => ['error_time'],
+ ts_error_jobid_idx => ['jobid'],
+ ],
+ },
+
+ ts_exitstatus => {
+ FIELDS => [
+ jobid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ funcid => {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
+ status => {TYPE => 'INT2'},
+ completion_time => {TYPE => 'INT4'},
+ delete_after => {TYPE => 'INT4'},
+ ],
+ INDEXES => [
+ ts_exitstatus_funcid_idx => ['funcid'],
+ ts_exitstatus_delete_after_idx => ['delete_after'],
+ ],
+ },
+
+ # SCHEMA STORAGE
+ # --------------
+
+ bz_schema => {
+ FIELDS => [
+ schema_data => {TYPE => 'LONGBLOB', NOTNULL => 1},
+ version => {TYPE => 'decimal(3,2)', NOTNULL => 1},
+ ],
+ },
+
+ bug_user_last_visit => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ last_visit_ts => {TYPE => 'DATETIME', NOTNULL => 1},
+ ],
+ INDEXES => [
+ bug_user_last_visit_idx => {FIELDS => ['user_id', 'bug_id'], TYPE => 'UNIQUE'},
+ bug_user_last_visit_last_visit_ts_idx => ['last_visit_ts'],
+ ],
+ },
+
+ user_api_keys => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ api_key => {TYPE => 'VARCHAR(40)', NOTNULL => 1},
+ description => {TYPE => 'VARCHAR(255)'},
+ revoked => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ last_used => {TYPE => 'DATETIME'},
+ ],
+ INDEXES => [
+ user_api_keys_api_key_idx => {FIELDS => ['api_key'], TYPE => 'UNIQUE'},
+ user_api_keys_user_id_idx => ['user_id'],
+ ],
+ },
+};
+
+# Foreign Keys are added in Bugzilla::DB::bz_add_field_tables
+use constant MULTI_SELECT_VALUE_TABLE => {
+ FIELDS => [
+ bug_id => {TYPE => 'INT3', NOTNULL => 1},
+ value => {TYPE => 'varchar(64)', NOTNULL => 1},
+ ],
+ INDEXES => [bug_id_idx => {FIELDS => [qw( bug_id value)], TYPE => 'UNIQUE'},],
};
#--------------------------------------------------------------------------
@@ -1821,27 +1770,28 @@ sub new {
=cut
- my $this = shift;
- my $class = ref($this) || $this;
- my $driver = shift;
+ my $this = shift;
+ my $class = ref($this) || $this;
+ my $driver = shift;
- if ($driver) {
- (my $subclass = $driver) =~ s/^(\S)/\U$1/;
- $class .= '::' . $subclass;
- eval "require $class;";
- die "The $class class could not be found ($subclass " .
- "not supported?): $@" if ($@);
- }
- die "$class is an abstract base class. Instantiate a subclass instead."
- if ($class eq __PACKAGE__);
+ if ($driver) {
+ (my $subclass = $driver) =~ s/^(\S)/\U$1/;
+ $class .= '::' . $subclass;
+ eval "require $class;";
+ die "The $class class could not be found ($subclass " . "not supported?): $@"
+ if ($@);
+ }
+ die "$class is an abstract base class. Instantiate a subclass instead."
+ if ($class eq __PACKAGE__);
+
+ my $self = {};
+ bless $self, $class;
+ $self = $self->_initialize(@_);
- my $self = {};
- bless $self, $class;
- $self = $self->_initialize(@_);
+ return ($self);
- return($self);
+} #eosub--new
-} #eosub--new
#--------------------------------------------------------------------------
sub _initialize {
@@ -1864,33 +1814,34 @@ sub _initialize {
=cut
- my $self = shift;
- my $abstract_schema = shift;
+ my $self = shift;
+ my $abstract_schema = shift;
- if (!$abstract_schema) {
- # While ABSTRACT_SCHEMA cannot be modified, $abstract_schema can be.
- # So, we dclone it to prevent anything from mucking with the constant.
- $abstract_schema = dclone(ABSTRACT_SCHEMA);
+ if (!$abstract_schema) {
- # Let extensions add tables, but make sure they can't modify existing
- # tables. If we don't lock/unlock keys, lock_value complains.
- lock_keys(%$abstract_schema);
- foreach my $table (keys %{ABSTRACT_SCHEMA()}) {
- lock_value(%$abstract_schema, $table)
- if exists $abstract_schema->{$table};
- }
- unlock_keys(%$abstract_schema);
- Bugzilla::Hook::process('db_schema_abstract_schema',
- { schema => $abstract_schema });
- unlock_hash(%$abstract_schema);
+ # While ABSTRACT_SCHEMA cannot be modified, $abstract_schema can be.
+ # So, we dclone it to prevent anything from mucking with the constant.
+ $abstract_schema = dclone(ABSTRACT_SCHEMA);
+
+ # Let extensions add tables, but make sure they can't modify existing
+ # tables. If we don't lock/unlock keys, lock_value complains.
+ lock_keys(%$abstract_schema);
+ foreach my $table (keys %{ABSTRACT_SCHEMA()}) {
+ lock_value(%$abstract_schema, $table) if exists $abstract_schema->{$table};
}
+ unlock_keys(%$abstract_schema);
+ Bugzilla::Hook::process('db_schema_abstract_schema',
+ {schema => $abstract_schema});
+ unlock_hash(%$abstract_schema);
+ }
+
+ $self->{schema} = dclone($abstract_schema);
+ $self->{abstract_schema} = $abstract_schema;
- $self->{schema} = dclone($abstract_schema);
- $self->{abstract_schema} = $abstract_schema;
+ return $self;
- return $self;
+} #eosub--_initialize
-} #eosub--_initialize
#--------------------------------------------------------------------------
sub _adjust_schema {
@@ -1906,36 +1857,41 @@ sub _adjust_schema {
=cut
- my $self = shift;
-
- # The _initialize method has already set up the db_specific hash with
- # the information on how to implement the abstract data types for the
- # instantiated DBMS-specific subclass.
- my $db_specific = $self->{db_specific};
-
- # Loop over each table in the abstract database schema.
- foreach my $table (keys %{ $self->{schema} }) {
- my %fields = (@{ $self->{schema}{$table}{FIELDS} });
- # Loop over the field definitions in each table.
- foreach my $field_def (values %fields) {
- # If the field type is an abstract data type defined in the
- # $db_specific hash, replace it with the DBMS-specific data type
- # that implements it.
- if (exists($db_specific->{$field_def->{TYPE}})) {
- $field_def->{TYPE} = $db_specific->{$field_def->{TYPE}};
- }
- # Replace abstract default values (such as 'TRUE' and 'FALSE')
- # with their database-specific implementations.
- if (exists($field_def->{DEFAULT})
- && exists($db_specific->{$field_def->{DEFAULT}})) {
- $field_def->{DEFAULT} = $db_specific->{$field_def->{DEFAULT}};
- }
- }
+ my $self = shift;
+
+ # The _initialize method has already set up the db_specific hash with
+ # the information on how to implement the abstract data types for the
+ # instantiated DBMS-specific subclass.
+ my $db_specific = $self->{db_specific};
+
+ # Loop over each table in the abstract database schema.
+ foreach my $table (keys %{$self->{schema}}) {
+ my %fields = (@{$self->{schema}{$table}{FIELDS}});
+
+ # Loop over the field definitions in each table.
+ foreach my $field_def (values %fields) {
+
+ # If the field type is an abstract data type defined in the
+ # $db_specific hash, replace it with the DBMS-specific data type
+ # that implements it.
+ if (exists($db_specific->{$field_def->{TYPE}})) {
+ $field_def->{TYPE} = $db_specific->{$field_def->{TYPE}};
+ }
+
+ # Replace abstract default values (such as 'TRUE' and 'FALSE')
+ # with their database-specific implementations.
+ if ( exists($field_def->{DEFAULT})
+ && exists($db_specific->{$field_def->{DEFAULT}}))
+ {
+ $field_def->{DEFAULT} = $db_specific->{$field_def->{DEFAULT}};
+ }
}
+ }
+
+ return $self;
- return $self;
+} #eosub--_adjust_schema
-} #eosub--_adjust_schema
#--------------------------------------------------------------------------
sub get_type_ddl {
@@ -1969,30 +1925,34 @@ C<ALTER TABLE> SQL statement
=cut
- my $self = shift;
- my $finfo = (@_ == 1 && ref($_[0]) eq 'HASH') ? $_[0] : { @_ };
- my $type = $finfo->{TYPE};
- confess "A valid TYPE was not specified for this column (got "
- . Dumper($finfo) . ")" unless ($type);
-
- my $default = $finfo->{DEFAULT};
- # Replace any abstract default value (such as 'TRUE' or 'FALSE')
- # with its database-specific implementation.
- if ( defined $default && exists($self->{db_specific}->{$default}) ) {
- $default = $self->{db_specific}->{$default};
- }
+ my $self = shift;
+ my $finfo = (@_ == 1 && ref($_[0]) eq 'HASH') ? $_[0] : {@_};
+ my $type = $finfo->{TYPE};
+ confess "A valid TYPE was not specified for this column (got "
+ . Dumper($finfo) . ")"
+ unless ($type);
+
+ my $default = $finfo->{DEFAULT};
+
+ # Replace any abstract default value (such as 'TRUE' or 'FALSE')
+ # with its database-specific implementation.
+ if (defined $default && exists($self->{db_specific}->{$default})) {
+ $default = $self->{db_specific}->{$default};
+ }
+
+ my $type_ddl = $self->convert_type($type);
+
+ # DEFAULT attribute must appear before any column constraints
+ # (e.g., NOT NULL), for Oracle
+ $type_ddl .= " DEFAULT $default" if (defined($default));
- my $type_ddl = $self->convert_type($type);
- # DEFAULT attribute must appear before any column constraints
- # (e.g., NOT NULL), for Oracle
- $type_ddl .= " DEFAULT $default" if (defined($default));
- # PRIMARY KEY must appear before NOT NULL for SQLite.
- $type_ddl .= " PRIMARY KEY" if ($finfo->{PRIMARYKEY});
- $type_ddl .= " NOT NULL" if ($finfo->{NOTNULL});
+ # PRIMARY KEY must appear before NOT NULL for SQLite.
+ $type_ddl .= " PRIMARY KEY" if ($finfo->{PRIMARYKEY});
+ $type_ddl .= " NOT NULL" if ($finfo->{NOTNULL});
- return($type_ddl);
+ return ($type_ddl);
-} #eosub--get_type_ddl
+} #eosub--get_type_ddl
sub get_fk_ddl {
@@ -2026,78 +1986,80 @@ is undefined.
=cut
- my ($self, $table, $column, $references) = @_;
- return "" if !$references;
+ my ($self, $table, $column, $references) = @_;
+ return "" if !$references;
- my $update = $references->{UPDATE} || 'CASCADE';
- my $delete = $references->{DELETE} || 'RESTRICT';
- 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 $update = $references->{UPDATE} || 'CASCADE';
+ my $delete = $references->{DELETE} || 'RESTRICT';
+ 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);
- return "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
- . " REFERENCES $to_table($to_column)\n"
- . " ON UPDATE $update ON DELETE $delete";
+ return
+ "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
+ . " REFERENCES $to_table($to_column)\n"
+ . " ON UPDATE $update ON DELETE $delete";
}
# Generates a name for a Foreign Key. It's separate from get_fk_ddl
# so that certain databases can override it (for shorter identifiers or
# other reasons).
sub _get_fk_name {
- my ($self, $table, $column, $references) = @_;
- my $to_table = $references->{TABLE};
- my $to_column = $references->{COLUMN};
- my $name = "fk_${table}_${column}_${to_table}_${to_column}";
+ my ($self, $table, $column, $references) = @_;
+ my $to_table = $references->{TABLE};
+ my $to_column = $references->{COLUMN};
+ my $name = "fk_${table}_${column}_${to_table}_${to_column}";
- if (length($name) > $self->MAX_IDENTIFIER_LEN) {
- $name = 'fk_' . $self->_hash_identifier($name);
- }
+ if (length($name) > $self->MAX_IDENTIFIER_LEN) {
+ $name = 'fk_' . $self->_hash_identifier($name);
+ }
- return $name;
+ return $name;
}
sub _hash_identifier {
- my ($invocant, $value) = @_;
- # We do -7 to allow prefixes like "idx_" or "fk_", or perhaps something
- # longer in the future.
- return substr(md5_hex($value), 0, $invocant->MAX_IDENTIFIER_LEN - 7);
+ my ($invocant, $value) = @_;
+
+ # We do -7 to allow prefixes like "idx_" or "fk_", or perhaps something
+ # longer in the future.
+ return substr(md5_hex($value), 0, $invocant->MAX_IDENTIFIER_LEN - 7);
}
sub get_add_fks_sql {
- my ($self, $table, $column_fks) = @_;
-
- my @add = $self->_column_fks_to_ddl($table, $column_fks);
-
- my @sql;
- if ($self->MULTIPLE_FKS_IN_ALTER) {
- my $alter = "ALTER TABLE $table ADD " . join(', ADD ', @add);
- push(@sql, $alter);
+ my ($self, $table, $column_fks) = @_;
+
+ my @add = $self->_column_fks_to_ddl($table, $column_fks);
+
+ my @sql;
+ if ($self->MULTIPLE_FKS_IN_ALTER) {
+ my $alter = "ALTER TABLE $table ADD " . join(', ADD ', @add);
+ push(@sql, $alter);
+ }
+ else {
+ foreach my $fk_string (@add) {
+ push(@sql, "ALTER TABLE $table ADD $fk_string");
}
- else {
- foreach my $fk_string (@add) {
- push(@sql, "ALTER TABLE $table ADD $fk_string");
- }
- }
- return @sql;
+ }
+ return @sql;
}
sub _column_fks_to_ddl {
- my ($self, $table, $column_fks) = @_;
- my @ddl;
- foreach my $column (keys %$column_fks) {
- my $def = $column_fks->{$column};
- my $fk_string = $self->get_fk_ddl($table, $column, $def);
- push(@ddl, $fk_string);
- }
- return @ddl;
+ my ($self, $table, $column_fks) = @_;
+ my @ddl;
+ foreach my $column (keys %$column_fks) {
+ my $def = $column_fks->{$column};
+ my $fk_string = $self->get_fk_ddl($table, $column, $def);
+ push(@ddl, $fk_string);
+ }
+ return @ddl;
}
-sub get_drop_fk_sql {
- my ($self, $table, $column, $references) = @_;
- my $fk_name = $self->_get_fk_name($table, $column, $references);
+sub get_drop_fk_sql {
+ my ($self, $table, $column, $references) = @_;
+ my $fk_name = $self->_get_fk_name($table, $column, $references);
- return ("ALTER TABLE $table DROP CONSTRAINT $fk_name");
+ return ("ALTER TABLE $table DROP CONSTRAINT $fk_name");
}
sub convert_type {
@@ -2108,8 +2070,8 @@ Converts a TYPE from the L</ABSTRACT_SCHEMA> format into the real SQL type.
=cut
- my ($self, $type) = @_;
- return $self->{db_specific}->{$type} || $type;
+ my ($self, $type) = @_;
+ return $self->{db_specific}->{$type} || $type;
}
sub get_column {
@@ -2126,16 +2088,16 @@ sub get_column {
=cut
- my($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- # Prevent a possible dereferencing of an undef hash, if the
- # table doesn't exist.
- if (exists $self->{schema}->{$table}) {
- my %fields = (@{ $self->{schema}{$table}{FIELDS} });
- return $fields{$column};
- }
- return undef;
-} #eosub--get_column
+ # Prevent a possible dereferencing of an undef hash, if the
+ # table doesn't exist.
+ if (exists $self->{schema}->{$table}) {
+ my %fields = (@{$self->{schema}{$table}{FIELDS}});
+ return $fields{$column};
+ }
+ return undef;
+} #eosub--get_column
sub get_table_list {
@@ -2150,8 +2112,8 @@ sub get_table_list {
=cut
- my $self = shift;
- return sort keys %{$self->{schema}};
+ my $self = shift;
+ return sort keys %{$self->{schema}};
}
sub get_table_columns {
@@ -2165,34 +2127,33 @@ sub get_table_columns {
=cut
- my($self, $table) = @_;
- my @ddl = ();
+ my ($self, $table) = @_;
+ my @ddl = ();
- my $thash = $self->{schema}{$table};
- die "Table $table does not exist in the database schema."
- unless (ref($thash));
+ my $thash = $self->{schema}{$table};
+ die "Table $table does not exist in the database schema." unless (ref($thash));
- my @columns = ();
- my @fields = @{ $thash->{FIELDS} };
- while (@fields) {
- push(@columns, shift(@fields));
- shift(@fields);
- }
+ my @columns = ();
+ my @fields = @{$thash->{FIELDS}};
+ while (@fields) {
+ push(@columns, shift(@fields));
+ shift(@fields);
+ }
- return @columns;
+ return @columns;
-} #eosub--get_table_columns
+} #eosub--get_table_columns
sub get_table_indexes_abstract {
- my ($self, $table) = @_;
- my $table_def = $self->get_table_abstract($table);
- my %indexes = @{$table_def->{INDEXES} || []};
- return \%indexes;
+ my ($self, $table) = @_;
+ my $table_def = $self->get_table_abstract($table);
+ my %indexes = @{$table_def->{INDEXES} || []};
+ return \%indexes;
}
sub get_create_database_sql {
- my ($self, $name) = @_;
- return ("CREATE DATABASE $name");
+ my ($self, $name) = @_;
+ return ("CREATE DATABASE $name");
}
sub get_table_ddl {
@@ -2209,30 +2170,29 @@ sub get_table_ddl {
=cut
- my($self, $table) = @_;
- my @ddl = ();
+ my ($self, $table) = @_;
+ my @ddl = ();
- die "Table $table does not exist in the database schema."
- unless (ref($self->{schema}{$table}));
+ die "Table $table does not exist in the database schema."
+ unless (ref($self->{schema}{$table}));
- my $create_table = $self->_get_create_table_ddl($table);
- push(@ddl, $create_table) if $create_table;
+ my $create_table = $self->_get_create_table_ddl($table);
+ push(@ddl, $create_table) if $create_table;
- my @indexes = @{ $self->{schema}{$table}{INDEXES} || [] };
- while (@indexes) {
- my $index_name = shift(@indexes);
- my $index_info = shift(@indexes);
- my $index_sql = $self->get_add_index_ddl($table, $index_name,
- $index_info);
- push(@ddl, $index_sql) if $index_sql;
- }
+ my @indexes = @{$self->{schema}{$table}{INDEXES} || []};
+ while (@indexes) {
+ my $index_name = shift(@indexes);
+ my $index_info = shift(@indexes);
+ my $index_sql = $self->get_add_index_ddl($table, $index_name, $index_info);
+ push(@ddl, $index_sql) if $index_sql;
+ }
- push(@ddl, @{ $self->{schema}{$table}{DB_EXTRAS} })
- if (ref($self->{schema}{$table}{DB_EXTRAS}));
+ push(@ddl, @{$self->{schema}{$table}{DB_EXTRAS}})
+ if (ref($self->{schema}{$table}{DB_EXTRAS}));
- return @ddl;
+ return @ddl;
-} #eosub--get_table_ddl
+} #eosub--get_table_ddl
sub _get_create_table_ddl {
@@ -2245,30 +2205,29 @@ sub _get_create_table_ddl {
=cut
- my($self, $table) = @_;
-
- my $thash = $self->{schema}{$table};
- die "Table $table does not exist in the database schema."
- unless ref $thash;
-
- my (@col_lines, @fk_lines);
- my @fields = @{ $thash->{FIELDS} };
- while (@fields) {
- my $field = shift(@fields);
- my $finfo = shift(@fields);
- push(@col_lines, "\t$field\t" . $self->get_type_ddl($finfo));
- if ($self->FK_ON_CREATE and $finfo->{REFERENCES}) {
- my $fk = $finfo->{REFERENCES};
- my $fk_ddl = $self->get_fk_ddl($table, $field, $fk);
- push(@fk_lines, $fk_ddl);
- }
+ my ($self, $table) = @_;
+
+ my $thash = $self->{schema}{$table};
+ die "Table $table does not exist in the database schema." unless ref $thash;
+
+ my (@col_lines, @fk_lines);
+ my @fields = @{$thash->{FIELDS}};
+ while (@fields) {
+ my $field = shift(@fields);
+ my $finfo = shift(@fields);
+ push(@col_lines, "\t$field\t" . $self->get_type_ddl($finfo));
+ if ($self->FK_ON_CREATE and $finfo->{REFERENCES}) {
+ my $fk = $finfo->{REFERENCES};
+ my $fk_ddl = $self->get_fk_ddl($table, $field, $fk);
+ push(@fk_lines, $fk_ddl);
}
-
- my $sql = "CREATE TABLE $table (\n" . join(",\n", @col_lines, @fk_lines)
- . "\n)";
- return $sql
+ }
-}
+ my $sql
+ = "CREATE TABLE $table (\n" . join(",\n", @col_lines, @fk_lines) . "\n)";
+ return $sql;
+
+}
sub _get_create_index_ddl {
@@ -2284,16 +2243,17 @@ sub _get_create_index_ddl {
=cut
- my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
+ my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
+
+ my $sql = "CREATE ";
+ $sql .= "$index_type " if ($index_type && $index_type eq 'UNIQUE');
+ $sql
+ .= "INDEX $index_name ON $table_name \(" . join(", ", @$index_fields) . "\)";
- my $sql = "CREATE ";
- $sql .= "$index_type " if ($index_type && $index_type eq 'UNIQUE');
- $sql .= "INDEX $index_name ON $table_name \(" .
- join(", ", @$index_fields) . "\)";
+ return ($sql);
- return($sql);
+} #eosub--_get_create_index_ddl
-} #eosub--_get_create_index_ddl
#--------------------------------------------------------------------------
sub get_add_column_ddl {
@@ -2312,22 +2272,25 @@ sub get_add_column_ddl {
=cut
- my ($self, $table, $column, $definition, $init_value) = @_;
- my @statements;
- push(@statements, "ALTER TABLE $table ". $self->ADD_COLUMN ." $column " .
- $self->get_type_ddl($definition));
-
- # XXX - Note that although this works for MySQL, most databases will fail
- # before this point, if we haven't set a default.
- (push(@statements, "UPDATE $table SET $column = $init_value"))
- if defined $init_value;
-
- if (defined $definition->{REFERENCES}) {
- push(@statements, $self->get_add_fks_sql($table, { $column =>
- $definition->{REFERENCES} }));
- }
-
- return (@statements);
+ my ($self, $table, $column, $definition, $init_value) = @_;
+ my @statements;
+ push(@statements,
+ "ALTER TABLE $table "
+ . $self->ADD_COLUMN
+ . " $column "
+ . $self->get_type_ddl($definition));
+
+ # XXX - Note that although this works for MySQL, most databases will fail
+ # before this point, if we haven't set a default.
+ (push(@statements, "UPDATE $table SET $column = $init_value"))
+ if defined $init_value;
+
+ if (defined $definition->{REFERENCES}) {
+ push(@statements,
+ $self->get_add_fks_sql($table, {$column => $definition->{REFERENCES}}));
+ }
+
+ return (@statements);
}
sub get_add_index_ddl {
@@ -2348,20 +2311,21 @@ sub get_add_index_ddl {
=cut
- my ($self, $table, $name, $definition) = @_;
+ my ($self, $table, $name, $definition) = @_;
- my ($index_fields, $index_type);
- # Index defs can be arrays or hashes
- if (ref($definition) eq 'HASH') {
- $index_fields = $definition->{FIELDS};
- $index_type = $definition->{TYPE};
- } else {
- $index_fields = $definition;
- $index_type = '';
- }
-
- return $self->_get_create_index_ddl($table, $name, $index_fields,
- $index_type);
+ my ($index_fields, $index_type);
+
+ # Index defs can be arrays or hashes
+ if (ref($definition) eq 'HASH') {
+ $index_fields = $definition->{FIELDS};
+ $index_type = $definition->{TYPE};
+ }
+ else {
+ $index_fields = $definition;
+ $index_type = '';
+ }
+
+ return $self->_get_create_index_ddl($table, $name, $index_fields, $index_type);
}
sub get_alter_column_ddl {
@@ -2384,85 +2348,88 @@ sub get_alter_column_ddl {
=cut
- my $self = shift;
- my ($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};
-
- if (defined $default) {
- $default = $specific->{$default} if exists $specific->{$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 ALTER COLUMN $column"
- . " DROP DEFAULT");
- }
- # If we went from no default to a default, or we changed the default.
- elsif ( (defined $default && !defined $default_old) ||
- ($default ne $default_old) )
- {
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column "
- . " SET DEFAULT $default");
- }
-
- # If we went from NULL to NOT NULL.
- if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
- push(@statements, $self->_set_nulls_sql(@_));
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
- . " SET NOT NULL");
- }
- # If we went from NOT NULL to NULL
- elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
- . " DROP NOT NULL");
- }
-
- # 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;
+ my $self = shift;
+ my ($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};
+
+ if (defined $default) {
+ $default = $specific->{$default} if exists $specific->{$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 ALTER COLUMN $column" . " DROP DEFAULT");
+ }
+
+ # If we went from no default to a default, or we changed the default.
+ elsif ((defined $default && !defined $default_old)
+ || ($default ne $default_old))
+ {
+ push(@statements,
+ "ALTER TABLE $table ALTER COLUMN $column " . " SET DEFAULT $default");
+ }
+
+ # If we went from NULL to NOT NULL.
+ if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
+ push(@statements, $self->_set_nulls_sql(@_));
+ push(@statements, "ALTER TABLE $table ALTER COLUMN $column" . " SET NOT NULL");
+ }
+
+ # If we went from NOT NULL to NULL
+ elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
+ push(@statements, "ALTER TABLE $table ALTER COLUMN $column" . " DROP NOT NULL");
+ }
+
+ # 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;
}
# Helps handle any fields that were NULL before, if we have a default,
# when doing an ALTER COLUMN.
sub _set_nulls_sql {
- my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
- my $default = $new_def->{DEFAULT};
- # If we have a set_nulls_to, that overrides the DEFAULT
- # (although nobody would usually specify both a default and
- # a set_nulls_to.)
- $default = $set_nulls_to if defined $set_nulls_to;
- if (defined $default) {
- my $specific = $self->{db_specific};
- $default = $specific->{$default} if exists $specific->{$default};
- }
- my @sql;
- if (defined $default) {
- push(@sql, "UPDATE $table SET $column = $default"
- . " WHERE $column IS NULL");
- }
- return @sql;
+ my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
+ my $default = $new_def->{DEFAULT};
+
+ # If we have a set_nulls_to, that overrides the DEFAULT
+ # (although nobody would usually specify both a default and
+ # a set_nulls_to.)
+ $default = $set_nulls_to if defined $set_nulls_to;
+ if (defined $default) {
+ my $specific = $self->{db_specific};
+ $default = $specific->{$default} if exists $specific->{$default};
+ }
+ my @sql;
+ if (defined $default) {
+ push(@sql, "UPDATE $table SET $column = $default" . " WHERE $column IS NULL");
+ }
+ return @sql;
}
sub get_drop_index_ddl {
@@ -2476,11 +2443,11 @@ sub get_drop_index_ddl {
=cut
- my ($self, $table, $name) = @_;
+ my ($self, $table, $name) = @_;
- # Although ANSI SQL-92 doesn't specify a method of dropping an index,
- # many DBs support this syntax.
- return ("DROP INDEX $name");
+ # Although ANSI SQL-92 doesn't specify a method of dropping an index,
+ # many DBs support this syntax.
+ return ("DROP INDEX $name");
}
sub get_drop_column_ddl {
@@ -2494,8 +2461,8 @@ sub get_drop_column_ddl {
=cut
- my ($self, $table, $column) = @_;
- return ("ALTER TABLE $table DROP COLUMN $column");
+ my ($self, $table, $column) = @_;
+ return ("ALTER TABLE $table DROP COLUMN $column");
}
=item C<get_drop_table_ddl($table)>
@@ -2507,8 +2474,8 @@ sub get_drop_column_ddl {
=cut
sub get_drop_table_ddl {
- my ($self, $table) = @_;
- return ("DROP TABLE $table");
+ my ($self, $table) = @_;
+ return ("DROP TABLE $table");
}
sub get_rename_column_ddl {
@@ -2526,8 +2493,8 @@ sub get_rename_column_ddl {
=cut
- die "ANSI SQL has no way to rename a column, and your database driver\n"
- . " has not implemented a method.";
+ die "ANSI SQL has no way to rename a column, and your database driver\n"
+ . " has not implemented a method.";
}
@@ -2557,8 +2524,8 @@ Gets SQL to rename a table in the database.
=cut
- my ($self, $old_name, $new_name) = @_;
- return ("ALTER TABLE $old_name RENAME TO $new_name");
+ my ($self, $old_name, $new_name) = @_;
+ return ("ALTER TABLE $old_name RENAME TO $new_name");
}
=item C<delete_table($name)>
@@ -2571,13 +2538,13 @@ Gets SQL to rename a table in the database.
=cut
sub delete_table {
- my ($self, $name) = @_;
+ my ($self, $name) = @_;
- die "Attempted to delete nonexistent table '$name'." unless
- $self->get_table_abstract($name);
+ die "Attempted to delete nonexistent table '$name'."
+ unless $self->get_table_abstract($name);
- delete $self->{abstract_schema}->{$name};
- delete $self->{schema}->{$name};
+ delete $self->{abstract_schema}->{$name};
+ delete $self->{schema}->{$name};
}
sub get_column_abstract {
@@ -2594,15 +2561,15 @@ sub get_column_abstract {
=cut
- my ($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- # Prevent a possible dereferencing of an undef hash, if the
- # table doesn't exist.
- if ($self->get_table_abstract($table)) {
- my %fields = (@{ $self->{abstract_schema}{$table}{FIELDS} });
- return $fields{$column};
- }
- return undef;
+ # Prevent a possible dereferencing of an undef hash, if the
+ # table doesn't exist.
+ if ($self->get_table_abstract($table)) {
+ my %fields = (@{$self->{abstract_schema}{$table}{FIELDS}});
+ return $fields{$column};
+ }
+ return undef;
}
=item C<get_indexes_on_column_abstract($table, $column)>
@@ -2620,29 +2587,31 @@ sub get_column_abstract {
=cut
sub get_indexes_on_column_abstract {
- my ($self, $table, $column) = @_;
- my %ret_hash;
-
- my $table_def = $self->get_table_abstract($table);
- if ($table_def && exists $table_def->{INDEXES}) {
- my %indexes = (@{ $table_def->{INDEXES} });
- foreach my $index_name (keys %indexes) {
- my $col_list;
- # Get the column list, depending on whether the index
- # is in hashref or arrayref format.
- if (ref($indexes{$index_name}) eq 'HASH') {
- $col_list = $indexes{$index_name}->{FIELDS};
- } else {
- $col_list = $indexes{$index_name};
- }
-
- if(grep($_ eq $column, @$col_list)) {
- $ret_hash{$index_name} = dclone($indexes{$index_name});
- }
- }
+ my ($self, $table, $column) = @_;
+ my %ret_hash;
+
+ my $table_def = $self->get_table_abstract($table);
+ if ($table_def && exists $table_def->{INDEXES}) {
+ my %indexes = (@{$table_def->{INDEXES}});
+ foreach my $index_name (keys %indexes) {
+ my $col_list;
+
+ # Get the column list, depending on whether the index
+ # is in hashref or arrayref format.
+ if (ref($indexes{$index_name}) eq 'HASH') {
+ $col_list = $indexes{$index_name}->{FIELDS};
+ }
+ else {
+ $col_list = $indexes{$index_name};
+ }
+
+ if (grep($_ eq $column, @$col_list)) {
+ $ret_hash{$index_name} = dclone($indexes{$index_name});
+ }
}
+ }
- return %ret_hash;
+ return %ret_hash;
}
sub get_index_abstract {
@@ -2658,16 +2627,16 @@ sub get_index_abstract {
=cut
- my ($self, $table, $index) = @_;
+ my ($self, $table, $index) = @_;
- # Prevent a possible dereferencing of an undef hash, if the
- # table doesn't exist.
- my $index_table = $self->get_table_abstract($table);
- if ($index_table && exists $index_table->{INDEXES}) {
- my %indexes = (@{ $index_table->{INDEXES} });
- return $indexes{$index};
- }
- return undef;
+ # Prevent a possible dereferencing of an undef hash, if the
+ # table doesn't exist.
+ my $index_table = $self->get_table_abstract($table);
+ if ($index_table && exists $index_table->{INDEXES}) {
+ my %indexes = (@{$index_table->{INDEXES}});
+ return $indexes{$index};
+ }
+ return undef;
}
=item C<get_table_abstract($table)>
@@ -2681,8 +2650,8 @@ sub get_index_abstract {
=cut
sub get_table_abstract {
- my ($self, $table) = @_;
- return $self->{abstract_schema}->{$table};
+ my ($self, $table) = @_;
+ return $self->{abstract_schema}->{$table};
}
=item C<add_table($name, \%definition)>
@@ -2698,22 +2667,20 @@ sub get_table_abstract {
=cut
sub add_table {
- my ($self, $name, $definition) = @_;
- (die "Table already exists: $name")
- if exists $self->{abstract_schema}->{$name};
- if ($definition) {
- $self->{abstract_schema}->{$name} = dclone($definition);
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
- }
- else {
- $self->{abstract_schema}->{$name} = {FIELDS => []};
- $self->{schema}->{$name} = {FIELDS => []};
- }
+ my ($self, $name, $definition) = @_;
+ (die "Table already exists: $name") if exists $self->{abstract_schema}->{$name};
+ if ($definition) {
+ $self->{abstract_schema}->{$name} = dclone($definition);
+ $self->{schema} = dclone($self->{abstract_schema});
+ $self->_adjust_schema();
+ }
+ else {
+ $self->{abstract_schema}->{$name} = {FIELDS => []};
+ $self->{schema}->{$name} = {FIELDS => []};
+ }
}
-
sub rename_table {
=item C<rename_table>
@@ -2723,10 +2690,10 @@ Renames a table from C<$old_name> to C<$new_name> in this Schema object.
=cut
- my ($self, $old_name, $new_name) = @_;
- my $table = $self->get_table_abstract($old_name);
- $self->delete_table($old_name);
- $self->add_table($new_name, $table);
+ my ($self, $old_name, $new_name) = @_;
+ my $table = $self->get_table_abstract($old_name);
+ $self->delete_table($old_name);
+ $self->add_table($new_name, $table);
}
sub delete_column {
@@ -2741,17 +2708,18 @@ sub delete_column {
=cut
- my ($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- my $abstract_fields = $self->{abstract_schema}{$table}{FIELDS};
- my $name_position = firstidx { $_ eq $column } @$abstract_fields;
- die "Attempted to delete nonexistent column ${table}.${column}"
- if $name_position == -1;
- # Delete the key/value pair from the array.
- splice(@$abstract_fields, $name_position, 2);
+ my $abstract_fields = $self->{abstract_schema}{$table}{FIELDS};
+ my $name_position = firstidx { $_ eq $column } @$abstract_fields;
+ die "Attempted to delete nonexistent column ${table}.${column}"
+ if $name_position == -1;
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
+ # Delete the key/value pair from the array.
+ splice(@$abstract_fields, $name_position, 2);
+
+ $self->{schema} = dclone($self->{abstract_schema});
+ $self->_adjust_schema();
}
sub rename_column {
@@ -2767,11 +2735,11 @@ sub rename_column {
=cut
- my ($self, $table, $old_name, $new_name) = @_;
- my $def = $self->get_column_abstract($table, $old_name);
- die "Renaming a column that doesn't exist" if !$def;
- $self->delete_column($table, $old_name);
- $self->set_column($table, $new_name, $def);
+ my ($self, $table, $old_name, $new_name) = @_;
+ my $def = $self->get_column_abstract($table, $old_name);
+ die "Renaming a column that doesn't exist" if !$def;
+ $self->delete_column($table, $old_name);
+ $self->set_column($table, $new_name, $def);
}
sub set_column {
@@ -2792,10 +2760,10 @@ sub set_column {
=cut
- my ($self, $table, $column, $new_def) = @_;
+ my ($self, $table, $column, $new_def) = @_;
- my $fields = $self->{abstract_schema}{$table}{FIELDS};
- $self->_set_object($table, $column, $new_def, $fields);
+ my $fields = $self->{abstract_schema}{$table}{FIELDS};
+ $self->_set_object($table, $column, $new_def, $fields);
}
=item C<set_fk($table, $column \%fk_def)>
@@ -2805,19 +2773,20 @@ Sets the C<REFERENCES> item on the specified column.
=cut
sub set_fk {
- my ($self, $table, $column, $fk_def) = @_;
- # Don't want to modify the source def before we explicitly set it below.
- # This is just us being extra-cautious.
- my $column_def = dclone($self->get_column_abstract($table, $column));
- die "Tried to set an fk on $table.$column, but that column doesn't exist"
- if !$column_def;
- if ($fk_def) {
- $column_def->{REFERENCES} = $fk_def;
- }
- else {
- delete $column_def->{REFERENCES};
- }
- $self->set_column($table, $column, $column_def);
+ my ($self, $table, $column, $fk_def) = @_;
+
+ # Don't want to modify the source def before we explicitly set it below.
+ # This is just us being extra-cautious.
+ my $column_def = dclone($self->get_column_abstract($table, $column));
+ die "Tried to set an fk on $table.$column, but that column doesn't exist"
+ if !$column_def;
+ if ($fk_def) {
+ $column_def->{REFERENCES} = $fk_def;
+ }
+ else {
+ delete $column_def->{REFERENCES};
+ }
+ $self->set_column($table, $column, $column_def);
}
sub set_index {
@@ -2838,36 +2807,39 @@ sub set_index {
=cut
- my ($self, $table, $name, $definition) = @_;
+ my ($self, $table, $name, $definition) = @_;
- if ( exists $self->{abstract_schema}{$table}
- && !exists $self->{abstract_schema}{$table}{INDEXES} ) {
- $self->{abstract_schema}{$table}{INDEXES} = [];
- }
+ if (exists $self->{abstract_schema}{$table}
+ && !exists $self->{abstract_schema}{$table}{INDEXES})
+ {
+ $self->{abstract_schema}{$table}{INDEXES} = [];
+ }
- my $indexes = $self->{abstract_schema}{$table}{INDEXES};
- $self->_set_object($table, $name, $definition, $indexes);
+ my $indexes = $self->{abstract_schema}{$table}{INDEXES};
+ $self->_set_object($table, $name, $definition, $indexes);
}
# A private helper for set_index and set_column.
# This does the actual "work" of those two functions.
# $array_to_change is an arrayref.
sub _set_object {
- my ($self, $table, $name, $definition, $array_to_change) = @_;
+ my ($self, $table, $name, $definition, $array_to_change) = @_;
- my $obj_position = (firstidx { $_ eq $name } @$array_to_change) + 1;
- # If the object doesn't exist, then add it.
- if (!$obj_position) {
- push(@$array_to_change, $name);
- push(@$array_to_change, $definition);
- }
- # We're modifying an existing object in the Schema.
- else {
- splice(@$array_to_change, $obj_position, 1, $definition);
- }
+ my $obj_position = (firstidx { $_ eq $name } @$array_to_change) + 1;
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
+ # If the object doesn't exist, then add it.
+ if (!$obj_position) {
+ push(@$array_to_change, $name);
+ push(@$array_to_change, $definition);
+ }
+
+ # We're modifying an existing object in the Schema.
+ else {
+ splice(@$array_to_change, $obj_position, 1, $definition);
+ }
+
+ $self->{schema} = dclone($self->{abstract_schema});
+ $self->_adjust_schema();
}
=item C<delete_index($table, $name)>
@@ -2885,16 +2857,17 @@ sub _set_object {
=cut
sub delete_index {
- my ($self, $table, $name) = @_;
-
- my $indexes = $self->{abstract_schema}{$table}{INDEXES};
- my $name_position = firstidx { $_ eq $name } @$indexes;
- die "Attempted to delete nonexistent index $name on the $table table"
- if $name_position == -1;
- # Delete the key/value pair from the array.
- splice(@$indexes, $name_position, 2);
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
+ my ($self, $table, $name) = @_;
+
+ my $indexes = $self->{abstract_schema}{$table}{INDEXES};
+ my $name_position = firstidx { $_ eq $name } @$indexes;
+ die "Attempted to delete nonexistent index $name on the $table table"
+ if $name_position == -1;
+
+ # Delete the key/value pair from the array.
+ splice(@$indexes, $name_position, 2);
+ $self->{schema} = dclone($self->{abstract_schema});
+ $self->_adjust_schema();
}
sub columns_equal {
@@ -2912,24 +2885,24 @@ sub columns_equal {
=cut
- my $self = shift;
- my $col_one = dclone(shift);
- my $col_two = dclone(shift);
+ my $self = shift;
+ my $col_one = dclone(shift);
+ my $col_two = dclone(shift);
- $col_one->{TYPE} = uc($col_one->{TYPE});
- $col_two->{TYPE} = uc($col_two->{TYPE});
+ $col_one->{TYPE} = uc($col_one->{TYPE});
+ $col_two->{TYPE} = uc($col_two->{TYPE});
- # We don't care about foreign keys when comparing column definitions.
- delete $col_one->{REFERENCES};
- delete $col_two->{REFERENCES};
+ # We don't care about foreign keys when comparing column definitions.
+ delete $col_one->{REFERENCES};
+ delete $col_two->{REFERENCES};
- my @col_one_array = %$col_one;
- my @col_two_array = %$col_two;
+ my @col_one_array = %$col_one;
+ my @col_two_array = %$col_two;
- my ($removed, $added) = diff_arrays(\@col_one_array, \@col_two_array);
+ my ($removed, $added) = diff_arrays(\@col_one_array, \@col_two_array);
- # If there are no differences between the arrays, then they are equal.
- return !scalar(@$removed) && !scalar(@$added) ? 1 : 0;
+ # If there are no differences between the arrays, then they are equal.
+ return !scalar(@$removed) && !scalar(@$added) ? 1 : 0;
}
@@ -2953,18 +2926,18 @@ sub columns_equal {
=cut
sub serialize_abstract {
- my ($self) = @_;
-
- # Make it ok to eval
- local $Data::Dumper::Purity = 1;
-
- # Avoid cross-refs
- local $Data::Dumper::Deepcopy = 1;
-
- # Always sort keys to allow textual compare
- local $Data::Dumper::Sortkeys = 1;
-
- return Dumper($self->{abstract_schema});
+ my ($self) = @_;
+
+ # Make it ok to eval
+ local $Data::Dumper::Purity = 1;
+
+ # Avoid cross-refs
+ local $Data::Dumper::Deepcopy = 1;
+
+ # Always sort keys to allow textual compare
+ local $Data::Dumper::Sortkeys = 1;
+
+ return Dumper($self->{abstract_schema});
}
=item C<deserialize_abstract($serialized, $version)>
@@ -2983,36 +2956,34 @@ sub serialize_abstract {
=cut
sub deserialize_abstract {
- my ($class, $serialized, $version) = @_;
-
- my $thawed_hash;
- if ($version < 2) {
- $thawed_hash = thaw($serialized);
- }
- else {
- my $cpt = new Safe;
- $cpt->reval($serialized) ||
- die "Unable to restore cached schema: " . $@;
- $thawed_hash = ${$cpt->varglob('VAR1')};
- }
-
- # Version 2 didn't have the "created" key for REFERENCES items.
- if ($version < 3) {
- my $standard = $class->new()->{abstract_schema};
- foreach my $table_name (keys %$thawed_hash) {
- my %standard_fields =
- @{ $standard->{$table_name}->{FIELDS} || [] };
- my $table = $thawed_hash->{$table_name};
- my %fields = @{ $table->{FIELDS} || [] };
- while (my ($field, $def) = each %fields) {
- if (exists $def->{REFERENCES}) {
- $def->{REFERENCES}->{created} = 1;
- }
- }
+ my ($class, $serialized, $version) = @_;
+
+ my $thawed_hash;
+ if ($version < 2) {
+ $thawed_hash = thaw($serialized);
+ }
+ else {
+ my $cpt = new Safe;
+ $cpt->reval($serialized) || die "Unable to restore cached schema: " . $@;
+ $thawed_hash = ${$cpt->varglob('VAR1')};
+ }
+
+ # Version 2 didn't have the "created" key for REFERENCES items.
+ if ($version < 3) {
+ my $standard = $class->new()->{abstract_schema};
+ foreach my $table_name (keys %$thawed_hash) {
+ my %standard_fields = @{$standard->{$table_name}->{FIELDS} || []};
+ my $table = $thawed_hash->{$table_name};
+ my %fields = @{$table->{FIELDS} || []};
+ while (my ($field, $def) = each %fields) {
+ if (exists $def->{REFERENCES}) {
+ $def->{REFERENCES}->{created} = 1;
}
+ }
}
+ }
- return $class->new(undef, $thawed_hash);
+ return $class->new(undef, $thawed_hash);
}
#####################################################################
@@ -3040,8 +3011,8 @@ object.
=cut
sub get_empty_schema {
- my ($class) = @_;
- return $class->deserialize_abstract(Dumper({}), SCHEMA_VERSION);
+ my ($class) = @_;
+ return $class->deserialize_abstract(Dumper({}), SCHEMA_VERSION);
}
1;
diff --git a/Bugzilla/DB/Schema/Mysql.pm b/Bugzilla/DB/Schema/Mysql.pm
index 7ff8ade9f..4c0d43523 100644
--- a/Bugzilla/DB/Schema/Mysql.pm
+++ b/Bugzilla/DB/Schema/Mysql.pm
@@ -21,7 +21,7 @@ use Bugzilla::Error;
use parent qw(Bugzilla::DB::Schema);
-# This is for column_info_to_column, to know when a tinyint is a
+# 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.
@@ -30,232 +30,260 @@ use parent qw(Bugzilla::DB::Schema);
# 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.
-#
+#
# THIS CONSTANT IS ONLY USED FOR UPGRADES FROM 2.18 OR EARLIER. DON'T
# UPDATE IT TO MODERN COLUMN NAMES OR DEFINITIONS.
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}
+ 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.
+
+ # 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);
+use constant MYISAM_TABLES => qw();
#------------------------------------------------------------------------------
sub _initialize {
- my $self = shift;
+ my $self = shift;
- $self = $self->SUPER::_initialize(@_);
+ $self = $self->SUPER::_initialize(@_);
- $self->{db_specific} = {
+ $self->{db_specific} = {
- BOOLEAN => 'tinyint',
- FALSE => '0',
- TRUE => '1',
+ BOOLEAN => 'tinyint',
+ FALSE => '0',
+ TRUE => '1',
- INT1 => 'tinyint',
- INT2 => 'smallint',
- INT3 => 'mediumint',
- INT4 => 'integer',
+ INT1 => 'tinyint',
+ INT2 => 'smallint',
+ INT3 => 'mediumint',
+ INT4 => 'integer',
- SMALLSERIAL => 'smallint auto_increment',
- MEDIUMSERIAL => 'mediumint auto_increment',
- INTSERIAL => 'integer auto_increment',
+ SMALLSERIAL => 'smallint auto_increment',
+ MEDIUMSERIAL => 'mediumint auto_increment',
+ INTSERIAL => 'integer auto_increment',
- TINYTEXT => 'tinytext',
- MEDIUMTEXT => 'mediumtext',
- LONGTEXT => 'mediumtext',
+ TINYTEXT => 'tinytext',
+ MEDIUMTEXT => 'mediumtext',
+ LONGTEXT => 'mediumtext',
- LONGBLOB => 'longblob',
+ LONGBLOB => 'longblob',
- DATETIME => 'datetime',
- DATE => 'date',
- };
+ DATETIME => 'datetime',
+ DATE => 'date',
+ };
- $self->_adjust_schema;
+ $self->_adjust_schema;
- return $self;
+ return $self;
+
+} #eosub--_initialize
-} #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) = @_;
+ # 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");
+ my $charset = Bugzilla->dbh->bz_db_is_utf8 ? "CHARACTER SET utf8" : '';
+ my $type = grep($_ eq $table, MYISAM_TABLES) ? 'MYISAM' : 'InnoDB';
+
+ my $ddl = $self->SUPER::_get_create_table_ddl($table);
+ $ddl =~ s/CREATE TABLE (.*) \(/CREATE TABLE `$1` (/;
+ $ddl .= " ENGINE = $type $charset";
+
+ return $ddl;
+
+} #eosub--_get_create_table_ddl
-} #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) = @_;
+ # 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) . "\)";
+ 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);
+ return ($sql);
+
+} #eosub--_get_create_index_ddl
-} #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");
+ 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 @statements;
-
- push(@statements, "UPDATE $table SET $column = $set_nulls_to
- WHERE $column IS NULL") if defined $set_nulls_to;
-
- # Calling SET DEFAULT or DROP DEFAULT is *way* faster than calling
- # CHANGE COLUMN, so just do that if we're just changing the default.
- my %old_defaultless = %$old_def;
- my %new_defaultless = %$new_def;
- delete $old_defaultless{DEFAULT};
- delete $new_defaultless{DEFAULT};
- if (!$self->columns_equal($old_def, $new_def)
- && $self->columns_equal(\%new_defaultless, \%old_defaultless))
- {
- if (!defined $new_def->{DEFAULT}) {
- push(@statements,
- "ALTER TABLE $table ALTER COLUMN $column DROP DEFAULT");
- }
- else {
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column
- SET DEFAULT " . $new_def->{DEFAULT});
- }
+ 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 @statements;
+
+ push(
+ @statements, "UPDATE $table SET $column = $set_nulls_to
+ WHERE $column IS NULL"
+ ) if defined $set_nulls_to;
+
+ # Calling SET DEFAULT or DROP DEFAULT is *way* faster than calling
+ # CHANGE COLUMN, so just do that if we're just changing the default.
+ my %old_defaultless = %$old_def;
+ my %new_defaultless = %$new_def;
+ delete $old_defaultless{DEFAULT};
+ delete $new_defaultless{DEFAULT};
+ if (!$self->columns_equal($old_def, $new_def)
+ && $self->columns_equal(\%new_defaultless, \%old_defaultless))
+ {
+ if (!defined $new_def->{DEFAULT}) {
+ push(@statements, "ALTER TABLE $table ALTER COLUMN $column DROP DEFAULT");
}
else {
- my $new_ddl = $self->get_type_ddl(\%new_def_copy);
- 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");
+ push(
+ @statements, "ALTER TABLE $table ALTER COLUMN $column
+ SET DEFAULT " . $new_def->{DEFAULT}
+ );
}
-
- return @statements;
+ }
+ else {
+ my $new_ddl = $self->get_type_ddl(\%new_def_copy);
+ 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;
+ 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");
+ 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
+# 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);
+ 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);
}
sub get_set_serial_sql {
- my ($self, $table, $column, $value) = @_;
- return ("ALTER TABLE $table AUTO_INCREMENT = $value");
+ my ($self, $table, $column, $value) = @_;
+ return ("ALTER TABLE $table AUTO_INCREMENT = $value");
}
# Converts a DBI column_info output to an abstract column definition.
@@ -263,124 +291,137 @@ sub get_set_serial_sql {
# 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;
- }
- }
+ my ($self, $column_info) = @_;
- # 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 =~ /^(-)?([0-9]+)(\.[0-9]+)?$/);
- $column->{DEFAULT} = $default;
- }
- }
+ # Unfortunately, we have to break Schema's normal "no database"
+ # barrier a few times in this function.
+ my $dbh = Bugzilla->dbh;
- my $type = $column_info->{TYPE_NAME};
+ my $table = $column_info->{TABLE_NAME};
+ my $col_name = $column_info->{COLUMN_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};
- }
+ my $column = {};
- # 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';
- }
- }
+ ($column->{NOTNULL} = 1) if $column_info->{NULLABLE} == 0;
- # 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;
+ 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};
- # 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};
+ # 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 =~ /^(-)?([0-9]+)(\.[0-9]+)?$/);
+ $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;
+
+ }
- $column->{TYPE} = $type;
+ # 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};
+ }
- #print "$table.$col_name: " . Data::Dumper->Dump([$column]) . "\n";
+ $column->{TYPE} = $type;
- return $column;
+ #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");
+ 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;
diff --git a/Bugzilla/DB/Schema/Oracle.pm b/Bugzilla/DB/Schema/Oracle.pm
index 8fb5479b1..416e9204b 100644
--- a/Bugzilla/DB/Schema/Oracle.pm
+++ b/Bugzilla/DB/Schema/Oracle.pm
@@ -21,8 +21,9 @@ use parent qw(Bugzilla::DB::Schema);
use Carp qw(confess);
use Bugzilla::Util;
-use constant ADD_COLUMN => 'ADD';
+use constant ADD_COLUMN => 'ADD';
use constant MULTIPLE_FKS_IN_ALTER => 0;
+
# 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.
@@ -31,123 +32,128 @@ use constant MAX_IDENTIFIER_LEN => 27;
#------------------------------------------------------------------------------
sub _initialize {
- my $self = shift;
+ my $self = shift;
+
+ $self = $self->SUPER::_initialize(@_);
- $self = $self->SUPER::_initialize(@_);
+ $self->{db_specific} = {
- $self->{db_specific} = {
+ BOOLEAN => 'integer',
+ FALSE => '0',
+ TRUE => '1',
- BOOLEAN => 'integer',
- FALSE => '0',
- TRUE => '1',
+ INT1 => 'integer',
+ INT2 => 'integer',
+ INT3 => 'integer',
+ INT4 => 'integer',
- INT1 => 'integer',
- INT2 => 'integer',
- INT3 => 'integer',
- INT4 => 'integer',
+ SMALLSERIAL => 'integer',
+ MEDIUMSERIAL => 'integer',
+ INTSERIAL => 'integer',
- SMALLSERIAL => 'integer',
- MEDIUMSERIAL => 'integer',
- INTSERIAL => 'integer',
+ TINYTEXT => 'varchar(255)',
+ MEDIUMTEXT => 'varchar(4000)',
+ LONGTEXT => 'clob',
- TINYTEXT => 'varchar(255)',
- MEDIUMTEXT => 'varchar(4000)',
- LONGTEXT => 'clob',
+ LONGBLOB => 'blob',
- LONGBLOB => 'blob',
+ DATETIME => 'date',
+ DATE => 'date',
+ };
- DATETIME => 'date',
- DATE => 'date',
- };
+ $self->_adjust_schema;
- $self->_adjust_schema;
+ return $self;
- return $self;
+} #eosub--_initialize
-} #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));
- }
+ 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));
}
- return @ddl;
-} #eosub--get_table_ddl
+ # 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;
-# Extend superclass method to create Oracle Text indexes if index type
+} #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));
+ 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) = @_;
+ my $self = shift;
+ my ($table, $name) = @_;
- $name = 'idx_' . $self->_hash_identifier($name);
- return $self->SUPER::get_drop_index_ddl($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
+# 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 = shift;
- my $ddl = $self->SUPER::get_fk_ddl(@_);
+ my $self = shift;
+ my $ddl = $self->SUPER::get_fk_ddl(@_);
- # iThe Bugzilla Oracle driver implements UPDATE via a trigger.
- $ddl =~ s/ON UPDATE \S+//i;
- # RESTRICT is the default for DELETE on Oracle and may not be specified.
- $ddl =~ s/ON DELETE RESTRICT//i;
+ # iThe Bugzilla Oracle driver implements UPDATE via a trigger.
+ $ddl =~ s/ON UPDATE \S+//i;
- return $ddl;
+ # RESTRICT is the default for DELETE on Oracle and may not be specified.
+ $ddl =~ s/ON DELETE RESTRICT//i;
+
+ return $ddl;
}
sub get_add_fks_sql {
- my $self = shift;
- my ($table, $column_fks) = @_;
- my @sql = $self->SUPER::get_add_fks_sql(@_);
-
- foreach my $column (keys %$column_fks) {
- my $fk = $column_fks->{$column};
- next if $fk->{UPDATE} && uc($fk->{UPDATE}) ne 'CASCADE';
- my $fk_name = $self->_get_fk_name($table, $column, $fk);
- my $to_column = $fk->{COLUMN};
- my $to_table = $fk->{TABLE};
-
- my $trigger = <<END;
+ my $self = shift;
+ my ($table, $column_fks) = @_;
+ my @sql = $self->SUPER::get_add_fks_sql(@_);
+
+ foreach my $column (keys %$column_fks) {
+ my $fk = $column_fks->{$column};
+ next if $fk->{UPDATE} && uc($fk->{UPDATE}) ne 'CASCADE';
+ my $fk_name = $self->_get_fk_name($table, $column, $fk);
+ my $to_column = $fk->{COLUMN};
+ my $to_table = $fk->{TABLE};
+
+ my $trigger = <<END;
CREATE OR REPLACE TRIGGER ${fk_name}_UC
AFTER UPDATE OF $to_column ON $to_table
REFERENCING NEW AS NEW OLD AS OLD
@@ -158,351 +164,371 @@ CREATE OR REPLACE TRIGGER ${fk_name}_UC
WHERE $column = :OLD.$to_column;
END ${fk_name}_UC;
END
- push(@sql, $trigger);
- }
+ push(@sql, $trigger);
+ }
- return @sql;
+ return @sql;
}
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;
+ 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;
+ 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_add_column_ddl {
- my $self = shift;
- my ($table, $column, $definition, $init_value) = @_;
- my @sql;
-
- # Create sequences and triggers to emulate SERIAL datatypes.
- if ($definition->{TYPE} =~ /SERIAL/i) {
- # Clone the definition to not alter the original one.
- my %def = %$definition;
- # Oracle requires to define the column is several steps.
- my $pk = delete $def{PRIMARYKEY};
- my $notnull = delete $def{NOTNULL};
- @sql = $self->SUPER::get_add_column_ddl($table, $column, \%def, $init_value);
- push(@sql, $self->_get_create_seq_ddl($table, $column));
- push(@sql, "UPDATE $table SET $column = ${table}_${column}_SEQ.NEXTVAL");
- push(@sql, "ALTER TABLE $table MODIFY $column NOT NULL") if $notnull;
- push(@sql, "ALTER TABLE $table ADD PRIMARY KEY ($column)") if $pk;
- }
- else {
- @sql = $self->SUPER::get_add_column_ddl(@_);
- # Create triggers to deal with empty string.
- if ($definition->{TYPE} =~ /varchar|TEXT/i && $definition->{NOTNULL}) {
- push(@sql, _get_notnull_trigger_ddl($table, $column));
- }
+ my $self = shift;
+ my ($table, $column, $definition, $init_value) = @_;
+ my @sql;
+
+ # Create sequences and triggers to emulate SERIAL datatypes.
+ if ($definition->{TYPE} =~ /SERIAL/i) {
+
+ # Clone the definition to not alter the original one.
+ my %def = %$definition;
+
+ # Oracle requires to define the column is several steps.
+ my $pk = delete $def{PRIMARYKEY};
+ my $notnull = delete $def{NOTNULL};
+ @sql = $self->SUPER::get_add_column_ddl($table, $column, \%def, $init_value);
+ push(@sql, $self->_get_create_seq_ddl($table, $column));
+ push(@sql, "UPDATE $table SET $column = ${table}_${column}_SEQ.NEXTVAL");
+ push(@sql, "ALTER TABLE $table MODIFY $column NOT NULL") if $notnull;
+ push(@sql, "ALTER TABLE $table ADD PRIMARY KEY ($column)") if $pk;
+ }
+ else {
+ @sql = $self->SUPER::get_add_column_ddl(@_);
+
+ # Create triggers to deal with empty string.
+ if ($definition->{TYPE} =~ /varchar|TEXT/i && $definition->{NOTNULL}) {
+ push(@sql, _get_notnull_trigger_ddl($table, $column));
}
+ }
- return @sql;
+ return @sql;
}
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};
-
- if (defined $default) {
- $default = $specific->{$default} if exists $specific->{$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) )
- {
- 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 = $default if defined $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)");
+ 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};
+
+ if (defined $default) {
+ $default = $specific->{$default} if exists $specific->{$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))
+ {
+ 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 = $default if defined $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");
}
- # 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;
+ 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};
+ 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");
}
-
- 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;
+ 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.
- my $old_seq = "${table}_${old_name}_SEQ";
- my $new_seq = "${table}_${new_name}_SEQ";
- push(@sql, "RENAME $old_seq TO $new_seq");
- push(@sql, $self->_get_create_trigger_ddl($table, $new_name, $new_seq));
- 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;
+ 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.
+ my $old_seq = "${table}_${old_name}_SEQ";
+ my $new_seq = "${table}_${new_name}_SEQ";
+ push(@sql, "RENAME $old_seq TO $new_seq");
+ push(@sql, $self->_get_create_trigger_ddl($table, $new_name, $new_seq));
+ 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_drop_column_ddl {
- my $self = shift;
- my ($table, $column) = @_;
- my @sql;
- push(@sql, $self->SUPER::get_drop_column_ddl(@_));
- my $dbh=Bugzilla->dbh;
- my $trigger_name = uc($table . "_" . $column);
- my $exist_trigger = $dbh->selectcol_arrayref(
- "SELECT OBJECT_NAME FROM USER_OBJECTS
- WHERE OBJECT_NAME = ?", undef, $trigger_name);
- if(@$exist_trigger) {
- push(@sql, "DROP TRIGGER $trigger_name");
- }
- # If this column is of type SERIAL, we need to drop the sequence
- # and trigger that went along with it.
- my $def = $self->get_column_abstract($table, $column);
- if ($def->{TYPE} =~ /SERIAL/i) {
- push(@sql, "DROP SEQUENCE ${table}_${column}_SEQ");
- push(@sql, "DROP TRIGGER ${table}_${column}_TR");
- }
- return @sql;
+ my $self = shift;
+ my ($table, $column) = @_;
+ my @sql;
+ push(@sql, $self->SUPER::get_drop_column_ddl(@_));
+ my $dbh = Bugzilla->dbh;
+ my $trigger_name = uc($table . "_" . $column);
+ my $exist_trigger = $dbh->selectcol_arrayref(
+ "SELECT OBJECT_NAME FROM USER_OBJECTS
+ WHERE OBJECT_NAME = ?", undef, $trigger_name
+ );
+ if (@$exist_trigger) {
+ push(@sql, "DROP TRIGGER $trigger_name");
+ }
+
+ # If this column is of type SERIAL, we need to drop the sequence
+ # and trigger that went along with it.
+ my $def = $self->get_column_abstract($table, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ push(@sql, "DROP SEQUENCE ${table}_${column}_SEQ");
+ push(@sql, "DROP TRIGGER ${table}_${column}_TR");
+ }
+ 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.
- return ();
- }
+ 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.
+ return ();
+ }
- my @sql = ("ALTER TABLE $old_name RENAME TO $new_name");
- my @columns = $self->get_table_columns($old_name);
- foreach my $column (@columns) {
- my $def = $self->get_column_abstract($old_name, $column);
- if ($def->{TYPE} =~ /SERIAL/i) {
- # If there's a SERIAL column on this table, we also need
- # to rename the sequence.
- my $old_seq = "${old_name}_${column}_SEQ";
- my $new_seq = "${new_name}_${column}_SEQ";
- push(@sql, "RENAME $old_seq TO $new_seq");
- push(@sql, $self->_get_create_trigger_ddl($new_name, $column, $new_seq));
- push(@sql, "DROP TRIGGER ${old_name}_${column}_TR");
- }
- if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL}) {
- push(@sql, _get_notnull_trigger_ddl($new_name, $column));
- push(@sql, "DROP TRIGGER ${old_name}_${column}");
- }
+ my @sql = ("ALTER TABLE $old_name RENAME TO $new_name");
+ my @columns = $self->get_table_columns($old_name);
+ foreach my $column (@columns) {
+ my $def = $self->get_column_abstract($old_name, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+
+ # If there's a SERIAL column on this table, we also need
+ # to rename the sequence.
+ my $old_seq = "${old_name}_${column}_SEQ";
+ my $new_seq = "${new_name}_${column}_SEQ";
+ push(@sql, "RENAME $old_seq TO $new_seq");
+ push(@sql, $self->_get_create_trigger_ddl($new_name, $column, $new_seq));
+ push(@sql, "DROP TRIGGER ${old_name}_${column}_TR");
+ }
+ if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL}) {
+ push(@sql, _get_notnull_trigger_ddl($new_name, $column));
+ push(@sql, "DROP TRIGGER ${old_name}_${column}");
}
+ }
- return @sql;
+ return @sql;
}
sub get_drop_table_ddl {
- my ($self, $name) = @_;
- my @sql;
-
- my @columns = $self->get_table_columns($name);
- foreach my $column (@columns) {
- my $def = $self->get_column_abstract($name, $column);
- if ($def->{TYPE} =~ /SERIAL/i) {
- # If there's a SERIAL column on this table, we also need
- # to remove the sequence.
- push(@sql, "DROP SEQUENCE ${name}_${column}_SEQ");
- }
+ my ($self, $name) = @_;
+ my @sql;
+
+ my @columns = $self->get_table_columns($name);
+ foreach my $column (@columns) {
+ my $def = $self->get_column_abstract($name, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+
+ # If there's a SERIAL column on this table, we also need
+ # to remove the sequence.
+ push(@sql, "DROP SEQUENCE ${name}_${column}_SEQ");
}
- push(@sql, "DROP TABLE $name CASCADE CONSTRAINTS PURGE");
+ }
+ push(@sql, "DROP TABLE $name CASCADE CONSTRAINTS PURGE");
- return @sql;
+ 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;
+ 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";
- push (@ddl, $seq_sql);
- push(@ddl, $self->_get_create_trigger_ddl($table, $column, $seq_name));
-
- return @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";
+ push(@ddl, $seq_sql);
+ push(@ddl, $self->_get_create_trigger_ddl($table, $column, $seq_name));
+
+ return @ddl;
}
sub _get_create_trigger_ddl {
- my ($self, $table, $column, $seq_name) = @_;
- 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;";
- return $serial_sql;
+ my ($self, $table, $column, $seq_name) = @_;
+ 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;";
+ return $serial_sql;
}
-sub get_set_serial_sql {
- my ($self, $table, $column, $value) = @_;
- my @sql;
- my $seq_name = "${table}_${column}_SEQ";
- push(@sql, "DROP SEQUENCE ${seq_name}");
- push(@sql, $self->_get_create_seq_ddl($table, $column, $value));
- return @sql;
-}
+sub get_set_serial_sql {
+ my ($self, $table, $column, $value) = @_;
+ my @sql;
+ my $seq_name = "${table}_${column}_SEQ";
+ push(@sql, "DROP SEQUENCE ${seq_name}");
+ push(@sql, $self->_get_create_seq_ddl($table, $column, $value));
+ return @sql;
+}
1;
diff --git a/Bugzilla/DB/Schema/Pg.pm b/Bugzilla/DB/Schema/Pg.pm
index 55a932272..cf28a02d9 100644
--- a/Bugzilla/DB/Schema/Pg.pm
+++ b/Bugzilla/DB/Schema/Pg.pm
@@ -23,169 +23,191 @@ 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');
- }
- }
+ 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} = {
+ $self->{db_specific} = {
- BOOLEAN => 'smallint',
- FALSE => '0',
- TRUE => '1',
+ BOOLEAN => 'smallint',
+ FALSE => '0',
+ TRUE => '1',
- INT1 => 'integer',
- INT2 => 'integer',
- INT3 => 'integer',
- INT4 => 'integer',
+ INT1 => 'integer',
+ INT2 => 'integer',
+ INT3 => 'integer',
+ INT4 => 'integer',
- SMALLSERIAL => 'serial unique',
- MEDIUMSERIAL => 'serial unique',
- INTSERIAL => 'serial unique',
+ SMALLSERIAL => 'serial unique',
+ MEDIUMSERIAL => 'serial unique',
+ INTSERIAL => 'serial unique',
- TINYTEXT => 'varchar(255)',
- MEDIUMTEXT => 'text',
- LONGTEXT => 'text',
+ TINYTEXT => 'varchar(255)',
+ MEDIUMTEXT => 'text',
+ LONGTEXT => 'text',
- LONGBLOB => 'bytea',
+ LONGBLOB => 'bytea',
- DATETIME => 'timestamp(0) without time zone',
- DATE => 'date',
- };
+ DATETIME => 'timestamp(0) without time zone',
+ DATE => 'date',
+ };
- $self->_adjust_schema;
+ $self->_adjust_schema;
- return $self;
+ return $self;
+
+} #eosub--_initialize
-} #eosub--_initialize
#--------------------------------------------------------------------
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 ? "ENCODING 'UTF8' TEMPLATE template0" : '';
- return ("CREATE DATABASE $name $charset");
+ 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 ? "ENCODING 'UTF8' TEMPLATE template0" : '';
+ return ("CREATE DATABASE $name $charset");
}
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.
- push(@sql, "ALTER SEQUENCE ${table}_${old_name}_seq
- RENAME TO ${table}_${new_name}_seq");
- }
- return @sql;
+ 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.
+ push(
+ @sql, "ALTER SEQUENCE ${table}_${old_name}_seq
+ RENAME TO ${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 ();
+ 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 ();
+ }
+
+ my @sql = ("ALTER TABLE $old_name RENAME TO $new_name");
+
+ # If there's a SERIAL column on this table, we also need to rename the
+ # sequence.
+ # If there is a PRIMARY KEY, we need to rename it too.
+ my @columns = $self->get_table_columns($old_name);
+ foreach my $column (@columns) {
+ my $def = $self->get_column_abstract($old_name, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ my $old_seq = "${old_name}_${column}_seq";
+ my $new_seq = "${new_name}_${column}_seq";
+ push(@sql, "ALTER SEQUENCE $old_seq RENAME TO $new_seq");
+ push(
+ @sql, "ALTER TABLE $new_name ALTER COLUMN $column
+ SET DEFAULT NEXTVAL('$new_seq')"
+ );
}
-
- my @sql = ("ALTER TABLE $old_name RENAME TO $new_name");
-
- # If there's a SERIAL column on this table, we also need to rename the
- # sequence.
- # If there is a PRIMARY KEY, we need to rename it too.
- my @columns = $self->get_table_columns($old_name);
- foreach my $column (@columns) {
- my $def = $self->get_column_abstract($old_name, $column);
- if ($def->{TYPE} =~ /SERIAL/i) {
- my $old_seq = "${old_name}_${column}_seq";
- my $new_seq = "${new_name}_${column}_seq";
- push(@sql, "ALTER SEQUENCE $old_seq RENAME TO $new_seq");
- push(@sql, "ALTER TABLE $new_name ALTER COLUMN $column
- SET DEFAULT NEXTVAL('$new_seq')");
- }
- if ($def->{PRIMARYKEY}) {
- my $old_pk = "${old_name}_pkey";
- my $new_pk = "${new_name}_pkey";
- push(@sql, "ALTER INDEX $old_pk RENAME to $new_pk");
- }
+ if ($def->{PRIMARYKEY}) {
+ my $old_pk = "${old_name}_pkey";
+ my $new_pk = "${new_name}_pkey";
+ push(@sql, "ALTER INDEX $old_pk RENAME to $new_pk");
}
+ }
- return @sql;
+ return @sql;
}
sub get_set_serial_sql {
- my ($self, $table, $column, $value) = @_;
- return ("SELECT setval('${table}_${column}_seq', $value, false)
- FROM $table");
+ my ($self, $table, $column, $value) = @_;
+ return (
+ "SELECT setval('${table}_${column}_seq', $value, false)
+ FROM $table"
+ );
}
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/\bserial\b/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
- OWNED BY $table.$column");
- push(@statements, "SELECT setval('${table}_${column}_seq',
+ 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/\bserial\b/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
+ OWNED BY $table.$column"
+ );
+ 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");
- push(@statements, "ALTER SEQUENCE ${table}_${column}_seq
- OWNED BY NONE");
- push(@statements, "DROP SEQUENCE ${table}_${column}_seq");
- }
-
- return @statements;
+ 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"
+ );
+ push(
+ @statements, "ALTER SEQUENCE ${table}_${column}_seq
+ OWNED BY NONE"
+ );
+ push(@statements, "DROP SEQUENCE ${table}_${column}_seq");
+ }
+
+ return @statements;
}
1;
diff --git a/Bugzilla/DB/Schema/Sqlite.pm b/Bugzilla/DB/Schema/Sqlite.pm
index ccdbfd8aa..57361d2bb 100644
--- a/Bugzilla/DB/Schema/Sqlite.pm
+++ b/Bugzilla/DB/Schema/Sqlite.pm
@@ -22,37 +22,37 @@ use constant FK_ON_CREATE => 1;
sub _initialize {
- my $self = shift;
+ my $self = shift;
- $self = $self->SUPER::_initialize(@_);
+ $self = $self->SUPER::_initialize(@_);
- $self->{db_specific} = {
- BOOLEAN => 'integer',
- FALSE => '0',
- TRUE => '1',
+ $self->{db_specific} = {
+ BOOLEAN => 'integer',
+ FALSE => '0',
+ TRUE => '1',
- INT1 => 'integer',
- INT2 => 'integer',
- INT3 => 'integer',
- INT4 => 'integer',
+ INT1 => 'integer',
+ INT2 => 'integer',
+ INT3 => 'integer',
+ INT4 => 'integer',
- SMALLSERIAL => 'SERIAL',
- MEDIUMSERIAL => 'SERIAL',
- INTSERIAL => 'SERIAL',
+ SMALLSERIAL => 'SERIAL',
+ MEDIUMSERIAL => 'SERIAL',
+ INTSERIAL => 'SERIAL',
- TINYTEXT => 'text',
- MEDIUMTEXT => 'text',
- LONGTEXT => 'text',
+ TINYTEXT => 'text',
+ MEDIUMTEXT => 'text',
+ LONGTEXT => 'text',
- LONGBLOB => 'blob',
+ LONGBLOB => 'blob',
- DATETIME => 'DATETIME',
- DATE => 'DATETIME',
- };
+ DATETIME => 'DATETIME',
+ DATE => 'DATETIME',
+ };
- $self->_adjust_schema;
+ $self->_adjust_schema;
- return $self;
+ return $self;
}
@@ -61,83 +61,86 @@ sub _initialize {
#################################
sub _sqlite_create_table {
- my ($self, $table) = @_;
- return scalar Bugzilla->dbh->selectrow_array(
- "SELECT sql FROM sqlite_master WHERE name = ? AND type = 'table'",
- undef, $table);
+ my ($self, $table) = @_;
+ return
+ scalar Bugzilla->dbh->selectrow_array(
+ "SELECT sql FROM sqlite_master WHERE name = ? AND type = 'table'",
+ undef, $table);
}
sub _sqlite_table_lines {
- my $self = shift;
- my $table_sql = $self->_sqlite_create_table(@_);
- $table_sql =~ s/\n*\)$//s;
- # The $ makes this work even if people some day add crazy stuff to their
- # schema like multi-column foreign keys.
- return split(/,\s*$/m, $table_sql);
+ my $self = shift;
+ my $table_sql = $self->_sqlite_create_table(@_);
+ $table_sql =~ s/\n*\)$//s;
+
+ # The $ makes this work even if people some day add crazy stuff to their
+ # schema like multi-column foreign keys.
+ return split(/,\s*$/m, $table_sql);
}
# This does most of the "heavy lifting" of the schema-altering functions.
sub _sqlite_alter_schema {
- my ($self, $table, $create_table, $options) = @_;
-
- # $create_table is sometimes an array in the form that _sqlite_table_lines
- # returns.
- if (ref $create_table) {
- $create_table = join(',', @$create_table) . "\n)";
- }
-
- my $dbh = Bugzilla->dbh;
-
- my $random = generate_random_password(5);
- my $rename_to = "${table}_$random";
-
- my @columns = $dbh->bz_table_columns_real($table);
- push(@columns, $options->{extra_column}) if $options->{extra_column};
- if (my $exclude = $options->{exclude_column}) {
- @columns = grep { $_ ne $exclude } @columns;
- }
- my @insert_cols = @columns;
- my @select_cols = @columns;
- if (my $rename = $options->{rename}) {
- foreach my $from (keys %$rename) {
- my $to = $rename->{$from};
- @insert_cols = map { $_ eq $from ? $to : $_ } @insert_cols;
- }
+ my ($self, $table, $create_table, $options) = @_;
+
+ # $create_table is sometimes an array in the form that _sqlite_table_lines
+ # returns.
+ if (ref $create_table) {
+ $create_table = join(',', @$create_table) . "\n)";
+ }
+
+ my $dbh = Bugzilla->dbh;
+
+ my $random = generate_random_password(5);
+ my $rename_to = "${table}_$random";
+
+ my @columns = $dbh->bz_table_columns_real($table);
+ push(@columns, $options->{extra_column}) if $options->{extra_column};
+ if (my $exclude = $options->{exclude_column}) {
+ @columns = grep { $_ ne $exclude } @columns;
+ }
+ my @insert_cols = @columns;
+ my @select_cols = @columns;
+ if (my $rename = $options->{rename}) {
+ foreach my $from (keys %$rename) {
+ my $to = $rename->{$from};
+ @insert_cols = map { $_ eq $from ? $to : $_ } @insert_cols;
}
-
- my $insert_str = join(',', @insert_cols);
- my $select_str = join(',', @select_cols);
- my $copy_sql = "INSERT INTO $table ($insert_str)"
- . " SELECT $select_str FROM $rename_to";
-
- # We have to turn FKs off before doing this. Otherwise, when we rename
- # the table, all of the FKs in the other tables will be automatically
- # updated to point to the renamed table. Note that PRAGMA foreign_keys
- # can only be set outside of a transaction--otherwise it is a no-op.
- if ($dbh->bz_in_transaction) {
- die "can't alter the schema inside of a transaction";
- }
- my @sql = (
- 'PRAGMA foreign_keys = OFF',
- 'BEGIN EXCLUSIVE TRANSACTION',
- @{ $options->{pre_sql} || [] },
- "ALTER TABLE $table RENAME TO $rename_to",
- $create_table,
- $copy_sql,
- "DROP TABLE $rename_to",
- 'COMMIT TRANSACTION',
- 'PRAGMA foreign_keys = ON',
- );
+ }
+
+ my $insert_str = join(',', @insert_cols);
+ my $select_str = join(',', @select_cols);
+ my $copy_sql
+ = "INSERT INTO $table ($insert_str)" . " SELECT $select_str FROM $rename_to";
+
+ # We have to turn FKs off before doing this. Otherwise, when we rename
+ # the table, all of the FKs in the other tables will be automatically
+ # updated to point to the renamed table. Note that PRAGMA foreign_keys
+ # can only be set outside of a transaction--otherwise it is a no-op.
+ if ($dbh->bz_in_transaction) {
+ die "can't alter the schema inside of a transaction";
+ }
+ my @sql = (
+ 'PRAGMA foreign_keys = OFF',
+ 'BEGIN EXCLUSIVE TRANSACTION',
+ @{$options->{pre_sql} || []},
+ "ALTER TABLE $table RENAME TO $rename_to",
+ $create_table,
+ $copy_sql,
+ "DROP TABLE $rename_to",
+ 'COMMIT TRANSACTION',
+ 'PRAGMA foreign_keys = ON',
+ );
}
# For finding a particular column's definition in a CREATE TABLE statement.
sub _sqlite_column_regex {
- my ($column) = @_;
- # 1 = Comma at start
- # 2 = Column name + Space
- # 3 = Definition
- # 4 = Ending comma
- return qr/(^|,)(\s\Q$column\E\s+)(.*?)(,|$)/m;
+ my ($column) = @_;
+
+ # 1 = Comma at start
+ # 2 = Column name + Space
+ # 3 = Definition
+ # 4 = Ending comma
+ return qr/(^|,)(\s\Q$column\E\s+)(.*?)(,|$)/m;
}
#############################
@@ -145,133 +148,137 @@ sub _sqlite_column_regex {
#############################
sub get_create_database_sql {
- # If we get here, it means there was some error creating the
- # database file during bz_create_database in Bugzilla::DB,
- # and we just want to display that error instead of doing
- # anything else.
- Bugzilla->dbh;
- die "Reached an unreachable point";
+
+ # If we get here, it means there was some error creating the
+ # database file during bz_create_database in Bugzilla::DB,
+ # and we just want to display that error instead of doing
+ # anything else.
+ Bugzilla->dbh;
+ die "Reached an unreachable point";
}
sub _get_create_table_ddl {
- my $self = shift;
- my ($table) = @_;
- my $ddl = $self->SUPER::_get_create_table_ddl(@_);
-
- # TheSchwartz uses its own driver to access its tables, meaning
- # that it doesn't understand "COLLATE bugzilla" and in fact
- # SQLite throws an error when TheSchwartz tries to access its
- # own tables, if COLLATE bugzilla is on them. We don't have
- # to fix this elsewhere currently, because we only create
- # TheSchwartz's tables, we never modify them.
- if ($table =~ /^ts_/) {
- $ddl =~ s/ COLLATE bugzilla//g;
- }
- return $ddl;
+ my $self = shift;
+ my ($table) = @_;
+ my $ddl = $self->SUPER::_get_create_table_ddl(@_);
+
+ # TheSchwartz uses its own driver to access its tables, meaning
+ # that it doesn't understand "COLLATE bugzilla" and in fact
+ # SQLite throws an error when TheSchwartz tries to access its
+ # own tables, if COLLATE bugzilla is on them. We don't have
+ # to fix this elsewhere currently, because we only create
+ # TheSchwartz's tables, we never modify them.
+ if ($table =~ /^ts_/) {
+ $ddl =~ s/ COLLATE bugzilla//g;
+ }
+ return $ddl;
}
sub get_type_ddl {
- my $self = shift;
- my $def = dclone($_[0]);
-
- my $ddl = $self->SUPER::get_type_ddl(@_);
- if ($def->{PRIMARYKEY} and $def->{TYPE} =~ /SERIAL/i) {
- $ddl =~ s/\bSERIAL\b/integer/;
- $ddl =~ s/\bPRIMARY KEY\b/PRIMARY KEY AUTOINCREMENT/;
- }
- if ($def->{TYPE} =~ /text/i or $def->{TYPE} =~ /char/i) {
- $ddl .= " COLLATE bugzilla";
- }
- # Don't collate DATETIME fields.
- if ($def->{TYPE} eq 'DATETIME') {
- $ddl =~ s/\bDATETIME\b/text COLLATE BINARY/;
- }
- return $ddl;
+ my $self = shift;
+ my $def = dclone($_[0]);
+
+ my $ddl = $self->SUPER::get_type_ddl(@_);
+ if ($def->{PRIMARYKEY} and $def->{TYPE} =~ /SERIAL/i) {
+ $ddl =~ s/\bSERIAL\b/integer/;
+ $ddl =~ s/\bPRIMARY KEY\b/PRIMARY KEY AUTOINCREMENT/;
+ }
+ if ($def->{TYPE} =~ /text/i or $def->{TYPE} =~ /char/i) {
+ $ddl .= " COLLATE bugzilla";
+ }
+
+ # Don't collate DATETIME fields.
+ if ($def->{TYPE} eq 'DATETIME') {
+ $ddl =~ s/\bDATETIME\b/text COLLATE BINARY/;
+ }
+ return $ddl;
}
sub get_alter_column_ddl {
- my $self = shift;
- my ($table, $column, $new_def, $set_nulls_to) = @_;
- my $dbh = Bugzilla->dbh;
-
- my $table_sql = $self->_sqlite_create_table($table);
- my $new_ddl = $self->get_type_ddl($new_def);
- # When we do ADD COLUMN, columns can show up all on one line separated
- # by commas, so we have to account for that.
- my $column_regex = _sqlite_column_regex($column);
- $table_sql =~ s/$column_regex/$1$2$new_ddl$4/
- || die "couldn't find $column in $table:\n$table_sql";
- my @pre_sql = $self->_set_nulls_sql(@_);
- return $self->_sqlite_alter_schema($table, $table_sql,
- { pre_sql => \@pre_sql });
+ my $self = shift;
+ my ($table, $column, $new_def, $set_nulls_to) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $new_ddl = $self->get_type_ddl($new_def);
+
+ # When we do ADD COLUMN, columns can show up all on one line separated
+ # by commas, so we have to account for that.
+ my $column_regex = _sqlite_column_regex($column);
+ $table_sql =~ s/$column_regex/$1$2$new_ddl$4/
+ || die "couldn't find $column in $table:\n$table_sql";
+ my @pre_sql = $self->_set_nulls_sql(@_);
+ return $self->_sqlite_alter_schema($table, $table_sql, {pre_sql => \@pre_sql});
}
sub get_add_column_ddl {
- my $self = shift;
- my ($table, $column, $definition, $init_value) = @_;
- # SQLite can use the normal ADD COLUMN when:
- # * The column isn't a PK
- if ($definition->{PRIMARYKEY}) {
- if ($definition->{NOTNULL} and $definition->{TYPE} !~ /SERIAL/i) {
- die "You can only add new SERIAL type PKs with SQLite";
- }
- my $table_sql = $self->_sqlite_new_column_sql(@_);
- # This works because _sqlite_alter_schema will exclude the new column
- # in its INSERT ... SELECT statement, meaning that when the "new"
- # table is populated, it will have AUTOINCREMENT values generated
- # for it.
- return $self->_sqlite_alter_schema($table, $table_sql);
- }
- # * The column has a default one way or another. Either it
- # defaults to NULL (it lacks NOT NULL) or it has a DEFAULT
- # clause. Since we also require this when doing bz_add_column (in
- # the way of forcing an init_value for NOT NULL columns with no
- # default), we first set the init_value as the default and then
- # alter the column.
- if ($definition->{NOTNULL} and !defined $definition->{DEFAULT}) {
- my %with_default = %$definition;
- $with_default{DEFAULT} = $init_value;
- my @pre_sql =
- $self->SUPER::get_add_column_ddl($table, $column, \%with_default);
- my $table_sql = $self->_sqlite_new_column_sql(@_);
- return $self->_sqlite_alter_schema($table, $table_sql,
- { pre_sql => \@pre_sql, extra_column => $column });
+ my $self = shift;
+ my ($table, $column, $definition, $init_value) = @_;
+
+ # SQLite can use the normal ADD COLUMN when:
+ # * The column isn't a PK
+ if ($definition->{PRIMARYKEY}) {
+ if ($definition->{NOTNULL} and $definition->{TYPE} !~ /SERIAL/i) {
+ die "You can only add new SERIAL type PKs with SQLite";
}
-
- return $self->SUPER::get_add_column_ddl(@_);
+ my $table_sql = $self->_sqlite_new_column_sql(@_);
+
+ # This works because _sqlite_alter_schema will exclude the new column
+ # in its INSERT ... SELECT statement, meaning that when the "new"
+ # table is populated, it will have AUTOINCREMENT values generated
+ # for it.
+ return $self->_sqlite_alter_schema($table, $table_sql);
+ }
+
+ # * The column has a default one way or another. Either it
+ # defaults to NULL (it lacks NOT NULL) or it has a DEFAULT
+ # clause. Since we also require this when doing bz_add_column (in
+ # the way of forcing an init_value for NOT NULL columns with no
+ # default), we first set the init_value as the default and then
+ # alter the column.
+ if ($definition->{NOTNULL} and !defined $definition->{DEFAULT}) {
+ my %with_default = %$definition;
+ $with_default{DEFAULT} = $init_value;
+ my @pre_sql = $self->SUPER::get_add_column_ddl($table, $column, \%with_default);
+ my $table_sql = $self->_sqlite_new_column_sql(@_);
+ return $self->_sqlite_alter_schema($table, $table_sql,
+ {pre_sql => \@pre_sql, extra_column => $column});
+ }
+
+ return $self->SUPER::get_add_column_ddl(@_);
}
sub _sqlite_new_column_sql {
- my ($self, $table, $column, $def) = @_;
- my $table_sql = $self->_sqlite_create_table($table);
- my $new_ddl = $self->get_type_ddl($def);
- my $new_line = "\t$column\t$new_ddl";
- $table_sql =~ s/^(CREATE TABLE \w+ \()/$1\n$new_line,/s
- || die "Can't find start of CREATE TABLE:\n$table_sql";
- return $table_sql;
+ my ($self, $table, $column, $def) = @_;
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $new_ddl = $self->get_type_ddl($def);
+ my $new_line = "\t$column\t$new_ddl";
+ $table_sql =~ s/^(CREATE TABLE \w+ \()/$1\n$new_line,/s
+ || die "Can't find start of CREATE TABLE:\n$table_sql";
+ return $table_sql;
}
sub get_drop_column_ddl {
- my ($self, $table, $column) = @_;
- my $table_sql = $self->_sqlite_create_table($table);
- my $column_regex = _sqlite_column_regex($column);
- $table_sql =~ s/$column_regex/$1/
- || die "Can't find column $column: $table_sql";
- # Make sure we don't end up with a comma at the end of the definition.
- $table_sql =~ s/,\s+\)$/\n)/s;
- return $self->_sqlite_alter_schema($table, $table_sql,
- { exclude_column => $column });
+ my ($self, $table, $column) = @_;
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $column_regex = _sqlite_column_regex($column);
+ $table_sql =~ s/$column_regex/$1/
+ || die "Can't find column $column: $table_sql";
+
+ # Make sure we don't end up with a comma at the end of the definition.
+ $table_sql =~ s/,\s+\)$/\n)/s;
+ return $self->_sqlite_alter_schema($table, $table_sql,
+ {exclude_column => $column});
}
sub get_rename_column_ddl {
- my ($self, $table, $old_name, $new_name) = @_;
- my $table_sql = $self->_sqlite_create_table($table);
- my $column_regex = _sqlite_column_regex($old_name);
- $table_sql =~ s/$column_regex/$1\t$new_name\t$3$4/
- || die "Can't find $old_name: $table_sql";
- my %rename = ($old_name => $new_name);
- return $self->_sqlite_alter_schema($table, $table_sql,
- { rename => \%rename });
+ my ($self, $table, $old_name, $new_name) = @_;
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $column_regex = _sqlite_column_regex($old_name);
+ $table_sql =~ s/$column_regex/$1\t$new_name\t$3$4/
+ || die "Can't find $old_name: $table_sql";
+ my %rename = ($old_name => $new_name);
+ return $self->_sqlite_alter_schema($table, $table_sql, {rename => \%rename});
}
################
@@ -279,24 +286,23 @@ sub get_rename_column_ddl {
################
sub get_add_fks_sql {
- my ($self, $table, $column_fks) = @_;
- my @clauses = $self->_sqlite_table_lines($table);
- my @add = $self->_column_fks_to_ddl($table, $column_fks);
- push(@clauses, @add);
- return $self->_sqlite_alter_schema($table, \@clauses);
+ my ($self, $table, $column_fks) = @_;
+ my @clauses = $self->_sqlite_table_lines($table);
+ my @add = $self->_column_fks_to_ddl($table, $column_fks);
+ push(@clauses, @add);
+ return $self->_sqlite_alter_schema($table, \@clauses);
}
sub get_drop_fk_sql {
- my ($self, $table, $column, $references) = @_;
- my @clauses = $self->_sqlite_table_lines($table);
- my $fk_name = $self->_get_fk_name($table, $column, $references);
-
- my $line_re = qr/^\s+CONSTRAINT $fk_name /s;
- grep { $line_re } @clauses
- or die "Can't find $fk_name: " . join(',', @clauses);
- @clauses = grep { $_ !~ $line_re } @clauses;
-
- return $self->_sqlite_alter_schema($table, \@clauses);
+ my ($self, $table, $column, $references) = @_;
+ my @clauses = $self->_sqlite_table_lines($table);
+ my $fk_name = $self->_get_fk_name($table, $column, $references);
+
+ my $line_re = qr/^\s+CONSTRAINT $fk_name /s;
+ grep {$line_re} @clauses or die "Can't find $fk_name: " . join(',', @clauses);
+ @clauses = grep { $_ !~ $line_re } @clauses;
+
+ return $self->_sqlite_alter_schema($table, \@clauses);
}
diff --git a/Bugzilla/DB/Sqlite.pm b/Bugzilla/DB/Sqlite.pm
index a56ed31ad..c180fd0d7 100644
--- a/Bugzilla/DB/Sqlite.pm
+++ b/Bugzilla/DB/Sqlite.pm
@@ -46,23 +46,23 @@ sub _sqlite_collate_ci { lc($_[0]) cmp lc($_[1]) }
sub _sqlite_mod { $_[0] % $_[1] }
sub _sqlite_now {
- my $now = DateTime->now(time_zone => Bugzilla->local_timezone);
- return $now->ymd . ' ' . $now->hms;
+ my $now = DateTime->now(time_zone => Bugzilla->local_timezone);
+ return $now->ymd . ' ' . $now->hms;
}
# SQL's POSITION starts its values from 1 instead of 0 (so we add 1).
sub _sqlite_position {
- my ($text, $fragment) = @_;
- if (!defined $text or !defined $fragment) {
- return undef;
- }
- my $pos = index $text, $fragment;
- return $pos + 1;
+ my ($text, $fragment) = @_;
+ if (!defined $text or !defined $fragment) {
+ return undef;
+ }
+ my $pos = index $text, $fragment;
+ return $pos + 1;
}
sub _sqlite_position_ci {
- my ($text, $fragment) = @_;
- return _sqlite_position(lc($text), lc($fragment));
+ my ($text, $fragment) = @_;
+ return _sqlite_position(lc($text), lc($fragment));
}
###############
@@ -70,76 +70,84 @@ sub _sqlite_position_ci {
###############
sub new {
- my ($class, $params) = @_;
- my $db_name = $params->{db_name};
-
- # Let people specify paths intead of data/ for the DB.
- if ($db_name and $db_name !~ m{[\\/]}) {
- # When the DB is first created, there's a chance that the
- # data directory doesn't exist at all, because the Install::Filesystem
- # code happens after DB creation. So we create the directory ourselves
- # if it doesn't exist.
- my $datadir = bz_locations()->{datadir};
- if (!-d $datadir) {
- mkdir $datadir or warn "$datadir: $!";
- }
- if (!-d "$datadir/db/") {
- mkdir "$datadir/db/" or warn "$datadir/db: $!";
- }
- $db_name = bz_locations()->{datadir} . "/db/$db_name";
+ my ($class, $params) = @_;
+ my $db_name = $params->{db_name};
+
+ # Let people specify paths intead of data/ for the DB.
+ if ($db_name and $db_name !~ m{[\\/]}) {
+
+ # When the DB is first created, there's a chance that the
+ # data directory doesn't exist at all, because the Install::Filesystem
+ # code happens after DB creation. So we create the directory ourselves
+ # if it doesn't exist.
+ my $datadir = bz_locations()->{datadir};
+ if (!-d $datadir) {
+ mkdir $datadir or warn "$datadir: $!";
}
-
- # construct the DSN from the parameters we got
- my $dsn = "dbi:SQLite:dbname=$db_name";
-
- my $attrs = {
- # XXX Should we just enforce this to be always on?
- sqlite_unicode => Bugzilla->params->{'utf8'},
- };
-
- my $self = $class->db_new({ dsn => $dsn, user => '',
- pass => '', attrs => $attrs });
- # Needed by TheSchwartz
- $self->{private_bz_dsn} = $dsn;
-
- my %pragmas = (
- # Make sure that the sqlite file doesn't grow without bound.
- auto_vacuum => 1,
- encoding => "'UTF-8'",
- foreign_keys => 'ON',
- # We want the latest file format.
- legacy_file_format => 'OFF',
- # This guarantees that we get column names like "foo"
- # instead of "table.foo" in selectrow_hashref.
- short_column_names => 'ON',
- # The write-ahead log mode in SQLite 3.7 gets us better concurrency,
- # but breaks backwards-compatibility with older versions of
- # SQLite. (Which is important because people may also want to use
- # command-line clients to access and back up their DB.) If you need
- # better concurrency and don't need 3.6 compatibility, then you can
- # uncomment this line.
- #journal_mode => "'WAL'",
- );
-
- while (my ($name, $value) = each %pragmas) {
- $self->do("PRAGMA $name = $value");
+ if (!-d "$datadir/db/") {
+ mkdir "$datadir/db/" or warn "$datadir/db: $!";
}
-
- $self->sqlite_create_collation('bugzilla', \&_sqlite_collate_ci);
- $self->sqlite_create_function('position', 2, \&_sqlite_position);
- $self->sqlite_create_function('iposition', 2, \&_sqlite_position_ci);
- # SQLite has a "substr" function, but other DBs call it "SUBSTRING"
- # so that's what we use, and I don't know of any way in SQLite to
- # alias the SQL "substr" function to be called "SUBSTRING".
- $self->sqlite_create_function('substring', 3, \&CORE::substr);
- $self->sqlite_create_function('char_length', 1, sub { length($_[0]) });
- $self->sqlite_create_function('mod', 2, \&_sqlite_mod);
- $self->sqlite_create_function('now', 0, \&_sqlite_now);
- $self->sqlite_create_function('localtimestamp', 1, \&_sqlite_now);
- $self->sqlite_create_function('floor', 1, \&POSIX::floor);
-
- bless ($self, $class);
- return $self;
+ $db_name = bz_locations()->{datadir} . "/db/$db_name";
+ }
+
+ # construct the DSN from the parameters we got
+ my $dsn = "dbi:SQLite:dbname=$db_name";
+
+ my $attrs = {
+
+ # XXX Should we just enforce this to be always on?
+ sqlite_unicode => Bugzilla->params->{'utf8'},
+ };
+
+ my $self
+ = $class->db_new({dsn => $dsn, user => '', pass => '', attrs => $attrs});
+
+ # Needed by TheSchwartz
+ $self->{private_bz_dsn} = $dsn;
+
+ my %pragmas = (
+
+ # Make sure that the sqlite file doesn't grow without bound.
+ auto_vacuum => 1,
+ encoding => "'UTF-8'",
+ foreign_keys => 'ON',
+
+ # We want the latest file format.
+ legacy_file_format => 'OFF',
+
+ # This guarantees that we get column names like "foo"
+ # instead of "table.foo" in selectrow_hashref.
+ short_column_names => 'ON',
+
+ # The write-ahead log mode in SQLite 3.7 gets us better concurrency,
+ # but breaks backwards-compatibility with older versions of
+ # SQLite. (Which is important because people may also want to use
+ # command-line clients to access and back up their DB.) If you need
+ # better concurrency and don't need 3.6 compatibility, then you can
+ # uncomment this line.
+ #journal_mode => "'WAL'",
+ );
+
+ while (my ($name, $value) = each %pragmas) {
+ $self->do("PRAGMA $name = $value");
+ }
+
+ $self->sqlite_create_collation('bugzilla', \&_sqlite_collate_ci);
+ $self->sqlite_create_function('position', 2, \&_sqlite_position);
+ $self->sqlite_create_function('iposition', 2, \&_sqlite_position_ci);
+
+ # SQLite has a "substr" function, but other DBs call it "SUBSTRING"
+ # so that's what we use, and I don't know of any way in SQLite to
+ # alias the SQL "substr" function to be called "SUBSTRING".
+ $self->sqlite_create_function('substring', 3, \&CORE::substr);
+ $self->sqlite_create_function('char_length', 1, sub { length($_[0]) });
+ $self->sqlite_create_function('mod', 2, \&_sqlite_mod);
+ $self->sqlite_create_function('now', 0, \&_sqlite_now);
+ $self->sqlite_create_function('localtimestamp', 1, \&_sqlite_now);
+ $self->sqlite_create_function('floor', 1, \&POSIX::floor);
+
+ bless($self, $class);
+ return $self;
}
###############
@@ -147,86 +155,89 @@ sub new {
###############
sub sql_position {
- my ($self, $fragment, $text) = @_;
- return "POSITION($text, $fragment)";
+ my ($self, $fragment, $text) = @_;
+ return "POSITION($text, $fragment)";
}
sub sql_iposition {
- my ($self, $fragment, $text) = @_;
- return "IPOSITION($text, $fragment)";
+ my ($self, $fragment, $text) = @_;
+ return "IPOSITION($text, $fragment)";
}
# SQLite does not have to GROUP BY the optional columns.
sub sql_group_by {
- my ($self, $needed_columns, $optional_columns) = @_;
- my $expression = "GROUP BY $needed_columns";
- return $expression;
+ my ($self, $needed_columns, $optional_columns) = @_;
+ my $expression = "GROUP BY $needed_columns";
+ return $expression;
}
# XXX SQLite does not support sorting a GROUP_CONCAT, so $sort is unimplemented.
sub sql_group_concat {
- my ($self, $column, $separator, $sort) = @_;
- $separator = $self->quote(', ') if !defined $separator;
- # In SQLite, a GROUP_CONCAT call with a DISTINCT argument can't
- # specify its separator, and has to accept the default of ",".
- if ($column =~ /^DISTINCT/) {
- return "GROUP_CONCAT($column)";
- }
- return "GROUP_CONCAT($column, $separator)";
+ my ($self, $column, $separator, $sort) = @_;
+ $separator = $self->quote(', ') if !defined $separator;
+
+ # In SQLite, a GROUP_CONCAT call with a DISTINCT argument can't
+ # specify its separator, and has to accept the default of ",".
+ if ($column =~ /^DISTINCT/) {
+ return "GROUP_CONCAT($column)";
+ }
+ return "GROUP_CONCAT($column, $separator)";
}
sub sql_istring {
- my ($self, $string) = @_;
- return $string;
+ my ($self, $string) = @_;
+ return $string;
}
sub sql_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "$expr REGEXP $pattern";
+ return "$expr REGEXP $pattern";
}
sub sql_not_regexp {
- my $self = shift;
- my $re_expression = $self->sql_regexp(@_);
- return "NOT($re_expression)";
+ my $self = shift;
+ my $re_expression = $self->sql_regexp(@_);
+ return "NOT($re_expression)";
}
sub sql_limit {
- my ($self, $limit, $offset) = @_;
-
- if (defined($offset)) {
- return "LIMIT $limit OFFSET $offset";
- } else {
- return "LIMIT $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 "DATETIME($days)";
+ my ($self, $days) = @_;
+ return "DATETIME($days)";
}
sub sql_to_days {
- my ($self, $date) = @_;
- return "JULIANDAY($date)";
+ my ($self, $date) = @_;
+ return "JULIANDAY($date)";
}
sub sql_date_format {
- my ($self, $date, $format) = @_;
- $format = "%Y.%m.%d %H:%M:%S" if !$format;
- $format =~ s/\%i/\%M/g;
- $format =~ s/\%s/\%S/g;
- return "STRFTIME(" . $self->quote($format) . ", $date)";
+ my ($self, $date, $format) = @_;
+ $format = "%Y.%m.%d %H:%M:%S" if !$format;
+ $format =~ s/\%i/\%M/g;
+ $format =~ s/\%s/\%S/g;
+ return "STRFTIME(" . $self->quote($format) . ", $date)";
}
sub sql_date_math {
- my ($self, $date, $operator, $interval, $units) = @_;
- # We do the || thing (concatenation) so that placeholders work properly.
- return "DATETIME($date, '$operator' || $interval || ' $units')";
+ my ($self, $date, $operator, $interval, $units) = @_;
+
+ # We do the || thing (concatenation) so that placeholders work properly.
+ return "DATETIME($date, '$operator' || $interval || ' $units')";
}
###############
@@ -234,56 +245,57 @@ sub sql_date_math {
###############
sub bz_setup_database {
- my $self = shift;
- $self->SUPER::bz_setup_database(@_);
-
- # If we created TheSchwartz tables with COLLATE bugzilla (during the
- # 4.1.x development series) re-create them without it.
- my @tables = $self->bz_table_list();
- my @ts_tables = grep { /^ts_/ } @tables;
- my $drop_ok;
- foreach my $table (@ts_tables) {
- my $create_table =
- $self->_bz_real_schema->_sqlite_create_table($table);
- if ($create_table =~ /COLLATE bugzilla/) {
- if (!$drop_ok) {
- _sqlite_jobqueue_drop_message();
- $drop_ok = 1;
- }
- $self->bz_drop_table($table);
- $self->bz_add_table($table);
- }
+ my $self = shift;
+ $self->SUPER::bz_setup_database(@_);
+
+ # If we created TheSchwartz tables with COLLATE bugzilla (during the
+ # 4.1.x development series) re-create them without it.
+ my @tables = $self->bz_table_list();
+ my @ts_tables = grep {/^ts_/} @tables;
+ my $drop_ok;
+ foreach my $table (@ts_tables) {
+ my $create_table = $self->_bz_real_schema->_sqlite_create_table($table);
+ if ($create_table =~ /COLLATE bugzilla/) {
+ if (!$drop_ok) {
+ _sqlite_jobqueue_drop_message();
+ $drop_ok = 1;
+ }
+ $self->bz_drop_table($table);
+ $self->bz_add_table($table);
}
+ }
}
sub _sqlite_jobqueue_drop_message {
- # This is not translated because this situation will only happen if
- # you are updating from a 4.1.x development version of Bugzilla using
- # SQLite, and we don't want to maintain this string in strings.txt.pl
- # forever for just this one uncommon circumstance.
- print <<END;
+
+ # This is not translated because this situation will only happen if
+ # you are updating from a 4.1.x development version of Bugzilla using
+ # SQLite, and we don't want to maintain this string in strings.txt.pl
+ # forever for just this one uncommon circumstance.
+ print <<END;
WARNING: We have to re-create all the database tables used by jobqueue.pl.
If there are any pending jobs in the database (that is, emails that
haven't been sent), they will be deleted.
END
- unless (Bugzilla->installation_answers->{NO_PAUSE}) {
- print install_string('enter_or_ctrl_c');
- getc;
- }
+ unless (Bugzilla->installation_answers->{NO_PAUSE}) {
+ print install_string('enter_or_ctrl_c');
+ getc;
+ }
}
# XXX This needs to be implemented.
sub bz_explain { }
sub bz_table_list_real {
- my $self = shift;
- my @tables = $self->SUPER::bz_table_list_real(@_);
- # SQLite includes a sqlite_sequence table in every database that isn't
- # one of our real tables. We exclude any table that starts with sqlite_,
- # just to be safe.
- @tables = grep { $_ !~ /^sqlite_/ } @tables;
- return @tables;
+ my $self = shift;
+ my @tables = $self->SUPER::bz_table_list_real(@_);
+
+ # SQLite includes a sqlite_sequence table in every database that isn't
+ # one of our real tables. We exclude any table that starts with sqlite_,
+ # just to be safe.
+ @tables = grep { $_ !~ /^sqlite_/ } @tables;
+ return @tables;
}
1;
diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm
index ef6320d15..ac56b9b02 100644
--- a/Bugzilla/Error.pm
+++ b/Bugzilla/Error.pm
@@ -26,176 +26,185 @@ 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;
+ 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 Bugzilla::Util;
- require Data::Dumper;
- my $mesg = "";
- for (1..75) { $mesg .= "-"; };
- $mesg .= "\n[$$] " . time2str("%D %H:%M:%S ", time());
- $mesg .= "$name $error ";
- $mesg .= Bugzilla::Util::remote_ip();
- $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;
- my $message;
- # There are some tests that throw and catch a lot of errors,
- # and calling $template->process over and over for those errors
- # is too slow. So instead, we just "die" with a dump of the arguments.
- if (Bugzilla->error_mode != ERROR_MODE_TEST) {
- $template->process($name, $vars, \$message)
- || ThrowTemplateError($template->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 Bugzilla::Util;
+ require Data::Dumper;
+ my $mesg = "";
+ for (1 .. 75) { $mesg .= "-"; }
+ $mesg .= "\n[$$] " . time2str("%D %H:%M:%S ", time());
+ $mesg .= "$name $error ";
+ $mesg .= Bugzilla::Util::remote_ip();
+ $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)"]);
}
-
- # Let's call the hook first, so that extensions can override
- # or extend the default behavior, or add their own error codes.
- Bugzilla::Hook::process('error_catch', { error => $error, vars => $vars,
- message => \$message });
-
- if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
- my $cgi = Bugzilla->cgi;
- $cgi->close_standby_message('text/html', 'inline', 'error', 'html');
- print $message;
- print $cgi->multipart_final() if $cgi->{_multipart_in_progress};
+ for my $var (sort keys %ENV) {
+ my $val = $ENV{$var};
+ $val = "*****" if $val =~ /password|http_pass/i;
+ $mesg .= "[$$] " . Data::Dumper->Dump([$val], ["env($var)"]);
}
- elsif (Bugzilla->error_mode == ERROR_MODE_TEST) {
- die Dumper($vars);
+ open(ERRORLOGFID, ">>", "$datadir/errorlog");
+ print ERRORLOGFID "$mesg\n";
+ close ERRORLOGFID;
+ }
+
+ my $template = Bugzilla->template;
+ my $message;
+
+ # There are some tests that throw and catch a lot of errors,
+ # and calling $template->process over and over for those errors
+ # is too slow. So instead, we just "die" with a dump of the arguments.
+ if (Bugzilla->error_mode != ERROR_MODE_TEST) {
+ $template->process($name, $vars, \$message)
+ || ThrowTemplateError($template->error());
+ }
+
+ # Let's call the hook first, so that extensions can override
+ # or extend the default behavior, or add their own error codes.
+ Bugzilla::Hook::process('error_catch',
+ {error => $error, vars => $vars, message => \$message});
+
+ if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
+ my $cgi = Bugzilla->cgi;
+ $cgi->close_standby_message('text/html', 'inline', 'error', 'html');
+ print $message;
+ print $cgi->multipart_final() if $cgi->{_multipart_in_progress};
+ }
+ elsif (Bugzilla->error_mode == ERROR_MODE_TEST) {
+ die Dumper($vars);
+ }
+ elsif (Bugzilla->error_mode == ERROR_MODE_DIE) {
+ die("$message\n");
+ }
+ elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT
+ || Bugzilla->error_mode == ERROR_MODE_JSON_RPC
+ || Bugzilla->error_mode == ERROR_MODE_REST)
+ {
+ # Clone the hash so we aren't modifying the constant.
+ my %error_map = %{WS_ERROR_CODE()};
+ 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;
}
- elsif (Bugzilla->error_mode == ERROR_MODE_DIE) {
- die("$message\n");
+
+ if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
+ die SOAP::Fault->faultcode($code)->faultstring($message);
}
- elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT
- || Bugzilla->error_mode == ERROR_MODE_JSON_RPC
- || Bugzilla->error_mode == ERROR_MODE_REST)
- {
- # Clone the hash so we aren't modifying the constant.
- my %error_map = %{ WS_ERROR_CODE() };
- 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;
- }
-
- if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
- die SOAP::Fault->faultcode($code)->faultstring($message);
- }
- else {
- my $server = Bugzilla->_json_server;
-
- my $status_code = 0;
- if (Bugzilla->error_mode == ERROR_MODE_REST) {
- my %status_code_map = %{ REST_STATUS_CODE_MAP() };
- $status_code = $status_code_map{$code} || $status_code_map{'_default'};
- }
- # Technically JSON-RPC isn't allowed to have error numbers
- # higher than 999, but we do this to avoid conflicts with
- # the internal JSON::RPC error codes.
- $server->raise_error(code => 100000 + $code,
- status_code => $status_code,
- message => $message,
- id => $server->{_bz_request_id},
- version => $server->version);
- # Most JSON-RPC Throw*Error calls happen within an eval inside
- # of JSON::RPC. So, in that circumstance, instead of exiting,
- # we die with no message. JSON::RPC checks raise_error before
- # it checks $@, so it returns the proper error.
- die if _in_eval();
- $server->response($server->error_response_header);
- }
+ else {
+ my $server = Bugzilla->_json_server;
+
+ my $status_code = 0;
+ if (Bugzilla->error_mode == ERROR_MODE_REST) {
+ my %status_code_map = %{REST_STATUS_CODE_MAP()};
+ $status_code = $status_code_map{$code} || $status_code_map{'_default'};
+ }
+
+ # Technically JSON-RPC isn't allowed to have error numbers
+ # higher than 999, but we do this to avoid conflicts with
+ # the internal JSON::RPC error codes.
+ $server->raise_error(
+ code => 100000 + $code,
+ status_code => $status_code,
+ message => $message,
+ id => $server->{_bz_request_id},
+ version => $server->version
+ );
+
+ # Most JSON-RPC Throw*Error calls happen within an eval inside
+ # of JSON::RPC. So, in that circumstance, instead of exiting,
+ # we die with no message. JSON::RPC checks raise_error before
+ # it checks $@, so it returns the proper error.
+ die if _in_eval();
+ $server->response($server->error_response_header);
}
- exit;
+ }
+ exit;
}
sub ThrowUserError {
- _throw_error("global/user-error.html.tmpl", @_);
+ _throw_error("global/user-error.html.tmpl", @_);
}
sub ThrowCodeError {
- my (undef, $vars) = @_;
+ my (undef, $vars) = @_;
+
+ # Don't show function arguments, in case they contain
+ # confidential data.
+ local $Carp::MaxArgNums = -1;
- # Don't show function arguments, in case they contain
- # confidential data.
- local $Carp::MaxArgNums = -1;
- # Don't show the error as coming from Bugzilla::Error, show it
- # as coming from the caller.
- local $Carp::CarpInternal{'Bugzilla::Error'} = 1;
- $vars->{traceback} = Carp::longmess();
+ # Don't show the error as coming from Bugzilla::Error, show it
+ # as coming from the caller.
+ local $Carp::CarpInternal{'Bugzilla::Error'} = 1;
+ $vars->{traceback} = Carp::longmess();
- _throw_error("global/code-error.html.tmpl", @_);
+ _throw_error("global/code-error.html.tmpl", @_);
}
sub ThrowTemplateError {
- my ($template_err) = @_;
- my $dbh = Bugzilla->dbh;
+ 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();
+ # 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");
- }
+ 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";
+ $vars->{'template_error_msg'} = $template_err;
+ $vars->{'error'} = "template_error";
- my $template = Bugzilla->template;
+ 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)) {
- require Bugzilla::Util;
- import Bugzilla::Util qw(html_quote);
- my $maintainer = Bugzilla->params->{'maintainer'};
- my $error = html_quote($vars->{'template_error_msg'});
- my $error2 = html_quote($template->error());
- my $url = html_quote(Bugzilla->cgi->self_url);
+ # 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)) {
+ require Bugzilla::Util;
+ import Bugzilla::Util qw(html_quote);
+ my $maintainer = Bugzilla->params->{'maintainer'};
+ my $error = html_quote($vars->{'template_error_msg'});
+ my $error2 = html_quote($template->error());
+ my $url = html_quote(Bugzilla->cgi->self_url);
- print <<END;
+ print <<END;
<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
@@ -206,8 +215,8 @@ sub ThrowTemplateError {
First error: $error<br>
Second error: $error2</p>
END
- }
- exit;
+ }
+ exit;
}
1;
diff --git a/Bugzilla/Extension.pm b/Bugzilla/Extension.pm
index e24ceb9eb..a5522583e 100644
--- a/Bugzilla/Extension.pm
+++ b/Bugzilla/Extension.pm
@@ -14,8 +14,8 @@ use warnings;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Install::Util qw(
- extension_code_files extension_template_directory
- extension_package_directory extension_web_directory);
+ extension_code_files extension_template_directory
+ extension_package_directory extension_web_directory);
use File::Basename;
use File::Spec;
@@ -25,10 +25,10 @@ use File::Spec;
####################
sub new {
- my ($class, $params) = @_;
- $params ||= {};
- bless $params, $class;
- return $params;
+ my ($class, $params) = @_;
+ $params ||= {};
+ bless $params, $class;
+ return $params;
}
#######################################
@@ -36,148 +36,151 @@ sub new {
#######################################
sub load {
- my ($class, $extension_file, $config_file) = @_;
- my $package;
-
- # This is needed during checksetup.pl, because Extension packages can
- # only be loaded once (they return "1" the second time they're loaded,
- # instead of their name). During checksetup.pl, extensions are loaded
- # once by Bugzilla::Install::Requirements, and then later again via
- # Bugzilla->extensions (because of hooks).
- my $map = Bugzilla->request_cache->{extension_requirement_package_map};
-
- if ($config_file) {
- if ($map and defined $map->{$config_file}) {
- $package = $map->{$config_file};
- }
- else {
- my $name = require $config_file;
- if ($name =~ /^\d+$/) {
- ThrowCodeError('extension_must_return_name',
- { extension => $config_file,
- returned => $name });
- }
- $package = "${class}::$name";
- }
-
- __do_call($package, 'modify_inc', $config_file);
- }
-
- if ($map and defined $map->{$extension_file}) {
- $package = $map->{$extension_file};
- $package->modify_inc($extension_file) if !$config_file;
+ my ($class, $extension_file, $config_file) = @_;
+ my $package;
+
+ # This is needed during checksetup.pl, because Extension packages can
+ # only be loaded once (they return "1" the second time they're loaded,
+ # instead of their name). During checksetup.pl, extensions are loaded
+ # once by Bugzilla::Install::Requirements, and then later again via
+ # Bugzilla->extensions (because of hooks).
+ my $map = Bugzilla->request_cache->{extension_requirement_package_map};
+
+ if ($config_file) {
+ if ($map and defined $map->{$config_file}) {
+ $package = $map->{$config_file};
}
else {
- my $name = require $extension_file;
- if ($name =~ /^\d+$/) {
- ThrowCodeError('extension_must_return_name',
- { extension => $extension_file, returned => $name });
- }
- $package = "${class}::$name";
- $package->modify_inc($extension_file) if !$config_file;
+ my $name = require $config_file;
+ if ($name =~ /^\d+$/) {
+ ThrowCodeError('extension_must_return_name',
+ {extension => $config_file, returned => $name});
+ }
+ $package = "${class}::$name";
}
- $class->_validate_package($package, $extension_file);
- return $package;
-}
-
-sub _validate_package {
- my ($class, $package, $extension_file) = @_;
-
- # For extensions from data/extensions/additional, we don't have a file
- # name, so we fake it.
- if (!$extension_file) {
- $extension_file = $package;
- $extension_file =~ s/::/\//g;
- $extension_file .= '.pm';
+ __do_call($package, 'modify_inc', $config_file);
+ }
+
+ if ($map and defined $map->{$extension_file}) {
+ $package = $map->{$extension_file};
+ $package->modify_inc($extension_file) if !$config_file;
+ }
+ else {
+ my $name = require $extension_file;
+ if ($name =~ /^\d+$/) {
+ ThrowCodeError('extension_must_return_name',
+ {extension => $extension_file, returned => $name});
}
+ $package = "${class}::$name";
+ $package->modify_inc($extension_file) if !$config_file;
+ }
- if (!eval { $package->NAME }) {
- ThrowCodeError('extension_no_name',
- { filename => $extension_file, package => $package });
- }
+ $class->_validate_package($package, $extension_file);
+ return $package;
+}
- if (!$package->isa($class)) {
- ThrowCodeError('extension_must_be_subclass',
- { filename => $extension_file,
- package => $package,
- class => $class });
- }
+sub _validate_package {
+ my ($class, $package, $extension_file) = @_;
+
+ # For extensions from data/extensions/additional, we don't have a file
+ # name, so we fake it.
+ if (!$extension_file) {
+ $extension_file = $package;
+ $extension_file =~ s/::/\//g;
+ $extension_file .= '.pm';
+ }
+
+ if (!eval { $package->NAME }) {
+ ThrowCodeError('extension_no_name',
+ {filename => $extension_file, package => $package});
+ }
+
+ if (!$package->isa($class)) {
+ ThrowCodeError('extension_must_be_subclass',
+ {filename => $extension_file, package => $package, class => $class});
+ }
}
sub load_all {
- my $class = shift;
- my ($file_sets, $extra_packages) = extension_code_files();
- my @packages;
- foreach my $file_set (@$file_sets) {
- my $package = $class->load(@$file_set);
- push(@packages, $package);
- }
-
- # Extensions from data/extensions/additional
- foreach my $package (@$extra_packages) {
- # Don't load an "additional" extension if we already have an extension
- # loaded with that name.
- next if grep($_ eq $package, @packages);
- # Untaint the package name
- $package =~ /([\w:]+)/;
- $package = $1;
- eval("require $package") || die $@;
- $package->_validate_package($package);
- push(@packages, $package);
- }
-
- return \@packages;
+ my $class = shift;
+ my ($file_sets, $extra_packages) = extension_code_files();
+ my @packages;
+ foreach my $file_set (@$file_sets) {
+ my $package = $class->load(@$file_set);
+ push(@packages, $package);
+ }
+
+ # Extensions from data/extensions/additional
+ foreach my $package (@$extra_packages) {
+
+ # Don't load an "additional" extension if we already have an extension
+ # loaded with that name.
+ next if grep($_ eq $package, @packages);
+
+ # Untaint the package name
+ $package =~ /([\w:]+)/;
+ $package = $1;
+ eval("require $package") || die $@;
+ $package->_validate_package($package);
+ push(@packages, $package);
+ }
+
+ return \@packages;
}
# Modifies @INC so that extensions can use modules like
# "use Bugzilla::Extension::Foo::Bar", when Bar.pm is in the lib/
# directory of the extension.
sub modify_inc {
- my ($class, $file) = @_;
-
- # Note that this package_dir call is necessary to set things up
- # for my_inc, even if we didn't take its return value.
- my $package_dir = __do_call($class, 'package_dir', $file);
- # Don't modify @INC for extensions that are just files in the extensions/
- # directory. We don't want Bugzilla's base lib/CGI.pm being loaded as
- # Bugzilla::Extension::Foo::CGI or any other confusing thing like that.
- return if $package_dir eq bz_locations->{'extensionsdir'};
- unshift(@INC, sub { __do_call($class, 'my_inc', @_) });
+ my ($class, $file) = @_;
+
+ # Note that this package_dir call is necessary to set things up
+ # for my_inc, even if we didn't take its return value.
+ my $package_dir = __do_call($class, 'package_dir', $file);
+
+ # Don't modify @INC for extensions that are just files in the extensions/
+ # directory. We don't want Bugzilla's base lib/CGI.pm being loaded as
+ # Bugzilla::Extension::Foo::CGI or any other confusing thing like that.
+ return if $package_dir eq bz_locations->{'extensionsdir'};
+ unshift(@INC, sub { __do_call($class, 'my_inc', @_) });
}
# This is what gets put into @INC by modify_inc.
sub my_inc {
- my ($class, undef, $file) = @_;
-
- # This avoids infinite recursion in case anything inside of this function
- # does a "require". (I know for sure that File::Spec->case_tolerant does
- # a "require" on Windows, for example.)
- return if $file !~ /^Bugzilla/;
-
- my $lib_dir = __do_call($class, 'lib_dir');
- my @class_parts = split('::', $class);
- my ($vol, $dir, $file_name) = File::Spec->splitpath($file);
- my @dir_parts = File::Spec->splitdir($dir);
- # File::Spec::Win32 (any maybe other OSes) add an empty directory at the
- # end of @dir_parts.
- @dir_parts = grep { $_ ne '' } @dir_parts;
- # Validate that this is a sub-package of Bugzilla::Extension::Foo ($class).
- for (my $i = 0; $i < scalar(@class_parts); $i++) {
- return if !@dir_parts;
- if (File::Spec->case_tolerant) {
- return if lc($class_parts[$i]) ne lc($dir_parts[0]);
- }
- else {
- return if $class_parts[$i] ne $dir_parts[0];
- }
- shift(@dir_parts);
+ my ($class, undef, $file) = @_;
+
+ # This avoids infinite recursion in case anything inside of this function
+ # does a "require". (I know for sure that File::Spec->case_tolerant does
+ # a "require" on Windows, for example.)
+ return if $file !~ /^Bugzilla/;
+
+ my $lib_dir = __do_call($class, 'lib_dir');
+ my @class_parts = split('::', $class);
+ my ($vol, $dir, $file_name) = File::Spec->splitpath($file);
+ my @dir_parts = File::Spec->splitdir($dir);
+
+ # File::Spec::Win32 (any maybe other OSes) add an empty directory at the
+ # end of @dir_parts.
+ @dir_parts = grep { $_ ne '' } @dir_parts;
+
+ # Validate that this is a sub-package of Bugzilla::Extension::Foo ($class).
+ for (my $i = 0; $i < scalar(@class_parts); $i++) {
+ return if !@dir_parts;
+ if (File::Spec->case_tolerant) {
+ return if lc($class_parts[$i]) ne lc($dir_parts[0]);
}
- # For Bugzilla::Extension::Foo::Bar, this would look something like
- # extensions/Example/lib/Bar.pm
- my $resolved_path = File::Spec->catfile($lib_dir, @dir_parts, $file_name);
- open(my $fh, '<', $resolved_path);
- return $fh;
+ else {
+ return if $class_parts[$i] ne $dir_parts[0];
+ }
+ shift(@dir_parts);
+ }
+
+ # For Bugzilla::Extension::Foo::Bar, this would look something like
+ # extensions/Example/lib/Bar.pm
+ my $resolved_path = File::Spec->catfile($lib_dir, @dir_parts, $file_name);
+ open(my $fh, '<', $resolved_path);
+ return $fh;
}
####################
@@ -187,23 +190,24 @@ sub my_inc {
use constant enabled => 1;
sub lib_dir {
- my $invocant = shift;
- my $package_dir = __do_call($invocant, 'package_dir');
- # For extensions that are just files in the extensions/ directory,
- # use the base lib/ dir as our "lib_dir". Note that Bugzilla never
- # uses lib_dir in this case, though, because modify_inc is prevented
- # from modifying @INC when we're just a file in the extensions/ directory.
- # So this particular code block exists just to make lib_dir return
- # something right in case an extension needs it for some odd reason.
- if ($package_dir eq bz_locations()->{'extensionsdir'}) {
- return bz_locations->{'ext_libpath'};
- }
- return File::Spec->catdir($package_dir, 'lib');
+ my $invocant = shift;
+ my $package_dir = __do_call($invocant, 'package_dir');
+
+ # For extensions that are just files in the extensions/ directory,
+ # use the base lib/ dir as our "lib_dir". Note that Bugzilla never
+ # uses lib_dir in this case, though, because modify_inc is prevented
+ # from modifying @INC when we're just a file in the extensions/ directory.
+ # So this particular code block exists just to make lib_dir return
+ # something right in case an extension needs it for some odd reason.
+ if ($package_dir eq bz_locations()->{'extensionsdir'}) {
+ return bz_locations->{'ext_libpath'};
+ }
+ return File::Spec->catdir($package_dir, 'lib');
}
sub template_dir { return extension_template_directory(@_); }
-sub package_dir { return extension_package_directory(@_); }
-sub web_dir { return extension_web_directory(@_); }
+sub package_dir { return extension_package_directory(@_); }
+sub web_dir { return extension_web_directory(@_); }
######################
# Helper Subroutines #
@@ -217,13 +221,13 @@ sub web_dir { return extension_web_directory(@_); }
# the method. This is necessary because Config.pm is not a subclass of
# Bugzilla::Extension.
sub __do_call {
- my ($class, $method, @args) = @_;
- if ($class->can($method)) {
- return $class->$method(@args);
- }
- my $function_ref;
- { no strict 'refs'; $function_ref = \&{$method}; }
- return $function_ref->($class, @args);
+ my ($class, $method, @args) = @_;
+ if ($class->can($method)) {
+ return $class->$method(@args);
+ }
+ my $function_ref;
+ { no strict 'refs'; $function_ref = \&{$method}; }
+ return $function_ref->($class, @args);
}
1;
diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm
index 761f7b94e..3f587b05f 100644
--- a/Bugzilla/Field.pm
+++ b/Bugzilla/Field.pm
@@ -81,82 +81,80 @@ use constant DB_TABLE => 'fielddefs';
use constant LIST_ORDER => 'sortkey, name';
use constant DB_COLUMNS => qw(
- id
- name
- description
- long_desc
- type
- custom
- mailhead
- sortkey
- obsolete
- enter_bug
- buglist
- visibility_field_id
- value_field_id
- reverse_desc
- is_mandatory
- is_numeric
+ id
+ name
+ description
+ long_desc
+ type
+ custom
+ mailhead
+ sortkey
+ obsolete
+ enter_bug
+ buglist
+ visibility_field_id
+ value_field_id
+ reverse_desc
+ is_mandatory
+ is_numeric
);
use constant VALIDATORS => {
- custom => \&_check_custom,
- description => \&_check_description,
- long_desc => \&_check_long_desc,
- enter_bug => \&_check_enter_bug,
- buglist => \&Bugzilla::Object::check_boolean,
- mailhead => \&_check_mailhead,
- name => \&_check_name,
- obsolete => \&_check_obsolete,
- reverse_desc => \&_check_reverse_desc,
- sortkey => \&_check_sortkey,
- type => \&_check_type,
- value_field_id => \&_check_value_field_id,
- visibility_field_id => \&_check_visibility_field_id,
- visibility_values => \&_check_visibility_values,
- is_mandatory => \&Bugzilla::Object::check_boolean,
- is_numeric => \&_check_is_numeric,
+ custom => \&_check_custom,
+ description => \&_check_description,
+ long_desc => \&_check_long_desc,
+ enter_bug => \&_check_enter_bug,
+ buglist => \&Bugzilla::Object::check_boolean,
+ mailhead => \&_check_mailhead,
+ name => \&_check_name,
+ obsolete => \&_check_obsolete,
+ reverse_desc => \&_check_reverse_desc,
+ sortkey => \&_check_sortkey,
+ type => \&_check_type,
+ value_field_id => \&_check_value_field_id,
+ visibility_field_id => \&_check_visibility_field_id,
+ visibility_values => \&_check_visibility_values,
+ is_mandatory => \&Bugzilla::Object::check_boolean,
+ is_numeric => \&_check_is_numeric,
};
use constant VALIDATOR_DEPENDENCIES => {
- is_numeric => ['type'],
- name => ['custom'],
- type => ['custom'],
- reverse_desc => ['type'],
- value_field_id => ['type'],
- visibility_values => ['visibility_field_id'],
+ is_numeric => ['type'],
+ name => ['custom'],
+ type => ['custom'],
+ reverse_desc => ['type'],
+ value_field_id => ['type'],
+ visibility_values => ['visibility_field_id'],
};
use constant UPDATE_COLUMNS => qw(
- description
- long_desc
- mailhead
- sortkey
- obsolete
- enter_bug
- buglist
- visibility_field_id
- value_field_id
- reverse_desc
- is_mandatory
- is_numeric
- type
+ description
+ long_desc
+ mailhead
+ sortkey
+ obsolete
+ enter_bug
+ buglist
+ visibility_field_id
+ value_field_id
+ reverse_desc
+ is_mandatory
+ is_numeric
+ type
);
# 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)',
- NOTNULL => 1, DEFAULT => "''"},
- FIELD_TYPE_SINGLE_SELECT, { TYPE => 'varchar(64)', NOTNULL => 1,
- DEFAULT => "'---'" },
- FIELD_TYPE_TEXTAREA, { TYPE => 'MEDIUMTEXT',
- NOTNULL => 1, DEFAULT => "''"},
- FIELD_TYPE_DATETIME, { TYPE => 'DATETIME' },
- FIELD_TYPE_DATE, { TYPE => 'DATE' },
- FIELD_TYPE_BUG_ID, { TYPE => 'INT3' },
- FIELD_TYPE_INTEGER, { TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0 },
+
+ # Using commas because these are constants and they shouldn't
+ # be auto-quoted by the "=>" operator.
+ FIELD_TYPE_FREETEXT, {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"},
+ FIELD_TYPE_SINGLE_SELECT,
+ {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "'---'"}, FIELD_TYPE_TEXTAREA,
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}, FIELD_TYPE_DATETIME,
+ {TYPE => 'DATETIME'}, FIELD_TYPE_DATE, {TYPE => 'DATE'}, FIELD_TYPE_BUG_ID,
+ {TYPE => 'INT3'}, FIELD_TYPE_INTEGER,
+ {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
};
# Field definitions for the fields that ship with Bugzilla.
@@ -164,110 +162,232 @@ use constant SQL_DEFINITIONS => {
# the fielddefs table.
# 'days_elapsed' is set in populate_field_definitions() itself.
use constant DEFAULT_FIELDS => (
- {name => 'bug_id', desc => 'Bug #', in_new_bugmail => 1,
- buglist => 1, is_numeric => 1},
- {name => 'short_desc', desc => 'Summary', in_new_bugmail => 1,
- is_mandatory => 1, buglist => 1},
- {name => 'classification', desc => 'Classification', in_new_bugmail => 1,
- type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
- {name => 'product', desc => 'Product', in_new_bugmail => 1,
- is_mandatory => 1,
- type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
- {name => 'version', desc => 'Version', in_new_bugmail => 1,
- is_mandatory => 1, buglist => 1},
- {name => 'rep_platform', desc => 'Platform', in_new_bugmail => 1,
- type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
- {name => 'bug_file_loc', desc => 'URL', in_new_bugmail => 1,
- buglist => 1},
- {name => 'op_sys', desc => 'OS/Version', in_new_bugmail => 1,
- type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
- {name => 'bug_status', desc => 'Status', in_new_bugmail => 1,
- type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
- {name => 'status_whiteboard', desc => 'Status Whiteboard',
- in_new_bugmail => 1, buglist => 1},
- {name => 'keywords', desc => 'Keywords', in_new_bugmail => 1,
- type => FIELD_TYPE_KEYWORDS, buglist => 1},
- {name => 'resolution', desc => 'Resolution',
- type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
- {name => 'bug_severity', desc => 'Severity', in_new_bugmail => 1,
- type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
- {name => 'priority', desc => 'Priority', in_new_bugmail => 1,
- type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
- {name => 'component', desc => 'Component', in_new_bugmail => 1,
- is_mandatory => 1,
- type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
- {name => 'assigned_to', desc => 'AssignedTo', in_new_bugmail => 1,
- buglist => 1},
- {name => 'reporter', desc => 'ReportedBy', in_new_bugmail => 1,
- buglist => 1},
- {name => 'qa_contact', desc => 'QAContact', in_new_bugmail => 1,
- buglist => 1},
- {name => 'assigned_to_realname', desc => 'AssignedToName',
- in_new_bugmail => 0, buglist => 1},
- {name => 'reporter_realname', desc => 'ReportedByName',
- in_new_bugmail => 0, buglist => 1},
- {name => 'qa_contact_realname', desc => 'QAContactName',
- in_new_bugmail => 0, buglist => 1},
- {name => 'cc', desc => 'CC', in_new_bugmail => 1},
- {name => 'dependson', desc => 'Depends on', in_new_bugmail => 1,
- is_numeric => 1, buglist => 1},
- {name => 'blocked', desc => 'Blocks', in_new_bugmail => 1,
- is_numeric => 1, buglist => 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',
- is_numeric => 1},
- {name => 'attachments.isobsolete', desc => 'Attachment is obsolete',
- is_numeric => 1},
- {name => 'attachments.isprivate', desc => 'Attachment is private',
- is_numeric => 1},
- {name => 'attachments.submitter', desc => 'Attachment creator'},
-
- {name => 'target_milestone', desc => 'Target Milestone',
- in_new_bugmail => 1, buglist => 1},
- {name => 'creation_ts', desc => 'Creation date',
- buglist => 1},
- {name => 'delta_ts', desc => 'Last changed date',
- buglist => 1},
- {name => 'longdesc', desc => 'Comment'},
- {name => 'longdescs.isprivate', desc => 'Comment is private',
- is_numeric => 1},
- {name => 'longdescs.count', desc => 'Number of Comments',
- buglist => 1, is_numeric => 1},
- {name => 'alias', desc => 'Alias', buglist => 1},
- {name => 'everconfirmed', desc => 'Ever Confirmed',
- is_numeric => 1},
- {name => 'reporter_accessible', desc => 'Reporter Accessible',
- is_numeric => 1},
- {name => 'cclist_accessible', desc => 'CC Accessible',
- is_numeric => 1},
- {name => 'bug_group', desc => 'Group', in_new_bugmail => 1},
- {name => 'estimated_time', desc => 'Estimated Hours',
- in_new_bugmail => 1, buglist => 1, is_numeric => 1},
- {name => 'remaining_time', desc => 'Remaining Hours', buglist => 1,
- is_numeric => 1},
- {name => 'deadline', desc => 'Deadline',
- type => FIELD_TYPE_DATETIME, in_new_bugmail => 1, buglist => 1},
- {name => 'commenter', desc => 'Commenter'},
- {name => 'flagtypes.name', desc => 'Flags', buglist => 1},
- {name => 'requestees.login_name', desc => 'Flag Requestee'},
- {name => 'setters.login_name', desc => 'Flag Setter'},
- {name => 'work_time', desc => 'Hours Worked', buglist => 1,
- is_numeric => 1},
- {name => 'percentage_complete', desc => 'Percentage Complete',
- buglist => 1, is_numeric => 1},
- {name => 'content', desc => 'Content'},
- {name => 'attach_data.thedata', desc => 'Attachment data'},
- {name => "owner_idle_time", desc => "Time Since Assignee Touched"},
- {name => 'see_also', desc => "See Also",
- type => FIELD_TYPE_BUG_URLS},
- {name => 'tag', desc => 'Personal Tags', buglist => 1,
- type => FIELD_TYPE_KEYWORDS},
- {name => 'last_visit_ts', desc => 'Last Visit', buglist => 1,
- type => FIELD_TYPE_DATETIME},
- {name => 'comment_tag', desc => 'Comment Tag'},
+ {
+ name => 'bug_id',
+ desc => 'Bug #',
+ in_new_bugmail => 1,
+ buglist => 1,
+ is_numeric => 1
+ },
+ {
+ name => 'short_desc',
+ desc => 'Summary',
+ in_new_bugmail => 1,
+ is_mandatory => 1,
+ buglist => 1
+ },
+ {
+ name => 'classification',
+ desc => 'Classification',
+ in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT,
+ buglist => 1
+ },
+ {
+ name => 'product',
+ desc => 'Product',
+ in_new_bugmail => 1,
+ is_mandatory => 1,
+ type => FIELD_TYPE_SINGLE_SELECT,
+ buglist => 1
+ },
+ {
+ name => 'version',
+ desc => 'Version',
+ in_new_bugmail => 1,
+ is_mandatory => 1,
+ buglist => 1
+ },
+ {
+ name => 'rep_platform',
+ desc => 'Platform',
+ in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT,
+ buglist => 1
+ },
+ {name => 'bug_file_loc', desc => 'URL', in_new_bugmail => 1, buglist => 1},
+ {
+ name => 'op_sys',
+ desc => 'OS/Version',
+ in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT,
+ buglist => 1
+ },
+ {
+ name => 'bug_status',
+ desc => 'Status',
+ in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT,
+ buglist => 1
+ },
+ {
+ name => 'status_whiteboard',
+ desc => 'Status Whiteboard',
+ in_new_bugmail => 1,
+ buglist => 1
+ },
+ {
+ name => 'keywords',
+ desc => 'Keywords',
+ in_new_bugmail => 1,
+ type => FIELD_TYPE_KEYWORDS,
+ buglist => 1
+ },
+ {
+ name => 'resolution',
+ desc => 'Resolution',
+ type => FIELD_TYPE_SINGLE_SELECT,
+ buglist => 1
+ },
+ {
+ name => 'bug_severity',
+ desc => 'Severity',
+ in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT,
+ buglist => 1
+ },
+ {
+ name => 'priority',
+ desc => 'Priority',
+ in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT,
+ buglist => 1
+ },
+ {
+ name => 'component',
+ desc => 'Component',
+ in_new_bugmail => 1,
+ is_mandatory => 1,
+ type => FIELD_TYPE_SINGLE_SELECT,
+ buglist => 1
+ },
+ {
+ name => 'assigned_to',
+ desc => 'AssignedTo',
+ in_new_bugmail => 1,
+ buglist => 1
+ },
+ {name => 'reporter', desc => 'ReportedBy', in_new_bugmail => 1, buglist => 1},
+ {name => 'qa_contact', desc => 'QAContact', in_new_bugmail => 1, buglist => 1},
+ {
+ name => 'assigned_to_realname',
+ desc => 'AssignedToName',
+ in_new_bugmail => 0,
+ buglist => 1
+ },
+ {
+ name => 'reporter_realname',
+ desc => 'ReportedByName',
+ in_new_bugmail => 0,
+ buglist => 1
+ },
+ {
+ name => 'qa_contact_realname',
+ desc => 'QAContactName',
+ in_new_bugmail => 0,
+ buglist => 1
+ },
+ {name => 'cc', desc => 'CC', in_new_bugmail => 1},
+ {
+ name => 'dependson',
+ desc => 'Depends on',
+ in_new_bugmail => 1,
+ is_numeric => 1,
+ buglist => 1
+ },
+ {
+ name => 'blocked',
+ desc => 'Blocks',
+ in_new_bugmail => 1,
+ is_numeric => 1,
+ buglist => 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', is_numeric => 1},
+ {
+ name => 'attachments.isobsolete',
+ desc => 'Attachment is obsolete',
+ is_numeric => 1
+ },
+ {
+ name => 'attachments.isprivate',
+ desc => 'Attachment is private',
+ is_numeric => 1
+ },
+ {name => 'attachments.submitter', desc => 'Attachment creator'},
+
+ {
+ name => 'target_milestone',
+ desc => 'Target Milestone',
+ in_new_bugmail => 1,
+ buglist => 1
+ },
+ {name => 'creation_ts', desc => 'Creation date', buglist => 1},
+ {name => 'delta_ts', desc => 'Last changed date', buglist => 1},
+ {name => 'longdesc', desc => 'Comment'},
+ {name => 'longdescs.isprivate', desc => 'Comment is private', is_numeric => 1},
+ {
+ name => 'longdescs.count',
+ desc => 'Number of Comments',
+ buglist => 1,
+ is_numeric => 1
+ },
+ {name => 'alias', desc => 'Alias', buglist => 1},
+ {name => 'everconfirmed', desc => 'Ever Confirmed', is_numeric => 1},
+ {name => 'reporter_accessible', desc => 'Reporter Accessible', is_numeric => 1},
+ {name => 'cclist_accessible', desc => 'CC Accessible', is_numeric => 1},
+ {name => 'bug_group', desc => 'Group', in_new_bugmail => 1},
+ {
+ name => 'estimated_time',
+ desc => 'Estimated Hours',
+ in_new_bugmail => 1,
+ buglist => 1,
+ is_numeric => 1
+ },
+ {
+ name => 'remaining_time',
+ desc => 'Remaining Hours',
+ buglist => 1,
+ is_numeric => 1
+ },
+ {
+ name => 'deadline',
+ desc => 'Deadline',
+ type => FIELD_TYPE_DATETIME,
+ in_new_bugmail => 1,
+ buglist => 1
+ },
+ {name => 'commenter', desc => 'Commenter'},
+ {name => 'flagtypes.name', desc => 'Flags', buglist => 1},
+ {name => 'requestees.login_name', desc => 'Flag Requestee'},
+ {name => 'setters.login_name', desc => 'Flag Setter'},
+ {name => 'work_time', desc => 'Hours Worked', buglist => 1, is_numeric => 1},
+ {
+ name => 'percentage_complete',
+ desc => 'Percentage Complete',
+ buglist => 1,
+ is_numeric => 1
+ },
+ {name => 'content', desc => 'Content'},
+ {name => 'attach_data.thedata', desc => 'Attachment data'},
+ {name => "owner_idle_time", desc => "Time Since Assignee Touched"},
+ {name => 'see_also', desc => "See Also", type => FIELD_TYPE_BUG_URLS},
+ {
+ name => 'tag',
+ desc => 'Personal Tags',
+ buglist => 1,
+ type => FIELD_TYPE_KEYWORDS
+ },
+ {
+ name => 'last_visit_ts',
+ desc => 'Last Visit',
+ buglist => 1,
+ type => FIELD_TYPE_DATETIME
+ },
+ {name => 'comment_tag', desc => 'Comment Tag'},
);
################
@@ -276,12 +396,12 @@ use constant DEFAULT_FIELDS => (
# Override match to add is_select.
sub match {
- my $self = shift;
- my ($params) = @_;
- if (delete $params->{is_select}) {
- $params->{type} = [FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT];
- }
- return $self->SUPER::match(@_);
+ my $self = shift;
+ my ($params) = @_;
+ if (delete $params->{is_select}) {
+ $params->{type} = [FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT];
+ }
+ return $self->SUPER::match(@_);
}
##############
@@ -291,151 +411,153 @@ sub match {
sub _check_custom { return $_[1] ? 1 : 0; }
sub _check_description {
- my ($invocant, $desc) = @_;
- $desc = clean_text($desc);
- $desc || ThrowUserError('field_missing_description');
- return $desc;
+ my ($invocant, $desc) = @_;
+ $desc = clean_text($desc);
+ $desc || ThrowUserError('field_missing_description');
+ return $desc;
}
sub _check_long_desc {
- my ($invocant, $long_desc) = @_;
- $long_desc = clean_text($long_desc || '');
- if (length($long_desc) > MAX_FIELD_LONG_DESC_LENGTH) {
- ThrowUserError('field_long_desc_too_long');
- }
- return $long_desc;
+ my ($invocant, $long_desc) = @_;
+ $long_desc = clean_text($long_desc || '');
+ if (length($long_desc) > MAX_FIELD_LONG_DESC_LENGTH) {
+ ThrowUserError('field_long_desc_too_long');
+ }
+ return $long_desc;
}
sub _check_enter_bug { return $_[1] ? 1 : 0; }
sub _check_is_numeric {
- my ($invocant, $value, undef, $params) = @_;
- my $type = blessed($invocant) ? $invocant->type : $params->{type};
- return 1 if $type == FIELD_TYPE_BUG_ID;
- return $value ? 1 : 0;
+ my ($invocant, $value, undef, $params) = @_;
+ my $type = blessed($invocant) ? $invocant->type : $params->{type};
+ return 1 if $type == FIELD_TYPE_BUG_ID;
+ return $value ? 1 : 0;
}
sub _check_mailhead { return $_[1] ? 1 : 0; }
sub _check_name {
- my ($class, $name, undef, $params) = @_;
- $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/^[a-zA-Z0-9_]+$/ if $params->{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_/ && $params->{custom}) {
- $name = 'cf_' . $name;
- }
+ my ($class, $name, undef, $params) = @_;
+ $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/^[a-zA-Z0-9_]+$/ if $params->{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_/ && $params->{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;
+ # 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;
+ 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;
+ 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, undef, $params) = @_;
- my $saved_type = $type;
- (detaint_natural($type) && $type < FIELD_TYPE_HIGHEST_PLUS_ONE)
- || ThrowCodeError('invalid_customfield_type', { type => $saved_type });
-
- my $custom = blessed($invocant) ? $invocant->custom : $params->{custom};
- if ($custom && !$type) {
- ThrowCodeError('field_type_not_specified');
- }
+ my ($invocant, $type, undef, $params) = @_;
+ my $saved_type = $type;
+ (detaint_natural($type) && $type < FIELD_TYPE_HIGHEST_PLUS_ONE)
+ || ThrowCodeError('invalid_customfield_type', {type => $saved_type});
+
+ my $custom = blessed($invocant) ? $invocant->custom : $params->{custom};
+ if ($custom && !$type) {
+ ThrowCodeError('field_type_not_specified');
+ }
- return $type;
+ return $type;
}
sub _check_value_field_id {
- my ($invocant, $field_id, undef, $params) = @_;
- my $is_select = $invocant->is_select($params);
- if ($field_id && !$is_select) {
- ThrowUserError('field_value_control_select_only');
- }
- return $invocant->_check_visibility_field_id($field_id);
+ my ($invocant, $field_id, undef, $params) = @_;
+ my $is_select = $invocant->is_select($params);
+ if ($field_id && !$is_select) {
+ ThrowUserError('field_value_control_select_only');
+ }
+ return $invocant->_check_visibility_field_id($field_id);
}
sub _check_visibility_field_id {
- my ($invocant, $field_id) = @_;
- $field_id = trim($field_id);
- return undef if !$field_id;
- my $field = Bugzilla::Field->check({ id => $field_id });
- if (blessed($invocant) && $field->id == $invocant->id) {
- ThrowUserError('field_cant_control_self', { field => $field });
- }
- if (!$field->is_select) {
- ThrowUserError('field_control_must_be_select',
- { field => $field });
- }
- return $field->id;
+ my ($invocant, $field_id) = @_;
+ $field_id = trim($field_id);
+ return undef if !$field_id;
+ my $field = Bugzilla::Field->check({id => $field_id});
+ if (blessed($invocant) && $field->id == $invocant->id) {
+ ThrowUserError('field_cant_control_self', {field => $field});
+ }
+ if (!$field->is_select) {
+ ThrowUserError('field_control_must_be_select', {field => $field});
+ }
+ return $field->id;
}
sub _check_visibility_values {
- my ($invocant, $values, undef, $params) = @_;
- my $field;
- if (blessed $invocant) {
- $field = $invocant->visibility_field;
- }
- elsif ($params->{visibility_field_id}) {
- $field = $invocant->new($params->{visibility_field_id});
- }
- # When no field is set, no values are set.
- return [] if !$field;
+ my ($invocant, $values, undef, $params) = @_;
+ my $field;
+ if (blessed $invocant) {
+ $field = $invocant->visibility_field;
+ }
+ elsif ($params->{visibility_field_id}) {
+ $field = $invocant->new($params->{visibility_field_id});
+ }
- if (!scalar @$values) {
- ThrowUserError('field_visibility_values_must_be_selected',
- { field => $field });
- }
+ # When no field is set, no values are set.
+ return [] if !$field;
+
+ if (!scalar @$values) {
+ ThrowUserError('field_visibility_values_must_be_selected', {field => $field});
+ }
- my @visibility_values;
- my $choice = Bugzilla::Field::Choice->type($field);
- foreach my $value (@$values) {
- if (!blessed $value) {
- $value = $choice->check({ id => $value });
- }
- push(@visibility_values, $value);
+ my @visibility_values;
+ my $choice = Bugzilla::Field::Choice->type($field);
+ foreach my $value (@$values) {
+ if (!blessed $value) {
+ $value = $choice->check({id => $value});
}
+ push(@visibility_values, $value);
+ }
- return \@visibility_values;
+ return \@visibility_values;
}
sub _check_reverse_desc {
- my ($invocant, $reverse_desc, undef, $params) = @_;
- my $type = blessed($invocant) ? $invocant->type : $params->{type};
- if ($type != FIELD_TYPE_BUG_ID) {
- return undef; # store NULL for non-reversible field types
- }
-
- $reverse_desc = clean_text($reverse_desc);
- return $reverse_desc;
+ my ($invocant, $reverse_desc, undef, $params) = @_;
+ my $type = blessed($invocant) ? $invocant->type : $params->{type};
+ if ($type != FIELD_TYPE_BUG_ID) {
+ return undef; # store NULL for non-reversible field types
+ }
+
+ $reverse_desc = clean_text($reverse_desc);
+ return $reverse_desc;
}
sub _check_is_mandatory { return $_[1] ? 1 : 0; }
@@ -583,11 +705,13 @@ objects.
=cut
sub is_select {
- my ($invocant, $params) = @_;
- # This allows this method to be called by create() validators.
- my $type = blessed($invocant) ? $invocant->type : $params->{type};
- return ($type == FIELD_TYPE_SINGLE_SELECT
- || $type == FIELD_TYPE_MULTI_SELECT) ? 1 : 0
+ my ($invocant, $params) = @_;
+
+ # This allows this method to be called by create() validators.
+ my $type = blessed($invocant) ? $invocant->type : $params->{type};
+ return ($type == FIELD_TYPE_SINGLE_SELECT || $type == FIELD_TYPE_MULTI_SELECT)
+ ? 1
+ : 0;
}
=over
@@ -608,19 +732,19 @@ This method returns C<1> if the field is "abnormal", C<0> otherwise.
=cut
sub is_abnormal {
- my $self = shift;
- return ABNORMAL_SELECTS->{$self->name} ? 1 : 0;
+ my $self = shift;
+ return ABNORMAL_SELECTS->{$self->name} ? 1 : 0;
}
sub legal_values {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{'legal_values'}) {
- require Bugzilla::Field::Choice;
- my @values = Bugzilla::Field::Choice->type($self)->get_all();
- $self->{'legal_values'} = \@values;
- }
- return $self->{'legal_values'};
+ if (!defined $self->{'legal_values'}) {
+ require Bugzilla::Field::Choice;
+ my @values = Bugzilla::Field::Choice->type($self)->get_all();
+ $self->{'legal_values'} = \@values;
+ }
+ return $self->{'legal_values'};
}
=pod
@@ -637,8 +761,8 @@ in the C<timetrackinggroup>.
=cut
sub is_timetracking {
- my ($self) = @_;
- return grep($_ eq $self->name, TIMETRACKING_FIELDS) ? 1 : 0;
+ my ($self) = @_;
+ return grep($_ eq $self->name, TIMETRACKING_FIELDS) ? 1 : 0;
}
=pod
@@ -657,12 +781,11 @@ Returns undef if there is no field that controls this field's visibility.
=cut
sub visibility_field {
- my $self = shift;
- if ($self->{visibility_field_id}) {
- $self->{visibility_field} ||=
- $self->new($self->{visibility_field_id});
- }
- return $self->{visibility_field};
+ my $self = shift;
+ if ($self->{visibility_field_id}) {
+ $self->{visibility_field} ||= $self->new($self->{visibility_field_id});
+ }
+ return $self->{visibility_field};
}
=pod
@@ -680,22 +803,23 @@ or undef if there is no C<visibility_field> set.
=cut
sub visibility_values {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- return [] if !$self->{visibility_field_id};
-
- if (!defined $self->{visibility_values}) {
- my $visibility_value_ids =
- $dbh->selectcol_arrayref("SELECT value_id FROM field_visibility
- WHERE field_id = ?", undef, $self->id);
-
- $self->{visibility_values} =
- Bugzilla::Field::Choice->type($self->visibility_field)
- ->new_from_list($visibility_value_ids);
- }
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ return [] if !$self->{visibility_field_id};
+
+ if (!defined $self->{visibility_values}) {
+ my $visibility_value_ids = $dbh->selectcol_arrayref(
+ "SELECT value_id FROM field_visibility
+ WHERE field_id = ?", undef, $self->id
+ );
- return $self->{visibility_values};
+ $self->{visibility_values}
+ = Bugzilla::Field::Choice->type($self->visibility_field)
+ ->new_from_list($visibility_value_ids);
+ }
+
+ return $self->{visibility_values};
}
=pod
@@ -712,10 +836,10 @@ field controls the visibility of.
=cut
sub controls_visibility_of {
- my $self = shift;
- $self->{controls_visibility_of} ||=
- Bugzilla::Field->match({ visibility_field_id => $self->id });
- return $self->{controls_visibility_of};
+ my $self = shift;
+ $self->{controls_visibility_of}
+ ||= Bugzilla::Field->match({visibility_field_id => $self->id});
+ return $self->{controls_visibility_of};
}
=pod
@@ -733,11 +857,11 @@ Returns undef if there is no field that controls this field's visibility.
=cut
sub value_field {
- my $self = shift;
- if ($self->{value_field_id}) {
- $self->{value_field} ||= $self->new($self->{value_field_id});
- }
- return $self->{value_field};
+ my $self = shift;
+ if ($self->{value_field_id}) {
+ $self->{value_field} ||= $self->new($self->{value_field_id});
+ }
+ return $self->{value_field};
}
=pod
@@ -754,10 +878,10 @@ field controls the values of.
=cut
sub controls_values_of {
- my $self = shift;
- $self->{controls_values_of} ||=
- Bugzilla::Field->match({ value_field_id => $self->id });
- return $self->{controls_values_of};
+ my $self = shift;
+ $self->{controls_values_of}
+ ||= Bugzilla::Field->match({value_field_id => $self->id});
+ return $self->{controls_values_of};
}
=over
@@ -771,15 +895,15 @@ See L<Bugzilla::Field::ChoiceInterface>.
=cut
sub is_visible_on_bug {
- my ($self, $bug) = @_;
+ my ($self, $bug) = @_;
- # Always return visible, if this field is not
- # visibility controlled.
- return 1 if !$self->{visibility_field_id};
+ # Always return visible, if this field is not
+ # visibility controlled.
+ return 1 if !$self->{visibility_field_id};
- my $visibility_values = $self->visibility_values;
+ my $visibility_values = $self->visibility_values;
- return (any { $_->is_set_on_bug($bug) } @$visibility_values) ? 1 : 0;
+ return (any { $_->is_set_on_bug($bug) } @$visibility_values) ? 1 : 0;
}
=over
@@ -795,13 +919,13 @@ dependency tree display, and similar functionality.
=cut
-sub is_relationship {
- my $self = shift;
- my $desc = $self->reverse_desc;
- if (defined $desc && $desc ne "") {
- return 1;
- }
- return 0;
+sub is_relationship {
+ my $self = shift;
+ my $desc = $self->reverse_desc;
+ if (defined $desc && $desc ne "") {
+ return 1;
+ }
+ return 0;
}
=over
@@ -888,29 +1012,32 @@ They will throw an error if you try to set the values to something invalid.
=cut
-sub set_description { $_[0]->set('description', $_[1]); }
-sub set_long_desc { $_[0]->set('long_desc', $_[1]); }
-sub set_enter_bug { $_[0]->set('enter_bug', $_[1]); }
-sub set_is_numeric { $_[0]->set('is_numeric', $_[1]); }
-sub set_obsolete { $_[0]->set('obsolete', $_[1]); }
-sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
-sub set_in_new_bugmail { $_[0]->set('mailhead', $_[1]); }
-sub set_buglist { $_[0]->set('buglist', $_[1]); }
-sub set_reverse_desc { $_[0]->set('reverse_desc', $_[1]); }
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_long_desc { $_[0]->set('long_desc', $_[1]); }
+sub set_enter_bug { $_[0]->set('enter_bug', $_[1]); }
+sub set_is_numeric { $_[0]->set('is_numeric', $_[1]); }
+sub set_obsolete { $_[0]->set('obsolete', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+sub set_in_new_bugmail { $_[0]->set('mailhead', $_[1]); }
+sub set_buglist { $_[0]->set('buglist', $_[1]); }
+sub set_reverse_desc { $_[0]->set('reverse_desc', $_[1]); }
+
sub set_visibility_field {
- my ($self, $value) = @_;
- $self->set('visibility_field_id', $value);
- delete $self->{visibility_field};
- delete $self->{visibility_values};
+ my ($self, $value) = @_;
+ $self->set('visibility_field_id', $value);
+ delete $self->{visibility_field};
+ delete $self->{visibility_values};
}
+
sub set_visibility_values {
- my ($self, $value_ids) = @_;
- $self->set('visibility_values', $value_ids);
+ my ($self, $value_ids) = @_;
+ $self->set('visibility_values', $value_ids);
}
+
sub set_value_field {
- my ($self, $value) = @_;
- $self->set('value_field_id', $value);
- delete $self->{value_field};
+ my ($self, $value) = @_;
+ $self->set('value_field_id', $value);
+ delete $self->{value_field};
}
sub set_is_mandatory { $_[0]->set('is_mandatory', $_[1]); }
@@ -934,69 +1061,73 @@ there are no values specified (or EVER specified) for the field.
=cut
sub remove_from_db {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- my $name = $self->name;
+ my $name = $self->name;
- if (!$self->custom) {
- ThrowCodeError('field_not_custom', {'name' => $name });
- }
+ if (!$self->custom) {
+ ThrowCodeError('field_not_custom', {'name' => $name});
+ }
- if (!$self->obsolete) {
- ThrowUserError('customfield_not_obsolete', {'name' => $self->name });
- }
+ if (!$self->obsolete) {
+ ThrowUserError('customfield_not_obsolete', {'name' => $self->name});
+ }
- $dbh->bz_start_transaction();
+ $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 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 = "";
+ # 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";
- if ($self->type != FIELD_TYPE_BUG_ID
- && $self->type != FIELD_TYPE_DATE
- && $self->type != FIELD_TYPE_DATETIME)
- {
- $bugs_query .= " AND $name != ''";
- }
- # Ignore the default single select value
- if ($self->type == FIELD_TYPE_SINGLE_SELECT) {
- $bugs_query .= " AND $name != '---'";
- }
+ 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";
+ if ( $self->type != FIELD_TYPE_BUG_ID
+ && $self->type != FIELD_TYPE_DATE
+ && $self->type != FIELD_TYPE_DATETIME)
+ {
+ $bugs_query .= " AND $name != ''";
}
- my $has_bugs = $dbh->selectrow_array($bugs_query);
- if ($has_bugs) {
- ThrowUserError('customfield_has_contents', {'name' => $name });
+ # Ignore the default single select value
+ if ($self->type == FIELD_TYPE_SINGLE_SELECT) {
+ $bugs_query .= " AND $name != '---'";
}
+ }
- # Once we reach here, we should be OK to delete.
- $self->SUPER::remove_from_db();
+ my $has_bugs = $dbh->selectrow_array($bugs_query);
+ if ($has_bugs) {
+ ThrowUserError('customfield_has_contents', {'name' => $name});
+ }
- my $type = $self->type;
+ # Once we reach here, we should be OK to delete.
+ $self->SUPER::remove_from_db();
- # the values for multi-select are stored in a seperate table
- if ($type != FIELD_TYPE_MULTI_SELECT) {
- $dbh->bz_drop_column('bugs', $name);
- }
+ my $type = $self->type;
- if ($self->is_select) {
- # Delete the table that holds the legal values for this field.
- $dbh->bz_drop_field_tables($self);
- }
+ # the values for multi-select are stored in a seperate table
+ if ($type != FIELD_TYPE_MULTI_SELECT) {
+ $dbh->bz_drop_column('bugs', $name);
+ }
+
+ if ($self->is_select) {
- $dbh->bz_commit_transaction()
+ # Delete the table that holds the legal values for this field.
+ $dbh->bz_drop_field_tables($self);
+ }
+
+ $dbh->bz_commit_transaction();
}
=pod
@@ -1042,90 +1173,95 @@ C<is_mandatory> - boolean - Whether this field is mandatory. Defaults to 0.
=cut
sub create {
- my $class = shift;
- my ($params) = @_;
- my $dbh = Bugzilla->dbh;
-
- # This makes sure the "sortkey" validator runs, even if
- # the parameter isn't sent to create().
- $params->{sortkey} = undef if !exists $params->{sortkey};
- $params->{type} ||= 0;
- # We mark the custom field as obsolete till it has been fully created,
- # to avoid race conditions when viewing bugs at the same time.
- my $is_obsolete = $params->{obsolete};
- $params->{obsolete} = 1 if $params->{custom};
-
- $dbh->bz_start_transaction();
- $class->check_required_create_fields(@_);
- my $field_values = $class->run_create_validators($params);
- my $visibility_values = delete $field_values->{visibility_values};
- my $field = $class->insert_create_data($field_values);
-
- $field->set_visibility_values($visibility_values);
- $field->_update_visibility_values();
-
- $dbh->bz_commit_transaction();
- Bugzilla->memcached->clear_config();
+ my $class = shift;
+ my ($params) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # This makes sure the "sortkey" validator runs, even if
+ # the parameter isn't sent to create().
+ $params->{sortkey} = undef if !exists $params->{sortkey};
+ $params->{type} ||= 0;
+
+ # We mark the custom field as obsolete till it has been fully created,
+ # to avoid race conditions when viewing bugs at the same time.
+ my $is_obsolete = $params->{obsolete};
+ $params->{obsolete} = 1 if $params->{custom};
+
+ $dbh->bz_start_transaction();
+ $class->check_required_create_fields(@_);
+ my $field_values = $class->run_create_validators($params);
+ my $visibility_values = delete $field_values->{visibility_values};
+ my $field = $class->insert_create_data($field_values);
+
+ $field->set_visibility_values($visibility_values);
+ $field->_update_visibility_values();
+
+ $dbh->bz_commit_transaction();
+ Bugzilla->memcached->clear_config();
+
+ 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 ($field->is_select) {
- 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 ($field->is_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 ('---')");
- }
-
- # Restore the original obsolete state of the custom field.
- $dbh->do('UPDATE fielddefs SET obsolete = 0 WHERE id = ?', undef, $field->id)
- unless $is_obsolete;
-
- Bugzilla->memcached->clear({ table => 'fielddefs', id => $field->id });
- Bugzilla->memcached->clear_config();
+ # Create the table that holds the legal values for this field.
+ $dbh->bz_add_field_tables($field);
}
- return $field;
-}
+ if ($type == FIELD_TYPE_SINGLE_SELECT) {
-sub update {
- my $self = shift;
- my $changes = $self->SUPER::update(@_);
- my $dbh = Bugzilla->dbh;
- if ($changes->{value_field_id} && $self->is_select) {
- $dbh->do("UPDATE " . $self->name . " SET visibility_value_id = NULL");
+ # Insert a default value of "---" into the legal values table.
+ $dbh->do("INSERT INTO $name (value) VALUES ('---')");
}
- $self->_update_visibility_values();
+
+ # Restore the original obsolete state of the custom field.
+ $dbh->do('UPDATE fielddefs SET obsolete = 0 WHERE id = ?', undef, $field->id)
+ unless $is_obsolete;
+
+ Bugzilla->memcached->clear({table => 'fielddefs', id => $field->id});
Bugzilla->memcached->clear_config();
- return $changes;
+ }
+
+ return $field;
+}
+
+sub update {
+ my $self = shift;
+ my $changes = $self->SUPER::update(@_);
+ my $dbh = Bugzilla->dbh;
+ if ($changes->{value_field_id} && $self->is_select) {
+ $dbh->do("UPDATE " . $self->name . " SET visibility_value_id = NULL");
+ }
+ $self->_update_visibility_values();
+ Bugzilla->memcached->clear_config();
+ return $changes;
}
sub _update_visibility_values {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- my @visibility_value_ids = map($_->id, @{$self->visibility_values});
- $self->_delete_visibility_values();
- for my $value_id (@visibility_value_ids) {
- $dbh->do("INSERT INTO field_visibility (field_id, value_id)
- VALUES (?, ?)", undef, $self->id, $value_id);
- }
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my @visibility_value_ids = map($_->id, @{$self->visibility_values});
+ $self->_delete_visibility_values();
+ for my $value_id (@visibility_value_ids) {
+ $dbh->do(
+ "INSERT INTO field_visibility (field_id, value_id)
+ VALUES (?, ?)", undef, $self->id, $value_id
+ );
+ }
}
sub _delete_visibility_values {
- my ($self) = @_;
- my $dbh = Bugzilla->dbh;
- $dbh->do("DELETE FROM field_visibility WHERE field_id = ?",
- undef, $self->id);
- delete $self->{visibility_values};
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("DELETE FROM field_visibility WHERE field_id = ?", undef, $self->id);
+ delete $self->{visibility_values};
}
=pod
@@ -1148,13 +1284,14 @@ Returns: a reference to a list of valid values.
=cut
sub get_legal_field_values {
- my ($field) = @_;
- my $dbh = Bugzilla->dbh;
- my $result_ref = $dbh->selectcol_arrayref(
- "SELECT value FROM $field
+ 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;
+ ORDER BY sortkey, value", undef, (1)
+ );
+ return $result_ref;
}
=over
@@ -1173,107 +1310,114 @@ Returns: nothing
=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->set_buglist($def->{buglist});
- $field->_set_type($def->{type}) if $def->{type};
- $field->set_is_mandatory($def->{is_mandatory});
- $field->set_is_numeric($def->{is_numeric});
- $field->update();
- }
- else {
- if (exists $def->{in_new_bugmail}) {
- $def->{mailhead} = $def->{in_new_bugmail};
- delete $def->{in_new_bugmail};
- }
- $def->{description} = delete $def->{desc};
- Bugzilla::Field->create($def);
- }
+ 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->set_buglist($def->{buglist});
+ $field->_set_type($def->{type}) if $def->{type};
+ $field->set_is_mandatory($def->{is_mandatory});
+ $field->set_is_numeric($def->{is_numeric});
+ $field->update();
}
+ else {
+ if (exists $def->{in_new_bugmail}) {
+ $def->{mailhead} = $def->{in_new_bugmail};
+ delete $def->{in_new_bugmail};
+ }
+ $def->{description} = 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)) {
- say "SQL fragment found in the 'fielddefs' table...";
- say "Old field name: $old_field_name";
- # 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.
- say "Fixing the 'fielddefs' table...";
- say "New field name: $new_field_name";
- $dbh->do('UPDATE fielddefs SET name = ? WHERE id = ?',
- undef, ($new_field_name, $old_field_id));
+ # 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)) {
+ say "SQL fragment found in the 'fielddefs' table...";
+ say "Old field name: $old_field_name";
+
+ # 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);
}
- # 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 });
+ # 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.
+ say "Fixing the 'fielddefs' table...";
+ say "New field name: $new_field_name";
+ $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
@@ -1305,32 +1449,32 @@ Returns: 1 on success; 0 on failure if $no_warn is true (else an
=cut
sub check_field {
- my ($name, $value, $legalsRef, $no_warn) = @_;
- my $dbh = Bugzilla->dbh;
-
- # If $legalsRef is undefined, we use the default valid values.
- # Valid values for this check are all possible values.
- # Using get_legal_values would only return active values, but since
- # some bugs may have inactive values set, we want to check them too.
- unless (defined $legalsRef) {
- $legalsRef = Bugzilla::Field->new({name => $name})->legal_values;
- my @values = map($_->name, @$legalsRef);
- $legalsRef = \@values;
+ my ($name, $value, $legalsRef, $no_warn) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # If $legalsRef is undefined, we use the default valid values.
+ # Valid values for this check are all possible values.
+ # Using get_legal_values would only return active values, but since
+ # some bugs may have inactive values set, we want to check them too.
+ unless (defined $legalsRef) {
+ $legalsRef = Bugzilla::Field->new({name => $name})->legal_values;
+ my @values = map($_->name, @$legalsRef);
+ $legalsRef = \@values;
- }
+ }
- if (!defined($value)
- or trim($value) eq ""
- or !grep { $_ eq $value } @$legalsRef)
- {
- return 0 if $no_warn; # We don't want an error to be thrown; return.
- trick_taint($name);
+ if ( !defined($value)
+ or trim($value) eq ""
+ or !grep { $_ eq $value } @$legalsRef)
+ {
+ 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;
+ my $field = new Bugzilla::Field({name => $name});
+ my $field_desc = $field ? $field->description : $name;
+ ThrowCodeError('illegal_field', {field => $field_desc});
+ }
+ return 1;
}
=pod
@@ -1352,10 +1496,10 @@ Returns: the corresponding field ID or an error if the field name
=cut
sub get_field_id {
- my $field = Bugzilla->fields({ by_name => 1 })->{$_[0]}
- or ThrowCodeError('invalid_field_name', {field => $_[0]});
+ my $field = Bugzilla->fields({by_name => 1})->{$_[0]}
+ or ThrowCodeError('invalid_field_name', {field => $_[0]});
- return $field->id;
+ return $field->id;
}
1;
diff --git a/Bugzilla/Field/Choice.pm b/Bugzilla/Field/Choice.pm
index a66f69cee..bd82a8e3f 100644
--- a/Bugzilla/Field/Choice.pm
+++ b/Bugzilla/Field/Choice.pm
@@ -28,42 +28,42 @@ use Scalar::Util qw(blessed);
use constant IS_CONFIG => 1;
use constant DB_COLUMNS => qw(
- id
- value
- sortkey
- isactive
- visibility_value_id
+ id
+ value
+ sortkey
+ isactive
+ visibility_value_id
);
use constant UPDATE_COLUMNS => qw(
- value
- sortkey
- isactive
- visibility_value_id
+ value
+ sortkey
+ isactive
+ visibility_value_id
);
use constant NAME_FIELD => 'value';
use constant LIST_ORDER => 'sortkey, value';
use constant VALIDATORS => {
- value => \&_check_value,
- sortkey => \&_check_sortkey,
- visibility_value_id => \&_check_visibility_value_id,
- isactive => \&_check_isactive,
+ value => \&_check_value,
+ sortkey => \&_check_sortkey,
+ visibility_value_id => \&_check_visibility_value_id,
+ isactive => \&_check_isactive,
};
use constant CLASS_MAP => {
- bug_status => 'Bugzilla::Status',
- classification => 'Bugzilla::Classification',
- component => 'Bugzilla::Component',
- product => 'Bugzilla::Product',
+ bug_status => 'Bugzilla::Status',
+ classification => 'Bugzilla::Classification',
+ component => 'Bugzilla::Component',
+ product => 'Bugzilla::Product',
};
use constant DEFAULT_MAP => {
- op_sys => 'defaultopsys',
- rep_platform => 'defaultplatform',
- priority => 'defaultpriority',
- bug_severity => 'defaultseverity',
+ op_sys => 'defaultopsys',
+ rep_platform => 'defaultplatform',
+ priority => 'defaultpriority',
+ bug_severity => 'defaultseverity',
};
#################
@@ -76,49 +76,50 @@ use constant DEFAULT_MAP => {
# are Bugzilla::Status objects.
sub type {
- my ($class, $field) = @_;
- my $field_obj = blessed $field ? $field : Bugzilla::Field->check($field);
- my $field_name = $field_obj->name;
-
- if (my $package = $class->CLASS_MAP->{$field_name}) {
- # Callers expect the module to be already loaded.
- eval "require $package";
- return $package;
- }
+ my ($class, $field) = @_;
+ my $field_obj = blessed $field ? $field : Bugzilla::Field->check($field);
+ my $field_name = $field_obj->name;
+
+ if (my $package = $class->CLASS_MAP->{$field_name}) {
- # For generic classes, we use a lowercase class name, so as
- # not to interfere with any real subclasses we might make some day.
- my $package = "Bugzilla::Field::Choice::$field_name";
- Bugzilla->request_cache->{"field_$package"} = $field_obj;
-
- # This package only needs to be created once. We check if the DB_TABLE
- # glob for this package already exists, which tells us whether or not
- # we need to create the package (this works even under mod_perl, where
- # this package definition will persist across requests)).
- if (!defined *{"${package}::DB_TABLE"}) {
- eval <<EOC;
+ # Callers expect the module to be already loaded.
+ eval "require $package";
+ return $package;
+ }
+
+ # For generic classes, we use a lowercase class name, so as
+ # not to interfere with any real subclasses we might make some day.
+ my $package = "Bugzilla::Field::Choice::$field_name";
+ Bugzilla->request_cache->{"field_$package"} = $field_obj;
+
+ # This package only needs to be created once. We check if the DB_TABLE
+ # glob for this package already exists, which tells us whether or not
+ # we need to create the package (this works even under mod_perl, where
+ # this package definition will persist across requests)).
+ if (!defined *{"${package}::DB_TABLE"}) {
+ eval <<EOC;
package $package;
use parent qw(Bugzilla::Field::Choice);
use constant DB_TABLE => '$field_name';
EOC
- }
+ }
- return $package;
+ return $package;
}
################
# Constructors #
################
-# We just make new() enforce this, which should give developers
+# We just make new() enforce this, which should give developers
# the understanding that you can't use Bugzilla::Field::Choice
# without calling type().
sub new {
- my $class = shift;
- if ($class eq 'Bugzilla::Field::Choice') {
- ThrowCodeError('field_choice_must_use_type');
- }
- $class->SUPER::new(@_);
+ my $class = shift;
+ if ($class eq 'Bugzilla::Field::Choice') {
+ ThrowCodeError('field_choice_must_use_type');
+ }
+ $class->SUPER::new(@_);
}
#########################
@@ -130,64 +131,66 @@ sub new {
# columns. (Normally Bugzilla::Object dies if you pass arguments
# that aren't valid columns.)
sub create {
- my $class = shift;
- my ($params) = @_;
- foreach my $key (keys %$params) {
- if (!grep {$_ eq $key} $class->_get_db_columns) {
- delete $params->{$key};
- }
+ my $class = shift;
+ my ($params) = @_;
+ foreach my $key (keys %$params) {
+ if (!grep { $_ eq $key } $class->_get_db_columns) {
+ delete $params->{$key};
}
- return $class->SUPER::create(@_);
+ }
+ return $class->SUPER::create(@_);
}
sub update {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $fname = $self->field->name;
-
- $dbh->bz_start_transaction();
-
- my ($changes, $old_self) = $self->SUPER::update(@_);
- if (exists $changes->{value}) {
- my ($old, $new) = @{ $changes->{value} };
- if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
- $dbh->do("UPDATE bug_$fname SET value = ? WHERE value = ?",
- undef, $new, $old);
- }
- else {
- $dbh->do("UPDATE bugs SET $fname = ? WHERE $fname = ?",
- undef, $new, $old);
- }
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $fname = $self->field->name;
- if ($old_self->is_default) {
- my $param = $self->DEFAULT_MAP->{$self->field->name};
- SetParam($param, $self->name);
- write_params();
- }
+ $dbh->bz_start_transaction();
+
+ my ($changes, $old_self) = $self->SUPER::update(@_);
+ if (exists $changes->{value}) {
+ my ($old, $new) = @{$changes->{value}};
+ if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
+ $dbh->do("UPDATE bug_$fname SET value = ? WHERE value = ?", undef, $new, $old);
}
+ else {
+ $dbh->do("UPDATE bugs SET $fname = ? WHERE $fname = ?", undef, $new, $old);
+ }
+
+ if ($old_self->is_default) {
+ my $param = $self->DEFAULT_MAP->{$self->field->name};
+ SetParam($param, $self->name);
+ write_params();
+ }
+ }
- $dbh->bz_commit_transaction();
- return wantarray ? ($changes, $old_self) : $changes;
+ $dbh->bz_commit_transaction();
+ return wantarray ? ($changes, $old_self) : $changes;
}
sub remove_from_db {
- my $self = shift;
- if ($self->is_default) {
- ThrowUserError('fieldvalue_is_default',
- { field => $self->field, value => $self,
- param_name => $self->DEFAULT_MAP->{$self->field->name},
- });
- }
- if ($self->is_static) {
- ThrowUserError('fieldvalue_not_deletable',
- { field => $self->field, value => $self });
- }
- if ($self->bug_count) {
- ThrowUserError("fieldvalue_still_has_bugs",
- { field => $self->field, value => $self });
- }
- $self->_check_if_controller(); # From ChoiceInterface.
- $self->SUPER::remove_from_db();
+ my $self = shift;
+ if ($self->is_default) {
+ ThrowUserError(
+ 'fieldvalue_is_default',
+ {
+ field => $self->field,
+ value => $self,
+ param_name => $self->DEFAULT_MAP->{$self->field->name},
+ }
+ );
+ }
+ if ($self->is_static) {
+ ThrowUserError('fieldvalue_not_deletable',
+ {field => $self->field, value => $self});
+ }
+ if ($self->bug_count) {
+ ThrowUserError("fieldvalue_still_has_bugs",
+ {field => $self->field, value => $self});
+ }
+ $self->_check_if_controller(); # From ChoiceInterface.
+ $self->SUPER::remove_from_db();
}
############
@@ -195,12 +198,13 @@ sub remove_from_db {
############
sub set_is_active { $_[0]->set('isactive', $_[1]); }
-sub set_name { $_[0]->set('value', $_[1]); }
-sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+sub set_name { $_[0]->set('value', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+
sub set_visibility_value {
- my ($self, $value) = @_;
- $self->set('visibility_value_id', $value);
- delete $self->{visibility_value};
+ my ($self, $value) = @_;
+ $self->set('visibility_value_id', $value);
+ delete $self->{visibility_value};
}
##############
@@ -208,73 +212,74 @@ sub set_visibility_value {
##############
sub _check_isactive {
- my ($invocant, $value) = @_;
- $value = Bugzilla::Object::check_boolean($invocant, $value);
- if (!$value and ref $invocant) {
- if ($invocant->is_default) {
- my $field = $invocant->field;
- ThrowUserError('fieldvalue_is_default',
- { value => $invocant, field => $field,
- param_name => $invocant->DEFAULT_MAP->{$field->name}
- });
- }
- if ($invocant->is_static) {
- ThrowUserError('fieldvalue_not_deletable',
- { value => $invocant, field => $invocant->field });
+ my ($invocant, $value) = @_;
+ $value = Bugzilla::Object::check_boolean($invocant, $value);
+ if (!$value and ref $invocant) {
+ if ($invocant->is_default) {
+ my $field = $invocant->field;
+ ThrowUserError(
+ 'fieldvalue_is_default',
+ {
+ value => $invocant,
+ field => $field,
+ param_name => $invocant->DEFAULT_MAP->{$field->name}
}
+ );
+ }
+ if ($invocant->is_static) {
+ ThrowUserError('fieldvalue_not_deletable',
+ {value => $invocant, field => $invocant->field});
}
- return $value;
+ }
+ return $value;
}
sub _check_value {
- my ($invocant, $value) = @_;
+ my ($invocant, $value) = @_;
- my $field = $invocant->field;
+ my $field = $invocant->field;
- $value = trim($value);
+ $value = trim($value);
- # Make sure people don't rename static values
- if (blessed($invocant) && $value ne $invocant->name
- && $invocant->is_static)
- {
- ThrowUserError('fieldvalue_not_editable',
- { field => $field, old_value => $invocant });
- }
+ # Make sure people don't rename static values
+ if (blessed($invocant) && $value ne $invocant->name && $invocant->is_static) {
+ ThrowUserError('fieldvalue_not_editable',
+ {field => $field, old_value => $invocant});
+ }
- ThrowUserError('fieldvalue_undefined') if !defined $value || $value eq "";
- ThrowUserError('fieldvalue_name_too_long', { value => $value })
- if length($value) > MAX_FIELD_VALUE_SIZE;
+ ThrowUserError('fieldvalue_undefined') if !defined $value || $value eq "";
+ ThrowUserError('fieldvalue_name_too_long', {value => $value})
+ if length($value) > MAX_FIELD_VALUE_SIZE;
- my $exists = $invocant->type($field)->new({ name => $value });
- if ($exists && (!blessed($invocant) || $invocant->id != $exists->id)) {
- ThrowUserError('fieldvalue_already_exists',
- { field => $field, value => $exists });
- }
+ my $exists = $invocant->type($field)->new({name => $value});
+ if ($exists && (!blessed($invocant) || $invocant->id != $exists->id)) {
+ ThrowUserError('fieldvalue_already_exists',
+ {field => $field, value => $exists});
+ }
- return $value;
+ return $value;
}
sub _check_sortkey {
- my ($invocant, $value) = @_;
- $value = trim($value);
- return 0 if !$value;
- # Store for the error message in case detaint_natural clears it.
- my $orig_value = $value;
- (detaint_natural($value) && $value <= MAX_SMALLINT)
- || ThrowUserError('fieldvalue_sortkey_invalid',
- { sortkey => $orig_value,
- field => $invocant->field });
- return $value;
+ my ($invocant, $value) = @_;
+ $value = trim($value);
+ return 0 if !$value;
+
+ # Store for the error message in case detaint_natural clears it.
+ my $orig_value = $value;
+ (detaint_natural($value) && $value <= MAX_SMALLINT)
+ || ThrowUserError('fieldvalue_sortkey_invalid',
+ {sortkey => $orig_value, field => $invocant->field});
+ return $value;
}
sub _check_visibility_value_id {
- my ($invocant, $value_id) = @_;
- $value_id = trim($value_id);
- my $field = $invocant->field->value_field;
- return undef if !$field || !$value_id;
- my $value_obj = Bugzilla::Field::Choice->type($field)
- ->check({ id => $value_id });
- return $value_obj->id;
+ my ($invocant, $value_id) = @_;
+ $value_id = trim($value_id);
+ my $field = $invocant->field->value_field;
+ return undef if !$field || !$value_id;
+ my $value_obj = Bugzilla::Field::Choice->type($field)->check({id => $value_id});
+ return $value_obj->id;
}
1;
diff --git a/Bugzilla/Field/ChoiceInterface.pm b/Bugzilla/Field/ChoiceInterface.pm
index 634d36ad1..eeedfca83 100644
--- a/Bugzilla/Field/ChoiceInterface.pm
+++ b/Bugzilla/Field/ChoiceInterface.pm
@@ -26,14 +26,19 @@ sub FIELD_NAME { return $_[0]->DB_TABLE; }
####################
sub _check_if_controller {
- my $self = shift;
- my $vis_fields = $self->controls_visibility_of_fields;
- my $values = $self->controlled_values_array;
- if (@$vis_fields || @$values) {
- ThrowUserError('fieldvalue_is_controller',
- { value => $self, fields => [map($_->name, @$vis_fields)],
- vals => $self->controlled_values });
- }
+ my $self = shift;
+ my $vis_fields = $self->controls_visibility_of_fields;
+ my $values = $self->controlled_values_array;
+ if (@$vis_fields || @$values) {
+ ThrowUserError(
+ 'fieldvalue_is_controller',
+ {
+ value => $self,
+ fields => [map($_->name, @$vis_fields)],
+ vals => $self->controlled_values
+ }
+ );
+ }
}
@@ -42,145 +47,149 @@ sub _check_if_controller {
#############
sub is_active { return $_[0]->{'isactive'}; }
-sub sortkey { return $_[0]->{'sortkey'}; }
+sub sortkey { return $_[0]->{'sortkey'}; }
sub bug_count {
- my $self = shift;
- return $self->{bug_count} if defined $self->{bug_count};
- my $dbh = Bugzilla->dbh;
- my $fname = $self->field->name;
- my $count;
- if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
- $count = $dbh->selectrow_array("SELECT COUNT(*) FROM bug_$fname
- WHERE value = ?", undef, $self->name);
- }
- else {
- $count = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs
- WHERE $fname = ?",
- undef, $self->name);
- }
- $self->{bug_count} = $count;
- return $count;
+ my $self = shift;
+ return $self->{bug_count} if defined $self->{bug_count};
+ my $dbh = Bugzilla->dbh;
+ my $fname = $self->field->name;
+ my $count;
+ if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
+ $count = $dbh->selectrow_array(
+ "SELECT COUNT(*) FROM bug_$fname
+ WHERE value = ?", undef, $self->name
+ );
+ }
+ else {
+ $count = $dbh->selectrow_array(
+ "SELECT COUNT(*) FROM bugs
+ WHERE $fname = ?", undef, $self->name
+ );
+ }
+ $self->{bug_count} = $count;
+ return $count;
}
sub field {
- my $invocant = shift;
- my $class = ref $invocant || $invocant;
- my $cache = Bugzilla->request_cache;
- # This is just to make life easier for subclasses. Our auto-generated
- # subclasses from Bugzilla::Field::Choice->type() already have this set.
- $cache->{"field_$class"} ||=
- new Bugzilla::Field({ name => $class->FIELD_NAME });
- return $cache->{"field_$class"};
+ my $invocant = shift;
+ my $class = ref $invocant || $invocant;
+ my $cache = Bugzilla->request_cache;
+
+ # This is just to make life easier for subclasses. Our auto-generated
+ # subclasses from Bugzilla::Field::Choice->type() already have this set.
+ $cache->{"field_$class"} ||= new Bugzilla::Field({name => $class->FIELD_NAME});
+ return $cache->{"field_$class"};
}
sub is_default {
- my $self = shift;
- my $name = $self->DEFAULT_MAP->{$self->field->name};
- # If it doesn't exist in DEFAULT_MAP, then there is no parameter
- # related to this field.
- return 0 unless $name;
- return ($self->name eq Bugzilla->params->{$name}) ? 1 : 0;
+ my $self = shift;
+ my $name = $self->DEFAULT_MAP->{$self->field->name};
+
+ # If it doesn't exist in DEFAULT_MAP, then there is no parameter
+ # related to this field.
+ return 0 unless $name;
+ return ($self->name eq Bugzilla->params->{$name}) ? 1 : 0;
}
sub is_static {
- my $self = shift;
- # If we need to special-case Resolution for *anything* else, it should
- # get its own subclass.
- if ($self->field->name eq 'resolution') {
- return grep($_ eq $self->name, ('', 'FIXED', 'DUPLICATE'))
- ? 1 : 0;
- }
- elsif ($self->field->custom) {
- return $self->name eq '---' ? 1 : 0;
- }
- return 0;
+ my $self = shift;
+
+ # If we need to special-case Resolution for *anything* else, it should
+ # get its own subclass.
+ if ($self->field->name eq 'resolution') {
+ return grep($_ eq $self->name, ('', 'FIXED', 'DUPLICATE')) ? 1 : 0;
+ }
+ elsif ($self->field->custom) {
+ return $self->name eq '---' ? 1 : 0;
+ }
+ return 0;
}
sub controls_visibility_of_fields {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!$self->{controls_visibility_of_fields}) {
- my $ids = $dbh->selectcol_arrayref(
- "SELECT id FROM fielddefs
+ if (!$self->{controls_visibility_of_fields}) {
+ my $ids = $dbh->selectcol_arrayref(
+ "SELECT id FROM fielddefs
INNER JOIN field_visibility
ON fielddefs.id = field_visibility.field_id
- WHERE value_id = ? AND visibility_field_id = ?", undef,
- $self->id, $self->field->id);
+ WHERE value_id = ? AND visibility_field_id = ?", undef, $self->id,
+ $self->field->id
+ );
- $self->{controls_visibility_of_fields} =
- Bugzilla::Field->new_from_list($ids);
- }
+ $self->{controls_visibility_of_fields} = Bugzilla::Field->new_from_list($ids);
+ }
- return $self->{controls_visibility_of_fields};
+ return $self->{controls_visibility_of_fields};
}
sub visibility_value {
- my $self = shift;
- if ($self->{visibility_value_id}) {
- require Bugzilla::Field::Choice;
- $self->{visibility_value} ||=
- Bugzilla::Field::Choice->type($self->field->value_field)->new(
- $self->{visibility_value_id});
- }
- return $self->{visibility_value};
+ my $self = shift;
+ if ($self->{visibility_value_id}) {
+ require Bugzilla::Field::Choice;
+ $self->{visibility_value}
+ ||= Bugzilla::Field::Choice->type($self->field->value_field)
+ ->new($self->{visibility_value_id});
+ }
+ return $self->{visibility_value};
}
sub controlled_values {
- my $self = shift;
- return $self->{controlled_values} if defined $self->{controlled_values};
- my $fields = $self->field->controls_values_of;
- my %controlled_values;
- require Bugzilla::Field::Choice;
- foreach my $field (@$fields) {
- $controlled_values{$field->name} =
- Bugzilla::Field::Choice->type($field)
- ->match({ visibility_value_id => $self->id });
- }
- $self->{controlled_values} = \%controlled_values;
- return $self->{controlled_values};
+ my $self = shift;
+ return $self->{controlled_values} if defined $self->{controlled_values};
+ my $fields = $self->field->controls_values_of;
+ my %controlled_values;
+ require Bugzilla::Field::Choice;
+ foreach my $field (@$fields) {
+ $controlled_values{$field->name} = Bugzilla::Field::Choice->type($field)
+ ->match({visibility_value_id => $self->id});
+ }
+ $self->{controlled_values} = \%controlled_values;
+ return $self->{controlled_values};
}
sub controlled_values_array {
- my ($self) = @_;
- my $values = $self->controlled_values;
- return [map { @{ $values->{$_} } } keys %$values];
+ my ($self) = @_;
+ my $values = $self->controlled_values;
+ return [map { @{$values->{$_}} } keys %$values];
}
sub is_visible_on_bug {
- my ($self, $bug) = @_;
+ my ($self, $bug) = @_;
- # Values currently set on the bug are always shown.
- return 1 if $self->is_set_on_bug($bug);
+ # Values currently set on the bug are always shown.
+ return 1 if $self->is_set_on_bug($bug);
- # Inactive values are, otherwise, never shown.
- return 0 if !$self->is_active;
+ # Inactive values are, otherwise, never shown.
+ return 0 if !$self->is_active;
- # Values without a visibility value are, otherwise, always shown.
- my $visibility_value = $self->visibility_value;
- return 1 if !$visibility_value;
+ # Values without a visibility value are, otherwise, always shown.
+ my $visibility_value = $self->visibility_value;
+ return 1 if !$visibility_value;
- # Values with a visibility value are only shown if the visibility
- # value is set on the bug.
- return $visibility_value->is_set_on_bug($bug);
+ # Values with a visibility value are only shown if the visibility
+ # value is set on the bug.
+ return $visibility_value->is_set_on_bug($bug);
}
sub is_set_on_bug {
- my ($self, $bug) = @_;
- my $field_name = $self->FIELD_NAME;
- # This allows bug/create/create.html.tmpl to pass in a hashref that
- # looks like a bug object.
- my $value = blessed($bug) ? $bug->$field_name : $bug->{$field_name};
- $value = $value->name if blessed($value);
- return 0 if !defined $value;
-
- if ($self->field->type == FIELD_TYPE_BUG_URLS
- or $self->field->type == FIELD_TYPE_MULTI_SELECT)
- {
- return grep($_ eq $self->name, @$value) ? 1 : 0;
- }
- return $value eq $self->name ? 1 : 0;
+ my ($self, $bug) = @_;
+ my $field_name = $self->FIELD_NAME;
+
+ # This allows bug/create/create.html.tmpl to pass in a hashref that
+ # looks like a bug object.
+ my $value = blessed($bug) ? $bug->$field_name : $bug->{$field_name};
+ $value = $value->name if blessed($value);
+ return 0 if !defined $value;
+
+ if ( $self->field->type == FIELD_TYPE_BUG_URLS
+ or $self->field->type == FIELD_TYPE_MULTI_SELECT)
+ {
+ return grep($_ eq $self->name, @$value) ? 1 : 0;
+ }
+ return $value eq $self->name ? 1 : 0;
}
1;
diff --git a/Bugzilla/Flag.pm b/Bugzilla/Flag.pm
index 50474b885..b065f4ea5 100644
--- a/Bugzilla/Flag.pm
+++ b/Bugzilla/Flag.pm
@@ -58,8 +58,9 @@ use parent qw(Bugzilla::Object Exporter);
#### Initialization ####
###############################
-use constant DB_TABLE => 'flags';
+use constant DB_TABLE => 'flags';
use constant LIST_ORDER => 'id';
+
# Flags are tracked in bugs_activity.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
@@ -68,35 +69,32 @@ use constant AUDIT_REMOVES => 0;
use constant SKIP_REQUESTEE_ON_ERROR => 1;
sub DB_COLUMNS {
- my $dbh = Bugzilla->dbh;
- return qw(
- id
- type_id
- bug_id
- attach_id
- requestee_id
- setter_id
- status),
- $dbh->sql_date_format('creation_date', '%Y.%m.%d %H:%i:%s') .
- ' AS creation_date',
- $dbh->sql_date_format('modification_date', '%Y.%m.%d %H:%i:%s') .
- ' AS modification_date';
+ my $dbh = Bugzilla->dbh;
+ return qw(
+ id
+ type_id
+ bug_id
+ attach_id
+ requestee_id
+ setter_id
+ status),
+ $dbh->sql_date_format('creation_date', '%Y.%m.%d %H:%i:%s')
+ . ' AS creation_date',
+ $dbh->sql_date_format('modification_date', '%Y.%m.%d %H:%i:%s')
+ . ' AS modification_date';
}
use constant UPDATE_COLUMNS => qw(
- requestee_id
- setter_id
- status
- type_id
+ requestee_id
+ setter_id
+ status
+ type_id
);
-use constant VALIDATORS => {
-};
+use constant VALIDATORS => {};
-use constant UPDATE_VALIDATORS => {
- setter => \&_check_setter,
- status => \&_check_status,
-};
+use constant UPDATE_VALIDATORS =>
+ {setter => \&_check_setter, status => \&_check_status,};
###############################
#### Accessors ######
@@ -138,15 +136,15 @@ Returns the timestamp when the flag was last modified.
=cut
-sub id { return $_[0]->{'id'}; }
-sub name { return $_[0]->type->name; }
-sub type_id { return $_[0]->{'type_id'}; }
-sub bug_id { return $_[0]->{'bug_id'}; }
-sub attach_id { return $_[0]->{'attach_id'}; }
-sub status { return $_[0]->{'status'}; }
-sub setter_id { return $_[0]->{'setter_id'}; }
-sub requestee_id { return $_[0]->{'requestee_id'}; }
-sub creation_date { return $_[0]->{'creation_date'}; }
+sub id { return $_[0]->{'id'}; }
+sub name { return $_[0]->type->name; }
+sub type_id { return $_[0]->{'type_id'}; }
+sub bug_id { return $_[0]->{'bug_id'}; }
+sub attach_id { return $_[0]->{'attach_id'}; }
+sub status { return $_[0]->{'status'}; }
+sub setter_id { return $_[0]->{'setter_id'}; }
+sub requestee_id { return $_[0]->{'requestee_id'}; }
+sub creation_date { return $_[0]->{'creation_date'}; }
sub modification_date { return $_[0]->{'modification_date'}; }
###############################
@@ -180,40 +178,42 @@ is an attachment flag, else undefined.
=cut
sub type {
- my $self = shift;
+ my $self = shift;
- return $self->{'type'} ||= new Bugzilla::FlagType($self->{'type_id'});
+ return $self->{'type'} ||= new Bugzilla::FlagType($self->{'type_id'});
}
sub setter {
- my $self = shift;
+ my $self = shift;
- return $self->{'setter'} ||= new Bugzilla::User({ id => $self->{'setter_id'}, cache => 1 });
+ return $self->{'setter'}
+ ||= new Bugzilla::User({id => $self->{'setter_id'}, cache => 1});
}
sub requestee {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{'requestee'} && $self->{'requestee_id'}) {
- $self->{'requestee'} = new Bugzilla::User({ id => $self->{'requestee_id'}, cache => 1 });
- }
- return $self->{'requestee'};
+ if (!defined $self->{'requestee'} && $self->{'requestee_id'}) {
+ $self->{'requestee'}
+ = new Bugzilla::User({id => $self->{'requestee_id'}, cache => 1});
+ }
+ return $self->{'requestee'};
}
sub attachment {
- my $self = shift;
- return undef unless $self->attach_id;
+ my $self = shift;
+ return undef unless $self->attach_id;
- require Bugzilla::Attachment;
- return $self->{'attachment'}
- ||= new Bugzilla::Attachment({ id => $self->attach_id, cache => 1 });
+ require Bugzilla::Attachment;
+ return $self->{'attachment'}
+ ||= new Bugzilla::Attachment({id => $self->attach_id, cache => 1});
}
sub bug {
- my $self = shift;
+ my $self = shift;
- require Bugzilla::Bug;
- return $self->{'bug'} ||= new Bugzilla::Bug({ id => $self->bug_id, cache => 1 });
+ require Bugzilla::Bug;
+ return $self->{'bug'} ||= new Bugzilla::Bug({id => $self->bug_id, cache => 1});
}
################################
@@ -235,26 +235,27 @@ and returns an array of matching records.
=cut
sub match {
- my $class = shift;
- my ($criteria) = @_;
-
- # If the caller specified only bug or attachment flags,
- # limit the query to those kinds of flags.
- if (my $type = delete $criteria->{'target_type'}) {
- if ($type eq 'bug') {
- $criteria->{'attach_id'} = IS_NULL;
- }
- elsif (!defined $criteria->{'attach_id'}) {
- $criteria->{'attach_id'} = NOT_NULL;
- }
+ my $class = shift;
+ my ($criteria) = @_;
+
+ # If the caller specified only bug or attachment flags,
+ # limit the query to those kinds of flags.
+ if (my $type = delete $criteria->{'target_type'}) {
+ if ($type eq 'bug') {
+ $criteria->{'attach_id'} = IS_NULL;
}
- # Flag->snapshot() calls Flag->match() with bug_id and attach_id
- # as hash keys, even if attach_id is undefined.
- if (exists $criteria->{'attach_id'} && !defined $criteria->{'attach_id'}) {
- $criteria->{'attach_id'} = IS_NULL;
+ elsif (!defined $criteria->{'attach_id'}) {
+ $criteria->{'attach_id'} = NOT_NULL;
}
+ }
+
+ # Flag->snapshot() calls Flag->match() with bug_id and attach_id
+ # as hash keys, even if attach_id is undefined.
+ if (exists $criteria->{'attach_id'} && !defined $criteria->{'attach_id'}) {
+ $criteria->{'attach_id'} = IS_NULL;
+ }
- return $class->SUPER::match(@_);
+ return $class->SUPER::match(@_);
}
=pod
@@ -272,8 +273,8 @@ and returns an array of matching records.
=cut
sub count {
- my $class = shift;
- return scalar @{$class->match(@_)};
+ my $class = shift;
+ return scalar @{$class->match(@_)};
}
######################################################################
@@ -281,144 +282,156 @@ sub count {
######################################################################
sub set_flag {
- my ($class, $obj, $params) = @_;
-
- my ($bug, $attachment, $obj_flag, $requestee_changed);
- if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) {
- $attachment = $obj;
- $bug = $attachment->bug;
+ my ($class, $obj, $params) = @_;
+
+ my ($bug, $attachment, $obj_flag, $requestee_changed);
+ if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) {
+ $attachment = $obj;
+ $bug = $attachment->bug;
+ }
+ elsif (blessed($obj) && $obj->isa('Bugzilla::Bug')) {
+ $bug = $obj;
+ }
+ else {
+ ThrowCodeError('flag_unexpected_object', {'caller' => ref $obj});
+ }
+
+ # Make sure the user can change flags
+ my $privs;
+ $bug->check_can_change_field('flagtypes.name', 0, 1, \$privs)
+ || ThrowUserError('illegal_change',
+ {field => 'flagtypes.name', privs => $privs});
+
+ # Update (or delete) an existing flag.
+ if ($params->{id}) {
+ my $flag = $class->check({id => $params->{id}});
+
+ # Security check: make sure the flag belongs to the bug/attachment.
+ # We don't check that the user editing the flag can see
+ # the bug/attachment. That's the job of the caller.
+ ($attachment && $flag->attach_id && $attachment->id == $flag->attach_id)
+ || (!$attachment && !$flag->attach_id && $bug->id == $flag->bug_id)
+ || ThrowCodeError('invalid_flag_association',
+ {bug_id => $bug->id, attach_id => $attachment ? $attachment->id : undef});
+
+ # Extract the current flag object from the object.
+ my ($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types};
+
+ # If no flagtype can be found for this flag, this means the bug is being
+ # moved into a product/component where the flag is no longer valid.
+ # So either we can attach the flag to another flagtype having the same
+ # name, or we remove the flag.
+ if (!$obj_flagtype) {
+ my $success = $flag->retarget($obj);
+ return unless $success;
+
+ ($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types};
+ push(@{$obj_flagtype->{flags}}, $flag);
}
- elsif (blessed($obj) && $obj->isa('Bugzilla::Bug')) {
- $bug = $obj;
- }
- else {
- ThrowCodeError('flag_unexpected_object', { 'caller' => ref $obj });
+ ($obj_flag) = grep { $_->id == $flag->id } @{$obj_flagtype->{flags}};
+
+ # If the flag has the correct type but cannot be found above, this means
+ # the flag is going to be removed (e.g. because this is a pending request
+ # and the attachment is being marked as obsolete).
+ return unless $obj_flag;
+
+ ($obj_flag, $requestee_changed)
+ = $class->_validate($obj_flag, $obj_flagtype, $params, $bug, $attachment);
+ }
+
+ # Create a new flag.
+ elsif ($params->{type_id}) {
+
+ # Don't bother validating types the user didn't touch.
+ return if $params->{status} eq 'X';
+
+ my $flagtype = Bugzilla::FlagType->check({id => $params->{type_id}});
+
+ # Security check: make sure the flag type belongs to the bug/attachment.
+ ( $attachment
+ && $flagtype->target_type eq 'attachment'
+ && scalar(grep { $_->id == $flagtype->id } @{$attachment->flag_types}))
+ || (!$attachment
+ && $flagtype->target_type eq 'bug'
+ && scalar(grep { $_->id == $flagtype->id } @{$bug->flag_types}))
+ || ThrowCodeError('invalid_flag_association',
+ {bug_id => $bug->id, attach_id => $attachment ? $attachment->id : undef});
+
+ # Make sure the flag type is active.
+ $flagtype->is_active
+ || ThrowCodeError('flag_type_inactive', {type => $flagtype->name});
+
+ # Extract the current flagtype object from the object.
+ my ($obj_flagtype) = grep { $_->id == $flagtype->id } @{$obj->flag_types};
+
+ # We cannot create a new flag if there is already one and this
+ # flag type is not multiplicable.
+ if (!$flagtype->is_multiplicable) {
+ if (scalar @{$obj_flagtype->{flags}}) {
+ ThrowUserError('flag_type_not_multiplicable', {type => $flagtype});
+ }
}
- # Make sure the user can change flags
- my $privs;
- $bug->check_can_change_field('flagtypes.name', 0, 1, \$privs)
- || ThrowUserError('illegal_change',
- { field => 'flagtypes.name', privs => $privs });
-
- # Update (or delete) an existing flag.
- if ($params->{id}) {
- my $flag = $class->check({ id => $params->{id} });
-
- # Security check: make sure the flag belongs to the bug/attachment.
- # We don't check that the user editing the flag can see
- # the bug/attachment. That's the job of the caller.
- ($attachment && $flag->attach_id && $attachment->id == $flag->attach_id)
- || (!$attachment && !$flag->attach_id && $bug->id == $flag->bug_id)
- || ThrowCodeError('invalid_flag_association',
- { bug_id => $bug->id,
- attach_id => $attachment ? $attachment->id : undef });
-
- # Extract the current flag object from the object.
- my ($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types};
- # If no flagtype can be found for this flag, this means the bug is being
- # moved into a product/component where the flag is no longer valid.
- # So either we can attach the flag to another flagtype having the same
- # name, or we remove the flag.
- if (!$obj_flagtype) {
- my $success = $flag->retarget($obj);
- return unless $success;
-
- ($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types};
- push(@{$obj_flagtype->{flags}}, $flag);
- }
- ($obj_flag) = grep { $_->id == $flag->id } @{$obj_flagtype->{flags}};
- # If the flag has the correct type but cannot be found above, this means
- # the flag is going to be removed (e.g. because this is a pending request
- # and the attachment is being marked as obsolete).
- return unless $obj_flag;
-
- ($obj_flag, $requestee_changed) =
- $class->_validate($obj_flag, $obj_flagtype, $params, $bug, $attachment);
- }
- # Create a new flag.
- elsif ($params->{type_id}) {
- # Don't bother validating types the user didn't touch.
- return if $params->{status} eq 'X';
-
- my $flagtype = Bugzilla::FlagType->check({ id => $params->{type_id} });
- # Security check: make sure the flag type belongs to the bug/attachment.
- ($attachment && $flagtype->target_type eq 'attachment'
- && scalar(grep { $_->id == $flagtype->id } @{$attachment->flag_types}))
- || (!$attachment && $flagtype->target_type eq 'bug'
- && scalar(grep { $_->id == $flagtype->id } @{$bug->flag_types}))
- || ThrowCodeError('invalid_flag_association',
- { bug_id => $bug->id,
- attach_id => $attachment ? $attachment->id : undef });
-
- # Make sure the flag type is active.
- $flagtype->is_active
- || ThrowCodeError('flag_type_inactive', { type => $flagtype->name });
-
- # Extract the current flagtype object from the object.
- my ($obj_flagtype) = grep { $_->id == $flagtype->id } @{$obj->flag_types};
-
- # We cannot create a new flag if there is already one and this
- # flag type is not multiplicable.
- if (!$flagtype->is_multiplicable) {
- if (scalar @{$obj_flagtype->{flags}}) {
- ThrowUserError('flag_type_not_multiplicable', { type => $flagtype });
- }
- }
-
- ($obj_flag, $requestee_changed) =
- $class->_validate(undef, $obj_flagtype, $params, $bug, $attachment);
- }
- else {
- ThrowCodeError('param_required', { function => $class . '->set_flag',
- param => 'id/type_id' });
- }
-
- if ($obj_flag
- && $requestee_changed
- && $obj_flag->requestee_id
- && $obj_flag->requestee->setting('requestee_cc') eq 'on')
- {
- $bug->add_cc($obj_flag->requestee);
- }
+ ($obj_flag, $requestee_changed)
+ = $class->_validate(undef, $obj_flagtype, $params, $bug, $attachment);
+ }
+ else {
+ ThrowCodeError('param_required',
+ {function => $class . '->set_flag', param => 'id/type_id'});
+ }
+
+ if ( $obj_flag
+ && $requestee_changed
+ && $obj_flag->requestee_id
+ && $obj_flag->requestee->setting('requestee_cc') eq 'on')
+ {
+ $bug->add_cc($obj_flag->requestee);
+ }
}
sub _validate {
- my ($class, $flag, $flag_type, $params, $bug, $attachment) = @_;
-
- # If it's a new flag, let's create it now.
- my $obj_flag = $flag || bless({ type_id => $flag_type->id,
- status => '',
- bug_id => $bug->id,
- attach_id => $attachment ?
- $attachment->id : undef},
- $class);
-
- my $old_status = $obj_flag->status;
- my $old_requestee_id = $obj_flag->requestee_id;
+ my ($class, $flag, $flag_type, $params, $bug, $attachment) = @_;
- $obj_flag->_set_status($params->{status});
- $obj_flag->_set_requestee($params->{requestee}, $bug, $attachment, $params->{skip_roe});
-
- # The requestee ID can be undefined.
- my $requestee_changed = ($obj_flag->requestee_id || 0) != ($old_requestee_id || 0);
-
- # The setter field MUST NOT be updated if neither the status
- # nor the requestee fields changed.
- if (($obj_flag->status ne $old_status) || $requestee_changed) {
- $obj_flag->_set_setter($params->{setter});
- }
-
- # If the flag is deleted, remove it from the list.
- if ($obj_flag->status eq 'X') {
- @{$flag_type->{flags}} = grep { $_->id != $obj_flag->id } @{$flag_type->{flags}};
- return;
- }
- # Add the newly created flag to the list.
- elsif (!$obj_flag->id) {
- push(@{$flag_type->{flags}}, $obj_flag);
- }
- return wantarray ? ($obj_flag, $requestee_changed) : $obj_flag;
+ # If it's a new flag, let's create it now.
+ my $obj_flag = $flag || bless(
+ {
+ type_id => $flag_type->id,
+ status => '',
+ bug_id => $bug->id,
+ attach_id => $attachment ? $attachment->id : undef
+ },
+ $class
+ );
+
+ my $old_status = $obj_flag->status;
+ my $old_requestee_id = $obj_flag->requestee_id;
+
+ $obj_flag->_set_status($params->{status});
+ $obj_flag->_set_requestee($params->{requestee}, $bug, $attachment,
+ $params->{skip_roe});
+
+ # The requestee ID can be undefined.
+ my $requestee_changed
+ = ($obj_flag->requestee_id || 0) != ($old_requestee_id || 0);
+
+ # The setter field MUST NOT be updated if neither the status
+ # nor the requestee fields changed.
+ if (($obj_flag->status ne $old_status) || $requestee_changed) {
+ $obj_flag->_set_setter($params->{setter});
+ }
+
+ # If the flag is deleted, remove it from the list.
+ if ($obj_flag->status eq 'X') {
+ @{$flag_type->{flags}}
+ = grep { $_->id != $obj_flag->id } @{$flag_type->{flags}};
+ return;
+ }
+
+ # Add the newly created flag to the list.
+ elsif (!$obj_flag->id) {
+ push(@{$flag_type->{flags}}, $obj_flag);
+ }
+ return wantarray ? ($obj_flag, $requestee_changed) : $obj_flag;
}
=pod
@@ -434,143 +447,151 @@ Creates a flag record in the database.
=cut
sub create {
- my ($class, $flag, $timestamp) = @_;
- $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ my ($class, $flag, $timestamp) = @_;
+ $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- my $params = {};
- my @columns = grep { $_ ne 'id' } $class->_get_db_columns;
+ my $params = {};
+ my @columns = grep { $_ ne 'id' } $class->_get_db_columns;
- # Some columns use date formatting so use alias instead
- @columns = map { /\s+AS\s+(.*)$/ ? $1 : $_ } @columns;
+ # Some columns use date formatting so use alias instead
+ @columns = map { /\s+AS\s+(.*)$/ ? $1 : $_ } @columns;
- $params->{$_} = $flag->{$_} foreach @columns;
+ $params->{$_} = $flag->{$_} foreach @columns;
- $params->{creation_date} = $params->{modification_date} = $timestamp;
+ $params->{creation_date} = $params->{modification_date} = $timestamp;
- $flag = $class->SUPER::create($params);
- return $flag;
+ $flag = $class->SUPER::create($params);
+ return $flag;
}
sub update {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $timestamp = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
-
- my $changes = $self->SUPER::update(@_);
-
- if (scalar(keys %$changes)) {
- $dbh->do('UPDATE flags SET modification_date = ? WHERE id = ?',
- undef, ($timestamp, $self->id));
- $self->{'modification_date'} =
- format_time($timestamp, '%Y.%m.%d %T', Bugzilla->local_timezone);
- Bugzilla->memcached->clear({ table => 'flags', id => $self->id });
- }
- return $changes;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $timestamp = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ my $changes = $self->SUPER::update(@_);
+
+ if (scalar(keys %$changes)) {
+ $dbh->do('UPDATE flags SET modification_date = ? WHERE id = ?',
+ undef, ($timestamp, $self->id));
+ $self->{'modification_date'}
+ = format_time($timestamp, '%Y.%m.%d %T', Bugzilla->local_timezone);
+ Bugzilla->memcached->clear({table => 'flags', id => $self->id});
+ }
+ return $changes;
}
sub snapshot {
- my ($class, $flags) = @_;
-
- my @summaries;
- foreach my $flag (@$flags) {
- my $summary = $flag->setter->nick . ':' . $flag->type->name . $flag->status;
- $summary .= "(" . $flag->requestee->login . ")" if $flag->requestee;
- push(@summaries, $summary);
- }
- return @summaries;
+ my ($class, $flags) = @_;
+
+ my @summaries;
+ foreach my $flag (@$flags) {
+ my $summary = $flag->setter->nick . ':' . $flag->type->name . $flag->status;
+ $summary .= "(" . $flag->requestee->login . ")" if $flag->requestee;
+ push(@summaries, $summary);
+ }
+ return @summaries;
}
sub update_activity {
- my ($class, $old_summaries, $new_summaries) = @_;
+ my ($class, $old_summaries, $new_summaries) = @_;
- my ($removed, $added) = diff_arrays($old_summaries, $new_summaries);
- if (scalar @$removed || scalar @$added) {
- # Remove flag requester/setter information
- foreach (@$removed, @$added) { s/^[^:]+:// }
+ my ($removed, $added) = diff_arrays($old_summaries, $new_summaries);
+ if (scalar @$removed || scalar @$added) {
- $removed = join(", ", @$removed);
- $added = join(", ", @$added);
- return ($removed, $added);
- }
- return ();
+ # Remove flag requester/setter information
+ foreach (@$removed, @$added) {s/^[^:]+://}
+
+ $removed = join(", ", @$removed);
+ $added = join(", ", @$added);
+ return ($removed, $added);
+ }
+ return ();
}
sub update_flags {
- my ($class, $self, $old_self, $timestamp) = @_;
+ my ($class, $self, $old_self, $timestamp) = @_;
- my @old_summaries = $class->snapshot($old_self->flags);
- my %old_flags = map { $_->id => $_ } @{$old_self->flags};
+ my @old_summaries = $class->snapshot($old_self->flags);
+ my %old_flags = map { $_->id => $_ } @{$old_self->flags};
- foreach my $new_flag (@{$self->flags}) {
- if (!$new_flag->id) {
- # This is a new flag.
- my $flag = $class->create($new_flag, $timestamp);
- $new_flag->{id} = $flag->id;
- $class->notify($new_flag, undef, $self, $timestamp);
- }
- else {
- my $changes = $new_flag->update($timestamp);
- if (scalar(keys %$changes)) {
- $class->notify($new_flag, $old_flags{$new_flag->id}, $self, $timestamp);
- }
- delete $old_flags{$new_flag->id};
- }
+ foreach my $new_flag (@{$self->flags}) {
+ if (!$new_flag->id) {
+
+ # This is a new flag.
+ my $flag = $class->create($new_flag, $timestamp);
+ $new_flag->{id} = $flag->id;
+ $class->notify($new_flag, undef, $self, $timestamp);
}
- # These flags have been deleted.
- foreach my $old_flag (values %old_flags) {
- $class->notify(undef, $old_flag, $self, $timestamp);
- $old_flag->remove_from_db();
+ else {
+ my $changes = $new_flag->update($timestamp);
+ if (scalar(keys %$changes)) {
+ $class->notify($new_flag, $old_flags{$new_flag->id}, $self, $timestamp);
+ }
+ delete $old_flags{$new_flag->id};
}
-
- # If the bug has been moved into another product or component,
- # we must also take care of attachment flags which are no longer valid,
- # as well as all bug flags which haven't been forgotten above.
- if ($self->isa('Bugzilla::Bug')
- && ($self->{_old_product_name} || $self->{_old_component_name}))
+ }
+
+ # These flags have been deleted.
+ foreach my $old_flag (values %old_flags) {
+ $class->notify(undef, $old_flag, $self, $timestamp);
+ $old_flag->remove_from_db();
+ }
+
+ # If the bug has been moved into another product or component,
+ # we must also take care of attachment flags which are no longer valid,
+ # as well as all bug flags which haven't been forgotten above.
+ if ($self->isa('Bugzilla::Bug')
+ && ($self->{_old_product_name} || $self->{_old_component_name}))
+ {
+ my @removed = $class->force_cleanup($self);
+ push(@old_summaries, @removed);
+ }
+
+ my @new_summaries = $class->snapshot($self->flags);
+ my @changes = $class->update_activity(\@old_summaries, \@new_summaries);
+
+ Bugzilla::Hook::process(
+ 'flag_end_of_update',
{
- my @removed = $class->force_cleanup($self);
- push(@old_summaries, @removed);
+ object => $self,
+ timestamp => $timestamp,
+ old_flags => \@old_summaries,
+ new_flags => \@new_summaries,
}
-
- my @new_summaries = $class->snapshot($self->flags);
- my @changes = $class->update_activity(\@old_summaries, \@new_summaries);
-
- Bugzilla::Hook::process('flag_end_of_update', { object => $self,
- timestamp => $timestamp,
- old_flags => \@old_summaries,
- new_flags => \@new_summaries,
- });
- return @changes;
+ );
+ return @changes;
}
sub retarget {
- my ($self, $obj) = @_;
-
- my @flagtypes = grep { $_->name eq $self->type->name } @{$obj->flag_types};
-
- my $success = 0;
- foreach my $flagtype (@flagtypes) {
- next if !$flagtype->is_active;
- next if (!$flagtype->is_multiplicable && scalar @{$flagtype->{flags}});
- next unless (($self->status eq '?' && $self->setter->can_request_flag($flagtype))
- || $self->setter->can_set_flag($flagtype));
-
- $self->{type_id} = $flagtype->id;
- delete $self->{type};
- $success = 1;
- last;
- }
- return $success;
+ my ($self, $obj) = @_;
+
+ my @flagtypes = grep { $_->name eq $self->type->name } @{$obj->flag_types};
+
+ my $success = 0;
+ foreach my $flagtype (@flagtypes) {
+ next if !$flagtype->is_active;
+ next if (!$flagtype->is_multiplicable && scalar @{$flagtype->{flags}});
+ next
+ unless (($self->status eq '?' && $self->setter->can_request_flag($flagtype))
+ || $self->setter->can_set_flag($flagtype));
+
+ $self->{type_id} = $flagtype->id;
+ delete $self->{type};
+ $success = 1;
+ last;
+ }
+ return $success;
}
# In case the bug's product/component has changed, clear flags that are
# no longer valid.
sub force_cleanup {
- my ($class, $bug) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($class, $bug) = @_;
+ my $dbh = Bugzilla->dbh;
- my $flag_ids = $dbh->selectcol_arrayref(
- 'SELECT DISTINCT flags.id
+ my $flag_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT flags.id
FROM flags
INNER JOIN bugs
ON flags.bug_id = bugs.bug_id
@@ -578,48 +599,50 @@ sub force_cleanup {
ON flags.type_id = i.type_id
AND (bugs.product_id = i.product_id OR i.product_id IS NULL)
AND (bugs.component_id = i.component_id OR i.component_id IS NULL)
- WHERE bugs.bug_id = ? AND i.type_id IS NULL',
- undef, $bug->id);
+ WHERE bugs.bug_id = ? AND i.type_id IS NULL', undef, $bug->id
+ );
- my @removed = $class->force_retarget($flag_ids, $bug);
+ my @removed = $class->force_retarget($flag_ids, $bug);
- $flag_ids = $dbh->selectcol_arrayref(
- 'SELECT DISTINCT flags.id
+ $flag_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT flags.id
FROM flags, bugs, flagexclusions e
WHERE bugs.bug_id = ?
AND flags.bug_id = bugs.bug_id
AND flags.type_id = e.type_id
AND (bugs.product_id = e.product_id OR e.product_id IS NULL)
AND (bugs.component_id = e.component_id OR e.component_id IS NULL)',
- undef, $bug->id);
+ undef, $bug->id
+ );
- push(@removed , $class->force_retarget($flag_ids, $bug));
- return @removed;
+ push(@removed, $class->force_retarget($flag_ids, $bug));
+ return @removed;
}
sub force_retarget {
- my ($class, $flag_ids, $bug) = @_;
- my $dbh = Bugzilla->dbh;
-
- my $flags = $class->new_from_list($flag_ids);
- my @removed;
- foreach my $flag (@$flags) {
- # $bug is undefined when e.g. editing inclusion and exclusion lists.
- my $obj = $flag->attachment || $bug || $flag->bug;
- my $is_retargetted = $flag->retarget($obj);
- if ($is_retargetted) {
- $dbh->do('UPDATE flags SET type_id = ? WHERE id = ?',
- undef, ($flag->type_id, $flag->id));
- Bugzilla->memcached->clear({ table => 'flags', id => $flag->id });
- }
- else {
- # Track deleted attachment flags.
- push(@removed, $class->snapshot([$flag])) if $flag->attach_id;
- $class->notify(undef, $flag, $bug || $flag->bug);
- $flag->remove_from_db();
- }
+ my ($class, $flag_ids, $bug) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $flags = $class->new_from_list($flag_ids);
+ my @removed;
+ foreach my $flag (@$flags) {
+
+ # $bug is undefined when e.g. editing inclusion and exclusion lists.
+ my $obj = $flag->attachment || $bug || $flag->bug;
+ my $is_retargetted = $flag->retarget($obj);
+ if ($is_retargetted) {
+ $dbh->do('UPDATE flags SET type_id = ? WHERE id = ?',
+ undef, ($flag->type_id, $flag->id));
+ Bugzilla->memcached->clear({table => 'flags', id => $flag->id});
+ }
+ else {
+ # Track deleted attachment flags.
+ push(@removed, $class->snapshot([$flag])) if $flag->attach_id;
+ $class->notify(undef, $flag, $bug || $flag->bug);
+ $flag->remove_from_db();
}
- return @removed;
+ }
+ return @removed;
}
###############################
@@ -627,164 +650,178 @@ sub force_retarget {
###############################
sub _set_requestee {
- my ($self, $requestee, $bug, $attachment, $skip_requestee_on_error) = @_;
+ my ($self, $requestee, $bug, $attachment, $skip_requestee_on_error) = @_;
- $self->{requestee} =
- $self->_check_requestee($requestee, $bug, $attachment, $skip_requestee_on_error);
+ $self->{requestee} = $self->_check_requestee($requestee, $bug, $attachment,
+ $skip_requestee_on_error);
- $self->{requestee_id} =
- $self->{requestee} ? $self->{requestee}->id : undef;
+ $self->{requestee_id} = $self->{requestee} ? $self->{requestee}->id : undef;
}
sub _set_setter {
- my ($self, $setter) = @_;
+ my ($self, $setter) = @_;
- $self->set('setter', $setter);
- $self->{setter_id} = $self->setter->id;
+ $self->set('setter', $setter);
+ $self->{setter_id} = $self->setter->id;
}
sub _set_status {
- my ($self, $status) = @_;
+ my ($self, $status) = @_;
- # Store the old flag status. It's needed by _check_setter().
- $self->{_old_status} = $self->status;
- $self->set('status', $status);
+ # Store the old flag status. It's needed by _check_setter().
+ $self->{_old_status} = $self->status;
+ $self->set('status', $status);
}
sub _check_requestee {
- my ($self, $requestee, $bug, $attachment, $skip_requestee_on_error) = @_;
-
- # If the flag status is not "?", then no requestee can be defined.
- return undef if ($self->status ne '?');
-
- # Store this value before updating the flag object.
- my $old_requestee = $self->requestee ? $self->requestee->login : '';
-
- if ($self->status eq '?' && $requestee) {
- $requestee = Bugzilla::User->check($requestee);
+ my ($self, $requestee, $bug, $attachment, $skip_requestee_on_error) = @_;
+
+ # If the flag status is not "?", then no requestee can be defined.
+ return undef if ($self->status ne '?');
+
+ # Store this value before updating the flag object.
+ my $old_requestee = $self->requestee ? $self->requestee->login : '';
+
+ if ($self->status eq '?' && $requestee) {
+ $requestee = Bugzilla::User->check($requestee);
+ }
+ else {
+ undef $requestee;
+ }
+
+ if ($requestee && $requestee->login ne $old_requestee) {
+
+ # Make sure the user didn't specify a requestee unless the flag
+ # is specifically requestable. For existing flags, if the requestee
+ # was set before the flag became specifically unrequestable, the
+ # user can either remove them or leave them alone.
+ ThrowUserError('flag_type_requestee_disabled', {type => $self->type})
+ if !$self->type->is_requesteeble;
+
+ # You can't ask a disabled account, as they don't have the ability to
+ # set the flag.
+ ThrowUserError('flag_requestee_disabled', {requestee => $requestee})
+ if !$requestee->is_enabled;
+
+ # Make sure the requestee can see the bug.
+ # Note that can_see_bug() will query the DB, so if the bug
+ # is being added/removed from some groups and these changes
+ # haven't been committed to the DB yet, they won't be taken
+ # into account here. In this case, old group restrictions matter.
+ # However, if the user has just been changed to the assignee,
+ # qa_contact, or added to the cc list of the bug and the bug
+ # is cclist_accessible, the requestee is allowed.
+ if (
+ !$requestee->can_see_bug($self->bug_id)
+ && ( !$bug->cclist_accessible
+ || !grep($_->id == $requestee->id, @{$bug->cc_users})
+ && $requestee->id != $bug->assigned_to->id
+ && (!$bug->qa_contact || $requestee->id != $bug->qa_contact->id))
+ )
+ {
+ if ($skip_requestee_on_error) {
+ undef $requestee;
+ }
+ else {
+ ThrowUserError(
+ 'flag_requestee_unauthorized',
+ {
+ flag_type => $self->type,
+ requestee => $requestee,
+ bug_id => $self->bug_id,
+ attach_id => $self->attach_id
+ }
+ );
+ }
}
- else {
+
+ # Make sure the requestee can see the private attachment.
+ elsif ($self->attach_id && $attachment->isprivate && !$requestee->is_insider) {
+ if ($skip_requestee_on_error) {
undef $requestee;
+ }
+ else {
+ ThrowUserError(
+ 'flag_requestee_unauthorized_attachment',
+ {
+ flag_type => $self->type,
+ requestee => $requestee,
+ bug_id => $self->bug_id,
+ attach_id => $self->attach_id
+ }
+ );
+ }
}
- if ($requestee && $requestee->login ne $old_requestee) {
- # Make sure the user didn't specify a requestee unless the flag
- # is specifically requestable. For existing flags, if the requestee
- # was set before the flag became specifically unrequestable, the
- # user can either remove them or leave them alone.
- ThrowUserError('flag_type_requestee_disabled', { type => $self->type })
- if !$self->type->is_requesteeble;
-
- # You can't ask a disabled account, as they don't have the ability to
- # set the flag.
- ThrowUserError('flag_requestee_disabled', { requestee => $requestee })
- if !$requestee->is_enabled;
-
- # Make sure the requestee can see the bug.
- # Note that can_see_bug() will query the DB, so if the bug
- # is being added/removed from some groups and these changes
- # haven't been committed to the DB yet, they won't be taken
- # into account here. In this case, old group restrictions matter.
- # However, if the user has just been changed to the assignee,
- # qa_contact, or added to the cc list of the bug and the bug
- # is cclist_accessible, the requestee is allowed.
- if (!$requestee->can_see_bug($self->bug_id)
- && (!$bug->cclist_accessible
- || !grep($_->id == $requestee->id, @{ $bug->cc_users })
- && $requestee->id != $bug->assigned_to->id
- && (!$bug->qa_contact || $requestee->id != $bug->qa_contact->id)))
- {
- if ($skip_requestee_on_error) {
- undef $requestee;
- }
- else {
- ThrowUserError('flag_requestee_unauthorized',
- { flag_type => $self->type,
- requestee => $requestee,
- bug_id => $self->bug_id,
- attach_id => $self->attach_id });
- }
- }
- # Make sure the requestee can see the private attachment.
- elsif ($self->attach_id && $attachment->isprivate && !$requestee->is_insider) {
- if ($skip_requestee_on_error) {
- undef $requestee;
- }
- else {
- ThrowUserError('flag_requestee_unauthorized_attachment',
- { flag_type => $self->type,
- requestee => $requestee,
- bug_id => $self->bug_id,
- attach_id => $self->attach_id });
- }
- }
- # Make sure the user is allowed to set the flag.
- elsif (!$requestee->can_set_flag($self->type)) {
- if ($skip_requestee_on_error) {
- undef $requestee;
- }
- else {
- ThrowUserError('flag_requestee_needs_privs',
- {'requestee' => $requestee,
- 'flagtype' => $self->type});
- }
- }
+ # Make sure the user is allowed to set the flag.
+ elsif (!$requestee->can_set_flag($self->type)) {
+ if ($skip_requestee_on_error) {
+ undef $requestee;
+ }
+ else {
+ ThrowUserError('flag_requestee_needs_privs',
+ {'requestee' => $requestee, 'flagtype' => $self->type});
+ }
}
- return $requestee;
+ }
+ return $requestee;
}
sub _check_setter {
- my ($self, $setter) = @_;
-
- # By default, the currently logged in user is the setter.
- $setter ||= Bugzilla->user;
- (blessed($setter) && $setter->isa('Bugzilla::User') && $setter->id)
- || ThrowUserError('invalid_user');
-
- # set_status() has already been called. So this refers
- # to the new flag status.
- my $status = $self->status;
-
- # Make sure the user is authorized to modify flags, see bug 180879:
- # - The flag exists and is unchanged.
- # - The flag setter can unset flag.
- # - Users in the request_group can clear pending requests and set flags
- # and can rerequest set flags.
- # - Users in the grant_group can set/clear flags, including "+" and "-".
- unless (($status eq $self->{_old_status})
- || ($status eq 'X' && $setter->id == Bugzilla->user->id)
- || (($status eq 'X' || $status eq '?')
- && $setter->can_request_flag($self->type))
- || $setter->can_set_flag($self->type))
- {
- ThrowUserError('flag_update_denied',
- { name => $self->type->name,
- status => $status,
- old_status => $self->{_old_status} });
- }
-
- # If the request is being retargetted, we don't update
- # the setter, so that the setter gets the notification.
- if ($status eq '?' && $self->{_old_status} eq '?') {
- return $self->setter;
- }
- return $setter;
+ my ($self, $setter) = @_;
+
+ # By default, the currently logged in user is the setter.
+ $setter ||= Bugzilla->user;
+ (blessed($setter) && $setter->isa('Bugzilla::User') && $setter->id)
+ || ThrowUserError('invalid_user');
+
+ # set_status() has already been called. So this refers
+ # to the new flag status.
+ my $status = $self->status;
+
+ # Make sure the user is authorized to modify flags, see bug 180879:
+ # - The flag exists and is unchanged.
+ # - The flag setter can unset flag.
+ # - Users in the request_group can clear pending requests and set flags
+ # and can rerequest set flags.
+ # - Users in the grant_group can set/clear flags, including "+" and "-".
+ unless (($status eq $self->{_old_status})
+ || ($status eq 'X' && $setter->id == Bugzilla->user->id)
+ || (($status eq 'X' || $status eq '?')
+ && $setter->can_request_flag($self->type))
+ || $setter->can_set_flag($self->type))
+ {
+ ThrowUserError(
+ 'flag_update_denied',
+ {
+ name => $self->type->name,
+ status => $status,
+ old_status => $self->{_old_status}
+ }
+ );
+ }
+
+ # If the request is being retargetted, we don't update
+ # the setter, so that the setter gets the notification.
+ if ($status eq '?' && $self->{_old_status} eq '?') {
+ return $self->setter;
+ }
+ return $setter;
}
sub _check_status {
- my ($self, $status) = @_;
-
- # - Make sure the status is valid.
- # - Make sure the user didn't request the flag unless it's requestable.
- # If the flag existed and was requested before it became unrequestable,
- # leave it as is.
- if (!grep($status eq $_ , qw(X + - ?))
- || ($status eq '?' && $self->status ne '?' && !$self->type->is_requestable))
- {
- ThrowUserError('flag_status_invalid', { id => $self->id,
- status => $status });
- }
- return $status;
+ my ($self, $status) = @_;
+
+ # - Make sure the status is valid.
+ # - Make sure the user didn't request the flag unless it's requestable.
+ # If the flag existed and was requested before it became unrequestable,
+ # leave it as is.
+ if (!grep($status eq $_, qw(X + - ?))
+ || ($status eq '?' && $self->status ne '?' && !$self->type->is_requestable))
+ {
+ ThrowUserError('flag_status_invalid', {id => $self->id, status => $status});
+ }
+ return $status;
}
######################################################################
@@ -805,128 +842,146 @@ array of hashes. This array is then passed to Flag::create().
=cut
sub extract_flags_from_cgi {
- my ($class, $bug, $attachment, $vars, $skip) = @_;
- my $cgi = Bugzilla->cgi;
-
- my $match_status = Bugzilla::User::match_field({
- '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
- }, undef, $skip);
-
- $vars->{'match_field'} = 'requestee';
- if ($match_status == USER_MATCH_FAILED) {
- $vars->{'message'} = 'user_match_failed';
- }
- elsif ($match_status == USER_MATCH_MULTIPLE) {
- $vars->{'message'} = 'user_match_multiple';
+ my ($class, $bug, $attachment, $vars, $skip) = @_;
+ my $cgi = Bugzilla->cgi;
+
+ my $match_status
+ = Bugzilla::User::match_field(
+ {'^requestee(_type)?-(\d+)$' => {'type' => 'multi'},},
+ undef, $skip);
+
+ $vars->{'match_field'} = 'requestee';
+ if ($match_status == USER_MATCH_FAILED) {
+ $vars->{'message'} = 'user_match_failed';
+ }
+ elsif ($match_status == USER_MATCH_MULTIPLE) {
+ $vars->{'message'} = 'user_match_multiple';
+ }
+
+ # Extract a list of flag type IDs from field names.
+ my @flagtype_ids = map(/^flag_type-(\d+)$/ ? $1 : (), $cgi->param());
+ @flagtype_ids = grep($cgi->param("flag_type-$_") ne 'X', @flagtype_ids);
+
+ # Extract a list of existing flag IDs.
+ my @flag_ids = map(/^flag-(\d+)$/ ? $1 : (), $cgi->param());
+
+ return ([], []) unless (scalar(@flagtype_ids) || scalar(@flag_ids));
+
+ my (@new_flags, @flags);
+ foreach my $flag_id (@flag_ids) {
+ my $flag = $class->new($flag_id);
+
+ # If the flag no longer exists, ignore it.
+ next unless $flag;
+
+ my $status = $cgi->param("flag-$flag_id");
+
+ # If the user entered more than one name into the requestee field
+ # (i.e. they want more than one person to set the flag) we can reuse
+ # the existing flag for the first person (who may well be the existing
+ # requestee), but we have to create new flags for each additional requestee.
+ my @requestees = $cgi->param("requestee-$flag_id");
+ my $requestee_email;
+ if ($status eq "?" && scalar(@requestees) > 1 && $flag->type->is_multiplicable)
+ {
+ # The first person, for which we'll reuse the existing flag.
+ $requestee_email = shift(@requestees);
+
+ # Create new flags like the existing one for each additional person.
+ foreach my $login (@requestees) {
+ push(
+ @new_flags,
+ {
+ type_id => $flag->type_id,
+ status => "?",
+ requestee => $login,
+ skip_roe => $skip
+ }
+ );
+ }
}
+ elsif ($status eq "?" && scalar(@requestees)) {
- # Extract a list of flag type IDs from field names.
- my @flagtype_ids = map(/^flag_type-(\d+)$/ ? $1 : (), $cgi->param());
- @flagtype_ids = grep($cgi->param("flag_type-$_") ne 'X', @flagtype_ids);
-
- # Extract a list of existing flag IDs.
- my @flag_ids = map(/^flag-(\d+)$/ ? $1 : (), $cgi->param());
-
- return ([], []) unless (scalar(@flagtype_ids) || scalar(@flag_ids));
-
- my (@new_flags, @flags);
- foreach my $flag_id (@flag_ids) {
- my $flag = $class->new($flag_id);
- # If the flag no longer exists, ignore it.
- next unless $flag;
-
- my $status = $cgi->param("flag-$flag_id");
-
- # If the user entered more than one name into the requestee field
- # (i.e. they want more than one person to set the flag) we can reuse
- # the existing flag for the first person (who may well be the existing
- # requestee), but we have to create new flags for each additional requestee.
- my @requestees = $cgi->param("requestee-$flag_id");
- my $requestee_email;
- if ($status eq "?"
- && scalar(@requestees) > 1
- && $flag->type->is_multiplicable)
- {
- # The first person, for which we'll reuse the existing flag.
- $requestee_email = shift(@requestees);
-
- # Create new flags like the existing one for each additional person.
- foreach my $login (@requestees) {
- push(@new_flags, { type_id => $flag->type_id,
- status => "?",
- requestee => $login,
- skip_roe => $skip });
- }
- }
- elsif ($status eq "?" && scalar(@requestees)) {
- # If there are several requestees and the flag type is not multiplicable,
- # this will fail. But that's the job of the validator to complain. All
- # we do here is to extract and convert data from the CGI.
- $requestee_email = trim($cgi->param("requestee-$flag_id") || '');
- }
-
- push(@flags, { id => $flag_id,
- status => $status,
- requestee => $requestee_email,
- skip_roe => $skip });
+ # If there are several requestees and the flag type is not multiplicable,
+ # this will fail. But that's the job of the validator to complain. All
+ # we do here is to extract and convert data from the CGI.
+ $requestee_email = trim($cgi->param("requestee-$flag_id") || '');
}
- # Get a list of active flag types available for this product/component.
- my $flag_types = Bugzilla::FlagType::match(
- { 'product_id' => $bug->{'product_id'},
- 'component_id' => $bug->{'component_id'},
- 'is_active' => 1 });
-
- foreach my $flagtype_id (@flagtype_ids) {
- # Checks if there are unexpected flags for the product/component.
- if (!scalar(grep { $_->id == $flagtype_id } @$flag_types)) {
- $vars->{'message'} = 'unexpected_flag_types';
- last;
- }
+ push(
+ @flags,
+ {
+ id => $flag_id,
+ status => $status,
+ requestee => $requestee_email,
+ skip_roe => $skip
+ }
+ );
+ }
+
+ # Get a list of active flag types available for this product/component.
+ my $flag_types = Bugzilla::FlagType::match({
+ 'product_id' => $bug->{'product_id'},
+ 'component_id' => $bug->{'component_id'},
+ 'is_active' => 1
+ });
+
+ foreach my $flagtype_id (@flagtype_ids) {
+
+ # Checks if there are unexpected flags for the product/component.
+ if (!scalar(grep { $_->id == $flagtype_id } @$flag_types)) {
+ $vars->{'message'} = 'unexpected_flag_types';
+ last;
}
-
- foreach my $flag_type (@$flag_types) {
- my $type_id = $flag_type->id;
-
- # Bug flags are only valid for bugs, and attachment flags are
- # only valid for attachments. So don't mix both.
- next unless ($flag_type->target_type eq 'bug' xor $attachment);
-
- # We are only interested in flags the user tries to create.
- next unless scalar(grep { $_ == $type_id } @flagtype_ids);
-
- # Get the number of flags of this type already set for this target.
- my $has_flags = $class->count(
- { 'type_id' => $type_id,
- 'target_type' => $attachment ? 'attachment' : 'bug',
- 'bug_id' => $bug->bug_id,
- 'attach_id' => $attachment ? $attachment->id : undef });
-
- # Do not create a new flag of this type if this flag type is
- # not multiplicable and already has a flag set.
- next if (!$flag_type->is_multiplicable && $has_flags);
-
- my $status = $cgi->param("flag_type-$type_id");
- trick_taint($status);
-
- my @logins = $cgi->param("requestee_type-$type_id");
- if ($status eq "?" && scalar(@logins)) {
- foreach my $login (@logins) {
- push (@new_flags, { type_id => $type_id,
- status => $status,
- requestee => $login,
- skip_roe => $skip });
- last unless $flag_type->is_multiplicable;
- }
- }
- else {
- push (@new_flags, { type_id => $type_id,
- status => $status });
- }
+ }
+
+ foreach my $flag_type (@$flag_types) {
+ my $type_id = $flag_type->id;
+
+ # Bug flags are only valid for bugs, and attachment flags are
+ # only valid for attachments. So don't mix both.
+ next unless ($flag_type->target_type eq 'bug' xor $attachment);
+
+ # We are only interested in flags the user tries to create.
+ next unless scalar(grep { $_ == $type_id } @flagtype_ids);
+
+ # Get the number of flags of this type already set for this target.
+ my $has_flags = $class->count({
+ 'type_id' => $type_id,
+ 'target_type' => $attachment ? 'attachment' : 'bug',
+ 'bug_id' => $bug->bug_id,
+ 'attach_id' => $attachment ? $attachment->id : undef
+ });
+
+ # Do not create a new flag of this type if this flag type is
+ # not multiplicable and already has a flag set.
+ next if (!$flag_type->is_multiplicable && $has_flags);
+
+ my $status = $cgi->param("flag_type-$type_id");
+ trick_taint($status);
+
+ my @logins = $cgi->param("requestee_type-$type_id");
+ if ($status eq "?" && scalar(@logins)) {
+ foreach my $login (@logins) {
+ push(
+ @new_flags,
+ {
+ type_id => $type_id,
+ status => $status,
+ requestee => $login,
+ skip_roe => $skip
+ }
+ );
+ last unless $flag_type->is_multiplicable;
+ }
+ }
+ else {
+ push(@new_flags, {type_id => $type_id, status => $status});
}
+ }
- # Return the list of flags to update and/or to create.
- return (\@flags, \@new_flags);
+ # Return the list of flags to update and/or to create.
+ return (\@flags, \@new_flags);
}
=pod
@@ -944,100 +999,109 @@ from the previous sub-routine as it is called for changing multiple bugs
=cut
sub multi_extract_flags_from_cgi {
- my ($class, $bug, $vars, $skip) = @_;
- my $cgi = Bugzilla->cgi;
-
- my $match_status = Bugzilla::User::match_field({
- '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
- }, undef, $skip);
-
- $vars->{'match_field'} = 'requestee';
- if ($match_status == USER_MATCH_FAILED) {
- $vars->{'message'} = 'user_match_failed';
+ my ($class, $bug, $vars, $skip) = @_;
+ my $cgi = Bugzilla->cgi;
+
+ my $match_status
+ = Bugzilla::User::match_field(
+ {'^requestee(_type)?-(\d+)$' => {'type' => 'multi'},},
+ undef, $skip);
+
+ $vars->{'match_field'} = 'requestee';
+ if ($match_status == USER_MATCH_FAILED) {
+ $vars->{'message'} = 'user_match_failed';
+ }
+ elsif ($match_status == USER_MATCH_MULTIPLE) {
+ $vars->{'message'} = 'user_match_multiple';
+ }
+
+ # Extract a list of flag type IDs from field names.
+ my @flagtype_ids = map(/^flag_type-(\d+)$/ ? $1 : (), $cgi->param());
+
+ my (@new_flags, @flags);
+
+ # Get a list of active flag types available for this product/component.
+ my $flag_types = Bugzilla::FlagType::match({
+ 'product_id' => $bug->{'product_id'},
+ 'component_id' => $bug->{'component_id'},
+ 'is_active' => 1
+ });
+
+ foreach my $flagtype_id (@flagtype_ids) {
+
+ # Checks if there are unexpected flags for the product/component.
+ if (!scalar(grep { $_->id == $flagtype_id } @$flag_types)) {
+ $vars->{'message'} = 'unexpected_flag_types';
+ last;
}
- elsif ($match_status == USER_MATCH_MULTIPLE) {
- $vars->{'message'} = 'user_match_multiple';
- }
-
- # Extract a list of flag type IDs from field names.
- my @flagtype_ids = map(/^flag_type-(\d+)$/ ? $1 : (), $cgi->param());
-
- my (@new_flags, @flags);
-
- # Get a list of active flag types available for this product/component.
- my $flag_types = Bugzilla::FlagType::match(
- { 'product_id' => $bug->{'product_id'},
- 'component_id' => $bug->{'component_id'},
- 'is_active' => 1 });
-
- foreach my $flagtype_id (@flagtype_ids) {
- # Checks if there are unexpected flags for the product/component.
- if (!scalar(grep { $_->id == $flagtype_id } @$flag_types)) {
- $vars->{'message'} = 'unexpected_flag_types';
- last;
- }
- }
-
- foreach my $flag_type (@$flag_types) {
- my $type_id = $flag_type->id;
-
- # Bug flags are only valid for bugs
- next unless ($flag_type->target_type eq 'bug');
-
- # We are only interested in flags the user tries to create.
- next unless scalar(grep { $_ == $type_id } @flagtype_ids);
-
- # Get the flags of this type already set for this bug.
- my $current_flags = $class->match(
- { 'type_id' => $type_id,
- 'target_type' => 'bug',
- 'bug_id' => $bug->bug_id });
-
- # We will update existing flags (instead of creating new ones)
- # if the flag exists and the user has not chosen the 'always add'
- # option
- my $update = scalar(@$current_flags) && ! $cgi->param("flags_add-$type_id");
-
- my $status = $cgi->param("flag_type-$type_id");
- trick_taint($status);
-
- my @logins = $cgi->param("requestee_type-$type_id");
- if ($status eq "?" && scalar(@logins)) {
- foreach my $login (@logins) {
- if ($update) {
- foreach my $current_flag (@$current_flags) {
- push (@flags, { id => $current_flag->id,
- status => $status,
- requestee => $login,
- skip_roe => $skip });
- }
- }
- else {
- push (@new_flags, { type_id => $type_id,
- status => $status,
- requestee => $login,
- skip_roe => $skip });
- }
-
- last unless $flag_type->is_multiplicable;
- }
+ }
+
+ foreach my $flag_type (@$flag_types) {
+ my $type_id = $flag_type->id;
+
+ # Bug flags are only valid for bugs
+ next unless ($flag_type->target_type eq 'bug');
+
+ # We are only interested in flags the user tries to create.
+ next unless scalar(grep { $_ == $type_id } @flagtype_ids);
+
+ # Get the flags of this type already set for this bug.
+ my $current_flags = $class->match(
+ {'type_id' => $type_id, 'target_type' => 'bug', 'bug_id' => $bug->bug_id});
+
+ # We will update existing flags (instead of creating new ones)
+ # if the flag exists and the user has not chosen the 'always add'
+ # option
+ my $update = scalar(@$current_flags) && !$cgi->param("flags_add-$type_id");
+
+ my $status = $cgi->param("flag_type-$type_id");
+ trick_taint($status);
+
+ my @logins = $cgi->param("requestee_type-$type_id");
+ if ($status eq "?" && scalar(@logins)) {
+ foreach my $login (@logins) {
+ if ($update) {
+ foreach my $current_flag (@$current_flags) {
+ push(
+ @flags,
+ {
+ id => $current_flag->id,
+ status => $status,
+ requestee => $login,
+ skip_roe => $skip
+ }
+ );
+ }
}
else {
- if ($update) {
- foreach my $current_flag (@$current_flags) {
- push (@flags, { id => $current_flag->id,
- status => $status });
- }
- }
- else {
- push (@new_flags, { type_id => $type_id,
- status => $status });
+ push(
+ @new_flags,
+ {
+ type_id => $type_id,
+ status => $status,
+ requestee => $login,
+ skip_roe => $skip
}
+ );
}
+
+ last unless $flag_type->is_multiplicable;
+ }
}
+ else {
+ if ($update) {
+ foreach my $current_flag (@$current_flags) {
+ push(@flags, {id => $current_flag->id, status => $status});
+ }
+ }
+ else {
+ push(@new_flags, {type_id => $type_id, status => $status});
+ }
+ }
+ }
- # Return the list of flags to update and/or to create.
- return (\@flags, \@new_flags);
+ # Return the list of flags to update and/or to create.
+ return (\@flags, \@new_flags);
}
=pod
@@ -1054,113 +1118,121 @@ or deleted.
=cut
sub notify {
- my ($class, $flag, $old_flag, $obj, $timestamp) = @_;
-
- my ($bug, $attachment);
- if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) {
- $attachment = $obj;
- $bug = $attachment->bug;
- }
- elsif (blessed($obj) && $obj->isa('Bugzilla::Bug')) {
- $bug = $obj;
- }
- else {
- # Not a good time to throw an error.
- return;
- }
-
- my $addressee;
- # If the flag is set to '?', maybe the requestee wants a notification.
- if ($flag && $flag->requestee_id
- && (!$old_flag || ($old_flag->requestee_id || 0) != $flag->requestee_id))
- {
- if ($flag->requestee->wants_mail([EVT_FLAG_REQUESTED])) {
- $addressee = $flag->requestee;
- }
- }
- elsif ($old_flag && $old_flag->status eq '?'
- && (!$flag || $flag->status ne '?'))
- {
- if ($old_flag->setter->wants_mail([EVT_REQUESTED_FLAG])) {
- $addressee = $old_flag->setter;
- }
+ my ($class, $flag, $old_flag, $obj, $timestamp) = @_;
+
+ my ($bug, $attachment);
+ if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) {
+ $attachment = $obj;
+ $bug = $attachment->bug;
+ }
+ elsif (blessed($obj) && $obj->isa('Bugzilla::Bug')) {
+ $bug = $obj;
+ }
+ else {
+ # Not a good time to throw an error.
+ return;
+ }
+
+ my $addressee;
+
+ # If the flag is set to '?', maybe the requestee wants a notification.
+ if ( $flag
+ && $flag->requestee_id
+ && (!$old_flag || ($old_flag->requestee_id || 0) != $flag->requestee_id))
+ {
+ if ($flag->requestee->wants_mail([EVT_FLAG_REQUESTED])) {
+ $addressee = $flag->requestee;
}
-
- my $cc_list = $flag ? $flag->type->cc_list : $old_flag->type->cc_list;
- # Is there someone to notify?
- return unless ($addressee || $cc_list);
-
- # The email client will display the Date: header in the desired timezone,
- # so we can always use UTC here.
- $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- $timestamp = format_time($timestamp, '%a, %d %b %Y %T %z', 'UTC');
-
- # If the target bug is restricted to one or more groups, then we need
- # to make sure we don't send email about it to unauthorized users
- # on the request type's CC: list, so we have to trawl the list for users
- # not in those groups or email addresses that don't have an account.
- my @bug_in_groups = grep {$_->{'ison'} || $_->{'mandatory'}} @{$bug->groups};
- my $attachment_is_private = $attachment ? $attachment->isprivate : undef;
-
- my %recipients;
- foreach my $cc (split(/[, ]+/, $cc_list)) {
- my $ccuser = new Bugzilla::User({ name => $cc });
- next if (scalar(@bug_in_groups) && (!$ccuser || !$ccuser->can_see_bug($bug->bug_id)));
- next if $attachment_is_private && (!$ccuser || !$ccuser->is_insider);
- # Prevent duplicated entries due to case sensitivity.
- $cc = $ccuser ? $ccuser->email : $cc;
- $recipients{$cc} = $ccuser;
- }
-
- # Only notify if the addressee is allowed to receive the email.
- if ($addressee && $addressee->email_enabled) {
- $recipients{$addressee->email} = $addressee;
- }
- # Process and send notification for each recipient.
- # If there are users in the CC list who don't have an account,
- # use the default language for email notifications.
- my $default_lang;
- if (grep { !$_ } values %recipients) {
- $default_lang = Bugzilla::User->new()->setting('lang');
- }
-
- # Get comments on the bug
- my $all_comments = $bug->comments({ after => $bug->lastdiffed });
- @$all_comments = grep { $_->type || $_->body =~ /\S/ } @$all_comments;
-
- # Get public only comments
- my $public_comments = [ grep { !$_->is_private } @$all_comments ];
-
- foreach my $to (keys %recipients) {
- # Add threadingmarker to allow flag notification emails to be the
- # threaded similar to normal bug change emails.
- my $thread_user_id = $recipients{$to} ? $recipients{$to}->id : 0;
-
- # We only want to show private comments to users in the is_insider group
- my $comments = $recipients{$to} && $recipients{$to}->is_insider
- ? $all_comments : $public_comments;
-
- my $vars = {
- flag => $flag,
- old_flag => $old_flag,
- to => $to,
- date => $timestamp,
- bug => $bug,
- attachment => $attachment,
- threadingmarker => build_thread_marker($bug->id, $thread_user_id),
- new_comments => $comments,
- };
-
- my $lang = $recipients{$to} ?
- $recipients{$to}->setting('lang') : $default_lang;
-
- my $template = Bugzilla->template_inner($lang);
- my $message;
- $template->process("email/flagmail.txt.tmpl", $vars, \$message)
- || ThrowTemplateError($template->error());
-
- MessageToMTA($message);
+ }
+ elsif ($old_flag
+ && $old_flag->status eq '?'
+ && (!$flag || $flag->status ne '?'))
+ {
+ if ($old_flag->setter->wants_mail([EVT_REQUESTED_FLAG])) {
+ $addressee = $old_flag->setter;
}
+ }
+
+ my $cc_list = $flag ? $flag->type->cc_list : $old_flag->type->cc_list;
+
+ # Is there someone to notify?
+ return unless ($addressee || $cc_list);
+
+ # The email client will display the Date: header in the desired timezone,
+ # so we can always use UTC here.
+ $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $timestamp = format_time($timestamp, '%a, %d %b %Y %T %z', 'UTC');
+
+ # If the target bug is restricted to one or more groups, then we need
+ # to make sure we don't send email about it to unauthorized users
+ # on the request type's CC: list, so we have to trawl the list for users
+ # not in those groups or email addresses that don't have an account.
+ my @bug_in_groups = grep { $_->{'ison'} || $_->{'mandatory'} } @{$bug->groups};
+ my $attachment_is_private = $attachment ? $attachment->isprivate : undef;
+
+ my %recipients;
+ foreach my $cc (split(/[, ]+/, $cc_list)) {
+ my $ccuser = new Bugzilla::User({name => $cc});
+ next
+ if (scalar(@bug_in_groups)
+ && (!$ccuser || !$ccuser->can_see_bug($bug->bug_id)));
+ next if $attachment_is_private && (!$ccuser || !$ccuser->is_insider);
+
+ # Prevent duplicated entries due to case sensitivity.
+ $cc = $ccuser ? $ccuser->email : $cc;
+ $recipients{$cc} = $ccuser;
+ }
+
+ # Only notify if the addressee is allowed to receive the email.
+ if ($addressee && $addressee->email_enabled) {
+ $recipients{$addressee->email} = $addressee;
+ }
+
+ # Process and send notification for each recipient.
+ # If there are users in the CC list who don't have an account,
+ # use the default language for email notifications.
+ my $default_lang;
+ if (grep { !$_ } values %recipients) {
+ $default_lang = Bugzilla::User->new()->setting('lang');
+ }
+
+ # Get comments on the bug
+ my $all_comments = $bug->comments({after => $bug->lastdiffed});
+ @$all_comments = grep { $_->type || $_->body =~ /\S/ } @$all_comments;
+
+ # Get public only comments
+ my $public_comments = [grep { !$_->is_private } @$all_comments];
+
+ foreach my $to (keys %recipients) {
+
+ # Add threadingmarker to allow flag notification emails to be the
+ # threaded similar to normal bug change emails.
+ my $thread_user_id = $recipients{$to} ? $recipients{$to}->id : 0;
+
+ # We only want to show private comments to users in the is_insider group
+ my $comments = $recipients{$to}
+ && $recipients{$to}->is_insider ? $all_comments : $public_comments;
+
+ my $vars = {
+ flag => $flag,
+ old_flag => $old_flag,
+ to => $to,
+ date => $timestamp,
+ bug => $bug,
+ attachment => $attachment,
+ threadingmarker => build_thread_marker($bug->id, $thread_user_id),
+ new_comments => $comments,
+ };
+
+ my $lang = $recipients{$to} ? $recipients{$to}->setting('lang') : $default_lang;
+
+ my $template = Bugzilla->template_inner($lang);
+ my $message;
+ $template->process("email/flagmail.txt.tmpl", $vars, \$message)
+ || ThrowTemplateError($template->error());
+
+ MessageToMTA($message);
+ }
}
# This is an internal function used by $bug->flag_types
@@ -1168,39 +1240,42 @@ sub notify {
# flag types and existing flags set on them. You should never
# call this function directly.
sub _flag_types {
- my ($class, $vars) = @_;
-
- my $target_type = $vars->{target_type};
- my $flags;
-
- # Retrieve all existing flags for this bug/attachment.
- if ($target_type eq 'bug') {
- my $bug_id = delete $vars->{bug_id};
- $flags = $class->match({target_type => 'bug', bug_id => $bug_id});
- }
- elsif ($target_type eq 'attachment') {
- my $attach_id = delete $vars->{attach_id};
- $flags = $class->match({attach_id => $attach_id});
- }
- else {
- ThrowCodeError('bad_arg', {argument => 'target_type',
- function => $class . '->_flag_types'});
- }
-
- # Get all available flag types for the given product and component.
- my $cache = Bugzilla->request_cache->{flag_types_per_component}->{$vars->{target_type}} ||= {};
- my $flag_data = $cache->{$vars->{component_id}} ||= Bugzilla::FlagType::match($vars);
- my $flag_types = dclone($flag_data);
-
- $_->{flags} = [] foreach @$flag_types;
- my %flagtypes = map { $_->id => $_ } @$flag_types;
-
- # Group existing flags per type, and skip those becoming invalid
- # (which can happen when a bug is being moved into a new product
- # or component).
- @$flags = grep { exists $flagtypes{$_->type_id} } @$flags;
- push(@{$flagtypes{$_->type_id}->{flags}}, $_) foreach @$flags;
- return $flag_types;
+ my ($class, $vars) = @_;
+
+ my $target_type = $vars->{target_type};
+ my $flags;
+
+ # Retrieve all existing flags for this bug/attachment.
+ if ($target_type eq 'bug') {
+ my $bug_id = delete $vars->{bug_id};
+ $flags = $class->match({target_type => 'bug', bug_id => $bug_id});
+ }
+ elsif ($target_type eq 'attachment') {
+ my $attach_id = delete $vars->{attach_id};
+ $flags = $class->match({attach_id => $attach_id});
+ }
+ else {
+ ThrowCodeError('bad_arg',
+ {argument => 'target_type', function => $class . '->_flag_types'});
+ }
+
+ # Get all available flag types for the given product and component.
+ my $cache
+ = Bugzilla->request_cache->{flag_types_per_component}->{$vars->{target_type}}
+ ||= {};
+ my $flag_data = $cache->{$vars->{component_id}}
+ ||= Bugzilla::FlagType::match($vars);
+ my $flag_types = dclone($flag_data);
+
+ $_->{flags} = [] foreach @$flag_types;
+ my %flagtypes = map { $_->id => $_ } @$flag_types;
+
+ # Group existing flags per type, and skip those becoming invalid
+ # (which can happen when a bug is being moved into a new product
+ # or component).
+ @$flags = grep { exists $flagtypes{$_->type_id} } @$flags;
+ push(@{$flagtypes{$_->type_id}->{flags}}, $_) foreach @$flags;
+ return $flag_types;
}
1;
diff --git a/Bugzilla/FlagType.pm b/Bugzilla/FlagType.pm
index 72b3f64c1..a08ee83b9 100644
--- a/Bugzilla/FlagType.pm
+++ b/Bugzilla/FlagType.pm
@@ -49,113 +49,114 @@ use parent qw(Bugzilla::Object);
#### Initialization ####
###############################
-use constant DB_TABLE => 'flagtypes';
+use constant DB_TABLE => 'flagtypes';
use constant LIST_ORDER => 'sortkey, name';
use constant DB_COLUMNS => qw(
- id
- name
- description
- cc_list
- target_type
- sortkey
- is_active
- is_requestable
- is_requesteeble
- is_multiplicable
- grant_group_id
- request_group_id
+ id
+ name
+ description
+ cc_list
+ target_type
+ sortkey
+ is_active
+ is_requestable
+ is_requesteeble
+ is_multiplicable
+ grant_group_id
+ request_group_id
);
use constant UPDATE_COLUMNS => qw(
- name
- description
- cc_list
- sortkey
- is_active
- is_requestable
- is_requesteeble
- is_multiplicable
- grant_group_id
- request_group_id
+ name
+ description
+ cc_list
+ sortkey
+ is_active
+ is_requestable
+ is_requesteeble
+ is_multiplicable
+ grant_group_id
+ request_group_id
);
use constant VALIDATORS => {
- name => \&_check_name,
- description => \&_check_description,
- cc_list => \&_check_cc_list,
- target_type => \&_check_target_type,
- sortkey => \&_check_sortkey,
- is_active => \&Bugzilla::Object::check_boolean,
- is_requestable => \&Bugzilla::Object::check_boolean,
- is_requesteeble => \&Bugzilla::Object::check_boolean,
- is_multiplicable => \&Bugzilla::Object::check_boolean,
- grant_group => \&_check_group,
- request_group => \&_check_group,
+ name => \&_check_name,
+ description => \&_check_description,
+ cc_list => \&_check_cc_list,
+ target_type => \&_check_target_type,
+ sortkey => \&_check_sortkey,
+ is_active => \&Bugzilla::Object::check_boolean,
+ is_requestable => \&Bugzilla::Object::check_boolean,
+ is_requesteeble => \&Bugzilla::Object::check_boolean,
+ is_multiplicable => \&Bugzilla::Object::check_boolean,
+ grant_group => \&_check_group,
+ request_group => \&_check_group,
};
-use constant UPDATE_VALIDATORS => {
- grant_group_id => \&_check_group,
- request_group_id => \&_check_group,
-};
+use constant UPDATE_VALIDATORS =>
+ {grant_group_id => \&_check_group, request_group_id => \&_check_group,};
###############################
sub create {
- my $class = shift;
- my $dbh = Bugzilla->dbh;
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
- $dbh->bz_start_transaction();
+ $class->check_required_create_fields(@_);
+ my $params = $class->run_create_validators(@_);
- $class->check_required_create_fields(@_);
- my $params = $class->run_create_validators(@_);
- # In the DB, only the first character of the target type is stored.
- $params->{target_type} = substr($params->{target_type}, 0, 1);
+ # In the DB, only the first character of the target type is stored.
+ $params->{target_type} = substr($params->{target_type}, 0, 1);
- # Extract everything which is not a valid column name.
- $params->{grant_group_id} = delete $params->{grant_group};
- $params->{request_group_id} = delete $params->{request_group};
- my $inclusions = delete $params->{inclusions};
- my $exclusions = delete $params->{exclusions};
+ # Extract everything which is not a valid column name.
+ $params->{grant_group_id} = delete $params->{grant_group};
+ $params->{request_group_id} = delete $params->{request_group};
+ my $inclusions = delete $params->{inclusions};
+ my $exclusions = delete $params->{exclusions};
- my $flagtype = $class->insert_create_data($params);
+ my $flagtype = $class->insert_create_data($params);
- $flagtype->set_clusions({ inclusions => $inclusions,
- exclusions => $exclusions });
- $flagtype->update();
+ $flagtype->set_clusions({inclusions => $inclusions, exclusions => $exclusions});
+ $flagtype->update();
- $dbh->bz_commit_transaction();
- return $flagtype;
+ $dbh->bz_commit_transaction();
+ return $flagtype;
}
sub update {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $flag_id = $self->id;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $flag_id = $self->id;
- $dbh->bz_start_transaction();
- my $changes = $self->SUPER::update(@_);
+ $dbh->bz_start_transaction();
+ my $changes = $self->SUPER::update(@_);
- # Update the flaginclusions and flagexclusions tables.
- foreach my $category ('inclusions', 'exclusions') {
- next unless delete $self->{"_update_$category"};
+ # Update the flaginclusions and flagexclusions tables.
+ foreach my $category ('inclusions', 'exclusions') {
+ next unless delete $self->{"_update_$category"};
- $dbh->do("DELETE FROM flag$category WHERE type_id = ?", undef, $flag_id);
+ $dbh->do("DELETE FROM flag$category WHERE type_id = ?", undef, $flag_id);
- my $sth = $dbh->prepare("INSERT INTO flag$category
- (type_id, product_id, component_id) VALUES (?, ?, ?)");
+ my $sth = $dbh->prepare(
+ "INSERT INTO flag$category
+ (type_id, product_id, component_id) VALUES (?, ?, ?)"
+ );
- foreach my $prod_comp (values %{$self->{$category}}) {
- my ($prod_id, $comp_id) = split(':', $prod_comp);
- $prod_id ||= undef;
- $comp_id ||= undef;
- $sth->execute($flag_id, $prod_id, $comp_id);
- }
- $changes->{$category} = [0, 1];
+ foreach my $prod_comp (values %{$self->{$category}}) {
+ my ($prod_id, $comp_id) = split(':', $prod_comp);
+ $prod_id ||= undef;
+ $comp_id ||= undef;
+ $sth->execute($flag_id, $prod_id, $comp_id);
}
+ $changes->{$category} = [0, 1];
+ }
- # Clear existing flags for bugs/attachments in categories no longer on
- # the list of inclusions or that have been added to the list of exclusions.
- my $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
+ # Clear existing flags for bugs/attachments in categories no longer on
+ # the list of inclusions or that have been added to the list of exclusions.
+ my $flag_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT flags.id
FROM flags
INNER JOIN bugs
ON flags.bug_id = bugs.bug_id
@@ -166,11 +167,13 @@ sub update {
AND (bugs.component_id = i.component_id
OR i.component_id IS NULL))
WHERE flags.type_id = ?
- AND i.type_id IS NULL',
- undef, $self->id);
- Bugzilla::Flag->force_retarget($flag_ids);
+ AND i.type_id IS NULL', undef,
+ $self->id
+ );
+ Bugzilla::Flag->force_retarget($flag_ids);
- $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
+ $flag_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT flags.id
FROM flags
INNER JOIN bugs
ON flags.bug_id = bugs.bug_id
@@ -181,26 +184,29 @@ sub update {
OR e.product_id IS NULL)
AND (bugs.component_id = e.component_id
OR e.component_id IS NULL)',
- undef, $self->id);
- Bugzilla::Flag->force_retarget($flag_ids);
-
- # Silently remove requestees from flags which are no longer
- # specifically requestable.
- if (!$self->is_requesteeble) {
- my $ids = $dbh->selectcol_arrayref(
- 'SELECT id FROM flags WHERE type_id = ? AND requestee_id IS NOT NULL',
- undef, $self->id);
-
- if (@$ids) {
- $dbh->do('UPDATE flags SET requestee_id = NULL WHERE ' . $dbh->sql_in('id', $ids));
- foreach my $id (@$ids) {
- Bugzilla->memcached->clear({ table => 'flags', id => $id });
- }
- }
+ undef, $self->id
+ );
+ Bugzilla::Flag->force_retarget($flag_ids);
+
+ # Silently remove requestees from flags which are no longer
+ # specifically requestable.
+ if (!$self->is_requesteeble) {
+ my $ids
+ = $dbh->selectcol_arrayref(
+ 'SELECT id FROM flags WHERE type_id = ? AND requestee_id IS NOT NULL',
+ undef, $self->id);
+
+ if (@$ids) {
+ $dbh->do(
+ 'UPDATE flags SET requestee_id = NULL WHERE ' . $dbh->sql_in('id', $ids));
+ foreach my $id (@$ids) {
+ Bugzilla->memcached->clear({table => 'flags', id => $id});
+ }
}
+ }
- $dbh->bz_commit_transaction();
- return $changes;
+ $dbh->bz_commit_transaction();
+ return $changes;
}
###############################
@@ -259,172 +265,174 @@ Returns the sortkey of the flagtype.
=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 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 sortkey { return $_[0]->{'sortkey'}; }
sub request_group_id { return $_[0]->{'request_group_id'}; }
-sub grant_group_id { return $_[0]->{'grant_group_id'}; }
+sub grant_group_id { return $_[0]->{'grant_group_id'}; }
################################
# Validators
################################
sub _check_name {
- my ($invocant, $name) = @_;
+ my ($invocant, $name) = @_;
- $name = trim($name);
- ($name && $name !~ /[\s,]/ && length($name) <= 50)
- || ThrowUserError('flag_type_name_invalid', { name => $name });
- return $name;
+ $name = trim($name);
+ ($name && $name !~ /[\s,]/ && length($name) <= 50)
+ || ThrowUserError('flag_type_name_invalid', {name => $name});
+ return $name;
}
sub _check_description {
- my ($invocant, $desc) = @_;
+ my ($invocant, $desc) = @_;
- $desc = trim($desc);
- $desc || ThrowUserError('flag_type_description_invalid');
- return $desc;
+ $desc = trim($desc);
+ $desc || ThrowUserError('flag_type_description_invalid');
+ return $desc;
}
sub _check_cc_list {
- my ($invocant, $cc_list) = @_;
-
- length($cc_list) <= 200
- || ThrowUserError('flag_type_cc_list_invalid', { cc_list => $cc_list });
-
- my @addresses = split(/[,\s]+/, $cc_list);
- my $addr_spec = $Email::Address::addr_spec;
- # We do not call check_email_syntax() because these addresses do not
- # require to match 'emailregexp' and do not depend on 'emailsuffix'.
- foreach my $address (@addresses) {
- ($address !~ /\P{ASCII}/ && $address =~ /^$addr_spec$/)
- || ThrowUserError('illegal_email_address',
- {addr => $address, default => 1});
- }
- return $cc_list;
+ my ($invocant, $cc_list) = @_;
+
+ length($cc_list) <= 200
+ || ThrowUserError('flag_type_cc_list_invalid', {cc_list => $cc_list});
+
+ my @addresses = split(/[,\s]+/, $cc_list);
+ my $addr_spec = $Email::Address::addr_spec;
+
+ # We do not call check_email_syntax() because these addresses do not
+ # require to match 'emailregexp' and do not depend on 'emailsuffix'.
+ foreach my $address (@addresses) {
+ ($address !~ /\P{ASCII}/ && $address =~ /^$addr_spec$/)
+ || ThrowUserError('illegal_email_address', {addr => $address, default => 1});
+ }
+ return $cc_list;
}
sub _check_target_type {
- my ($invocant, $target_type) = @_;
+ my ($invocant, $target_type) = @_;
- ($target_type eq 'bug' || $target_type eq 'attachment')
- || ThrowCodeError('flag_type_target_type_invalid', { target_type => $target_type });
- return $target_type;
+ ($target_type eq 'bug' || $target_type eq 'attachment')
+ || ThrowCodeError('flag_type_target_type_invalid',
+ {target_type => $target_type});
+ return $target_type;
}
sub _check_sortkey {
- my ($invocant, $sortkey) = @_;
+ my ($invocant, $sortkey) = @_;
- (detaint_natural($sortkey) && $sortkey <= MAX_SMALLINT)
- || ThrowUserError('flag_type_sortkey_invalid', { sortkey => $sortkey });
- return $sortkey;
+ (detaint_natural($sortkey) && $sortkey <= MAX_SMALLINT)
+ || ThrowUserError('flag_type_sortkey_invalid', {sortkey => $sortkey});
+ return $sortkey;
}
sub _check_group {
- my ($invocant, $group) = @_;
- return unless $group;
+ my ($invocant, $group) = @_;
+ return unless $group;
- trick_taint($group);
- $group = Bugzilla::Group->check($group);
- return $group->id;
+ trick_taint($group);
+ $group = Bugzilla::Group->check($group);
+ return $group->id;
}
###############################
#### Methods ####
###############################
-sub set_name { $_[0]->set('name', $_[1]); }
-sub set_description { $_[0]->set('description', $_[1]); }
-sub set_cc_list { $_[0]->set('cc_list', $_[1]); }
-sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
-sub set_is_active { $_[0]->set('is_active', $_[1]); }
-sub set_is_requestable { $_[0]->set('is_requestable', $_[1]); }
-sub set_is_specifically_requestable { $_[0]->set('is_requesteeble', $_[1]); }
-sub set_is_multiplicable { $_[0]->set('is_multiplicable', $_[1]); }
-sub set_grant_group { $_[0]->set('grant_group_id', $_[1]); }
-sub set_request_group { $_[0]->set('request_group_id', $_[1]); }
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_cc_list { $_[0]->set('cc_list', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+sub set_is_active { $_[0]->set('is_active', $_[1]); }
+sub set_is_requestable { $_[0]->set('is_requestable', $_[1]); }
+sub set_is_specifically_requestable { $_[0]->set('is_requesteeble', $_[1]); }
+sub set_is_multiplicable { $_[0]->set('is_multiplicable', $_[1]); }
+sub set_grant_group { $_[0]->set('grant_group_id', $_[1]); }
+sub set_request_group { $_[0]->set('request_group_id', $_[1]); }
sub set_clusions {
- my ($self, $list) = @_;
- my $user = Bugzilla->user;
- my %products;
- my $params = {};
-
- # If the user has editcomponents privs, then we only need to make sure
- # that the product exists.
- if ($user->in_group('editcomponents')) {
- $params->{allow_inaccessible} = 1;
- }
-
- foreach my $category (keys %$list) {
- my %clusions;
- my %clusions_as_hash;
-
- foreach my $prod_comp (@{$list->{$category} || []}) {
- my ($prod_id, $comp_id) = split(':', $prod_comp);
- my $prod_name = '__Any__';
- my $comp_name = '__Any__';
- # Does the product exist?
- if ($prod_id) {
- detaint_natural($prod_id)
- || ThrowCodeError('param_must_be_numeric',
- { function => 'Bugzilla::FlagType::set_clusions' });
-
- if (!$products{$prod_id}) {
- $params->{id} = $prod_id;
- $products{$prod_id} = Bugzilla::Product->check($params);
- }
- $prod_name = $products{$prod_id}->name;
-
- # Does the component belong to this product?
- if ($comp_id) {
- detaint_natural($comp_id)
- || ThrowCodeError('param_must_be_numeric',
- { function => 'Bugzilla::FlagType::set_clusions' });
-
- my ($component) = grep { $_->id == $comp_id } @{$products{$prod_id}->components}
- or ThrowUserError('product_unknown_component',
- { product => $prod_name, comp_id => $comp_id });
- $comp_name = $component->name;
- }
- else {
- $comp_id = 0;
- }
- }
- else {
- $prod_id = 0;
- $comp_id = 0;
- }
- $clusions{"$prod_name:$comp_name"} = "$prod_id:$comp_id";
- $clusions_as_hash{$prod_id}->{$comp_id} = 1;
+ my ($self, $list) = @_;
+ my $user = Bugzilla->user;
+ my %products;
+ my $params = {};
+
+ # If the user has editcomponents privs, then we only need to make sure
+ # that the product exists.
+ if ($user->in_group('editcomponents')) {
+ $params->{allow_inaccessible} = 1;
+ }
+
+ foreach my $category (keys %$list) {
+ my %clusions;
+ my %clusions_as_hash;
+
+ foreach my $prod_comp (@{$list->{$category} || []}) {
+ my ($prod_id, $comp_id) = split(':', $prod_comp);
+ my $prod_name = '__Any__';
+ my $comp_name = '__Any__';
+
+ # Does the product exist?
+ if ($prod_id) {
+ detaint_natural($prod_id)
+ || ThrowCodeError('param_must_be_numeric',
+ {function => 'Bugzilla::FlagType::set_clusions'});
+
+ if (!$products{$prod_id}) {
+ $params->{id} = $prod_id;
+ $products{$prod_id} = Bugzilla::Product->check($params);
}
-
- # Check the user has the editcomponent permission on products that are changing
- if (! $user->in_group('editcomponents')) {
- my $current_clusions = $self->$category;
- my ($removed, $added)
- = diff_arrays([ values %$current_clusions ], [ values %clusions ]);
- my @changed_product_ids
- = uniq map { substr($_, 0, index($_, ':')) } @$removed, @$added;
- foreach my $product_id (@changed_product_ids) {
- $user->in_group('editcomponents', $product_id)
- || ThrowUserError('product_access_denied',
- { name => $products{$product_id}->name });
- }
+ $prod_name = $products{$prod_id}->name;
+
+ # Does the component belong to this product?
+ if ($comp_id) {
+ detaint_natural($comp_id)
+ || ThrowCodeError('param_must_be_numeric',
+ {function => 'Bugzilla::FlagType::set_clusions'});
+
+ my ($component) = grep { $_->id == $comp_id } @{$products{$prod_id}->components}
+ or ThrowUserError('product_unknown_component',
+ {product => $prod_name, comp_id => $comp_id});
+ $comp_name = $component->name;
+ }
+ else {
+ $comp_id = 0;
}
+ }
+ else {
+ $prod_id = 0;
+ $comp_id = 0;
+ }
+ $clusions{"$prod_name:$comp_name"} = "$prod_id:$comp_id";
+ $clusions_as_hash{$prod_id}->{$comp_id} = 1;
+ }
- # Set the changes
- $self->{$category} = \%clusions;
- $self->{"${category}_as_hash"} = \%clusions_as_hash;
- $self->{"_update_$category"} = 1;
+ # Check the user has the editcomponent permission on products that are changing
+ if (!$user->in_group('editcomponents')) {
+ my $current_clusions = $self->$category;
+ my ($removed, $added)
+ = diff_arrays([values %$current_clusions], [values %clusions]);
+ my @changed_product_ids = uniq map { substr($_, 0, index($_, ':')) } @$removed,
+ @$added;
+ foreach my $product_id (@changed_product_ids) {
+ $user->in_group('editcomponents', $product_id)
+ || ThrowUserError('product_access_denied',
+ {name => $products{$product_id}->name});
+ }
}
+
+ # Set the changes
+ $self->{$category} = \%clusions;
+ $self->{"${category}_as_hash"} = \%clusions_as_hash;
+ $self->{"_update_$category"} = 1;
+ }
}
=pod
@@ -465,76 +473,79 @@ explicitly excluded from the flagtype.
=cut
sub grant_list {
- my $self = shift;
- require Bugzilla::User;
- 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;
+ my $self = shift;
+ require Bugzilla::User;
+ 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;
+ 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'};
+ 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;
+ 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'};
+ 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'};
+ 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;
+ my $self = shift;
- if (!defined $self->{inclusions}) {
- ($self->{inclusions}, $self->{inclusions_as_hash}) = get_clusions($self->id, 'in');
- }
- return $self->{inclusions};
+ if (!defined $self->{inclusions}) {
+ ($self->{inclusions}, $self->{inclusions_as_hash})
+ = get_clusions($self->id, 'in');
+ }
+ return $self->{inclusions};
}
sub inclusions_as_hash {
- my $self = shift;
+ my $self = shift;
- $self->inclusions unless defined $self->{inclusions_as_hash};
- return $self->{inclusions_as_hash};
+ $self->inclusions unless defined $self->{inclusions_as_hash};
+ return $self->{inclusions_as_hash};
}
sub exclusions {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{exclusions}) {
- ($self->{exclusions}, $self->{exclusions_as_hash}) = get_clusions($self->id, 'ex');
- }
- return $self->{exclusions};
+ if (!defined $self->{exclusions}) {
+ ($self->{exclusions}, $self->{exclusions_as_hash})
+ = get_clusions($self->id, 'ex');
+ }
+ return $self->{exclusions};
}
sub exclusions_as_hash {
- my $self = shift;
+ my $self = shift;
- $self->exclusions unless defined $self->{exclusions_as_hash};
- return $self->{exclusions_as_hash};
+ $self->exclusions unless defined $self->{exclusions_as_hash};
+ return $self->{exclusions_as_hash};
}
######################################################################
@@ -558,11 +569,11 @@ $clusions{'product_name:component_name'} = "product_ID:component_ID"
=cut
sub get_clusions {
- my ($id, $type) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($id, $type) = @_;
+ my $dbh = Bugzilla->dbh;
- my $list =
- $dbh->selectall_arrayref("SELECT products.id, products.name,
+ my $list = $dbh->selectall_arrayref(
+ "SELECT products.id, products.name,
components.id, components.name
FROM flagtypes
INNER JOIN flag${type}clusions
@@ -571,19 +582,19 @@ sub get_clusions {
ON flag${type}clusions.product_id = products.id
LEFT JOIN components
ON flag${type}clusions.component_id = components.id
- WHERE flagtypes.id = ?",
- undef, $id);
- my (%clusions, %clusions_as_hash);
- 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";
- $clusions_as_hash{$product_id}->{$component_id} = 1;
- }
- return (\%clusions, \%clusions_as_hash);
+ WHERE flagtypes.id = ?", undef, $id
+ );
+ my (%clusions, %clusions_as_hash);
+ 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";
+ $clusions_as_hash{$product_id}->{$component_id} = 1;
+ }
+ return (\%clusions, \%clusions_as_hash);
}
=pod
@@ -600,18 +611,19 @@ and returns a list of matching flagtype objects.
=cut
sub match {
- my ($criteria) = @_;
- my $dbh = Bugzilla->dbh;
+ 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);
+ # 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");
+ my $flagtype_ids
+ = $dbh->selectcol_arrayref("SELECT id FROM $tables WHERE $criteria");
- return Bugzilla::FlagType->new_from_list($flagtype_ids);
+ return Bugzilla::FlagType->new_from_list($flagtype_ids);
}
=pod
@@ -627,18 +639,20 @@ Returns the total number of flag types matching the given criteria.
=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;
+ 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;
}
######################################################################
@@ -646,93 +660,98 @@ sub count {
######################################################################
# 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
+# $criteria is a reference to the criteria (field => value),
+# $tables is a reference to an array of tables being accessed
# by the query.
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}) {
- if (ref($criteria->{name}) eq 'ARRAY') {
- my @names = map { $dbh->quote($_) } @{$criteria->{name}};
- # Detaint data as we have quoted it.
- foreach my $name (@names) {
- trick_taint($name);
- }
- push @criteria, $dbh->sql_in('flagtypes.name', \@names);
- }
- else {
- my $name = $dbh->quote($criteria->{name});
- trick_taint($name); # Detaint data as we have quoted it.
- push(@criteria, "flagtypes.name = $name");
- }
+ 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}) {
+ if (ref($criteria->{name}) eq 'ARRAY') {
+ my @names = map { $dbh->quote($_) } @{$criteria->{name}};
+
+ # Detaint data as we have quoted it.
+ foreach my $name (@names) {
+ trick_taint($name);
+ }
+ push @criteria, $dbh->sql_in('flagtypes.name', \@names);
}
- 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'");
+ else {
+ my $name = $dbh->quote($criteria->{name});
+ trick_taint($name); # Detaint data as we have quoted it.
+ push(@criteria, "flagtypes.name = $name");
}
- if (exists($criteria->{is_active})) {
- my $is_active = $criteria->{is_active} ? "1" : "0";
- push(@criteria, "flagtypes.is_active = $is_active");
- }
- if ($criteria->{product_id}) {
- my $product_id = $criteria->{product_id};
- detaint_natural($product_id)
- || ThrowCodeError('bad_arg', { argument => 'product_id',
- function => 'Bugzilla::FlagType::sqlify_criteria' });
-
- # 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)");
-
- # 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 ";
-
- my $addl_join_clause = "";
- if ($criteria->{component_id}) {
- my $component_id = $criteria->{component_id};
- detaint_natural($component_id)
- || ThrowCodeError('bad_arg', { argument => 'component_id',
- function => 'Bugzilla::FlagType::sqlify_criteria' });
-
- push(@criteria, "(i.component_id = $component_id OR i.component_id IS NULL)");
- $join_clause .= "AND (e.component_id = $component_id OR e.component_id IS NULL) ";
- }
- else {
- $addl_join_clause = "AND e.component_id IS NULL OR (i.component_id = e.component_id) ";
- }
- $join_clause .= "AND ((e.product_id = $product_id $addl_join_clause) OR e.product_id IS NULL)";
-
- push(@$tables, "LEFT JOIN flagexclusions AS e ON ($join_clause)");
- push(@criteria, "e.type_id IS NULL");
+ }
+ 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}) {
+ my $product_id = $criteria->{product_id};
+ detaint_natural($product_id)
+ || ThrowCodeError('bad_arg',
+ {argument => 'product_id', function => 'Bugzilla::FlagType::sqlify_criteria'});
+
+ # 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)");
+
+ # 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 ";
+
+ my $addl_join_clause = "";
+ if ($criteria->{component_id}) {
+ my $component_id = $criteria->{component_id};
+ detaint_natural($component_id) || ThrowCodeError('bad_arg',
+ {argument => 'component_id', function => 'Bugzilla::FlagType::sqlify_criteria'}
+ );
+
+ push(@criteria, "(i.component_id = $component_id OR i.component_id IS NULL)");
+ $join_clause
+ .= "AND (e.component_id = $component_id OR e.component_id IS NULL) ";
}
- if ($criteria->{group}) {
- my $gid = $criteria->{group};
- detaint_natural($gid)
- || ThrowCodeError('bad_arg', { argument => 'group',
- function => 'Bugzilla::FlagType::sqlify_criteria' });
-
- push(@criteria, "(flagtypes.grant_group_id = $gid " .
- " OR flagtypes.request_group_id = $gid)");
+ else {
+ $addl_join_clause
+ = "AND e.component_id IS NULL OR (i.component_id = e.component_id) ";
}
-
- return @criteria;
+ $join_clause
+ .= "AND ((e.product_id = $product_id $addl_join_clause) OR e.product_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)
+ || ThrowCodeError('bad_arg',
+ {argument => 'group', function => 'Bugzilla::FlagType::sqlify_criteria'});
+
+ push(@criteria,
+ "(flagtypes.grant_group_id = $gid " . " OR flagtypes.request_group_id = $gid)");
+ }
+
+ return @criteria;
}
1;
diff --git a/Bugzilla/Group.pm b/Bugzilla/Group.pm
index f7a50f7f1..8715c4cfe 100644
--- a/Bugzilla/Group.pm
+++ b/Bugzilla/Group.pm
@@ -25,13 +25,13 @@ use Bugzilla::Config qw(:admin);
use constant IS_CONFIG => 1;
use constant DB_COLUMNS => qw(
- groups.id
- groups.name
- groups.description
- groups.isbuggroup
- groups.userregexp
- groups.isactive
- groups.icon_url
+ groups.id
+ groups.name
+ groups.description
+ groups.isbuggroup
+ groups.userregexp
+ groups.isactive
+ groups.icon_url
);
use constant DB_TABLE => 'groups';
@@ -39,136 +39,136 @@ 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,
+ 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 UPDATE_COLUMNS => qw(
- name
- description
- userregexp
- isactive
- icon_url
+ name
+ description
+ userregexp
+ isactive
+ icon_url
);
# Parameters that are lists of groups.
use constant GROUP_PARAMS => qw(
- chartgroup comment_taggers_group debug_group insidergroup
- querysharegroup timetrackinggroup
+ chartgroup comment_taggers_group debug_group insidergroup
+ querysharegroup timetrackinggroup
);
###############################
#### 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 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 bugs {
- my $self = shift;
- return $self->{bugs} if exists $self->{bugs};
- my $bug_ids = Bugzilla->dbh->selectcol_arrayref(
- 'SELECT bug_id FROM bug_group_map WHERE group_id = ?',
- undef, $self->id);
- require Bugzilla::Bug;
- $self->{bugs} = Bugzilla::Bug->new_from_list($bug_ids);
- return $self->{bugs};
+ my $self = shift;
+ return $self->{bugs} if exists $self->{bugs};
+ my $bug_ids
+ = Bugzilla->dbh->selectcol_arrayref(
+ 'SELECT bug_id FROM bug_group_map WHERE group_id = ?',
+ undef, $self->id);
+ require Bugzilla::Bug;
+ $self->{bugs} = Bugzilla::Bug->new_from_list($bug_ids);
+ return $self->{bugs};
}
sub members_direct {
- my ($self) = @_;
- $self->{members_direct} ||= $self->_get_members(GRANT_DIRECT);
- return $self->{members_direct};
+ my ($self) = @_;
+ $self->{members_direct} ||= $self->_get_members(GRANT_DIRECT);
+ return $self->{members_direct};
}
sub members_non_inherited {
- my ($self) = @_;
- $self->{members_non_inherited} ||= $self->_get_members();
- return $self->{members_non_inherited};
+ my ($self) = @_;
+ $self->{members_non_inherited} ||= $self->_get_members();
+ return $self->{members_non_inherited};
}
# A helper for members_direct and members_non_inherited
sub _get_members {
- my ($self, $grant_type) = @_;
- my $dbh = Bugzilla->dbh;
- my $grant_clause = defined($grant_type) ? "AND grant_type = $grant_type"
- : "";
- my $user_ids = $dbh->selectcol_arrayref(
- "SELECT DISTINCT user_id
+ my ($self, $grant_type) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $grant_clause = defined($grant_type) ? "AND grant_type = $grant_type" : "";
+ my $user_ids = $dbh->selectcol_arrayref(
+ "SELECT DISTINCT user_id
FROM user_group_map
- WHERE isbless = 0 $grant_clause AND group_id = ?", undef, $self->id);
- require Bugzilla::User;
- return Bugzilla::User->new_from_list($user_ids);
+ WHERE isbless = 0 $grant_clause AND group_id = ?", undef, $self->id
+ );
+ require Bugzilla::User;
+ return Bugzilla::User->new_from_list($user_ids);
}
sub flag_types {
- my $self = shift;
- require Bugzilla::FlagType;
- $self->{flag_types} ||= Bugzilla::FlagType::match({ group => $self->id });
- return $self->{flag_types};
+ my $self = shift;
+ require Bugzilla::FlagType;
+ $self->{flag_types} ||= Bugzilla::FlagType::match({group => $self->id});
+ return $self->{flag_types};
}
sub grant_direct {
- my ($self, $type) = @_;
- $self->{grant_direct} ||= {};
- return $self->{grant_direct}->{$type}
- if defined $self->{grant_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};
+ my ($self, $type) = @_;
+ $self->{grant_direct} ||= {};
+ return $self->{grant_direct}->{$type} if defined $self->{grant_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};
+ 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};
}
sub products {
- my $self = shift;
- return $self->{products} if exists $self->{products};
- my $product_data = Bugzilla->dbh->selectall_arrayref(
- 'SELECT product_id, entry, membercontrol, othercontrol,
+ my $self = shift;
+ return $self->{products} if exists $self->{products};
+ my $product_data = Bugzilla->dbh->selectall_arrayref(
+ 'SELECT product_id, entry, membercontrol, othercontrol,
canedit, editcomponents, editbugs, canconfirm
- FROM group_control_map WHERE group_id = ?', {Slice=>{}},
- $self->id);
- my @ids = map { $_->{product_id} } @$product_data;
- require Bugzilla::Product;
- my $products = Bugzilla::Product->new_from_list(\@ids);
- my %data_map = map { $_->{product_id} => $_ } @$product_data;
- my @retval;
- foreach my $product (@$products) {
- # Data doesn't need to contain product_id--we already have
- # the product object.
- delete $data_map{$product->id}->{product_id};
- push(@retval, { controls => $data_map{$product->id},
- product => $product });
- }
- $self->{products} = \@retval;
- return $self->{products};
+ FROM group_control_map WHERE group_id = ?', {Slice => {}}, $self->id
+ );
+ my @ids = map { $_->{product_id} } @$product_data;
+ require Bugzilla::Product;
+ my $products = Bugzilla::Product->new_from_list(\@ids);
+ my %data_map = map { $_->{product_id} => $_ } @$product_data;
+ my @retval;
+ foreach my $product (@$products) {
+
+ # Data doesn't need to contain product_id--we already have
+ # the product object.
+ delete $data_map{$product->id}->{product_id};
+ push(@retval, {controls => $data_map{$product->id}, product => $product});
+ }
+ $self->{products} = \@retval;
+ return $self->{products};
}
###############################
@@ -176,126 +176,127 @@ sub products {
###############################
sub check_members_are_visible {
- my $self = shift;
- my $user = Bugzilla->user;
- return if !Bugzilla->params->{'usevisibilitygroups'};
-
- my $group_id = $self->id;
- my $is_visible = grep { $_ == $group_id } @{ $user->visible_groups_inherited };
- if (!$is_visible) {
- ThrowUserError('group_not_visible', { group => $self });
- }
+ my $self = shift;
+ my $user = Bugzilla->user;
+ return if !Bugzilla->params->{'usevisibilitygroups'};
+
+ my $group_id = $self->id;
+ my $is_visible = grep { $_ == $group_id } @{$user->visible_groups_inherited};
+ if (!$is_visible) {
+ ThrowUserError('group_not_visible', {group => $self});
+ }
}
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 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 $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- 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;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ 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]);
+ # 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};
+ $self->_rederive_regexp() if exists $changes->{userregexp};
- Bugzilla::Hook::process('group_end_of_update',
- { group => $self, changes => $changes });
- $dbh->bz_commit_transaction();
- Bugzilla->memcached->clear_config();
- return $changes;
+ Bugzilla::Hook::process('group_end_of_update',
+ {group => $self, changes => $changes});
+ $dbh->bz_commit_transaction();
+ Bugzilla->memcached->clear_config();
+ return $changes;
}
sub check_remove {
- my ($self, $params) = @_;
-
- # System groups cannot be deleted!
- if (!$self->is_bug_group) {
- ThrowUserError("system_group_not_deletable", { name => $self->name });
+ my ($self, $params) = @_;
+
+ # System groups cannot be deleted!
+ if (!$self->is_bug_group) {
+ ThrowUserError("system_group_not_deletable", {name => $self->name});
+ }
+
+ # Groups having a special role cannot be deleted.
+ my @special_groups;
+ foreach my $special_group (GROUP_PARAMS) {
+ if ($self->name eq Bugzilla->params->{$special_group}) {
+ push(@special_groups, $special_group);
}
+ }
+ if (scalar(@special_groups)) {
+ ThrowUserError('group_has_special_role',
+ {name => $self->name, groups => \@special_groups});
+ }
- # Groups having a special role cannot be deleted.
- my @special_groups;
- foreach my $special_group (GROUP_PARAMS) {
- if ($self->name eq Bugzilla->params->{$special_group}) {
- push(@special_groups, $special_group);
- }
- }
- if (scalar(@special_groups)) {
- ThrowUserError('group_has_special_role',
- { name => $self->name,
- groups => \@special_groups });
- }
+ return if $params->{'test_only'};
- return if $params->{'test_only'};
+ my $cantdelete = 0;
- my $cantdelete = 0;
+ my $users = $self->members_non_inherited;
+ if (scalar(@$users) && !$params->{'remove_from_users'}) {
+ $cantdelete = 1;
+ }
- my $users = $self->members_non_inherited;
- if (scalar(@$users) && !$params->{'remove_from_users'}) {
- $cantdelete = 1;
- }
+ my $bugs = $self->bugs;
+ if (scalar(@$bugs) && !$params->{'remove_from_bugs'}) {
+ $cantdelete = 1;
+ }
- my $bugs = $self->bugs;
- if (scalar(@$bugs) && !$params->{'remove_from_bugs'}) {
- $cantdelete = 1;
- }
-
- my $products = $self->products;
- if (scalar(@$products) && !$params->{'remove_from_products'}) {
- $cantdelete = 1;
- }
+ my $products = $self->products;
+ if (scalar(@$products) && !$params->{'remove_from_products'}) {
+ $cantdelete = 1;
+ }
- my $flag_types = $self->flag_types;
- if (scalar(@$flag_types) && !$params->{'remove_from_flags'}) {
- $cantdelete = 1;
- }
+ my $flag_types = $self->flag_types;
+ if (scalar(@$flag_types) && !$params->{'remove_from_flags'}) {
+ $cantdelete = 1;
+ }
- ThrowUserError('group_cannot_delete', { group => $self }) if $cantdelete;
+ ThrowUserError('group_cannot_delete', {group => $self}) if $cantdelete;
}
sub remove_from_db {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- $self->check_remove(@_);
- $dbh->bz_start_transaction();
- Bugzilla::Hook::process('group_before_delete', { group => $self });
- $dbh->do('DELETE FROM whine_schedules
- WHERE mailto_type = ? AND mailto = ?',
- undef, MAILTO_GROUP, $self->id);
- # All the other tables will be handled by foreign keys when we
- # drop the main "groups" row.
- $self->SUPER::remove_from_db(@_);
- $dbh->bz_commit_transaction();
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ $self->check_remove(@_);
+ $dbh->bz_start_transaction();
+ Bugzilla::Hook::process('group_before_delete', {group => $self});
+ $dbh->do(
+ 'DELETE FROM whine_schedules
+ WHERE mailto_type = ? AND mailto = ?', undef, MAILTO_GROUP, $self->id
+ );
+
+ # All the other tables will be handled by foreign keys when we
+ # drop the main "groups" row.
+ $self->SUPER::remove_from_db(@_);
+ $dbh->bz_commit_transaction();
}
# 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 ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $gid = $self->id;
- my $bug_ids =
- $dbh->selectcol_arrayref('SELECT bugs.bug_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
@@ -304,156 +305,171 @@ sub _enforce_mandatory {
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);
- }
+ 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;
+ my $self = shift;
+ return $self->is_active && $self->is_bug_group;
}
sub _rederive_regexp {
- my ($self) = @_;
+ my ($self) = @_;
- my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare("SELECT userid, login_name, group_id
+ 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
+ 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
+ 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($self->id, GRANT_REGEXP);
- my $regexp = $self->user_regexp;
- while (my ($uid, $login, $present) = $sth->fetchrow_array) {
- if ($regexp ne '' and $login =~ /$regexp/i) {
- $sthadd->execute($uid, $self->id, GRANT_REGEXP) unless $present;
- } else {
- $sthdel->execute($uid, $self->id, GRANT_REGEXP) if $present;
- }
+ AND grant_type = ? and isbless = 0"
+ );
+ $sth->execute($self->id, GRANT_REGEXP);
+ my $regexp = $self->user_regexp;
+
+ while (my ($uid, $login, $present) = $sth->fetchrow_array) {
+ if ($regexp ne '' and $login =~ /$regexp/i) {
+ $sthadd->execute($uid, $self->id, GRANT_REGEXP) unless $present;
+ }
+ else {
+ $sthdel->execute($uid, $self->id, GRANT_REGEXP) if $present;
}
+ }
}
sub flatten_group_membership {
- my ($self, @groups) = @_;
-
- my $dbh = Bugzilla->dbh;
- my $sth;
- my @groupidstocheck = @groups;
- my %groupidschecked = ();
- $sth = $dbh->prepare("SELECT member_id FROM group_group_map
+ my ($self, @groups) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ my $sth;
+ my @groupidstocheck = @groups;
+ my %groupidschecked = ();
+ $sth = $dbh->prepare(
+ "SELECT member_id FROM group_group_map
WHERE grantor_id = ?
- AND grant_type = " . GROUP_MEMBERSHIP);
- while (my $node = shift @groupidstocheck) {
- $sth->execute($node);
- my $member;
- while (($member) = $sth->fetchrow_array) {
- if (!$groupidschecked{$member}) {
- $groupidschecked{$member} = 1;
- push @groupidstocheck, $member;
- push @groups, $member unless grep $_ == $member, @groups;
- }
- }
+ AND grant_type = " . GROUP_MEMBERSHIP
+ );
+ while (my $node = shift @groupidstocheck) {
+ $sth->execute($node);
+ my $member;
+ while (($member) = $sth->fetchrow_array) {
+ if (!$groupidschecked{$member}) {
+ $groupidschecked{$member} = 1;
+ push @groupidstocheck, $member;
+ push @groups, $member unless grep $_ == $member, @groups;
+ }
}
- return \@groups;
+ }
+ return \@groups;
}
-
-
################################
##### Module Subroutines ###
################################
sub create {
- my $class = shift;
- my ($params) = @_;
- my $dbh = Bugzilla->dbh;
-
- my $silently = delete $params->{silently};
- my $use_in_all_products = delete $params->{use_in_all_products};
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE and !$silently) {
- print get_text('install_group_create', { name => $params->{name} }),
- "\n";
- }
+ my $class = shift;
+ my ($params) = @_;
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ my $silently = delete $params->{silently};
+ my $use_in_all_products = delete $params->{use_in_all_products};
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE and !$silently) {
+ print get_text('install_group_create', {name => $params->{name}}), "\n";
+ }
- my $group = $class->SUPER::create(@_);
+ $dbh->bz_start_transaction();
- # 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);
- }
+ my $group = $class->SUPER::create(@_);
- # Permit all existing products to use the new group if requested.
- if ($use_in_all_products) {
- $dbh->do('INSERT INTO group_control_map
+ # 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);
+ }
+
+ # Permit all existing products to use the new group if requested.
+ if ($use_in_all_products) {
+ $dbh->do(
+ 'INSERT INTO group_control_map
(group_id, product_id, membercontrol, othercontrol)
- SELECT ?, products.id, ?, ? FROM products',
- undef, ($group->id, CONTROLMAPSHOWN, CONTROLMAPNA));
- }
+ SELECT ?, products.id, ?, ? FROM products', undef,
+ ($group->id, CONTROLMAPSHOWN, CONTROLMAPNA)
+ );
+ }
- $group->_rederive_regexp() if $group->user_regexp;
+ $group->_rederive_regexp() if $group->user_regexp;
- Bugzilla::Hook::process('group_end_of_create', { group => $group });
- $dbh->bz_commit_transaction();
- return $group;
+ Bugzilla::Hook::process('group_end_of_create', {group => $group});
+ $dbh->bz_commit_transaction();
+ 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 ($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 $sth = $dbh->prepare($query);
- $sth->execute($name);
- my ($ret) = $sth->fetchrow_array();
- return $ret;
+ my $visible = join(', ', @visible);
+ $query .= " AND id IN($visible)";
+ }
+ my $sth = $dbh->prepare($query);
+ $sth->execute($name);
+ my ($ret) = $sth->fetchrow_array();
+ return $ret;
}
sub check_no_disclose {
- my ($class, $params) = @_;
- my $action = delete $params->{action};
+ my ($class, $params) = @_;
+ my $action = delete $params->{action};
- $action =~ /^(?:add|remove)$/
- or ThrowCodeError('bad_arg', { argument => $action,
- function => "${class}::check_no_disclose" });
+ $action =~ /^(?:add|remove)$/
+ or ThrowCodeError('bad_arg',
+ {argument => $action, function => "${class}::check_no_disclose"});
- $params->{_error} = ($action eq 'add') ? 'group_restriction_not_allowed'
- : 'group_invalid_removal';
+ $params->{_error}
+ = ($action eq 'add')
+ ? 'group_restriction_not_allowed'
+ : 'group_invalid_removal';
- my $group = $class->check($params);
- return $group;
+ my $group = $class->check($params);
+ return $group;
}
###############################
@@ -461,34 +477,36 @@ sub check_no_disclose {
###############################
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) || lc($invocant->name) ne lc($name)) {
- my $exists = new Bugzilla::Group({name => $name });
- ThrowUserError("group_exists", { name => $name }) if $exists;
- }
- return $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) || lc($invocant->name) ne lc($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;
+ 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;
+ 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;
+ return $_[1] ? 1 : 0;
}
sub _check_icon_url { return $_[1] ? clean_text($_[1]) : undef; }
diff --git a/Bugzilla/Hook.pm b/Bugzilla/Hook.pm
index d6ba5e1d0..f5c67c692 100644
--- a/Bugzilla/Hook.pm
+++ b/Bugzilla/Hook.pm
@@ -12,33 +12,33 @@ use strict;
use warnings;
sub process {
- my ($name, $args) = @_;
+ my ($name, $args) = @_;
- _entering($name);
+ _entering($name);
- foreach my $extension (@{ Bugzilla->extensions }) {
- if ($extension->can($name)) {
- $extension->$name($args);
- }
+ foreach my $extension (@{Bugzilla->extensions}) {
+ if ($extension->can($name)) {
+ $extension->$name($args);
}
+ }
- _leaving($name);
+ _leaving($name);
}
sub in {
- my $hook_name = shift;
- my $currently_in = Bugzilla->request_cache->{hook_stack}->[-1] || '';
- return $hook_name eq $currently_in ? 1 : 0;
+ my $hook_name = shift;
+ my $currently_in = Bugzilla->request_cache->{hook_stack}->[-1] || '';
+ return $hook_name eq $currently_in ? 1 : 0;
}
sub _entering {
- my ($hook_name) = @_;
- my $hook_stack = Bugzilla->request_cache->{hook_stack} ||= [];
- push(@$hook_stack, $hook_name);
+ my ($hook_name) = @_;
+ my $hook_stack = Bugzilla->request_cache->{hook_stack} ||= [];
+ push(@$hook_stack, $hook_name);
}
sub _leaving {
- pop @{ Bugzilla->request_cache->{hook_stack} };
+ pop @{Bugzilla->request_cache->{hook_stack}};
}
1;
@@ -1479,6 +1479,21 @@ look at the code for C<create> in L<Bugzilla::Template>.)
=back
+=head2 template_after_create
+
+This hook allows you to manipulate the Template object before it is used.
+You can use this to define new vmethods or filters in extensions.
+
+Params:
+
+=over
+
+=item C<template>
+
+This is the L<Bugzilla::Template> object.
+
+=back
+
=head2 template_before_process
This hook is called any time Bugzilla processes a template file, including
diff --git a/Bugzilla/Install.pm b/Bugzilla/Install.pm
index 07bc9d6c3..64c2a2cbb 100644
--- a/Bugzilla/Install.pm
+++ b/Bugzilla/Install.pm
@@ -7,7 +7,7 @@
package Bugzilla::Install;
-# Functions in this this package can assume that the database
+# 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.
#
@@ -31,412 +31,421 @@ use Bugzilla::Util qw(get_text);
use Bugzilla::Version;
use constant STATUS_WORKFLOW => (
- [undef, 'UNCONFIRMED'],
- [undef, 'CONFIRMED'],
- [undef, 'IN_PROGRESS'],
- ['UNCONFIRMED', 'CONFIRMED'],
- ['UNCONFIRMED', 'IN_PROGRESS'],
- ['UNCONFIRMED', 'RESOLVED'],
- ['CONFIRMED', 'IN_PROGRESS'],
- ['CONFIRMED', 'RESOLVED'],
- ['IN_PROGRESS', 'CONFIRMED'],
- ['IN_PROGRESS', 'RESOLVED'],
- ['RESOLVED', 'UNCONFIRMED'],
- ['RESOLVED', 'CONFIRMED'],
- ['RESOLVED', 'VERIFIED'],
- ['VERIFIED', 'UNCONFIRMED'],
- ['VERIFIED', 'CONFIRMED'],
+ [undef, 'UNCONFIRMED'],
+ [undef, 'CONFIRMED'],
+ [undef, 'IN_PROGRESS'],
+ ['UNCONFIRMED', 'CONFIRMED'],
+ ['UNCONFIRMED', 'IN_PROGRESS'],
+ ['UNCONFIRMED', 'RESOLVED'],
+ ['CONFIRMED', 'IN_PROGRESS'],
+ ['CONFIRMED', 'RESOLVED'],
+ ['IN_PROGRESS', 'CONFIRMED'],
+ ['IN_PROGRESS', 'RESOLVED'],
+ ['RESOLVED', 'UNCONFIRMED'],
+ ['RESOLVED', 'CONFIRMED'],
+ ['RESOLVED', 'VERIFIED'],
+ ['VERIFIED', 'UNCONFIRMED'],
+ ['VERIFIED', 'CONFIRMED'],
);
sub SETTINGS {
- return {
+ return {
# 2005-03-03 travis@sedsystems.ca -- Bug 41972
- display_quips => { options => ["on", "off"], default => "on" },
+ 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" },
+ 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" },
+ 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 => ',' },
+ csv_colsepchar => {options => [',', ';'], default => ','},
+
# 2005-10-26 wurblzap@gmail.com -- Bug 291459
- zoom_textareas => { options => ["on", "off"], default => "on" },
+ zoom_textareas => {options => ["on", "off"], default => "on"},
+
# 2006-05-01 olav@bkor.dhs.org -- Bug 7710
- state_addselfcc => { options => ['always', 'never', 'cc_unless_role'],
- default => 'cc_unless_role' },
+ 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' },
+ skin => {subclass => 'Skin', default => 'Dusk'},
+
# 2006-12-10 LpSolit@gmail.com -- Bug 297186
- lang => { subclass => 'Lang',
- default => ${Bugzilla->languages}[0] },
+ 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" },
+ quote_replies => {
+ options => ['quoted_reply', 'simple_reply', 'off'],
+ default => "quoted_reply"
+ },
+
# 2009-02-01 mozilla@matt.mchenryfamily.org -- Bug 398473
- comment_box_position => { options => ['before_comments', 'after_comments'],
- default => 'before_comments' },
+ comment_box_position => {
+ options => ['before_comments', 'after_comments'],
+ default => 'before_comments'
+ },
+
# 2008-08-27 LpSolit@gmail.com -- Bug 182238
- timezone => { subclass => 'Timezone', default => 'local' },
+ timezone => {subclass => 'Timezone', default => 'local'},
+
# 2011-02-07 dkl@mozilla.com -- Bug 580490
- quicksearch_fulltext => { options => ['on', 'off'], default => 'on' },
+ quicksearch_fulltext => {options => ['on', 'off'], default => 'on'},
+
# 2011-06-21 glob@mozilla.com -- Bug 589128
- email_format => { options => ['html', 'text_only'],
- default => 'html' },
+ email_format => {options => ['html', 'text_only'], default => 'html'},
+
# 2011-10-11 glob@mozilla.com -- Bug 301656
- requestee_cc => { options => ['on', 'off'], default => 'on' },
+ requestee_cc => {options => ['on', 'off'], default => 'on'},
+
# 2012-04-30 glob@mozilla.com -- Bug 663747
- bugmail_new_prefix => { options => ['on', 'off'], default => 'on' },
+ bugmail_new_prefix => {options => ['on', 'off'], default => 'on'},
+
# 2013-07-26 joshi_sunil@in.com -- Bug 669535
- possible_duplicates => { options => ['on', 'off'], default => 'on' },
- }
-};
+ possible_duplicates => {options => ['on', 'off'], default => 'on'},
+ };
+}
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_canusewhineatothers',
- description => 'Can configure whine reports for other users',
- },
- {
- name => 'bz_canusewhines',
- description => 'User can configure whine reports for self',
- # inherited_by means that users in the groups listed below are
- # automatically members of bz_canusewhines.
- inherited_by => ['editbugs', 'bz_canusewhineatothers'],
- },
- {
- name => 'bz_sudoers',
- description => 'Can perform actions as other users',
- },
- {
- name => 'bz_sudo_protect',
- description => 'Can not be impersonated by other users',
- inherited_by => ['bz_sudoers'],
- },
- {
- name => 'bz_quip_moderators',
- description => 'Can moderate quips',
- },
+ {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_canusewhineatothers',
+ description => 'Can configure whine reports for other users',
+ },
+ {
+ name => 'bz_canusewhines',
+ description => 'User can configure whine reports for self',
+
+ # inherited_by means that users in the groups listed below are
+ # automatically members of bz_canusewhines.
+ inherited_by => ['editbugs', 'bz_canusewhineatothers'],
+ },
+ {name => 'bz_sudoers', description => 'Can perform actions as other users',},
+ {
+ name => 'bz_sudo_protect',
+ description => 'Can not be impersonated by other users',
+ inherited_by => ['bz_sudoers'],
+ },
+ {name => 'bz_quip_moderators', description => 'Can moderate quips',},
);
-use constant DEFAULT_CLASSIFICATION => {
- name => 'Unclassified',
- description => 'Not assigned to any classification'
-};
+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.',
- version => Bugzilla::Version::DEFAULT_VERSION,
- classification => 'Unclassified',
- defaultmilestone => DEFAULT_MILESTONE,
+ 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.',
+ version => Bugzilla::Version::DEFAULT_VERSION,
+ classification => 'Unclassified',
+ defaultmilestone => DEFAULT_MILESTONE,
};
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.'
+ 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 $dbh = Bugzilla->dbh;
- # If we're setting up settings for the first time, we want to be quieter.
- my $any_settings = $dbh->selectrow_array(
- 'SELECT 1 FROM setting ' . $dbh->sql_limit(1));
- if (!$any_settings) {
- say get_text('install_setting_setup');
- }
-
- my %settings = %{SETTINGS()};
- foreach my $setting (keys %settings) {
- add_setting($setting,
- $settings{$setting}->{options},
- $settings{$setting}->{default},
- $settings{$setting}->{subclass}, undef,
- !$any_settings);
- }
-
- # Delete the obsolete 'per_bug_queries' user preference. Bug 616191.
- $dbh->do('DELETE FROM setting WHERE name = ?', undef, 'per_bug_queries');
+ my $dbh = Bugzilla->dbh;
+
+ # If we're setting up settings for the first time, we want to be quieter.
+ my $any_settings
+ = $dbh->selectrow_array('SELECT 1 FROM setting ' . $dbh->sql_limit(1));
+ if (!$any_settings) {
+ say get_text('install_setting_setup');
+ }
+
+ my %settings = %{SETTINGS()};
+ foreach my $setting (keys %settings) {
+ add_setting(
+ $setting,
+ $settings{$setting}->{options},
+ $settings{$setting}->{default},
+ $settings{$setting}->{subclass},
+ undef, !$any_settings
+ );
+ }
+
+ # Delete the obsolete 'per_bug_queries' user preference. Bug 616191.
+ $dbh->do('DELETE FROM setting WHERE name = ?', undef, 'per_bug_queries');
}
sub update_system_groups {
- my $dbh = Bugzilla->dbh;
-
- $dbh->bz_start_transaction();
-
- # If there is no editbugs group, this is the first time we're
- # adding groups.
- my $editbugs_exists = new Bugzilla::Group({ name => 'editbugs' });
- if (!$editbugs_exists) {
- say get_text('install_groups_setup');
- }
-
- # Create most of the system groups
- foreach my $definition (SYSTEM_GROUPS) {
- my $exists = new Bugzilla::Group({ name => $definition->{name} });
- if (!$exists) {
- $definition->{isbuggroup} = 0;
- $definition->{silently} = !$editbugs_exists;
- my $inherited_by = delete $definition->{inherited_by};
- my $created = Bugzilla::Group->create($definition);
- # Each group in inherited_by is automatically a member of this
- # group.
- if ($inherited_by) {
- foreach my $name (@$inherited_by) {
- my $member = Bugzilla::Group->check($name);
- $dbh->do('INSERT INTO group_group_map (grantor_id,
- member_id) VALUES (?,?)',
- undef, $created->id, $member->id);
- }
- }
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+
+ # If there is no editbugs group, this is the first time we're
+ # adding groups.
+ my $editbugs_exists = new Bugzilla::Group({name => 'editbugs'});
+ if (!$editbugs_exists) {
+ say get_text('install_groups_setup');
+ }
+
+ # Create most of the system groups
+ foreach my $definition (SYSTEM_GROUPS) {
+ my $exists = new Bugzilla::Group({name => $definition->{name}});
+ if (!$exists) {
+ $definition->{isbuggroup} = 0;
+ $definition->{silently} = !$editbugs_exists;
+ my $inherited_by = delete $definition->{inherited_by};
+ my $created = Bugzilla::Group->create($definition);
+
+ # Each group in inherited_by is automatically a member of this
+ # group.
+ if ($inherited_by) {
+ foreach my $name (@$inherited_by) {
+ my $member = Bugzilla::Group->check($name);
+ $dbh->do(
+ 'INSERT INTO group_group_map (grantor_id,
+ member_id) VALUES (?,?)', undef, $created->id,
+ $member->id
+ );
}
+ }
}
+ }
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
}
sub create_default_classification {
- my $dbh = Bugzilla->dbh;
-
- # Make the default Classification if it doesn't already exist.
- if (!$dbh->selectrow_array('SELECT 1 FROM classifications')) {
- print get_text('install_default_classification',
- { name => DEFAULT_CLASSIFICATION->{name} }) . "\n";
- Bugzilla::Classification->create(DEFAULT_CLASSIFICATION);
- }
+ my $dbh = Bugzilla->dbh;
+
+ # Make the default Classification if it doesn't already exist.
+ if (!$dbh->selectrow_array('SELECT 1 FROM classifications')) {
+ print get_text('install_default_classification',
+ {name => DEFAULT_CLASSIFICATION->{name}})
+ . "\n";
+ Bugzilla::Classification->create(DEFAULT_CLASSIFICATION);
+ }
}
# This function should be called only after creating the admin user.
sub create_default_product {
- my $dbh = Bugzilla->dbh;
-
- # And same for the default product/component.
- if (!$dbh->selectrow_array('SELECT 1 FROM products')) {
- print get_text('install_default_product',
- { name => DEFAULT_PRODUCT->{name} }) . "\n";
-
- my $product = Bugzilla::Product->create(DEFAULT_PRODUCT);
-
- # Get the user who will be the owner of the Component.
- # We pick the admin with the lowest id, which is probably the
- # admin checksetup.pl just created.
- 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);
- my $admin = Bugzilla::User->new($admin_id);
-
- Bugzilla::Component->create({
- %{ DEFAULT_COMPONENT() }, product => $product,
- initialowner => $admin->login });
- }
+ my $dbh = Bugzilla->dbh;
-}
+ # And same for the default product/component.
+ if (!$dbh->selectrow_array('SELECT 1 FROM products')) {
+ print get_text('install_default_product', {name => DEFAULT_PRODUCT->{name}})
+ . "\n";
-sub init_workflow {
- my $dbh = Bugzilla->dbh;
- my $has_workflow = $dbh->selectrow_array('SELECT 1 FROM status_workflow');
- return if $has_workflow;
+ my $product = Bugzilla::Product->create(DEFAULT_PRODUCT);
- say get_text('install_workflow_init');
+ # Get the user who will be the owner of the Component.
+ # We pick the admin with the lowest id, which is probably the
+ # admin checksetup.pl just created.
+ 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
+ );
+ my $admin = Bugzilla::User->new($admin_id);
- my %status_ids = @{ $dbh->selectcol_arrayref(
- 'SELECT value, id FROM bug_status', {Columns=>[1,2]}) };
+ Bugzilla::Component->create(
+ {%{DEFAULT_COMPONENT()}, product => $product, initialowner => $admin->login});
+ }
- foreach my $pair (STATUS_WORKFLOW) {
- my $old_id = $pair->[0] ? $status_ids{$pair->[0]} : undef;
- my $new_id = $status_ids{$pair->[1]};
- $dbh->do('INSERT INTO status_workflow (old_status, new_status)
- VALUES (?,?)', undef, $old_id, $new_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::Group->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) {
- say "\n" . get_text('install_admin_setup') . "\n";
- }
+sub init_workflow {
+ my $dbh = Bugzilla->dbh;
+ my $has_workflow = $dbh->selectrow_array('SELECT 1 FROM status_workflow');
+ return if $has_workflow;
+
+ say get_text('install_workflow_init');
+
+ my %status_ids = @{
+ $dbh->selectcol_arrayref('SELECT value, id FROM bug_status',
+ {Columns => [1, 2]})
+ };
+
+ foreach my $pair (STATUS_WORKFLOW) {
+ my $old_id = $pair->[0] ? $status_ids{$pair->[0]} : undef;
+ my $new_id = $status_ids{$pair->[1]};
+ $dbh->do(
+ 'INSERT INTO status_workflow (old_status, new_status)
+ VALUES (?,?)', undef, $old_id, $new_id
+ );
+ }
+}
- while (!$login) {
- print get_text('install_admin_get_email') . ' ';
- $login = <STDIN>;
- chomp $login;
- eval { Bugzilla::User->check_login_name($login); };
- if ($@) {
- say $@;
- undef $login;
- }
+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::Group->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) {
+ say "\n" . get_text('install_admin_setup') . "\n";
+ }
+
+ while (!$login) {
+ print get_text('install_admin_get_email') . ' ';
+ $login = <STDIN>;
+ chomp $login;
+ eval { Bugzilla::User->check_login_name($login); };
+ if ($@) {
+ say $@;
+ undef $login;
}
+ }
- while (!defined $full_name) {
- print get_text('install_admin_get_name') . ' ';
- $full_name = <STDIN>;
- chomp($full_name);
- }
+ 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'));
- }
+ 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);
+ 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 $group_insert = $dbh->prepare(
- 'INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
- VALUES (?, ?, ?, ?)');
-
- # Admins get explicit membership and bless capability for the admin group
- my $admin_group = new Bugzilla::Group({ name => 'admin' });
- # 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);
- };
-
- # If there is no maintainer set, make this user the maintainer.
- if (!Bugzilla->params->{'maintainer'}) {
- SetParam('maintainer', $user->email);
- write_params();
- }
-
- # Make sure the new admin isn't disabled
- if ($user->disabledtext) {
- $user->set_disabledtext('');
- $user->update();
- }
+ my ($user) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $user
+ = ref($user) ? $user : new Bugzilla::User(login_to_id($user, THROW_ERROR));
+
+ my $group_insert = $dbh->prepare(
+ 'INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
+ VALUES (?, ?, ?, ?)'
+ );
+
+ # Admins get explicit membership and bless capability for the admin group
+ my $admin_group = new Bugzilla::Group({name => 'admin'});
+
+ # 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); };
+
+ # If there is no maintainer set, make this user the maintainer.
+ if (!Bugzilla->params->{'maintainer'}) {
+ SetParam('maintainer', $user->email);
+ write_params();
+ }
+
+ # Make sure the new admin isn't disabled
+ if ($user->disabledtext) {
+ $user->set_disabledtext('');
+ $user->update();
+ }
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- say "\n", get_text('install_admin_created', { user => $user });
- }
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ say "\n", get_text('install_admin_created', {user => $user});
+ }
}
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 ($@) {
- say "\n$@";
- undef $password;
- }
- system("stty","echo") unless ON_WINDOWS;
+ 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 ($@) {
+ say "\n$@";
+ undef $password;
}
- return $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;
+
+ # 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();
- say "\n", get_text('install_reset_password_done');
+ 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();
+ say "\n", get_text('install_reset_password_done');
}
1;
diff --git a/Bugzilla/Install/CPAN.pm b/Bugzilla/Install/CPAN.pm
index 094784e1a..4321cac61 100644
--- a/Bugzilla/Install/CPAN.pm
+++ b/Bugzilla/Install/CPAN.pm
@@ -13,11 +13,11 @@ use warnings;
use parent qw(Exporter);
our @EXPORT = qw(
- BZ_LIB
+ BZ_LIB
- check_cpan_requirements
- set_cpan_config
- install_module
+ check_cpan_requirements
+ set_cpan_config
+ install_module
);
use Bugzilla::Constants;
@@ -32,32 +32,28 @@ use File::Path qw(rmtree);
# These are required for install-module.pl to be able to install
# all modules properly.
use constant REQUIREMENTS => (
- {
- module => 'CPAN',
- package => 'CPAN',
- version => '1.81',
- },
- {
- # When Module::Build isn't installed, the YAML module allows
- # CPAN to read META.yml to determine that Module::Build first
- # needs to be installed to compile a module.
- module => 'YAML',
- package => 'YAML',
- version => 0,
- },
- {
- # Many modules on CPAN are now built with Dist::Zilla, which
- # unfortunately means they require this version of EU::MM to install.
- module => 'ExtUtils::MakeMaker',
- package => 'ExtUtils-MakeMaker',
- version => '6.31',
- },
+ {module => 'CPAN', package => 'CPAN', version => '1.81',},
+ {
+ # When Module::Build isn't installed, the YAML module allows
+ # CPAN to read META.yml to determine that Module::Build first
+ # needs to be installed to compile a module.
+ module => 'YAML',
+ package => 'YAML',
+ version => 0,
+ },
+ {
+ # Many modules on CPAN are now built with Dist::Zilla, which
+ # unfortunately means they require this version of EU::MM to install.
+ module => 'ExtUtils::MakeMaker',
+ package => 'ExtUtils-MakeMaker',
+ version => '6.31',
+ },
);
# 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 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});
@@ -66,217 +62,229 @@ use constant BZ_LIB => abs_path(bz_locations()->{ext_libpath});
# 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,
- build_requires_install_policy => 'yes',
- cache_metadata => 1,
- colorize_output => 1,
- colorize_print => 'bold',
- index_expire => 1,
- scan_cache => 'atstart',
-
- inhibit_startup_message => 1,
-
- bzip2 => bin_loc('bzip2'),
- 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 => ['http://www.cpan.org/'],
+ auto_commit => 0,
+
+ # We always force builds, so there's no reason to cache them.
+ build_cache => 0,
+ build_requires_install_policy => 'yes',
+ cache_metadata => 1,
+ colorize_output => 1,
+ colorize_print => 'bold',
+ index_expire => 1,
+ scan_cache => 'atstart',
+
+ inhibit_startup_message => 1,
+
+ bzip2 => bin_loc('bzip2'),
+ 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 => ['http://www.cpan.org/'],
};
sub check_cpan_requirements {
- my ($original_dir, $original_args) = @_;
+ my ($original_dir, $original_args) = @_;
- _require_compiler();
+ _require_compiler();
- my @install;
- foreach my $module (REQUIREMENTS) {
- my $installed = have_vers($module, 1);
- push(@install, $module) if !$installed;
- }
+ my @install;
+ foreach my $module (REQUIREMENTS) {
+ my $installed = have_vers($module, 1);
+ push(@install, $module) if !$installed;
+ }
- return if !@install;
+ return if !@install;
- my $restart_required;
- foreach my $module (@install) {
- $restart_required = 1 if $module->{module} eq 'CPAN';
- install_module($module->{module}, 1);
- }
+ my $restart_required;
+ foreach my $module (@install) {
+ $restart_required = 1 if $module->{module} eq 'CPAN';
+ install_module($module->{module}, 1);
+ }
- if ($restart_required) {
- chdir $original_dir;
- exec($^X, $0, @$original_args);
- }
+ if ($restart_required) {
+ chdir $original_dir;
+ exec($^X, $0, @$original_args);
+ }
}
sub _require_compiler {
- my @errors;
+ my @errors;
- my $cc_name = $Config{cc};
- my $cc_exists = bin_loc($cc_name);
+ my $cc_name = $Config{cc};
+ my $cc_exists = bin_loc($cc_name);
- if (!$cc_exists) {
- push(@errors, install_string('install_no_compiler'));
- }
+ if (!$cc_exists) {
+ push(@errors, install_string('install_no_compiler'));
+ }
- my $make_name = $CPAN::Config->{make};
- my $make_exists = bin_loc($make_name);
+ my $make_name = $CPAN::Config->{make};
+ my $make_exists = bin_loc($make_name);
- if (!$make_exists) {
- push(@errors, install_string('install_no_make'));
- }
+ if (!$make_exists) {
+ push(@errors, install_string('install_no_make'));
+ }
- die @errors if @errors;
+ die @errors if @errors;
}
sub install_module {
- my ($name, $test) = @_;
- my $bzlib = BZ_LIB;
-
- # Make Module::AutoInstall install all dependencies and never prompt.
- local $ENV{PERL_AUTOINSTALL} = '--alldeps';
- # This makes Net::SSLeay not prompt the user, if it gets installed.
- # It also makes any other MakeMaker prompts accept their defaults.
- local $ENV{PERL_MM_USE_DEFAULT} = 1;
-
- # 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 'SOAP::Lite') {
- $CPAN::Config->{makepl_arg} .= " --noprompt";
- }
-
- my $module = CPAN::Shell->expand('Module', $name);
- if (!$module) {
- die install_string('no_such_module', { module => $name }) . "\n";
- }
-
- print install_string('install_module',
- { module => $name, version => $module->cpan_version }) . "\n";
-
- if ($test) {
- CPAN::Shell->force('install', $name);
- }
- else {
- CPAN::Shell->notest('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;
+ my ($name, $test) = @_;
+ my $bzlib = BZ_LIB;
+
+ # Make Module::AutoInstall install all dependencies and never prompt.
+ local $ENV{PERL_AUTOINSTALL} = '--alldeps';
+
+ # This makes Net::SSLeay not prompt the user, if it gets installed.
+ # It also makes any other MakeMaker prompts accept their defaults.
+ local $ENV{PERL_MM_USE_DEFAULT} = 1;
+
+ # 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 'SOAP::Lite') {
+ $CPAN::Config->{makepl_arg} .= " --noprompt";
+ }
+
+ my $module = CPAN::Shell->expand('Module', $name);
+ if (!$module) {
+ die install_string('no_such_module', {module => $name}) . "\n";
+ }
+
+ print install_string('install_module',
+ {module => $name, version => $module->cpan_version})
+ . "\n";
+
+ if ($test) {
+ CPAN::Shell->force('install', $name);
+ }
+ else {
+ CPAN::Shell->notest('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 STDERR install_string('cpan_bugzilla_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) {
- require Config;
-
- $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\""
- . " --install_path lib=\"$bzlib\""
- . " --install_path arch=\"$bzlib/$Config::Config{archname}\"";
- $CPAN::Config->{mbuild_install_arg} = $CPAN::Config->{mbuild_arg};
-
- # 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;
+ 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 STDERR install_string('cpan_bugzilla_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) {
+ require Config;
+
+ $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\""
+ . " --install_path lib=\"$bzlib\""
+ . " --install_path arch=\"$bzlib/$Config::Config{archname}\"";
+ $CPAN::Config->{mbuild_install_arg} = $CPAN::Config->{mbuild_arg};
+
+ # 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} = '';
- }
+ # 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;
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index ed2539251..d3cfcdeeb 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -8,7 +8,7 @@
package Bugzilla::Install::DB;
# NOTE: This package may "use" any modules that it likes,
-# localconfig is available, and params are up to date.
+# localconfig is available, and params are up to date.
use 5.10.1;
use strict;
@@ -34,99 +34,104 @@ use URI::QueryParam;
# NOTE: This is NOT the function for general table updates. See
# update_table_definitions for that. This is only for the fielddefs table.
sub update_fielddefs_definition {
- my $dbh = Bugzilla->dbh;
-
- # 2005-02-21 - LpSolit@gmail.com - Bug 279910
- # qacontact_accessible and assignee_accessible field names no longer exist
- # in the 'bugs' table. Their corresponding entries in the 'bugs_activity'
- # table should therefore be marked as obsolete, meaning that they cannot
- # be used anymore when querying the database - they are not deleted in
- # order to keep track of these fields in the activity table.
- if (!$dbh->bz_column_info('fielddefs', 'obsolete')) {
- $dbh->bz_add_column('fielddefs', 'obsolete',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- print "Marking qacontact_accessible and assignee_accessible as",
- " obsolete fields...\n";
- $dbh->do("UPDATE fielddefs SET obsolete = 1
+ my $dbh = Bugzilla->dbh;
+
+ # 2005-02-21 - LpSolit@gmail.com - Bug 279910
+ # qacontact_accessible and assignee_accessible field names no longer exist
+ # in the 'bugs' table. Their corresponding entries in the 'bugs_activity'
+ # table should therefore be marked as obsolete, meaning that they cannot
+ # be used anymore when querying the database - they are not deleted in
+ # order to keep track of these fields in the activity table.
+ if (!$dbh->bz_column_info('fielddefs', 'obsolete')) {
+ $dbh->bz_add_column('fielddefs', 'obsolete',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ print "Marking qacontact_accessible and assignee_accessible as",
+ " obsolete fields...\n";
+ $dbh->do(
+ "UPDATE fielddefs SET obsolete = 1
WHERE name = 'qacontact_accessible'
- OR name = 'assignee_accessible'");
- }
-
- # 2005-08-10 Myk Melez <myk@mozilla.org> bug 287325
- # Record each field's type and whether or not it's a custom field,
- # in fielddefs.
- $dbh->bz_add_column('fielddefs', 'type',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
- $dbh->bz_add_column('fielddefs', 'custom',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
-
- $dbh->bz_add_column('fielddefs', 'enter_bug',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
-
- # Change the name of the fieldid column to id, so that fielddefs
- # can use Bugzilla::Object easily. We have to do this up here, because
- # otherwise adding these field definitions will fail.
- $dbh->bz_rename_column('fielddefs', 'fieldid', 'id');
-
- # If the largest fielddefs sortkey is less than 100, then
- # we're using the old sorting system, and we should convert
- # it to the new one before adding any new definitions.
- if (!$dbh->selectrow_arrayref(
- 'SELECT COUNT(id) FROM fielddefs WHERE sortkey >= 100'))
- {
- print "Updating the sortkeys for the fielddefs table...\n";
- my $field_ids = $dbh->selectcol_arrayref(
- 'SELECT id FROM fielddefs ORDER BY sortkey');
- my $sortkey = 100;
- foreach my $field_id (@$field_ids) {
- $dbh->do('UPDATE fielddefs SET sortkey = ? WHERE id = ?',
- undef, $sortkey, $field_id);
- $sortkey += 100;
- }
+ OR name = 'assignee_accessible'"
+ );
+ }
+
+ # 2005-08-10 Myk Melez <myk@mozilla.org> bug 287325
+ # Record each field's type and whether or not it's a custom field,
+ # in fielddefs.
+ $dbh->bz_add_column('fielddefs', 'type',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_add_column('fielddefs', 'custom',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+
+ $dbh->bz_add_column('fielddefs', 'enter_bug',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+
+ # Change the name of the fieldid column to id, so that fielddefs
+ # can use Bugzilla::Object easily. We have to do this up here, because
+ # otherwise adding these field definitions will fail.
+ $dbh->bz_rename_column('fielddefs', 'fieldid', 'id');
+
+ # If the largest fielddefs sortkey is less than 100, then
+ # we're using the old sorting system, and we should convert
+ # it to the new one before adding any new definitions.
+ if (!$dbh->selectrow_arrayref(
+ 'SELECT COUNT(id) FROM fielddefs WHERE sortkey >= 100'))
+ {
+ print "Updating the sortkeys for the fielddefs table...\n";
+ my $field_ids
+ = $dbh->selectcol_arrayref('SELECT id FROM fielddefs ORDER BY sortkey');
+ my $sortkey = 100;
+ foreach my $field_id (@$field_ids) {
+ $dbh->do('UPDATE fielddefs SET sortkey = ? WHERE id = ?',
+ undef, $sortkey, $field_id);
+ $sortkey += 100;
}
+ }
- $dbh->bz_add_column('fielddefs', 'visibility_field_id', {TYPE => 'INT3'});
- $dbh->bz_add_column('fielddefs', 'value_field_id', {TYPE => 'INT3'});
- $dbh->bz_add_index('fielddefs', 'fielddefs_value_field_id_idx',
- ['value_field_id']);
+ $dbh->bz_add_column('fielddefs', 'visibility_field_id', {TYPE => 'INT3'});
+ $dbh->bz_add_column('fielddefs', 'value_field_id', {TYPE => 'INT3'});
+ $dbh->bz_add_index('fielddefs', 'fielddefs_value_field_id_idx',
+ ['value_field_id']);
- # Bug 344878
- if (!$dbh->bz_column_info('fielddefs', 'buglist')) {
- $dbh->bz_add_column('fielddefs', 'buglist',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- # Set non-multiselect custom fields as valid buglist fields
- # Note that default fields will be handled in Field.pm
- $dbh->do('UPDATE fielddefs SET buglist = 1 WHERE custom = 1 AND type != ' . FIELD_TYPE_MULTI_SELECT);
- }
+ # Bug 344878
+ if (!$dbh->bz_column_info('fielddefs', 'buglist')) {
+ $dbh->bz_add_column('fielddefs', 'buglist',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- #2008-08-26 elliotte_martin@yahoo.com - Bug 251556
- $dbh->bz_add_column('fielddefs', 'reverse_desc', {TYPE => 'TINYTEXT'});
+ # Set non-multiselect custom fields as valid buglist fields
+ # Note that default fields will be handled in Field.pm
+ $dbh->do('UPDATE fielddefs SET buglist = 1 WHERE custom = 1 AND type != '
+ . FIELD_TYPE_MULTI_SELECT);
+ }
- $dbh->do('UPDATE fielddefs SET buglist = 1
- WHERE custom = 1 AND type = ' . FIELD_TYPE_MULTI_SELECT);
+ #2008-08-26 elliotte_martin@yahoo.com - Bug 251556
+ $dbh->bz_add_column('fielddefs', 'reverse_desc', {TYPE => 'TINYTEXT'});
- $dbh->bz_add_column('fielddefs', 'is_mandatory',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- $dbh->bz_add_index('fielddefs', 'fielddefs_is_mandatory_idx',
- ['is_mandatory']);
+ $dbh->do(
+ 'UPDATE fielddefs SET buglist = 1
+ WHERE custom = 1 AND type = ' . FIELD_TYPE_MULTI_SELECT
+ );
- # 2010-04-05 dkl@redhat.com - Bug 479400
- _migrate_field_visibility_value();
+ $dbh->bz_add_column('fielddefs', 'is_mandatory',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_add_index('fielddefs', 'fielddefs_is_mandatory_idx', ['is_mandatory']);
- $dbh->bz_add_column('fielddefs', 'is_numeric',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- $dbh->do('UPDATE fielddefs SET is_numeric = 1 WHERE type = '
- . FIELD_TYPE_BUG_ID);
+ # 2010-04-05 dkl@redhat.com - Bug 479400
+ _migrate_field_visibility_value();
- # 2012-04-12 aliustek@gmail.com - Bug 728138
- $dbh->bz_add_column('fielddefs', 'long_desc',
- {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}, '');
+ $dbh->bz_add_column('fielddefs', 'is_numeric',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->do(
+ 'UPDATE fielddefs SET is_numeric = 1 WHERE type = ' . FIELD_TYPE_BUG_ID);
- Bugzilla::Hook::process('install_update_db_fielddefs');
+ # 2012-04-12 aliustek@gmail.com - Bug 728138
+ $dbh->bz_add_column('fielddefs', 'long_desc',
+ {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}, '');
- # Remember, this is not the function for adding general table changes.
- # That is below. Add new changes to the fielddefs table above this
- # comment.
+ Bugzilla::Hook::process('install_update_db_fielddefs');
+
+ # Remember, this is not the function for adding general table changes.
+ # That is below. Add new changes to the fielddefs table above this
+ # comment.
}
# Small changes can be put directly into this function.
@@ -136,14 +141,14 @@ sub update_fielddefs_definition {
#
# This function runs in historical order--from upgrades that older
# installations need, to upgrades that newer installations need.
-# The order of items inside this function should only be changed if
+# The order of items inside this function should only be changed if
# absolutely necessary.
#
# The subroutines should have long, descriptive names, so that you
# can easily see what is being done, just by reading this function.
#
# This function is mostly self-documenting. If you're curious about
-# what each of the added/removed columns does, you should see the schema
+# what each of the added/removed columns does, you should see the schema
# docs at:
# http://www.ravenbrook.com/project/p4dti/tool/cgi/bugzilla-schema/
#
@@ -152,1062 +157,1114 @@ sub update_fielddefs_definition {
# the purpose of a column.
#
sub update_table_definitions {
- my $old_params = shift;
- my $dbh = Bugzilla->dbh;
- _update_pre_checksetup_bugzillas();
-
- $dbh->bz_add_column('attachments', 'submitter_id',
- {TYPE => 'INT3', NOTNULL => 1}, 0);
-
- $dbh->bz_rename_column('bugs_activity', 'when', 'bug_when');
-
- _add_bug_vote_cache();
- _update_product_name_definition();
-
- $dbh->bz_add_column('profiles', 'disabledtext',
- {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
-
- _populate_longdescs();
- _update_bugs_activity_field_to_fieldid();
-
- if (!$dbh->bz_column_info('bugs', 'lastdiffed')) {
- $dbh->bz_add_column('bugs', 'lastdiffed', {TYPE =>'DATETIME'});
- $dbh->do('UPDATE bugs SET lastdiffed = NOW()');
- }
-
- _add_unique_login_name_index_to_profiles();
-
- $dbh->bz_add_column('profiles', 'mybugslink',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
-
- _update_component_user_fields_to_ids();
-
- $dbh->bz_add_column('bugs', 'everconfirmed',
- {TYPE => 'BOOLEAN', NOTNULL => 1}, 1);
+ my $old_params = shift;
+ my $dbh = Bugzilla->dbh;
+ _update_pre_checksetup_bugzillas();
- _populate_milestones_table();
+ $dbh->bz_add_column('attachments', 'submitter_id',
+ {TYPE => 'INT3', NOTNULL => 1}, 0);
- _add_products_defaultmilestone();
+ $dbh->bz_rename_column('bugs_activity', 'when', 'bug_when');
- # 2000-03-24 Added unique indexes into the cc and keyword tables. This
- # prevents certain database inconsistencies, and, moreover, is required for
- # new generalized list code to work.
- if (!$dbh->bz_index_info('cc', 'cc_bug_id_idx')
- || !$dbh->bz_index_info('cc', 'cc_bug_id_idx')->{TYPE})
- {
- $dbh->bz_drop_index('cc', 'cc_bug_id_idx');
- $dbh->bz_add_index('cc', 'cc_bug_id_idx',
- {TYPE => 'UNIQUE', FIELDS => [qw(bug_id who)]});
- }
- if (!$dbh->bz_index_info('keywords', 'keywords_bug_id_idx')
- || !$dbh->bz_index_info('keywords', 'keywords_bug_id_idx')->{TYPE})
- {
- $dbh->bz_drop_index('keywords', 'keywords_bug_id_idx');
- $dbh->bz_add_index('keywords', 'keywords_bug_id_idx',
- {TYPE => 'UNIQUE', FIELDS => [qw(bug_id keywordid)]});
- }
+ _add_bug_vote_cache();
+ _update_product_name_definition();
- _copy_from_comments_to_longdescs();
- _populate_duplicates_table();
+ $dbh->bz_add_column('profiles', 'disabledtext',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
- if (!$dbh->bz_column_info('email_setting', 'user_id')) {
- $dbh->bz_add_column('profiles', 'emailflags', {TYPE => 'MEDIUMTEXT'});
- }
-
- $dbh->bz_add_column('groups', 'isactive',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ _populate_longdescs();
+ _update_bugs_activity_field_to_fieldid();
- $dbh->bz_add_column('attachments', 'isobsolete',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ if (!$dbh->bz_column_info('bugs', 'lastdiffed')) {
+ $dbh->bz_add_column('bugs', 'lastdiffed', {TYPE => 'DATETIME'});
+ $dbh->do('UPDATE bugs SET lastdiffed = NOW()');
+ }
- $dbh->bz_drop_column("profiles", "emailnotification");
- $dbh->bz_drop_column("profiles", "newemailtech");
+ _add_unique_login_name_index_to_profiles();
- # 2003-11-19; chicks@chicks.net; bug 225973: fix field size to accommodate
- # wider algorithms such as Blowfish. Note that this needs to be run
- # before recrypting passwords in the following block.
- $dbh->bz_alter_column('profiles', 'cryptpassword',
- {TYPE => 'varchar(128)'});
+ $dbh->bz_add_column('profiles', 'mybugslink',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
- _recrypt_plaintext_passwords();
+ _update_component_user_fields_to_ids();
- # 2001-06-15 kiko@async.com.br - Change bug:version size to avoid
- # truncates re http://bugzilla.mozilla.org/show_bug.cgi?id=9352
- $dbh->bz_alter_column('bugs', 'version',
- {TYPE => 'varchar(64)', NOTNULL => 1});
+ $dbh->bz_add_column('bugs', 'everconfirmed', {TYPE => 'BOOLEAN', NOTNULL => 1},
+ 1);
- _update_bugs_activity_to_only_record_changes();
+ _populate_milestones_table();
- # bug 90933: Make disabledtext NOT NULL
- if (!$dbh->bz_column_info('profiles', 'disabledtext')->{NOTNULL}) {
- $dbh->bz_alter_column("profiles", "disabledtext",
- {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
- }
+ _add_products_defaultmilestone();
- $dbh->bz_add_column("bugs", "reporter_accessible",
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
- $dbh->bz_add_column("bugs", "cclist_accessible",
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ # 2000-03-24 Added unique indexes into the cc and keyword tables. This
+ # prevents certain database inconsistencies, and, moreover, is required for
+ # new generalized list code to work.
+ if ( !$dbh->bz_index_info('cc', 'cc_bug_id_idx')
+ || !$dbh->bz_index_info('cc', 'cc_bug_id_idx')->{TYPE})
+ {
+ $dbh->bz_drop_index('cc', 'cc_bug_id_idx');
+ $dbh->bz_add_index('cc', 'cc_bug_id_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(bug_id who)]});
+ }
+ if ( !$dbh->bz_index_info('keywords', 'keywords_bug_id_idx')
+ || !$dbh->bz_index_info('keywords', 'keywords_bug_id_idx')->{TYPE})
+ {
+ $dbh->bz_drop_index('keywords', 'keywords_bug_id_idx');
+ $dbh->bz_add_index('keywords', 'keywords_bug_id_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(bug_id keywordid)]});
+ }
- $dbh->bz_add_column("bugs_activity", "attach_id", {TYPE => 'INT3'});
+ _copy_from_comments_to_longdescs();
+ _populate_duplicates_table();
- _delete_logincookies_cryptpassword_and_handle_invalid_cookies();
+ if (!$dbh->bz_column_info('email_setting', 'user_id')) {
+ $dbh->bz_add_column('profiles', 'emailflags', {TYPE => 'MEDIUMTEXT'});
+ }
- # qacontact/assignee should always be able to see bugs: bug 97471
- $dbh->bz_drop_column("bugs", "qacontact_accessible");
- $dbh->bz_drop_column("bugs", "assignee_accessible");
+ $dbh->bz_add_column('groups', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
- # 2002-02-20 jeff.hedlund@matrixsi.com - bug 24789 time tracking
- $dbh->bz_add_column("longdescs", "work_time",
- {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'});
- $dbh->bz_add_column("bugs", "estimated_time",
- {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'});
- $dbh->bz_add_column("bugs", "remaining_time",
- {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'});
- $dbh->bz_add_column("bugs", "deadline", {TYPE => 'DATETIME'});
+ $dbh->bz_add_column('attachments', 'isobsolete',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- _use_ip_instead_of_hostname_in_logincookies();
+ $dbh->bz_drop_column("profiles", "emailnotification");
+ $dbh->bz_drop_column("profiles", "newemailtech");
- $dbh->bz_add_column('longdescs', 'isprivate',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- $dbh->bz_add_column('attachments', 'isprivate',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ # 2003-11-19; chicks@chicks.net; bug 225973: fix field size to accommodate
+ # wider algorithms such as Blowfish. Note that this needs to be run
+ # before recrypting passwords in the following block.
+ $dbh->bz_alter_column('profiles', 'cryptpassword', {TYPE => 'varchar(128)'});
+
+ _recrypt_plaintext_passwords();
+
+ # 2001-06-15 kiko@async.com.br - Change bug:version size to avoid
+ # truncates re http://bugzilla.mozilla.org/show_bug.cgi?id=9352
+ $dbh->bz_alter_column('bugs', 'version', {TYPE => 'varchar(64)', NOTNULL => 1});
- _move_quips_into_db();
+ _update_bugs_activity_to_only_record_changes();
- $dbh->bz_drop_column("namedqueries", "watchfordiffs");
+ # bug 90933: Make disabledtext NOT NULL
+ if (!$dbh->bz_column_info('profiles', 'disabledtext')->{NOTNULL}) {
+ $dbh->bz_alter_column("profiles", "disabledtext",
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
+ }
- _use_ids_for_products_and_components();
- _convert_groups_system_from_groupset();
- _convert_attachment_statuses_to_flags();
- _remove_spaces_and_commas_from_flagtypes();
- _setup_usebuggroups_backward_compatibility();
- _remove_user_series_map();
+ $dbh->bz_add_column("bugs", "reporter_accessible",
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ $dbh->bz_add_column("bugs", "cclist_accessible",
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
- # 2006-08-03 remi_zara@mac.com bug 346241, make series.creator nullable
- # This must happen before calling _copy_old_charts_into_database().
- if ($dbh->bz_column_info('series', 'creator')->{NOTNULL}) {
- $dbh->bz_alter_column('series', 'creator', {TYPE => 'INT3'});
- $dbh->do("UPDATE series SET creator = NULL WHERE creator = 0");
- }
+ $dbh->bz_add_column("bugs_activity", "attach_id", {TYPE => 'INT3'});
- _copy_old_charts_into_database();
+ _delete_logincookies_cryptpassword_and_handle_invalid_cookies();
- _add_user_group_map_grant_type();
- _add_group_group_map_grant_type();
+ # qacontact/assignee should always be able to see bugs: bug 97471
+ $dbh->bz_drop_column("bugs", "qacontact_accessible");
+ $dbh->bz_drop_column("bugs", "assignee_accessible");
- $dbh->bz_add_column("profiles", "extern_id", {TYPE => 'varchar(64)'});
+ # 2002-02-20 jeff.hedlund@matrixsi.com - bug 24789 time tracking
+ $dbh->bz_add_column("longdescs", "work_time",
+ {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'});
+ $dbh->bz_add_column("bugs", "estimated_time",
+ {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'});
+ $dbh->bz_add_column("bugs", "remaining_time",
+ {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'});
+ $dbh->bz_add_column("bugs", "deadline", {TYPE => 'DATETIME'});
- $dbh->bz_add_column('flagtypes', 'grant_group_id', {TYPE => 'INT3'});
- $dbh->bz_add_column('flagtypes', 'request_group_id', {TYPE => 'INT3'});
+ _use_ip_instead_of_hostname_in_logincookies();
- # mailto is no longer just userids
- $dbh->bz_rename_column('whine_schedules', 'mailto_userid', 'mailto');
- $dbh->bz_add_column('whine_schedules', 'mailto_type',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'});
+ $dbh->bz_add_column('longdescs', 'isprivate',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_add_column('attachments', 'isprivate',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+
+ _move_quips_into_db();
+
+ $dbh->bz_drop_column("namedqueries", "watchfordiffs");
+
+ _use_ids_for_products_and_components();
+ _convert_groups_system_from_groupset();
+ _convert_attachment_statuses_to_flags();
+ _remove_spaces_and_commas_from_flagtypes();
+ _setup_usebuggroups_backward_compatibility();
+ _remove_user_series_map();
+
+ # 2006-08-03 remi_zara@mac.com bug 346241, make series.creator nullable
+ # This must happen before calling _copy_old_charts_into_database().
+ if ($dbh->bz_column_info('series', 'creator')->{NOTNULL}) {
+ $dbh->bz_alter_column('series', 'creator', {TYPE => 'INT3'});
+ $dbh->do("UPDATE series SET creator = NULL WHERE creator = 0");
+ }
+
+ _copy_old_charts_into_database();
+
+ _add_user_group_map_grant_type();
+ _add_group_group_map_grant_type();
+
+ $dbh->bz_add_column("profiles", "extern_id", {TYPE => 'varchar(64)'});
+
+ $dbh->bz_add_column('flagtypes', 'grant_group_id', {TYPE => 'INT3'});
+ $dbh->bz_add_column('flagtypes', 'request_group_id', {TYPE => 'INT3'});
+
+ # mailto is no longer just userids
+ $dbh->bz_rename_column('whine_schedules', 'mailto_userid', 'mailto');
+ $dbh->bz_add_column('whine_schedules', 'mailto_type',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'});
+
+ _add_longdescs_already_wrapped();
- _add_longdescs_already_wrapped();
+ # Moved enum types to separate tables so we need change the old enum
+ # types to standard varchars in the bugs table.
+ $dbh->bz_alter_column('bugs', 'bug_status',
+ {TYPE => 'varchar(64)', NOTNULL => 1});
+
+ # 2005-03-23 Tomas.Kopal@altap.cz - add default value to resolution,
+ # bug 286695
+ $dbh->bz_alter_column('bugs', 'resolution',
+ {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"});
+ $dbh->bz_alter_column('bugs', 'priority',
+ {TYPE => 'varchar(64)', NOTNULL => 1});
+ $dbh->bz_alter_column('bugs', 'bug_severity',
+ {TYPE => 'varchar(64)', NOTNULL => 1});
+ $dbh->bz_alter_column('bugs', 'rep_platform',
+ {TYPE => 'varchar(64)', NOTNULL => 1}, '');
+ $dbh->bz_alter_column('bugs', 'op_sys', {TYPE => 'varchar(64)', NOTNULL => 1});
+
+ # When migrating quips from the '$datadir/comments' file to the DB,
+ # the user ID should be NULL instead of 0 (which is an invalid user ID).
+ if ($dbh->bz_column_info('quips', 'userid')->{NOTNULL}) {
+ $dbh->bz_alter_column('quips', 'userid', {TYPE => 'INT3'});
+ print "Changing owner to NULL for quips where the owner is", " unknown...\n";
+ $dbh->do('UPDATE quips SET userid = NULL WHERE userid = 0');
+ }
+
+ _convert_attachments_filename_from_mediumtext();
+
+ $dbh->bz_add_column('quips', 'approved',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+
+ # 2002-12-20 Bug 180870 - remove manual shadowdb replication code
+ $dbh->bz_drop_table("shadowlog");
+
+ _rename_votes_count_and_force_group_refresh();
- # Moved enum types to separate tables so we need change the old enum
- # types to standard varchars in the bugs table.
- $dbh->bz_alter_column('bugs', 'bug_status',
- {TYPE => 'varchar(64)', NOTNULL => 1});
- # 2005-03-23 Tomas.Kopal@altap.cz - add default value to resolution,
- # bug 286695
- $dbh->bz_alter_column('bugs', 'resolution',
- {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"});
- $dbh->bz_alter_column('bugs', 'priority',
- {TYPE => 'varchar(64)', NOTNULL => 1});
- $dbh->bz_alter_column('bugs', 'bug_severity',
- {TYPE => 'varchar(64)', NOTNULL => 1});
- $dbh->bz_alter_column('bugs', 'rep_platform',
- {TYPE => 'varchar(64)', NOTNULL => 1}, '');
- $dbh->bz_alter_column('bugs', 'op_sys',
- {TYPE => 'varchar(64)', NOTNULL => 1});
+ # 2004/02/15 - Summaries shouldn't be null - see bug 220232
+ if (!exists $dbh->bz_column_info('bugs', 'short_desc')->{NOTNULL}) {
+ $dbh->bz_alter_column('bugs', 'short_desc',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
+ }
- # When migrating quips from the '$datadir/comments' file to the DB,
- # the user ID should be NULL instead of 0 (which is an invalid user ID).
- if ($dbh->bz_column_info('quips', 'userid')->{NOTNULL}) {
- $dbh->bz_alter_column('quips', 'userid', {TYPE => 'INT3'});
- print "Changing owner to NULL for quips where the owner is",
- " unknown...\n";
- $dbh->do('UPDATE quips SET userid = NULL WHERE userid = 0');
+ $dbh->bz_add_column('products', 'classification_id',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '1'});
+
+ _fix_group_with_empty_name();
+
+ $dbh->bz_add_index('bugs_activity', 'bugs_activity_who_idx', [qw(who)]);
+
+ # Add defaults for some fields that should have them but didn't.
+ $dbh->bz_alter_column('bugs', 'status_whiteboard',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
+ if ($dbh->bz_column_info('bugs', 'votes')) {
+ $dbh->bz_alter_column('bugs', 'votes',
+ {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'});
+ }
+
+ $dbh->bz_alter_column('bugs', 'lastdiffed', {TYPE => 'DATETIME'});
+
+ # 2005-03-09 qa_contact should be NULL instead of 0, bug 285534
+ if ($dbh->bz_column_info('bugs', 'qa_contact')->{NOTNULL}) {
+ $dbh->bz_alter_column('bugs', 'qa_contact', {TYPE => 'INT3'});
+ $dbh->do("UPDATE bugs SET qa_contact = NULL WHERE qa_contact = 0");
+ }
+
+ # 2005-03-27 initialqacontact should be NULL instead of 0, bug 287483
+ if ($dbh->bz_column_info('components', 'initialqacontact')->{NOTNULL}) {
+ $dbh->bz_alter_column('components', 'initialqacontact', {TYPE => 'INT3'});
+ }
+ $dbh->do("UPDATE components SET initialqacontact = NULL "
+ . "WHERE initialqacontact = 0");
+
+ _migrate_email_prefs_to_new_table();
+ _initialize_new_email_prefs();
+ _change_all_mysql_booleans_to_tinyint();
+
+ # make classification_id field type be consistent with DB:Schema
+ $dbh->bz_alter_column('products', 'classification_id',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '1'});
+
+ # initialowner was accidentally NULL when we checked-in Schema,
+ # when it really should be NOT NULL.
+ $dbh->bz_alter_column('components', 'initialowner',
+ {TYPE => 'INT3', NOTNULL => 1}, 0);
+
+ # 2005-03-28 - bug 238800 - index flags.type_id for editflagtypes.cgi
+ $dbh->bz_add_index('flags', 'flags_type_id_idx', [qw(type_id)]);
+
+ # For a short time, the flags_type_id_idx was misnamed in upgraded installs.
+ $dbh->bz_drop_index('flags', 'type_id');
+
+ # 2005-04-28 - LpSolit@gmail.com - Bug 7233: add an index to versions
+ $dbh->bz_alter_column('versions', 'value',
+ {TYPE => 'varchar(64)', NOTNULL => 1});
+ _add_versions_product_id_index();
+
+ if (!exists $dbh->bz_column_info('milestones', 'sortkey')->{DEFAULT}) {
+ $dbh->bz_alter_column('milestones', 'sortkey',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+ }
+
+ # 2005-06-14 - LpSolit@gmail.com - Bug 292544
+ $dbh->bz_alter_column('bugs', 'creation_ts', {TYPE => 'DATETIME'});
+
+ _fix_whine_queries_title_and_op_sys_value();
+ _fix_attachments_submitter_id_idx();
+ _copy_attachments_thedata_to_attach_data();
+ _fix_broken_all_closed_series();
+
+ # 2005-08-14 bugreport@peshkin.net -- Bug 304583
+ # Get rid of leftover DERIVED group permissions
+ use constant GRANT_DERIVED => 1;
+ $dbh->do("DELETE FROM user_group_map WHERE grant_type = " . GRANT_DERIVED);
+
+ _rederive_regex_groups();
+
+ # PUBLIC is a reserved word in Oracle.
+ $dbh->bz_rename_column('series', 'public', 'is_public');
+
+ # 2005-11-04 LpSolit@gmail.com - Bug 305927
+ $dbh->bz_alter_column('groups', 'userregexp',
+ {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"});
+
+ # 2005-09-26 - olav@bkor.dhs.org - Bug 119524
+ $dbh->bz_alter_column('logincookies', 'cookie',
+ {TYPE => 'varchar(16)', PRIMARYKEY => 1, NOTNULL => 1});
+
+ _clean_control_characters_from_short_desc();
+
+ # 2005-12-07 altlst@sonic.net -- Bug 225221
+ $dbh->bz_add_column('longdescs', 'comment_id',
+ {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+
+ _stop_storing_inactive_flags();
+ _change_short_desc_from_mediumtext_to_varchar();
+
+ # 2006-07-01 wurblzap@gmail.com -- Bug 69000
+ $dbh->bz_add_column('namedqueries', 'id',
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ _move_namedqueries_linkinfooter_to_its_own_table();
+
+ _add_classifications_sortkey();
+ _move_data_nomail_into_db();
+
+ # The products table lacked sensible defaults.
+ if ($dbh->bz_column_info('products', 'milestoneurl')) {
+ $dbh->bz_alter_column('products', 'milestoneurl',
+ {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"});
+ }
+ if ($dbh->bz_column_info('products', 'disallownew')) {
+ $dbh->bz_alter_column('products', 'disallownew',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
+
+ if ($dbh->bz_column_info('products', 'votesperuser')) {
+ $dbh->bz_alter_column('products', 'votesperuser',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_alter_column('products', 'votestoconfirm',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
}
+ }
- _convert_attachments_filename_from_mediumtext();
+ # 2006-08-04 LpSolit@gmail.com - Bug 305941
+ $dbh->bz_drop_column('profiles', 'refreshed_when');
+ $dbh->bz_drop_column('groups', 'last_changed');
- $dbh->bz_add_column('quips', 'approved',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ # 2019-01-31 dylan@hardison.net - Bug TODO
+ _update_flagtypes_id();
- # 2002-12-20 Bug 180870 - remove manual shadowdb replication code
- $dbh->bz_drop_table("shadowlog");
+ $dbh->bz_alter_column('keyworddefs', 'id',
+ {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- _rename_votes_count_and_force_group_refresh();
+ # 2006-08-19 LpSolit@gmail.com - Bug 87795
+ $dbh->bz_alter_column('tokens', 'userid', {TYPE => 'INT3'});
- # 2004/02/15 - Summaries shouldn't be null - see bug 220232
- if (!exists $dbh->bz_column_info('bugs', 'short_desc')->{NOTNULL}) {
- $dbh->bz_alter_column('bugs', 'short_desc',
- {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
- }
+ $dbh->bz_drop_index('bugs', 'bugs_short_desc_idx');
- $dbh->bz_add_column('products', 'classification_id',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '1'});
+ # The profiles table was missing some defaults.
+ $dbh->bz_alter_column('profiles', 'disabledtext',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
+ $dbh->bz_alter_column('profiles', 'realname',
+ {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
- _fix_group_with_empty_name();
+ _update_longdescs_who_index();
- $dbh->bz_add_index('bugs_activity', 'bugs_activity_who_idx', [qw(who)]);
+ $dbh->bz_add_column('setting', 'subclass', {TYPE => 'varchar(32)'});
- # Add defaults for some fields that should have them but didn't.
- $dbh->bz_alter_column('bugs', 'status_whiteboard',
- {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
- if ($dbh->bz_column_info('bugs', 'votes')) {
- $dbh->bz_alter_column('bugs', 'votes',
- {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'});
- }
+ $dbh->bz_alter_column('longdescs', 'thetext',
+ {TYPE => 'LONGTEXT', NOTNULL => 1}, '');
- $dbh->bz_alter_column('bugs', 'lastdiffed', {TYPE => 'DATETIME'});
+ # 2006-10-20 LpSolit@gmail.com - Bug 189627
+ $dbh->bz_add_column('group_control_map', 'editcomponents',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_add_column('group_control_map', 'editbugs',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_add_column('group_control_map', 'canconfirm',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- # 2005-03-09 qa_contact should be NULL instead of 0, bug 285534
- if ($dbh->bz_column_info('bugs', 'qa_contact')->{NOTNULL}) {
- $dbh->bz_alter_column('bugs', 'qa_contact', {TYPE => 'INT3'});
- $dbh->do("UPDATE bugs SET qa_contact = NULL WHERE qa_contact = 0");
- }
+ # 2006-11-07 LpSolit@gmail.com - Bug 353656
+ $dbh->bz_add_column('longdescs', 'type',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'});
+ $dbh->bz_add_column('longdescs', 'extra_data', {TYPE => 'varchar(255)'});
- # 2005-03-27 initialqacontact should be NULL instead of 0, bug 287483
- if ($dbh->bz_column_info('components', 'initialqacontact')->{NOTNULL}) {
- $dbh->bz_alter_column('components', 'initialqacontact',
- {TYPE => 'INT3'});
- }
- $dbh->do("UPDATE components SET initialqacontact = NULL " .
- "WHERE initialqacontact = 0");
+ $dbh->bz_add_column('versions', 'id',
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ $dbh->bz_add_column('milestones', 'id',
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- _migrate_email_prefs_to_new_table();
- _initialize_new_email_prefs();
- _change_all_mysql_booleans_to_tinyint();
+ _fix_uppercase_custom_field_names();
+ _fix_uppercase_index_names();
- # make classification_id field type be consistent with DB:Schema
- $dbh->bz_alter_column('products', 'classification_id',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '1'});
+ # 2007-05-17 LpSolit@gmail.com - Bug 344965
+ _initialize_workflow_for_upgrade($old_params);
- # initialowner was accidentally NULL when we checked-in Schema,
- # when it really should be NOT NULL.
- $dbh->bz_alter_column('components', 'initialowner',
- {TYPE => 'INT3', NOTNULL => 1}, 0);
+ # 2007-08-08 LpSolit@gmail.com - Bug 332149
+ $dbh->bz_add_column('groups', 'icon_url', {TYPE => 'TINYTEXT'});
- # 2005-03-28 - bug 238800 - index flags.type_id for editflagtypes.cgi
- $dbh->bz_add_index('flags', 'flags_type_id_idx', [qw(type_id)]);
+ # 2007-08-21 wurblzap@gmail.com - Bug 365378
+ _make_lang_setting_dynamic();
- # For a short time, the flags_type_id_idx was misnamed in upgraded installs.
- $dbh->bz_drop_index('flags', 'type_id');
+ # 2007-11-29 xiaoou.wu@oracle.com - Bug 153129
+ _change_text_types();
- # 2005-04-28 - LpSolit@gmail.com - Bug 7233: add an index to versions
- $dbh->bz_alter_column('versions', 'value',
- {TYPE => 'varchar(64)', NOTNULL => 1});
- _add_versions_product_id_index();
+ # 2007-09-09 LpSolit@gmail.com - Bug 99215
+ _fix_attachment_modification_date();
- if (!exists $dbh->bz_column_info('milestones', 'sortkey')->{DEFAULT}) {
- $dbh->bz_alter_column('milestones', 'sortkey',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
- }
-
- # 2005-06-14 - LpSolit@gmail.com - Bug 292544
- $dbh->bz_alter_column('bugs', 'creation_ts', {TYPE => 'DATETIME'});
+ $dbh->bz_drop_index('longdescs', 'longdescs_thetext_idx');
+ _populate_bugs_fulltext();
- _fix_whine_queries_title_and_op_sys_value();
- _fix_attachments_submitter_id_idx();
- _copy_attachments_thedata_to_attach_data();
- _fix_broken_all_closed_series();
- # 2005-08-14 bugreport@peshkin.net -- Bug 304583
- # Get rid of leftover DERIVED group permissions
- use constant GRANT_DERIVED => 1;
- $dbh->do("DELETE FROM user_group_map WHERE grant_type = " . GRANT_DERIVED);
+ # 2008-01-18 xiaoou.wu@oracle.com - Bug 414292
+ $dbh->bz_alter_column('series', 'query', {TYPE => 'MEDIUMTEXT', NOTNULL => 1});
- _rederive_regex_groups();
+ # Add FK to multi select field tables
+ _add_foreign_keys_to_multiselects();
- # PUBLIC is a reserved word in Oracle.
- $dbh->bz_rename_column('series', 'public', 'is_public');
+ # 2008-07-28 tfu@redhat.com - Bug 431669
+ $dbh->bz_alter_column('group_control_map', 'product_id',
+ {TYPE => 'INT2', NOTNULL => 1});
- # 2005-11-04 LpSolit@gmail.com - Bug 305927
- $dbh->bz_alter_column('groups', 'userregexp',
- {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"});
+ # 2008-09-07 LpSolit@gmail.com - Bug 452893
+ _fix_illegal_flag_modification_dates();
- # 2005-09-26 - olav@bkor.dhs.org - Bug 119524
- $dbh->bz_alter_column('logincookies', 'cookie',
- {TYPE => 'varchar(16)', PRIMARYKEY => 1, NOTNULL => 1});
+ _add_visiblity_value_to_value_tables();
- _clean_control_characters_from_short_desc();
-
- # 2005-12-07 altlst@sonic.net -- Bug 225221
- $dbh->bz_add_column('longdescs', 'comment_id',
- {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ # 2009-03-02 arbingersys@gmail.com - Bug 423613
+ _add_extern_id_index();
- _stop_storing_inactive_flags();
- _change_short_desc_from_mediumtext_to_varchar();
+ # 2009-03-31 LpSolit@gmail.com - Bug 478972
+ $dbh->bz_alter_column('group_control_map', 'entry',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_alter_column('group_control_map', 'canedit',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- # 2006-07-01 wurblzap@gmail.com -- Bug 69000
- $dbh->bz_add_column('namedqueries', 'id',
- {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- _move_namedqueries_linkinfooter_to_its_own_table();
+ # 2009-01-16 oreomike@gmail.com - Bug 302420
+ $dbh->bz_add_column('whine_events', 'mailifnobugs',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- _add_classifications_sortkey();
- _move_data_nomail_into_db();
+ _convert_disallownew_to_isactive();
- # The products table lacked sensible defaults.
- if ($dbh->bz_column_info('products', 'milestoneurl')) {
- $dbh->bz_alter_column('products', 'milestoneurl',
- {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"});
- }
- if ($dbh->bz_column_info('products', 'disallownew')){
- $dbh->bz_alter_column('products', 'disallownew',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
-
- if ($dbh->bz_column_info('products', 'votesperuser')) {
- $dbh->bz_alter_column('products', 'votesperuser',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
- $dbh->bz_alter_column('products', 'votestoconfirm',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
- }
- }
+ $dbh->bz_alter_column('bugs_activity', 'added', {TYPE => 'varchar(255)'});
+ $dbh->bz_add_index('bugs_activity', 'bugs_activity_added_idx', ['added']);
- # 2006-08-04 LpSolit@gmail.com - Bug 305941
- $dbh->bz_drop_column('profiles', 'refreshed_when');
- $dbh->bz_drop_column('groups', 'last_changed');
+ # 2009-09-28 LpSolit@gmail.com - Bug 519032
+ $dbh->bz_drop_column('series', 'last_viewed');
- # 2006-08-06 LpSolit@gmail.com - Bug 347521
- $dbh->bz_alter_column('flagtypes', 'id',
- {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ # 2009-09-28 LpSolit@gmail.com - Bug 399073
+ _fix_logincookies_ipaddr();
- $dbh->bz_alter_column('keyworddefs', 'id',
- {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ # 2009-11-01 LpSolit@gmail.com - Bug 525025
+ _fix_invalid_custom_field_names();
- # 2006-08-19 LpSolit@gmail.com - Bug 87795
- $dbh->bz_alter_column('tokens', 'userid', {TYPE => 'INT3'});
+ _set_attachment_comment_types();
- $dbh->bz_drop_index('bugs', 'bugs_short_desc_idx');
+ $dbh->bz_drop_column('products', 'milestoneurl');
- # The profiles table was missing some defaults.
- $dbh->bz_alter_column('profiles', 'disabledtext',
- {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
- $dbh->bz_alter_column('profiles', 'realname',
- {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
+ _add_allows_unconfirmed_to_product_table();
+ _convert_flagtypes_fks_to_set_null();
+ _fix_decimal_types();
+ _fix_series_creator_fk();
- _update_longdescs_who_index();
+ # 2009-11-14 dkl@redhat.com - Bug 310450
+ $dbh->bz_add_column('bugs_activity', 'comment_id', {TYPE => 'INT4'});
- $dbh->bz_add_column('setting', 'subclass', {TYPE => 'varchar(32)'});
+ # 2010-04-07 LpSolit@gmail.com - Bug 69621
+ $dbh->bz_drop_column('bugs', 'keywords');
- $dbh->bz_alter_column('longdescs', 'thetext',
- {TYPE => 'LONGTEXT', NOTNULL => 1}, '');
+ # 2010-05-07 ewong@pw-wspx.org - Bug 463945
+ $dbh->bz_alter_column('group_control_map', 'membercontrol',
+ {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA});
+ $dbh->bz_alter_column('group_control_map', 'othercontrol',
+ {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA});
- # 2006-10-20 LpSolit@gmail.com - Bug 189627
- $dbh->bz_add_column('group_control_map', 'editcomponents',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- $dbh->bz_add_column('group_control_map', 'editbugs',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- $dbh->bz_add_column('group_control_map', 'canconfirm',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ # Add NOT NULL to some columns that need it, and DEFAULT to
+ # attachments.ispatch.
+ $dbh->bz_alter_column('attachments', 'ispatch',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_alter_column('keyworddefs', 'description',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
+ $dbh->bz_alter_column('products', 'description',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
- # 2006-11-07 LpSolit@gmail.com - Bug 353656
- $dbh->bz_add_column('longdescs', 'type',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'});
- $dbh->bz_add_column('longdescs', 'extra_data', {TYPE => 'varchar(255)'});
+ # Change the default of allows_unconfirmed to TRUE as part
+ # of the new workflow.
+ $dbh->bz_alter_column('products', 'allows_unconfirmed',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
- $dbh->bz_add_column('versions', 'id',
- {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- $dbh->bz_add_column('milestones', 'id',
- {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ # 2010-07-18 LpSolit@gmail.com - Bug 119703
+ _remove_attachment_isurl();
- _fix_uppercase_custom_field_names();
- _fix_uppercase_index_names();
+ # 2009-05-07 ghendricks@novell.com - Bug 77193
+ _add_isactive_to_product_fields();
- # 2007-05-17 LpSolit@gmail.com - Bug 344965
- _initialize_workflow_for_upgrade($old_params);
+ # 2010-10-09 LpSolit@gmail.com - Bug 505165
+ $dbh->bz_alter_column('flags', 'setter_id', {TYPE => 'INT3', NOTNULL => 1});
- # 2007-08-08 LpSolit@gmail.com - Bug 332149
- $dbh->bz_add_column('groups', 'icon_url', {TYPE => 'TINYTEXT'});
+ # 2010-10-09 LpSolit@gmail.com - Bug 451735
+ _fix_series_indexes();
- # 2007-08-21 wurblzap@gmail.com - Bug 365378
- _make_lang_setting_dynamic();
-
- # 2007-11-29 xiaoou.wu@oracle.com - Bug 153129
- _change_text_types();
+ $dbh->bz_add_column('bug_see_also', 'id',
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- # 2007-09-09 LpSolit@gmail.com - Bug 99215
- _fix_attachment_modification_date();
+ _rename_tags_to_tag();
- $dbh->bz_drop_index('longdescs', 'longdescs_thetext_idx');
- _populate_bugs_fulltext();
+ # 2011-01-29 LpSolit@gmail.com - Bug 616185
+ _migrate_user_tags();
- # 2008-01-18 xiaoou.wu@oracle.com - Bug 414292
- $dbh->bz_alter_column('series', 'query',
- { TYPE => 'MEDIUMTEXT', NOTNULL => 1 });
+ _populate_bug_see_also_class();
- # Add FK to multi select field tables
- _add_foreign_keys_to_multiselects();
+ # 2011-06-15 dkl@mozilla.com - Bug 658929
+ _migrate_disabledtext_boolean();
- # 2008-07-28 tfu@redhat.com - Bug 431669
- $dbh->bz_alter_column('group_control_map', 'product_id',
- { TYPE => 'INT2', NOTNULL => 1 });
+ # 2011-11-01 glob@mozilla.com - Bug 240437
+ $dbh->bz_add_column('profiles', 'last_seen_date', {TYPE => 'DATETIME'});
- # 2008-09-07 LpSolit@gmail.com - Bug 452893
- _fix_illegal_flag_modification_dates();
+ # 2011-10-11 miketosh - Bug 690173
+ _on_delete_set_null_for_audit_log_userid();
- _add_visiblity_value_to_value_tables();
+ # 2011-11-23 gerv@gerv.net - Bug 705058 - make filenames longer
+ $dbh->bz_alter_column('attachments', 'filename',
+ {TYPE => 'varchar(255)', NOTNULL => 1});
- # 2009-03-02 arbingersys@gmail.com - Bug 423613
- _add_extern_id_index();
+ # 2011-11-28 dkl@mozilla.com - Bug 685611
+ _fix_notnull_defaults();
- # 2009-03-31 LpSolit@gmail.com - Bug 478972
- $dbh->bz_alter_column('group_control_map', 'entry',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- $dbh->bz_alter_column('group_control_map', 'canedit',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ # 2012-02-15 LpSolit@gmail.com - Bug 722113
+ if ($dbh->bz_index_info('profile_search', 'profile_search_user_id')) {
+ $dbh->bz_drop_index('profile_search', 'profile_search_user_id');
+ $dbh->bz_add_index('profile_search', 'profile_search_user_id_idx',
+ [qw(user_id)]);
+ }
- # 2009-01-16 oreomike@gmail.com - Bug 302420
- $dbh->bz_add_column('whine_events', 'mailifnobugs',
- { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
-
- _convert_disallownew_to_isactive();
+ # 2012-03-23 LpSolit@gmail.com - Bug 448551
+ $dbh->bz_alter_column('bugs', 'target_milestone',
+ {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "'---'"});
- $dbh->bz_alter_column('bugs_activity', 'added',
- { TYPE => 'varchar(255)' });
- $dbh->bz_add_index('bugs_activity', 'bugs_activity_added_idx', ['added']);
+ $dbh->bz_alter_column('milestones', 'value',
+ {TYPE => 'varchar(64)', NOTNULL => 1});
- # 2009-09-28 LpSolit@gmail.com - Bug 519032
- $dbh->bz_drop_column('series', 'last_viewed');
+ $dbh->bz_alter_column('products', 'defaultmilestone',
+ {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "'---'"});
- # 2009-09-28 LpSolit@gmail.com - Bug 399073
- _fix_logincookies_ipaddr();
+ # 2012-04-15 Frank@Frank-Becker.de - Bug 740536
+ $dbh->bz_add_index('audit_log', 'audit_log_class_idx', ['class', 'at_time']);
- # 2009-11-01 LpSolit@gmail.com - Bug 525025
- _fix_invalid_custom_field_names();
+ # 2012-06-06 dkl@mozilla.com - Bug 762288
+ $dbh->bz_alter_column('bugs_activity', 'removed', {TYPE => 'varchar(255)'});
+ $dbh->bz_add_index('bugs_activity', 'bugs_activity_removed_idx', ['removed']);
- _set_attachment_comment_types();
+ # 2012-06-13 dkl@mozilla.com - Bug 764457
+ $dbh->bz_add_column('bugs_activity', 'id',
+ {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- $dbh->bz_drop_column('products', 'milestoneurl');
+ # 2012-06-13 dkl@mozilla.com - Bug 764466
+ $dbh->bz_add_column('profiles_activity', 'id',
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- _add_allows_unconfirmed_to_product_table();
- _convert_flagtypes_fks_to_set_null();
- _fix_decimal_types();
- _fix_series_creator_fk();
+ # 2012-07-24 dkl@mozilla.com - Bug 776972
+ $dbh->bz_alter_column('bugs_activity', 'id',
+ {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- # 2009-11-14 dkl@redhat.com - Bug 310450
- $dbh->bz_add_column('bugs_activity', 'comment_id', {TYPE => 'INT4'});
- # 2010-04-07 LpSolit@gmail.com - Bug 69621
- $dbh->bz_drop_column('bugs', 'keywords');
-
- # 2010-05-07 ewong@pw-wspx.org - Bug 463945
- $dbh->bz_alter_column('group_control_map', 'membercontrol',
- {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA});
- $dbh->bz_alter_column('group_control_map', 'othercontrol',
- {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA});
+ # 2012-07-24 dkl@mozilla.com - Bug 776982
+ _fix_longdescs_primary_key();
- # Add NOT NULL to some columns that need it, and DEFAULT to
- # attachments.ispatch.
- $dbh->bz_alter_column('attachments', 'ispatch',
- { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
- $dbh->bz_alter_column('keyworddefs', 'description',
- { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }, '');
- $dbh->bz_alter_column('products', 'description',
- { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }, '');
+ # 2012-08-02 dkl@mozilla.com - Bug 756953
+ _fix_dependencies_dupes();
- # Change the default of allows_unconfirmed to TRUE as part
- # of the new workflow.
- $dbh->bz_alter_column('products', 'allows_unconfirmed',
- { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE' });
+ # 2012-08-01 koosha.khajeh@gmail.com - Bug 187753
+ _shorten_long_quips();
- # 2010-07-18 LpSolit@gmail.com - Bug 119703
- _remove_attachment_isurl();
+ # 2012-12-29 reed@reedloden.com - Bug 785283
+ _add_password_salt_separator();
- # 2009-05-07 ghendricks@novell.com - Bug 77193
- _add_isactive_to_product_fields();
+ # 2013-01-02 LpSolit@gmail.com - Bug 824361
+ _fix_longdescs_indexes();
- # 2010-10-09 LpSolit@gmail.com - Bug 505165
- $dbh->bz_alter_column('flags', 'setter_id', {TYPE => 'INT3', NOTNULL => 1});
+ # 2013-02-04 dkl@mozilla.com - Bug 824346
+ _fix_flagclusions_indexes();
- # 2010-10-09 LpSolit@gmail.com - Bug 451735
- _fix_series_indexes();
+ # 2013-08-26 sgreen@redhat.com - Bug 903895
+ _fix_components_primary_key();
- $dbh->bz_add_column('bug_see_also', 'id',
- {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ # 2014-06-09 dylan@mozilla.com - Bug 1022923
+ $dbh->bz_add_index('bug_user_last_visit',
+ 'bug_user_last_visit_last_visit_ts_idx',
+ ['last_visit_ts']);
- _rename_tags_to_tag();
+ # 2014-07-14 sgreen@redhat.com - Bug 726696
+ $dbh->bz_alter_column('tokens', 'tokentype',
+ {TYPE => 'varchar(16)', NOTNULL => 1});
- # 2011-01-29 LpSolit@gmail.com - Bug 616185
- _migrate_user_tags();
+ # 2014-07-27 LpSolit@gmail.com - Bug 1044561
+ _fix_user_api_keys_indexes();
- _populate_bug_see_also_class();
+ # 2014-08-11 sgreen@redhat.com - Bug 1012506
+ _update_alias();
- # 2011-06-15 dkl@mozilla.com - Bug 658929
- _migrate_disabledtext_boolean();
+ # 2014-11-10 dkl@mozilla.com - Bug 1093928
+ $dbh->bz_drop_column('longdescs', 'is_markdown');
- # 2011-11-01 glob@mozilla.com - Bug 240437
- $dbh->bz_add_column('profiles', 'last_seen_date', {TYPE => 'DATETIME'});
+ # 2015-12-16 LpSolit@gmail.com - Bug 1232578
+ _sanitize_audit_log_table();
- # 2011-10-11 miketosh - Bug 690173
- _on_delete_set_null_for_audit_log_userid();
-
- # 2011-11-23 gerv@gerv.net - Bug 705058 - make filenames longer
- $dbh->bz_alter_column('attachments', 'filename',
- { TYPE => 'varchar(255)', NOTNULL => 1 });
+ ################################################################
+ # New --TABLE-- changes should go *** A B O V E *** this point #
+ ################################################################
- # 2011-11-28 dkl@mozilla.com - Bug 685611
- _fix_notnull_defaults();
+ Bugzilla::Hook::process('install_update_db');
- # 2012-02-15 LpSolit@gmail.com - Bug 722113
- if ($dbh->bz_index_info('profile_search', 'profile_search_user_id')) {
- $dbh->bz_drop_index('profile_search', 'profile_search_user_id');
- $dbh->bz_add_index('profile_search', 'profile_search_user_id_idx', [qw(user_id)]);
- }
+ # We do this here because otherwise the foreign key from
+ # products.classification_id to classifications.id will fail
+ # (because products.classification_id defaults to "1", so on upgraded
+ # installations it's already been set before the first Classification
+ # exists).
+ Bugzilla::Install::create_default_classification();
- # 2012-03-23 LpSolit@gmail.com - Bug 448551
- $dbh->bz_alter_column('bugs', 'target_milestone',
- {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "'---'"});
-
- $dbh->bz_alter_column('milestones', 'value', {TYPE => 'varchar(64)', NOTNULL => 1});
-
- $dbh->bz_alter_column('products', 'defaultmilestone',
- {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "'---'"});
-
- # 2012-04-15 Frank@Frank-Becker.de - Bug 740536
- $dbh->bz_add_index('audit_log', 'audit_log_class_idx', ['class', 'at_time']);
-
- # 2012-06-06 dkl@mozilla.com - Bug 762288
- $dbh->bz_alter_column('bugs_activity', 'removed',
- { TYPE => 'varchar(255)' });
- $dbh->bz_add_index('bugs_activity', 'bugs_activity_removed_idx', ['removed']);
-
- # 2012-06-13 dkl@mozilla.com - Bug 764457
- $dbh->bz_add_column('bugs_activity', 'id',
- {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
-
- # 2012-06-13 dkl@mozilla.com - Bug 764466
- $dbh->bz_add_column('profiles_activity', 'id',
- {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
-
- # 2012-07-24 dkl@mozilla.com - Bug 776972
- $dbh->bz_alter_column('bugs_activity', 'id',
- {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
-
-
- # 2012-07-24 dkl@mozilla.com - Bug 776982
- _fix_longdescs_primary_key();
-
- # 2012-08-02 dkl@mozilla.com - Bug 756953
- _fix_dependencies_dupes();
-
- # 2012-08-01 koosha.khajeh@gmail.com - Bug 187753
- _shorten_long_quips();
-
- # 2012-12-29 reed@reedloden.com - Bug 785283
- _add_password_salt_separator();
-
- # 2013-01-02 LpSolit@gmail.com - Bug 824361
- _fix_longdescs_indexes();
-
- # 2013-02-04 dkl@mozilla.com - Bug 824346
- _fix_flagclusions_indexes();
-
- # 2013-08-26 sgreen@redhat.com - Bug 903895
- _fix_components_primary_key();
-
- # 2014-06-09 dylan@mozilla.com - Bug 1022923
- $dbh->bz_add_index('bug_user_last_visit',
- 'bug_user_last_visit_last_visit_ts_idx',
- ['last_visit_ts']);
-
- # 2014-07-14 sgreen@redhat.com - Bug 726696
- $dbh->bz_alter_column('tokens', 'tokentype',
- {TYPE => 'varchar(16)', NOTNULL => 1});
-
- # 2014-07-27 LpSolit@gmail.com - Bug 1044561
- _fix_user_api_keys_indexes();
-
- # 2014-08-11 sgreen@redhat.com - Bug 1012506
- _update_alias();
-
- # 2014-11-10 dkl@mozilla.com - Bug 1093928
- $dbh->bz_drop_column('longdescs', 'is_markdown');
-
- # 2015-12-16 LpSolit@gmail.com - Bug 1232578
- _sanitize_audit_log_table();
-
- ################################################################
- # New --TABLE-- changes should go *** A B O V E *** this point #
- ################################################################
-
- Bugzilla::Hook::process('install_update_db');
-
- # We do this here because otherwise the foreign key from
- # products.classification_id to classifications.id will fail
- # (because products.classification_id defaults to "1", so on upgraded
- # installations it's already been set before the first Classification
- # exists).
- Bugzilla::Install::create_default_classification();
-
- $dbh->bz_setup_foreign_keys();
+ $dbh->bz_setup_foreign_keys();
}
# Subroutines should be ordered in the order that they are called.
# Thus, newer subroutines should be at the bottom.
sub _update_pre_checksetup_bugzillas {
- my $dbh = Bugzilla->dbh;
- # really old fields that were added before checksetup.pl existed
- # but aren't in very old bugzilla's (like 2.1)
- # Steve Stock (sstock@iconnect-inc.com)
-
- $dbh->bz_add_column('bugs', 'target_milestone',
- {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"});
- $dbh->bz_add_column('bugs', 'qa_contact', {TYPE => 'INT3'});
- $dbh->bz_add_column('bugs', 'status_whiteboard',
- {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
- if (!$dbh->bz_column_info('products', 'isactive')){
- $dbh->bz_add_column('products', 'disallownew',
- {TYPE => 'BOOLEAN', NOTNULL => 1}, 0);
- }
-
- $dbh->bz_add_column('components', 'initialqacontact',
- {TYPE => 'TINYTEXT'});
- $dbh->bz_add_column('components', 'description',
- {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
+ my $dbh = Bugzilla->dbh;
+
+ # really old fields that were added before checksetup.pl existed
+ # but aren't in very old bugzilla's (like 2.1)
+ # Steve Stock (sstock@iconnect-inc.com)
+
+ $dbh->bz_add_column('bugs', 'target_milestone',
+ {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"});
+ $dbh->bz_add_column('bugs', 'qa_contact', {TYPE => 'INT3'});
+ $dbh->bz_add_column('bugs', 'status_whiteboard',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
+ if (!$dbh->bz_column_info('products', 'isactive')) {
+ $dbh->bz_add_column('products', 'disallownew',
+ {TYPE => 'BOOLEAN', NOTNULL => 1}, 0);
+ }
+
+ $dbh->bz_add_column('components', 'initialqacontact', {TYPE => 'TINYTEXT'});
+ $dbh->bz_add_column('components', 'description',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
}
sub _add_bug_vote_cache {
- my $dbh = Bugzilla->dbh;
- # 1999-10-11 Restructured voting database to add a cached value in each
- # bug recording how many total votes that bug has. While I'm at it,
- # I removed the unused "area" field from the bugs database. It is
- # distressing to realize that the bugs table has reached the maximum
- # number of indices allowed by MySQL (16), which may make future
- # enhancements awkward.
- # (P.S. All is not lost; it appears that the latest betas of MySQL
- # support a new table format which will allow 32 indices.)
-
- if ($dbh->bz_column_info('bugs', 'area')) {
- $dbh->bz_drop_column('bugs', 'area');
- $dbh->bz_add_column('bugs', 'votes', {TYPE => 'INT3', NOTNULL => 1,
- DEFAULT => 0});
- $dbh->bz_add_index('bugs', 'bugs_votes_idx', [qw(votes)]);
- $dbh->bz_add_column('products', 'votesperuser',
- {TYPE => 'INT2', NOTNULL => 1}, 0);
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 1999-10-11 Restructured voting database to add a cached value in each
+ # bug recording how many total votes that bug has. While I'm at it,
+ # I removed the unused "area" field from the bugs database. It is
+ # distressing to realize that the bugs table has reached the maximum
+ # number of indices allowed by MySQL (16), which may make future
+ # enhancements awkward.
+ # (P.S. All is not lost; it appears that the latest betas of MySQL
+ # support a new table format which will allow 32 indices.)
+
+ if ($dbh->bz_column_info('bugs', 'area')) {
+ $dbh->bz_drop_column('bugs', 'area');
+ $dbh->bz_add_column('bugs', 'votes',
+ {TYPE => 'INT3', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_add_index('bugs', 'bugs_votes_idx', [qw(votes)]);
+ $dbh->bz_add_column('products', 'votesperuser', {TYPE => 'INT2', NOTNULL => 1},
+ 0);
+ }
}
sub _update_product_name_definition {
- my $dbh = Bugzilla->dbh;
- # The product name used to be very different in various tables.
- #
- # It was varchar(16) in bugs
- # tinytext in components
- # tinytext in products
- # tinytext in versions
- #
- # tinytext is equivalent to varchar(255), which is quite huge, so I change
- # them all to varchar(64).
-
- # Only do this if these fields still exist - they're removed in
- # a later change
- if ($dbh->bz_column_info('products', 'product')) {
- $dbh->bz_alter_column('bugs', 'product',
- {TYPE => 'varchar(64)', NOTNULL => 1});
- $dbh->bz_alter_column('components', 'program', {TYPE => 'varchar(64)'});
- $dbh->bz_alter_column('products', 'product', {TYPE => 'varchar(64)'});
- $dbh->bz_alter_column('versions', 'program',
- {TYPE => 'varchar(64)', NOTNULL => 1});
+ my $dbh = Bugzilla->dbh;
+
+ # The product name used to be very different in various tables.
+ #
+ # It was varchar(16) in bugs
+ # tinytext in components
+ # tinytext in products
+ # tinytext in versions
+ #
+ # tinytext is equivalent to varchar(255), which is quite huge, so I change
+ # them all to varchar(64).
+
+ # Only do this if these fields still exist - they're removed in
+ # a later change
+ if ($dbh->bz_column_info('products', 'product')) {
+ $dbh->bz_alter_column('bugs', 'product', {TYPE => 'varchar(64)', NOTNULL => 1});
+ $dbh->bz_alter_column('components', 'program', {TYPE => 'varchar(64)'});
+ $dbh->bz_alter_column('products', 'product', {TYPE => 'varchar(64)'});
+ $dbh->bz_alter_column('versions', 'program',
+ {TYPE => 'varchar(64)', NOTNULL => 1});
+ }
+}
+
+sub _update_flagtypes_id {
+ my $dbh = Bugzilla->dbh;
+ my @fixes = (
+ {table => 'flaginclusions', column => 'type_id'},
+ {table => 'flagexclusions', column => 'type_id'},
+ {table => 'flags', column => 'type_id'},
+ );
+ my $flagtypes_def = $dbh->bz_column_info('flagtypes', 'id');
+ foreach my $fix (@fixes) {
+ my $def = $dbh->bz_column_info($fix->{table}, $fix->{column});
+ if ($def->{TYPE} eq 'INT2') {
+ warn "Dropping $fix->{table}\n";
+ $dbh->bz_drop_related_fks($fix->{table}, $fix->{column});
+ $def->{TYPE} = 'INT3';
+ $dbh->bz_alter_column($fix->{table}, $fix->{column}, $def);
}
+ }
+
+ if ($flagtypes_def->{TYPE} eq 'SMALLSERIAL') {
+ $flagtypes_def->{TYPE} = 'MEDIUMSERIAL';
+ $dbh->bz_alter_column('flagtypes', 'id', $flagtypes_def);
+ }
}
# A helper for the function below.
sub _write_one_longdesc {
- my ($id, $who, $when, $buffer) = (@_);
- my $dbh = Bugzilla->dbh;
- $buffer = trim($buffer);
- return if !$buffer;
- $dbh->do("INSERT INTO longdescs (bug_id, who, bug_when, thetext)
- VALUES (?,?,?,?)", undef, $id, $who,
- time2str("%Y/%m/%d %H:%M:%S", $when), $buffer);
+ my ($id, $who, $when, $buffer) = (@_);
+ my $dbh = Bugzilla->dbh;
+ $buffer = trim($buffer);
+ return if !$buffer;
+ $dbh->do(
+ "INSERT INTO longdescs (bug_id, who, bug_when, thetext)
+ VALUES (?,?,?,?)", undef, $id, $who,
+ time2str("%Y/%m/%d %H:%M:%S", $when), $buffer
+ );
}
sub _populate_longdescs {
- my $dbh = Bugzilla->dbh;
- # 2000-01-20 Added a new "longdescs" table, which is supposed to have
- # all the long descriptions in it, replacing the old long_desc field
- # in the bugs table. The below hideous code populates this new table
- # with things from the old field, with ugly parsing and heuristics.
-
- if ($dbh->bz_column_info('bugs', 'long_desc')) {
- my ($total) = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs");
-
- print "Populating new long_desc table. This is slow. There are",
- " $total\nbugs to process; a line of dots will be printed",
- " for each 50.\n\n";
- local $| = 1;
-
- # On MySQL, longdescs doesn't benefit from transactions, but this
- # doesn't hurt.
- $dbh->bz_start_transaction();
-
- $dbh->do('DELETE FROM longdescs');
-
- my $sth = $dbh->prepare("SELECT bug_id, creation_ts, reporter,
- long_desc FROM bugs ORDER BY bug_id");
- $sth->execute();
- my $count = 0;
- while (my ($id, $createtime, $reporterid, $desc) =
- $sth->fetchrow_array())
+ my $dbh = Bugzilla->dbh;
+
+ # 2000-01-20 Added a new "longdescs" table, which is supposed to have
+ # all the long descriptions in it, replacing the old long_desc field
+ # in the bugs table. The below hideous code populates this new table
+ # with things from the old field, with ugly parsing and heuristics.
+
+ if ($dbh->bz_column_info('bugs', 'long_desc')) {
+ my ($total) = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs");
+
+ print "Populating new long_desc table. This is slow. There are",
+ " $total\nbugs to process; a line of dots will be printed",
+ " for each 50.\n\n";
+ local $| = 1;
+
+ # On MySQL, longdescs doesn't benefit from transactions, but this
+ # doesn't hurt.
+ $dbh->bz_start_transaction();
+
+ $dbh->do('DELETE FROM longdescs');
+
+ my $sth = $dbh->prepare(
+ "SELECT bug_id, creation_ts, reporter,
+ long_desc FROM bugs ORDER BY bug_id"
+ );
+ $sth->execute();
+ my $count = 0;
+ while (my ($id, $createtime, $reporterid, $desc) = $sth->fetchrow_array()) {
+ $count++;
+ indicate_progress({total => $total, current => $count});
+ $desc =~ s/\r//g;
+ my $who = $reporterid;
+ my $when = str2time($createtime);
+ my $buffer = "";
+ foreach my $line (split(/\n/, $desc)) {
+ $line =~ s/\s+$//g; # Trim trailing whitespace.
+ if ($line =~ /^------- Additional Comments From ([^\s]+)\s+(\d.+\d)\s+-------$/)
{
- $count++;
- indicate_progress({ total => $total, current => $count });
- $desc =~ s/\r//g;
- my $who = $reporterid;
- my $when = str2time($createtime);
- my $buffer = "";
- foreach my $line (split(/\n/, $desc)) {
- $line =~ s/\s+$//g; # Trim trailing whitespace.
- if ($line =~ /^------- Additional Comments From ([^\s]+)\s+(\d.+\d)\s+-------$/)
- {
- my $name = $1;
- my $date = str2time($2);
- # Oy, what a hack. The creation time is accurate to the
- # second. But the long text only contains things accurate
- # to the And so, if someone makes a comment within a
- # minute of the original bug creation, then the comment can
- # come *before* the bug creation. So, we add 59 seconds to
- # the time of all comments, so that they are always
- # considered to have happened at the *end* of the given
- # minute, not the beginning.
- $date += 59;
- if ($date >= $when) {
- _write_one_longdesc($id, $who, $when, $buffer);
- $buffer = "";
- $when = $date;
- my $s2 = $dbh->prepare("SELECT userid FROM profiles " .
- "WHERE login_name = ?");
- $s2->execute($name);
- ($who) = ($s2->fetchrow_array());
-
- if (!$who) {
- # This username doesn't exist. Maybe someone
- # renamed them or something. Invent a new profile
- # entry disabled, just to represent them.
- $dbh->do("INSERT INTO profiles (login_name,
+ my $name = $1;
+ my $date = str2time($2);
+
+ # Oy, what a hack. The creation time is accurate to the
+ # second. But the long text only contains things accurate
+ # to the And so, if someone makes a comment within a
+ # minute of the original bug creation, then the comment can
+ # come *before* the bug creation. So, we add 59 seconds to
+ # the time of all comments, so that they are always
+ # considered to have happened at the *end* of the given
+ # minute, not the beginning.
+ $date += 59;
+ if ($date >= $when) {
+ _write_one_longdesc($id, $who, $when, $buffer);
+ $buffer = "";
+ $when = $date;
+ my $s2 = $dbh->prepare("SELECT userid FROM profiles " . "WHERE login_name = ?");
+ $s2->execute($name);
+ ($who) = ($s2->fetchrow_array());
+
+ if (!$who) {
+
+ # This username doesn't exist. Maybe someone
+ # renamed them or something. Invent a new profile
+ # entry disabled, just to represent them.
+ $dbh->do(
+ "INSERT INTO profiles (login_name,
cryptpassword, disabledtext)
VALUES (?,?,?)", undef, $name, '*',
- "Account created only to maintain"
- . " database integrity");
- $who = $dbh->bz_last_key('profiles', 'userid');
- }
- next;
- }
- }
- $buffer .= $line . "\n";
+ "Account created only to maintain" . " database integrity"
+ );
+ $who = $dbh->bz_last_key('profiles', 'userid');
}
- _write_one_longdesc($id, $who, $when, $buffer);
- } # while loop
+ next;
+ }
+ }
+ $buffer .= $line . "\n";
+ }
+ _write_one_longdesc($id, $who, $when, $buffer);
+ } # while loop
- print "\n\n";
- $dbh->bz_drop_column('bugs', 'long_desc');
- $dbh->bz_commit_transaction();
- } # main if
+ print "\n\n";
+ $dbh->bz_drop_column('bugs', 'long_desc');
+ $dbh->bz_commit_transaction();
+ } # main if
}
sub _update_bugs_activity_field_to_fieldid {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- # 2000-01-18 Added a new table fielddefs that records information about the
- # different fields we keep an activity log on. The bugs_activity table
- # now has a pointer into that table instead of recording the name directly.
- if ($dbh->bz_column_info('bugs_activity', 'field')) {
- $dbh->bz_add_column('bugs_activity', 'fieldid',
- {TYPE => 'INT3', NOTNULL => 1}, 0);
+ # 2000-01-18 Added a new table fielddefs that records information about the
+ # different fields we keep an activity log on. The bugs_activity table
+ # now has a pointer into that table instead of recording the name directly.
+ if ($dbh->bz_column_info('bugs_activity', 'field')) {
+ $dbh->bz_add_column('bugs_activity', 'fieldid', {TYPE => 'INT3', NOTNULL => 1},
+ 0);
- $dbh->bz_add_index('bugs_activity', 'bugs_activity_fieldid_idx',
- [qw(fieldid)]);
- print "Populating new bugs_activity.fieldid field...\n";
+ $dbh->bz_add_index('bugs_activity', 'bugs_activity_fieldid_idx', [qw(fieldid)]);
+ print "Populating new bugs_activity.fieldid field...\n";
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- my $ids = $dbh->selectall_arrayref(
- 'SELECT DISTINCT fielddefs.id, bugs_activity.field
+ my $ids = $dbh->selectall_arrayref(
+ 'SELECT DISTINCT fielddefs.id, bugs_activity.field
FROM bugs_activity LEFT JOIN fielddefs
- ON bugs_activity.field = fielddefs.name', {Slice=>{}});
-
- foreach my $item (@$ids) {
- my $id = $item->{id};
- my $field = $item->{field};
- # If the id is NULL
- if (!$id) {
- $dbh->do("INSERT INTO fielddefs (name, description) VALUES " .
- "(?, ?)", undef, $field, $field);
- $id = $dbh->bz_last_key('fielddefs', 'id');
- }
- $dbh->do("UPDATE bugs_activity SET fieldid = ? WHERE field = ?",
- undef, $id, $field);
- }
- $dbh->bz_commit_transaction();
+ ON bugs_activity.field = fielddefs.name', {Slice => {}}
+ );
- $dbh->bz_drop_column('bugs_activity', 'field');
+ foreach my $item (@$ids) {
+ my $id = $item->{id};
+ my $field = $item->{field};
+
+ # If the id is NULL
+ if (!$id) {
+ $dbh->do("INSERT INTO fielddefs (name, description) VALUES " . "(?, ?)",
+ undef, $field, $field);
+ $id = $dbh->bz_last_key('fielddefs', 'id');
+ }
+ $dbh->do("UPDATE bugs_activity SET fieldid = ? WHERE field = ?",
+ undef, $id, $field);
}
+ $dbh->bz_commit_transaction();
+
+ $dbh->bz_drop_column('bugs_activity', 'field');
+ }
}
sub _add_unique_login_name_index_to_profiles {
- my $dbh = Bugzilla->dbh;
-
- # 2000-01-22 The "login_name" field in the "profiles" table was not
- # declared to be unique. Sure enough, somehow, I got 22 duplicated entries
- # in my database. This code detects that, cleans up the duplicates, and
- # then tweaks the table to declare the field to be unique. What a pain.
- if (!$dbh->bz_index_info('profiles', 'profiles_login_name_idx')
- || !$dbh->bz_index_info('profiles', 'profiles_login_name_idx')->{TYPE})
- {
- print "Searching for duplicate entries in the profiles table...\n";
- while (1) {
- # This code is weird in that it loops around and keeps doing this
- # select again. That's because I'm paranoid about deleting entries
- # out from under us in the profiles table. Things get weird if
- # there are *three* or more entries for the same user...
- my $sth = $dbh->prepare("SELECT p1.userid, p2.userid, p1.login_name
+ my $dbh = Bugzilla->dbh;
+
+ # 2000-01-22 The "login_name" field in the "profiles" table was not
+ # declared to be unique. Sure enough, somehow, I got 22 duplicated entries
+ # in my database. This code detects that, cleans up the duplicates, and
+ # then tweaks the table to declare the field to be unique. What a pain.
+ if ( !$dbh->bz_index_info('profiles', 'profiles_login_name_idx')
+ || !$dbh->bz_index_info('profiles', 'profiles_login_name_idx')->{TYPE})
+ {
+ print "Searching for duplicate entries in the profiles table...\n";
+ while (1) {
+
+ # This code is weird in that it loops around and keeps doing this
+ # select again. That's because I'm paranoid about deleting entries
+ # out from under us in the profiles table. Things get weird if
+ # there are *three* or more entries for the same user...
+ my $sth = $dbh->prepare(
+ "SELECT p1.userid, p2.userid, p1.login_name
FROM profiles AS p1, profiles AS p2
WHERE p1.userid < p2.userid
AND p1.login_name = p2.login_name
- ORDER BY p1.login_name");
- $sth->execute();
- my ($u1, $u2, $n) = ($sth->fetchrow_array);
- last if !$u1;
-
- print "Both $u1 & $u2 are ids for $n! Merging $u2 into $u1...\n";
- foreach my $i (["bugs", "reporter"],
- ["bugs", "assigned_to"],
- ["bugs", "qa_contact"],
- ["attachments", "submitter_id"],
- ["bugs_activity", "who"],
- ["cc", "who"],
- ["votes", "who"],
- ["longdescs", "who"]) {
- my ($table, $field) = (@$i);
- if ($dbh->bz_table_info($table)) {
- print " Updating $table.$field...\n";
- $dbh->do("UPDATE $table SET $field = $u1 " .
- "WHERE $field = $u2");
- }
- }
- $dbh->do("DELETE FROM profiles WHERE userid = $u2");
+ ORDER BY p1.login_name"
+ );
+ $sth->execute();
+ my ($u1, $u2, $n) = ($sth->fetchrow_array);
+ last if !$u1;
+
+ print "Both $u1 & $u2 are ids for $n! Merging $u2 into $u1...\n";
+ foreach my $i (
+ ["bugs", "reporter"],
+ ["bugs", "assigned_to"],
+ ["bugs", "qa_contact"],
+ ["attachments", "submitter_id"],
+ ["bugs_activity", "who"],
+ ["cc", "who"],
+ ["votes", "who"],
+ ["longdescs", "who"]
+ )
+ {
+ my ($table, $field) = (@$i);
+ if ($dbh->bz_table_info($table)) {
+ print " Updating $table.$field...\n";
+ $dbh->do("UPDATE $table SET $field = $u1 " . "WHERE $field = $u2");
}
- print "OK, changing index type to prevent duplicates in the",
- " future...\n";
-
- $dbh->bz_drop_index('profiles', 'profiles_login_name_idx');
- $dbh->bz_add_index('profiles', 'profiles_login_name_idx',
- {TYPE => 'UNIQUE', FIELDS => [qw(login_name)]});
+ }
+ $dbh->do("DELETE FROM profiles WHERE userid = $u2");
}
+ print "OK, changing index type to prevent duplicates in the", " future...\n";
+
+ $dbh->bz_drop_index('profiles', 'profiles_login_name_idx');
+ $dbh->bz_add_index('profiles', 'profiles_login_name_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(login_name)]});
+ }
}
sub _update_component_user_fields_to_ids {
- my $dbh = Bugzilla->dbh;
-
- # components.initialowner
- my $comp_init_owner = $dbh->bz_column_info('components', 'initialowner');
- if ($comp_init_owner && $comp_init_owner->{TYPE} eq 'TINYTEXT') {
- my $sth = $dbh->prepare("SELECT program, value, initialowner
- FROM components");
- $sth->execute();
- while (my ($program, $value, $initialowner) = $sth->fetchrow_array()) {
- my ($id) = $dbh->selectrow_array(
- "SELECT userid FROM profiles WHERE login_name = ?",
- undef, $initialowner);
-
- unless (defined $id) {
- print "Warning: You have an invalid default assignee",
- " '$initialowner'\n in component '$value' of program",
- " '$program'!\n";
- $id = 0;
- }
+ my $dbh = Bugzilla->dbh;
- $dbh->do("UPDATE components SET initialowner = ?
- WHERE program = ? AND value = ?", undef,
- $id, $program, $value);
- }
- $dbh->bz_alter_column('components','initialowner',{TYPE => 'INT3'});
+ # components.initialowner
+ my $comp_init_owner = $dbh->bz_column_info('components', 'initialowner');
+ if ($comp_init_owner && $comp_init_owner->{TYPE} eq 'TINYTEXT') {
+ my $sth = $dbh->prepare(
+ "SELECT program, value, initialowner
+ FROM components"
+ );
+ $sth->execute();
+ while (my ($program, $value, $initialowner) = $sth->fetchrow_array()) {
+ my ($id)
+ = $dbh->selectrow_array("SELECT userid FROM profiles WHERE login_name = ?",
+ undef, $initialowner);
+
+ unless (defined $id) {
+ print "Warning: You have an invalid default assignee",
+ " '$initialowner'\n in component '$value' of program", " '$program'!\n";
+ $id = 0;
+ }
+
+ $dbh->do(
+ "UPDATE components SET initialowner = ?
+ WHERE program = ? AND value = ?", undef, $id, $program, $value
+ );
}
+ $dbh->bz_alter_column('components', 'initialowner', {TYPE => 'INT3'});
+ }
- # components.initialqacontact
- my $comp_init_qa = $dbh->bz_column_info('components', 'initialqacontact');
- if ($comp_init_qa && $comp_init_qa->{TYPE} eq 'TINYTEXT') {
- my $sth = $dbh->prepare("SELECT program, value, initialqacontact
- FROM components");
- $sth->execute();
- while (my ($program, $value, $initialqacontact) =
- $sth->fetchrow_array())
- {
- my ($id) = $dbh->selectrow_array(
- "SELECT userid FROM profiles WHERE login_name = ?",
- undef, $initialqacontact);
-
- unless (defined $id) {
- if ($initialqacontact) {
- print "Warning: You have an invalid default QA contact",
- " $initialqacontact' in program '$program',",
- " component '$value'!\n";
- }
- $id = 0;
- }
-
- $dbh->do("UPDATE components SET initialqacontact = ?
- WHERE program = ? AND value = ?", undef,
- $id, $program, $value);
+ # components.initialqacontact
+ my $comp_init_qa = $dbh->bz_column_info('components', 'initialqacontact');
+ if ($comp_init_qa && $comp_init_qa->{TYPE} eq 'TINYTEXT') {
+ my $sth = $dbh->prepare(
+ "SELECT program, value, initialqacontact
+ FROM components"
+ );
+ $sth->execute();
+ while (my ($program, $value, $initialqacontact) = $sth->fetchrow_array()) {
+ my ($id)
+ = $dbh->selectrow_array("SELECT userid FROM profiles WHERE login_name = ?",
+ undef, $initialqacontact);
+
+ unless (defined $id) {
+ if ($initialqacontact) {
+ print "Warning: You have an invalid default QA contact",
+ " $initialqacontact' in program '$program',", " component '$value'!\n";
}
+ $id = 0;
+ }
- $dbh->bz_alter_column('components','initialqacontact',{TYPE => 'INT3'});
+ $dbh->do(
+ "UPDATE components SET initialqacontact = ?
+ WHERE program = ? AND value = ?", undef, $id, $program, $value
+ );
}
+
+ $dbh->bz_alter_column('components', 'initialqacontact', {TYPE => 'INT3'});
+ }
}
sub _populate_milestones_table {
- my $dbh = Bugzilla->dbh;
- # 2000-03-21 Adding a table for target milestones to
- # database - matthew@zeroknowledge.com
- # If the milestones table is empty, and we're still back in a Bugzilla
- # that has a bugs.product field, that means that we just created
- # the milestones table and it needs to be populated.
- my $milestones_exist = $dbh->selectrow_array(
- "SELECT DISTINCT 1 FROM milestones");
- if (!$milestones_exist && $dbh->bz_column_info('bugs', 'product')) {
- print "Replacing blank milestones...\n";
-
- $dbh->do("UPDATE bugs
+ my $dbh = Bugzilla->dbh;
+
+ # 2000-03-21 Adding a table for target milestones to
+ # database - matthew@zeroknowledge.com
+ # If the milestones table is empty, and we're still back in a Bugzilla
+ # that has a bugs.product field, that means that we just created
+ # the milestones table and it needs to be populated.
+ my $milestones_exist
+ = $dbh->selectrow_array("SELECT DISTINCT 1 FROM milestones");
+ if (!$milestones_exist && $dbh->bz_column_info('bugs', 'product')) {
+ print "Replacing blank milestones...\n";
+
+ $dbh->do(
+ "UPDATE bugs
SET target_milestone = '---'
- WHERE target_milestone = ' '");
-
- # If we are upgrading from 2.8 or earlier, we will have *created*
- # the milestones table with a product_id field, but Bugzilla expects
- # it to have a "product" field. So we change the field backward so
- # other code can run. The change will be reversed later in checksetup.
- if ($dbh->bz_column_info('milestones', 'product_id')) {
- # Dropping the column leaves us with a milestones_product_id_idx
- # index that is only on the "value" column. We need to drop the
- # whole index so that it can be correctly re-created later.
- $dbh->bz_drop_index('milestones', 'milestones_product_id_idx');
- $dbh->bz_drop_column('milestones', 'product_id');
- $dbh->bz_add_column('milestones', 'product',
- {TYPE => 'varchar(64)', NOTNULL => 1}, '');
- }
+ WHERE target_milestone = ' '"
+ );
- # Populate the milestone table with all existing values in the database
- my $sth = $dbh->prepare("SELECT DISTINCT target_milestone, product
- FROM bugs");
- $sth->execute();
+ # If we are upgrading from 2.8 or earlier, we will have *created*
+ # the milestones table with a product_id field, but Bugzilla expects
+ # it to have a "product" field. So we change the field backward so
+ # other code can run. The change will be reversed later in checksetup.
+ if ($dbh->bz_column_info('milestones', 'product_id')) {
+
+ # Dropping the column leaves us with a milestones_product_id_idx
+ # index that is only on the "value" column. We need to drop the
+ # whole index so that it can be correctly re-created later.
+ $dbh->bz_drop_index('milestones', 'milestones_product_id_idx');
+ $dbh->bz_drop_column('milestones', 'product_id');
+ $dbh->bz_add_column('milestones', 'product',
+ {TYPE => 'varchar(64)', NOTNULL => 1}, '');
+ }
- print "Populating milestones table...\n";
+ # Populate the milestone table with all existing values in the database
+ my $sth = $dbh->prepare(
+ "SELECT DISTINCT target_milestone, product
+ FROM bugs"
+ );
+ $sth->execute();
- while (my ($value, $product) = $sth->fetchrow_array()) {
- # check if the value already exists
- my $sortkey = substr($value, 1);
- if ($sortkey !~ /^\d+$/) {
- $sortkey = 0;
- } else {
- $sortkey *= 10;
- }
- my $ms_exists = $dbh->selectrow_array(
- "SELECT value FROM milestones
- WHERE value = ? AND product = ?", undef, $value, $product);
+ print "Populating milestones table...\n";
- if (!$ms_exists) {
- $dbh->do("INSERT INTO milestones(value, product, sortkey)
- VALUES (?,?,?)", undef, $value, $product, $sortkey);
- }
- }
+ while (my ($value, $product) = $sth->fetchrow_array()) {
+
+ # check if the value already exists
+ my $sortkey = substr($value, 1);
+ if ($sortkey !~ /^\d+$/) {
+ $sortkey = 0;
+ }
+ else {
+ $sortkey *= 10;
+ }
+ my $ms_exists = $dbh->selectrow_array(
+ "SELECT value FROM milestones
+ WHERE value = ? AND product = ?", undef, $value, $product
+ );
+
+ if (!$ms_exists) {
+ $dbh->do(
+ "INSERT INTO milestones(value, product, sortkey)
+ VALUES (?,?,?)", undef, $value, $product, $sortkey
+ );
+ }
}
+ }
}
sub _add_products_defaultmilestone {
- my $dbh = Bugzilla->dbh;
-
- # 2000-03-23 Added a defaultmilestone field to the products table, so that
- # we know which milestone to initially assign bugs to.
- if (!$dbh->bz_column_info('products', 'defaultmilestone')) {
- $dbh->bz_add_column('products', 'defaultmilestone',
- {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"});
- my $sth = $dbh->prepare(
- "SELECT product, defaultmilestone FROM products");
- $sth->execute();
- while (my ($product, $default_ms) = $sth->fetchrow_array()) {
- my $exists = $dbh->selectrow_array(
- "SELECT value FROM milestones
- WHERE value = ? AND product = ?",
- undef, $default_ms, $product);
- if (!$exists) {
- $dbh->do("INSERT INTO milestones(value, product) " .
- "VALUES (?, ?)", undef, $default_ms, $product);
- }
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 2000-03-23 Added a defaultmilestone field to the products table, so that
+ # we know which milestone to initially assign bugs to.
+ if (!$dbh->bz_column_info('products', 'defaultmilestone')) {
+ $dbh->bz_add_column('products', 'defaultmilestone',
+ {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"});
+ my $sth = $dbh->prepare("SELECT product, defaultmilestone FROM products");
+ $sth->execute();
+ while (my ($product, $default_ms) = $sth->fetchrow_array()) {
+ my $exists = $dbh->selectrow_array(
+ "SELECT value FROM milestones
+ WHERE value = ? AND product = ?", undef, $default_ms, $product
+ );
+ if (!$exists) {
+ $dbh->do("INSERT INTO milestones(value, product) " . "VALUES (?, ?)",
+ undef, $default_ms, $product);
+ }
}
+ }
}
sub _copy_from_comments_to_longdescs {
- my $dbh = Bugzilla->dbh;
- # 2000-11-27 For Bugzilla 2.5 and later. Copy data from 'comments' to
- # 'longdescs' - the new name of the comments table.
- if ($dbh->bz_table_info('comments')) {
- print "Copying data from 'comments' to 'longdescs'...\n";
- my $quoted_when = $dbh->quote_identifier('when');
- $dbh->do("INSERT INTO longdescs (bug_when, bug_id, who, thetext)
+ my $dbh = Bugzilla->dbh;
+
+ # 2000-11-27 For Bugzilla 2.5 and later. Copy data from 'comments' to
+ # 'longdescs' - the new name of the comments table.
+ if ($dbh->bz_table_info('comments')) {
+ print "Copying data from 'comments' to 'longdescs'...\n";
+ my $quoted_when = $dbh->quote_identifier('when');
+ $dbh->do(
+ "INSERT INTO longdescs (bug_when, bug_id, who, thetext)
SELECT $quoted_when, bug_id, who, comment
- FROM comments");
- $dbh->bz_drop_table("comments");
- }
+ FROM comments"
+ );
+ $dbh->bz_drop_table("comments");
+ }
}
sub _populate_duplicates_table {
- my $dbh = Bugzilla->dbh;
- # 2000-07-15 Added duplicates table so Bugzilla tracks duplicates in a
- # better way than it used to. This code searches the comments to populate
- # the table initially. It's executed if the table is empty; if it's
- # empty because there are no dupes (as opposed to having just created
- # the table) it won't have any effect anyway, so it doesn't matter.
- my ($dups_exist) = $dbh->selectrow_array(
- "SELECT DISTINCT 1 FROM duplicates");
- # We also check against a schema change that happened later.
- if (!$dups_exist && !$dbh->bz_column_info('groups', 'isactive')) {
- # populate table
- print "Populating duplicates table from comments...\n";
-
- my $sth = $dbh->prepare(
- "SELECT longdescs.bug_id, thetext
+ my $dbh = Bugzilla->dbh;
+
+ # 2000-07-15 Added duplicates table so Bugzilla tracks duplicates in a
+ # better way than it used to. This code searches the comments to populate
+ # the table initially. It's executed if the table is empty; if it's
+ # empty because there are no dupes (as opposed to having just created
+ # the table) it won't have any effect anyway, so it doesn't matter.
+ my ($dups_exist) = $dbh->selectrow_array("SELECT DISTINCT 1 FROM duplicates");
+
+ # We also check against a schema change that happened later.
+ if (!$dups_exist && !$dbh->bz_column_info('groups', 'isactive')) {
+
+ # populate table
+ print "Populating duplicates table from comments...\n";
+
+ my $sth = $dbh->prepare(
+ "SELECT longdescs.bug_id, thetext
FROM longdescs LEFT JOIN bugs
ON longdescs.bug_id = bugs.bug_id
- WHERE (" . $dbh->sql_regexp("thetext",
- "'[.*.]{3} This bug has been marked as a duplicate"
- . " of [[:digit:]]+ [.*.]{3}'")
- . ")
+ WHERE ("
+ . $dbh->sql_regexp("thetext",
+ "'[.*.]{3} This bug has been marked as a duplicate"
+ . " of [[:digit:]]+ [.*.]{3}'")
+ . ")
AND resolution = 'DUPLICATE'
- ORDER BY longdescs.bug_when");
- $sth->execute();
-
- my (%dupes, $key);
- # Because of the way hashes work, this loop removes all but the
- # last dupe resolution found for a given bug.
- while (my ($dupe, $dupe_of) = $sth->fetchrow_array()) {
- $dupes{$dupe} = $dupe_of;
- }
+ ORDER BY longdescs.bug_when"
+ );
+ $sth->execute();
- foreach $key (keys(%dupes)){
- $dupes{$key} =~ /^.*\*\*\* This bug has been marked as a duplicate of (\d+) \*\*\*$/ms;
- $dupes{$key} = $1;
- $dbh->do("INSERT INTO duplicates VALUES(?, ?)", undef,
- $dupes{$key}, $key);
- # BugItsADupeOf Dupe
- }
+ my (%dupes, $key);
+
+ # Because of the way hashes work, this loop removes all but the
+ # last dupe resolution found for a given bug.
+ while (my ($dupe, $dupe_of) = $sth->fetchrow_array()) {
+ $dupes{$dupe} = $dupe_of;
+ }
+
+ foreach $key (keys(%dupes)) {
+ $dupes{$key}
+ =~ /^.*\*\*\* This bug has been marked as a duplicate of (\d+) \*\*\*$/ms;
+ $dupes{$key} = $1;
+ $dbh->do("INSERT INTO duplicates VALUES(?, ?)", undef, $dupes{$key}, $key);
+
+ # BugItsADupeOf Dupe
}
+ }
}
sub _recrypt_plaintext_passwords {
- my $dbh = Bugzilla->dbh;
- # 2001-06-12; myk@mozilla.org; bugs 74032, 77473:
- # Recrypt passwords using Perl &crypt instead of the mysql equivalent
- # and delete plaintext passwords from the database.
- if ($dbh->bz_column_info('profiles', 'password')) {
+ my $dbh = Bugzilla->dbh;
+
+ # 2001-06-12; myk@mozilla.org; bugs 74032, 77473:
+ # Recrypt passwords using Perl &crypt instead of the mysql equivalent
+ # and delete plaintext passwords from the database.
+ if ($dbh->bz_column_info('profiles', 'password')) {
- print <<ENDTEXT;
+ print <<ENDTEXT;
Your current installation of Bugzilla stores passwords in plaintext
in the database and uses mysql's encrypt function instead of Perl's
crypt function to crypt passwords. Passwords are now going to be
@@ -1216,299 +1273,315 @@ deleted from the database. This could take a while if your
installation has many users.
ENDTEXT
- # Re-crypt everyone's password.
- my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM profiles');
- my $sth = $dbh->prepare("SELECT userid, password FROM profiles");
- $sth->execute();
-
- my $i = 1;
+ # Re-crypt everyone's password.
+ my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM profiles');
+ my $sth = $dbh->prepare("SELECT userid, password FROM profiles");
+ $sth->execute();
- print "Fixing passwords...\n";
- while (my ($userid, $password) = $sth->fetchrow_array()) {
- my $cryptpassword = $dbh->quote(bz_crypt($password));
- $dbh->do("UPDATE profiles " .
- "SET cryptpassword = $cryptpassword " .
- "WHERE userid = $userid");
- indicate_progress({ total => $total, current => $i, every => 10 });
- }
- print "\n";
+ my $i = 1;
- # Drop the plaintext password field.
- $dbh->bz_drop_column('profiles', 'password');
+ print "Fixing passwords...\n";
+ while (my ($userid, $password) = $sth->fetchrow_array()) {
+ my $cryptpassword = $dbh->quote(bz_crypt($password));
+ $dbh->do("UPDATE profiles "
+ . "SET cryptpassword = $cryptpassword "
+ . "WHERE userid = $userid");
+ indicate_progress({total => $total, current => $i, every => 10});
}
+ print "\n";
+
+ # Drop the plaintext password field.
+ $dbh->bz_drop_column('profiles', 'password');
+ }
}
sub _update_bugs_activity_to_only_record_changes {
- my $dbh = Bugzilla->dbh;
- # 2001-07-20 jake@bugzilla.org - Change bugs_activity to only record changes
- # http://bugzilla.mozilla.org/show_bug.cgi?id=55161
- if ($dbh->bz_column_info('bugs_activity', 'oldvalue')) {
- $dbh->bz_add_column("bugs_activity", "removed", {TYPE => "TINYTEXT"});
- $dbh->bz_add_column("bugs_activity", "added", {TYPE => "TINYTEXT"});
-
- # Need to get field id's for the fields that have multiple values
- my @multi;
- foreach my $f ("cc", "dependson", "blocked", "keywords") {
- my $sth = $dbh->prepare("SELECT id " .
- "FROM fielddefs " .
- "WHERE name = '$f'");
- $sth->execute();
- my ($fid) = $sth->fetchrow_array();
- push (@multi, $fid);
+ my $dbh = Bugzilla->dbh;
+
+ # 2001-07-20 jake@bugzilla.org - Change bugs_activity to only record changes
+ # http://bugzilla.mozilla.org/show_bug.cgi?id=55161
+ if ($dbh->bz_column_info('bugs_activity', 'oldvalue')) {
+ $dbh->bz_add_column("bugs_activity", "removed", {TYPE => "TINYTEXT"});
+ $dbh->bz_add_column("bugs_activity", "added", {TYPE => "TINYTEXT"});
+
+ # Need to get field id's for the fields that have multiple values
+ my @multi;
+ foreach my $f ("cc", "dependson", "blocked", "keywords") {
+ my $sth = $dbh->prepare("SELECT id " . "FROM fielddefs " . "WHERE name = '$f'");
+ $sth->execute();
+ my ($fid) = $sth->fetchrow_array();
+ push(@multi, $fid);
+ }
+
+ # Now we need to process the bugs_activity table and reformat the data
+ print "Fixing activity log...\n";
+ my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM bugs_activity');
+ my $sth = $dbh->prepare(
+ "SELECT bug_id, who, bug_when, fieldid,
+ oldvalue, newvalue FROM bugs_activity"
+ );
+ $sth->execute;
+ my $i = 0;
+ while (my ($bug_id, $who, $bug_when, $fieldid, $oldvalue, $newvalue)
+ = $sth->fetchrow_array())
+ {
+ $i++;
+ indicate_progress({total => $total, current => $i, every => 10});
+
+ # Make sure (old|new)value isn't null (to suppress warnings)
+ $oldvalue ||= "";
+ $newvalue ||= "";
+ my ($added, $removed) = "";
+ if (grep ($_ eq $fieldid, @multi)) {
+ $oldvalue =~ s/[\s,]+/ /g;
+ $newvalue =~ s/[\s,]+/ /g;
+ my @old = split(" ", $oldvalue);
+ my @new = split(" ", $newvalue);
+ my (@add, @remove) = ();
+
+ # Find values that were "added"
+ foreach my $value (@new) {
+ if (!grep ($_ eq $value, @old)) {
+ push(@add, $value);
+ }
}
- # Now we need to process the bugs_activity table and reformat the data
- print "Fixing activity log...\n";
- my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM bugs_activity');
- my $sth = $dbh->prepare("SELECT bug_id, who, bug_when, fieldid,
- oldvalue, newvalue FROM bugs_activity");
- $sth->execute;
- my $i = 0;
- while (my ($bug_id, $who, $bug_when, $fieldid, $oldvalue, $newvalue)
- = $sth->fetchrow_array())
- {
- $i++;
- indicate_progress({ total => $total, current => $i, every => 10 });
- # Make sure (old|new)value isn't null (to suppress warnings)
- $oldvalue ||= "";
- $newvalue ||= "";
- my ($added, $removed) = "";
- if (grep ($_ eq $fieldid, @multi)) {
- $oldvalue =~ s/[\s,]+/ /g;
- $newvalue =~ s/[\s,]+/ /g;
- my @old = split(" ", $oldvalue);
- my @new = split(" ", $newvalue);
- my (@add, @remove) = ();
- # Find values that were "added"
- foreach my $value(@new) {
- if (! grep ($_ eq $value, @old)) {
- push (@add, $value);
- }
- }
- # Find values that were removed
- foreach my $value(@old) {
- if (! grep ($_ eq $value, @new)) {
- push (@remove, $value);
- }
- }
- $added = join (", ", @add);
- $removed = join (", ", @remove);
- # If we can't determine what changed, put a ? in both fields
- unless ($added || $removed) {
- $added = "?";
- $removed = "?";
- }
- # If the original field (old|new)value was full, then this
- # could be incomplete data.
- if (length($oldvalue) == 255 || length($newvalue) == 255) {
- $added = "? $added";
- $removed = "? $removed";
- }
- } else {
- $removed = $oldvalue;
- $added = $newvalue;
- }
- $added = $dbh->quote($added);
- $removed = $dbh->quote($removed);
- $dbh->do("UPDATE bugs_activity
+ # Find values that were removed
+ foreach my $value (@old) {
+ if (!grep ($_ eq $value, @new)) {
+ push(@remove, $value);
+ }
+ }
+ $added = join(", ", @add);
+ $removed = join(", ", @remove);
+
+ # If we can't determine what changed, put a ? in both fields
+ unless ($added || $removed) {
+ $added = "?";
+ $removed = "?";
+ }
+
+ # If the original field (old|new)value was full, then this
+ # could be incomplete data.
+ if (length($oldvalue) == 255 || length($newvalue) == 255) {
+ $added = "? $added";
+ $removed = "? $removed";
+ }
+ }
+ else {
+ $removed = $oldvalue;
+ $added = $newvalue;
+ }
+ $added = $dbh->quote($added);
+ $removed = $dbh->quote($removed);
+ $dbh->do(
+ "UPDATE bugs_activity
SET removed = $removed, added = $added
WHERE bug_id = $bug_id AND who = $who
AND bug_when = '$bug_when'
- AND fieldid = $fieldid");
- }
- print "\n";
- $dbh->bz_drop_column("bugs_activity", "oldvalue");
- $dbh->bz_drop_column("bugs_activity", "newvalue");
+ AND fieldid = $fieldid"
+ );
}
+ print "\n";
+ $dbh->bz_drop_column("bugs_activity", "oldvalue");
+ $dbh->bz_drop_column("bugs_activity", "newvalue");
+ }
}
sub _delete_logincookies_cryptpassword_and_handle_invalid_cookies {
- my $dbh = Bugzilla->dbh;
- # 2002-02-04 bbaetz@student.usyd.edu.au bug 95732
- # Remove logincookies.cryptpassword, and delete entries which become
- # invalid
- if ($dbh->bz_column_info("logincookies", "cryptpassword")) {
- # We need to delete any cookies which are invalid before dropping the
- # column
- print "Removing invalid login cookies...\n";
-
- # mysql doesn't support DELETE with multi-table queries, so we have
- # to iterate
- my $sth = $dbh->prepare("SELECT cookie FROM logincookies, profiles " .
- "WHERE logincookies.cryptpassword != " .
- "profiles.cryptpassword AND " .
- "logincookies.userid = profiles.userid");
- $sth->execute();
- while (my ($cookie) = $sth->fetchrow_array()) {
- $dbh->do("DELETE FROM logincookies WHERE cookie = $cookie");
- }
-
- $dbh->bz_drop_column("logincookies", "cryptpassword");
+ my $dbh = Bugzilla->dbh;
+
+ # 2002-02-04 bbaetz@student.usyd.edu.au bug 95732
+ # Remove logincookies.cryptpassword, and delete entries which become
+ # invalid
+ if ($dbh->bz_column_info("logincookies", "cryptpassword")) {
+
+ # We need to delete any cookies which are invalid before dropping the
+ # column
+ print "Removing invalid login cookies...\n";
+
+ # mysql doesn't support DELETE with multi-table queries, so we have
+ # to iterate
+ my $sth
+ = $dbh->prepare("SELECT cookie FROM logincookies, profiles "
+ . "WHERE logincookies.cryptpassword != "
+ . "profiles.cryptpassword AND "
+ . "logincookies.userid = profiles.userid");
+ $sth->execute();
+ while (my ($cookie) = $sth->fetchrow_array()) {
+ $dbh->do("DELETE FROM logincookies WHERE cookie = $cookie");
}
+
+ $dbh->bz_drop_column("logincookies", "cryptpassword");
+ }
}
sub _use_ip_instead_of_hostname_in_logincookies {
- my $dbh = Bugzilla->dbh;
-
- # 2002-03-15 bbaetz@student.usyd.edu.au - bug 129466
- # 2002-05-13 preed@sigkill.com - bug 129446 patch backported to the
- # BUGZILLA-2_14_1-BRANCH as a security blocker for the 2.14.2 release
- #
- # Use the ip, not the hostname, in the logincookies table
- if ($dbh->bz_column_info("logincookies", "hostname")) {
- print "Clearing the logincookies table...\n";
- # We've changed what we match against, so all entries are now invalid
- $dbh->do("DELETE FROM logincookies");
-
- # Now update the logincookies schema
- $dbh->bz_drop_column("logincookies", "hostname");
- $dbh->bz_add_column("logincookies", "ipaddr",
- {TYPE => 'varchar(40)'});
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 2002-03-15 bbaetz@student.usyd.edu.au - bug 129466
+ # 2002-05-13 preed@sigkill.com - bug 129446 patch backported to the
+ # BUGZILLA-2_14_1-BRANCH as a security blocker for the 2.14.2 release
+ #
+ # Use the ip, not the hostname, in the logincookies table
+ if ($dbh->bz_column_info("logincookies", "hostname")) {
+ print "Clearing the logincookies table...\n";
+
+ # We've changed what we match against, so all entries are now invalid
+ $dbh->do("DELETE FROM logincookies");
+
+ # Now update the logincookies schema
+ $dbh->bz_drop_column("logincookies", "hostname");
+ $dbh->bz_add_column("logincookies", "ipaddr", {TYPE => 'varchar(40)'});
+ }
}
sub _move_quips_into_db {
- my $dbh = Bugzilla->dbh;
- my $datadir = bz_locations->{'datadir'};
- # 2002-07-15 davef@tetsubo.com - bug 67950
- # Move quips to the db.
- if (-e "$datadir/comments") {
- print "Populating quips table from $datadir/comments...\n";
- my $comments = new IO::File("$datadir/comments", 'r')
- || die "$datadir/comments: $!";
- $comments->untaint;
- while (<$comments>) {
- chomp;
- $dbh->do("INSERT INTO quips (quip) VALUES (?)", undef, $_);
- }
-
- print "\n", install_string('update_quips', { data => $datadir }), "\n";
- $comments->close;
- rename("$datadir/comments", "$datadir/comments.bak")
- || warn "Failed to rename: $!";
+ my $dbh = Bugzilla->dbh;
+ my $datadir = bz_locations->{'datadir'};
+
+ # 2002-07-15 davef@tetsubo.com - bug 67950
+ # Move quips to the db.
+ if (-e "$datadir/comments") {
+ print "Populating quips table from $datadir/comments...\n";
+ my $comments = new IO::File("$datadir/comments", 'r')
+ || die "$datadir/comments: $!";
+ $comments->untaint;
+ while (<$comments>) {
+ chomp;
+ $dbh->do("INSERT INTO quips (quip) VALUES (?)", undef, $_);
}
+
+ print "\n", install_string('update_quips', {data => $datadir}), "\n";
+ $comments->close;
+ rename("$datadir/comments", "$datadir/comments.bak")
+ || warn "Failed to rename: $!";
+ }
}
sub _use_ids_for_products_and_components {
- my $dbh = Bugzilla->dbh;
- # 2002-08-12 jake@bugzilla.org/bbaetz@student.usyd.edu.au - bug 43600
- # Use integer IDs for products and components.
- if ($dbh->bz_column_info("products", "product")) {
- print "Updating database to use product IDs.\n";
-
- # First, we need to remove possible NULL entries
- # NULLs may exist, but won't have been used, since all the uses of them
- # are in NOT NULL fields in other tables
- $dbh->do("DELETE FROM products WHERE product IS NULL");
- $dbh->do("DELETE FROM components WHERE value IS NULL");
-
- $dbh->bz_add_column("products", "id",
- {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- $dbh->bz_add_column("components", "product_id",
- {TYPE => 'INT2', NOTNULL => 1}, 0);
- $dbh->bz_add_column("versions", "product_id",
- {TYPE => 'INT2', NOTNULL => 1}, 0);
- $dbh->bz_add_column("milestones", "product_id",
- {TYPE => 'INT2', NOTNULL => 1}, 0);
- $dbh->bz_add_column("bugs", "product_id",
- {TYPE => 'INT2', NOTNULL => 1}, 0);
-
- # The attachstatusdefs table was added in version 2.15, but
- # removed again in early 2.17. If it exists now, we still need
- # to perform this change with product_id because the code later on
- # which converts the attachment statuses to flags depends on it.
- # But we need to avoid this if the user is upgrading from 2.14
- # or earlier (because it won't be there to convert).
- if ($dbh->bz_table_info("attachstatusdefs")) {
- $dbh->bz_add_column("attachstatusdefs", "product_id",
- {TYPE => 'INT2', NOTNULL => 1}, 0);
- }
-
- my %products;
- my $sth = $dbh->prepare("SELECT id, product FROM products");
- $sth->execute;
- while (my ($product_id, $product) = $sth->fetchrow_array()) {
- if (exists $products{$product}) {
- print "Ignoring duplicate product $product\n";
- $dbh->do("DELETE FROM products WHERE id = $product_id");
- next;
- }
- $products{$product} = 1;
- $dbh->do("UPDATE components SET product_id = $product_id " .
- "WHERE program = " . $dbh->quote($product));
- $dbh->do("UPDATE versions SET product_id = $product_id " .
- "WHERE program = " . $dbh->quote($product));
- $dbh->do("UPDATE milestones SET product_id = $product_id " .
- "WHERE product = " . $dbh->quote($product));
- $dbh->do("UPDATE bugs SET product_id = $product_id " .
- "WHERE product = " . $dbh->quote($product));
- $dbh->do("UPDATE attachstatusdefs SET product_id = $product_id " .
- "WHERE product = " . $dbh->quote($product))
- if $dbh->bz_table_info("attachstatusdefs");
- }
-
- print "Updating the database to use component IDs.\n";
+ my $dbh = Bugzilla->dbh;
+
+ # 2002-08-12 jake@bugzilla.org/bbaetz@student.usyd.edu.au - bug 43600
+ # Use integer IDs for products and components.
+ if ($dbh->bz_column_info("products", "product")) {
+ print "Updating database to use product IDs.\n";
+
+ # First, we need to remove possible NULL entries
+ # NULLs may exist, but won't have been used, since all the uses of them
+ # are in NOT NULL fields in other tables
+ $dbh->do("DELETE FROM products WHERE product IS NULL");
+ $dbh->do("DELETE FROM components WHERE value IS NULL");
+
+ $dbh->bz_add_column("products", "id",
+ {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ $dbh->bz_add_column("components", "product_id", {TYPE => 'INT2', NOTNULL => 1},
+ 0);
+ $dbh->bz_add_column("versions", "product_id", {TYPE => 'INT2', NOTNULL => 1},
+ 0);
+ $dbh->bz_add_column("milestones", "product_id", {TYPE => 'INT2', NOTNULL => 1},
+ 0);
+ $dbh->bz_add_column("bugs", "product_id", {TYPE => 'INT2', NOTNULL => 1}, 0);
+
+ # The attachstatusdefs table was added in version 2.15, but
+ # removed again in early 2.17. If it exists now, we still need
+ # to perform this change with product_id because the code later on
+ # which converts the attachment statuses to flags depends on it.
+ # But we need to avoid this if the user is upgrading from 2.14
+ # or earlier (because it won't be there to convert).
+ if ($dbh->bz_table_info("attachstatusdefs")) {
+ $dbh->bz_add_column("attachstatusdefs", "product_id",
+ {TYPE => 'INT2', NOTNULL => 1}, 0);
+ }
- $dbh->bz_add_column("components", "id",
- {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- $dbh->bz_add_column("bugs", "component_id",
- {TYPE => 'INT3', NOTNULL => 1}, 0);
+ my %products;
+ my $sth = $dbh->prepare("SELECT id, product FROM products");
+ $sth->execute;
+ while (my ($product_id, $product) = $sth->fetchrow_array()) {
+ if (exists $products{$product}) {
+ print "Ignoring duplicate product $product\n";
+ $dbh->do("DELETE FROM products WHERE id = $product_id");
+ next;
+ }
+ $products{$product} = 1;
+ $dbh->do("UPDATE components SET product_id = $product_id "
+ . "WHERE program = "
+ . $dbh->quote($product));
+ $dbh->do("UPDATE versions SET product_id = $product_id "
+ . "WHERE program = "
+ . $dbh->quote($product));
+ $dbh->do("UPDATE milestones SET product_id = $product_id "
+ . "WHERE product = "
+ . $dbh->quote($product));
+ $dbh->do("UPDATE bugs SET product_id = $product_id "
+ . "WHERE product = "
+ . $dbh->quote($product));
+ $dbh->do("UPDATE attachstatusdefs SET product_id = $product_id "
+ . "WHERE product = "
+ . $dbh->quote($product))
+ if $dbh->bz_table_info("attachstatusdefs");
+ }
- my %components;
- $sth = $dbh->prepare("SELECT id, value, product_id FROM components");
- $sth->execute;
- while (my ($component_id, $component, $product_id)
- = $sth->fetchrow_array())
- {
- if (exists $components{$component}) {
- if (exists $components{$component}{$product_id}) {
- print "Ignoring duplicate component $component for",
- " product $product_id\n";
- $dbh->do("DELETE FROM components WHERE id = $component_id");
- next;
- }
- } else {
- $components{$component} = {};
- }
- $components{$component}{$product_id} = 1;
- $dbh->do("UPDATE bugs SET component_id = $component_id " .
- "WHERE component = " . $dbh->quote($component) .
- " AND product_id = $product_id");
+ print "Updating the database to use component IDs.\n";
+
+ $dbh->bz_add_column("components", "id",
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ $dbh->bz_add_column("bugs", "component_id", {TYPE => 'INT3', NOTNULL => 1}, 0);
+
+ my %components;
+ $sth = $dbh->prepare("SELECT id, value, product_id FROM components");
+ $sth->execute;
+ while (my ($component_id, $component, $product_id) = $sth->fetchrow_array()) {
+ if (exists $components{$component}) {
+ if (exists $components{$component}{$product_id}) {
+ print "Ignoring duplicate component $component for", " product $product_id\n";
+ $dbh->do("DELETE FROM components WHERE id = $component_id");
+ next;
}
- print "Fixing Indexes and Uniqueness.\n";
- $dbh->bz_drop_index('milestones', 'milestones_product_idx');
-
- $dbh->bz_add_index('milestones', 'milestones_product_id_idx',
- {TYPE => 'UNIQUE', FIELDS => [qw(product_id value)]});
-
- $dbh->bz_drop_index('bugs', 'bugs_product_idx');
- $dbh->bz_add_index('bugs', 'bugs_product_id_idx', [qw(product_id)]);
- $dbh->bz_drop_index('bugs', 'bugs_component_idx');
- $dbh->bz_add_index('bugs', 'bugs_component_id_idx', [qw(component_id)]);
-
- print "Removing, renaming, and retyping old product and",
- " component fields.\n";
- $dbh->bz_drop_column("components", "program");
- $dbh->bz_drop_column("versions", "program");
- $dbh->bz_drop_column("milestones", "product");
- $dbh->bz_drop_column("bugs", "product");
- $dbh->bz_drop_column("bugs", "component");
- $dbh->bz_drop_column("attachstatusdefs", "product")
- if $dbh->bz_table_info("attachstatusdefs");
- $dbh->bz_rename_column("products", "product", "name");
- $dbh->bz_alter_column("products", "name",
- {TYPE => 'varchar(64)', NOTNULL => 1});
- $dbh->bz_rename_column("components", "value", "name");
- $dbh->bz_alter_column("components", "name",
- {TYPE => 'varchar(64)', NOTNULL => 1});
-
- print "Adding indexes for products and components tables.\n";
- $dbh->bz_add_index('products', 'products_name_idx',
- {TYPE => 'UNIQUE', FIELDS => [qw(name)]});
- $dbh->bz_add_index('components', 'components_product_id_idx',
- {TYPE => 'UNIQUE', FIELDS => [qw(product_id name)]});
- $dbh->bz_add_index('components', 'components_name_idx', [qw(name)]);
+ }
+ else {
+ $components{$component} = {};
+ }
+ $components{$component}{$product_id} = 1;
+ $dbh->do("UPDATE bugs SET component_id = $component_id "
+ . "WHERE component = "
+ . $dbh->quote($component)
+ . " AND product_id = $product_id");
}
+ print "Fixing Indexes and Uniqueness.\n";
+ $dbh->bz_drop_index('milestones', 'milestones_product_idx');
+
+ $dbh->bz_add_index('milestones', 'milestones_product_id_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(product_id value)]});
+
+ $dbh->bz_drop_index('bugs', 'bugs_product_idx');
+ $dbh->bz_add_index('bugs', 'bugs_product_id_idx', [qw(product_id)]);
+ $dbh->bz_drop_index('bugs', 'bugs_component_idx');
+ $dbh->bz_add_index('bugs', 'bugs_component_id_idx', [qw(component_id)]);
+
+ print "Removing, renaming, and retyping old product and",
+ " component fields.\n";
+ $dbh->bz_drop_column("components", "program");
+ $dbh->bz_drop_column("versions", "program");
+ $dbh->bz_drop_column("milestones", "product");
+ $dbh->bz_drop_column("bugs", "product");
+ $dbh->bz_drop_column("bugs", "component");
+ $dbh->bz_drop_column("attachstatusdefs", "product")
+ if $dbh->bz_table_info("attachstatusdefs");
+ $dbh->bz_rename_column("products", "product", "name");
+ $dbh->bz_alter_column("products", "name",
+ {TYPE => 'varchar(64)', NOTNULL => 1});
+ $dbh->bz_rename_column("components", "value", "name");
+ $dbh->bz_alter_column("components", "name",
+ {TYPE => 'varchar(64)', NOTNULL => 1});
+
+ print "Adding indexes for products and components tables.\n";
+ $dbh->bz_add_index('products', 'products_name_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(name)]});
+ $dbh->bz_add_index('components', 'components_product_id_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(product_id name)]});
+ $dbh->bz_add_index('components', 'components_name_idx', [qw(name)]);
+ }
}
# Helper for the below function.
@@ -1519,1211 +1592,1340 @@ sub _use_ids_for_products_and_components {
# group names, _list_bits is used to fill in a list of references
# to groupset bits for groups that no longer exist.
sub _list_bits {
- my ($num) = @_;
- my $dbh = Bugzilla->dbh;
- my @res;
- my $curr = 1;
- while (1) {
- # Convert a big integer to a list of bits
- my $sth = $dbh->prepare("SELECT ($num & ~$curr) > 0,
+ my ($num) = @_;
+ my $dbh = Bugzilla->dbh;
+ my @res;
+ my $curr = 1;
+ while (1) {
+
+ # Convert a big integer to a list of bits
+ my $sth = $dbh->prepare(
+ "SELECT ($num & ~$curr) > 0,
($num & $curr),
($num & ~$curr),
- $curr << 1");
- $sth->execute;
- my ($more, $thisbit, $remain, $nval) = $sth->fetchrow_array;
- push @res,"UNKNOWN<$curr>" if ($thisbit);
- $curr = $nval;
- $num = $remain;
- last if !$more;
- }
- return @res;
+ $curr << 1"
+ );
+ $sth->execute;
+ my ($more, $thisbit, $remain, $nval) = $sth->fetchrow_array;
+ push @res, "UNKNOWN<$curr>" if ($thisbit);
+ $curr = $nval;
+ $num = $remain;
+ last if !$more;
+ }
+ return @res;
}
sub _convert_groups_system_from_groupset {
- my $dbh = Bugzilla->dbh;
- # 2002-09-22 - bugreport@peshkin.net - bug 157756
- #
- # If the whole groups system is new, but the installation isn't,
- # convert all the old groupset groups, etc...
- #
- # This requires:
- # 1) define groups ids in group table
- # 2) populate user_group_map with grants from old groupsets
- # and blessgroupsets
- # 3) populate bug_group_map with data converted from old bug groupsets
- # 4) convert activity logs to use group names instead of numbers
- # 5) identify the admin from the old all-ones groupset
-
- # The groups system needs to be converted if groupset exists
- if ($dbh->bz_column_info("profiles", "groupset")) {
- # Some mysql versions will promote any unique key to primary key
- # so all unique keys are removed first and then added back in
- $dbh->bz_drop_index('groups', 'groups_bit_idx');
- $dbh->bz_drop_index('groups', 'groups_name_idx');
- my @primary_key = $dbh->primary_key(undef, undef, 'groups');
- if (@primary_key) {
- $dbh->do("ALTER TABLE groups DROP PRIMARY KEY");
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 2002-09-22 - bugreport@peshkin.net - bug 157756
+ #
+ # If the whole groups system is new, but the installation isn't,
+ # convert all the old groupset groups, etc...
+ #
+ # This requires:
+ # 1) define groups ids in group table
+ # 2) populate user_group_map with grants from old groupsets
+ # and blessgroupsets
+ # 3) populate bug_group_map with data converted from old bug groupsets
+ # 4) convert activity logs to use group names instead of numbers
+ # 5) identify the admin from the old all-ones groupset
+
+ # The groups system needs to be converted if groupset exists
+ if ($dbh->bz_column_info("profiles", "groupset")) {
+
+ # Some mysql versions will promote any unique key to primary key
+ # so all unique keys are removed first and then added back in
+ $dbh->bz_drop_index('groups', 'groups_bit_idx');
+ $dbh->bz_drop_index('groups', 'groups_name_idx');
+ my @primary_key = $dbh->primary_key(undef, undef, 'groups');
+ if (@primary_key) {
+ $dbh->do("ALTER TABLE groups DROP PRIMARY KEY");
+ }
- $dbh->bz_add_column('groups', 'id',
- {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
-
- $dbh->bz_add_index('groups', 'groups_name_idx',
- {TYPE => 'UNIQUE', FIELDS => [qw(name)]});
-
- # Convert all existing groupset records to map entries before removing
- # groupset fields or removing "bit" from groups.
- my $sth = $dbh->prepare("SELECT bit, id FROM groups WHERE bit > 0");
- $sth->execute();
- while (my ($bit, $gid) = $sth->fetchrow_array) {
- # Create user_group_map membership grants for old groupsets.
- # Get each user with the old groupset bit set
- my $sth2 = $dbh->prepare("SELECT userid FROM profiles
- WHERE (groupset & $bit) != 0");
- $sth2->execute();
- while (my ($uid) = $sth2->fetchrow_array) {
- # Check to see if the user is already a member of the group
- # and, if not, insert a new record.
- my $query = "SELECT user_id FROM user_group_map
+ $dbh->bz_add_column('groups', 'id',
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+
+ $dbh->bz_add_index('groups', 'groups_name_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(name)]});
+
+ # Convert all existing groupset records to map entries before removing
+ # groupset fields or removing "bit" from groups.
+ my $sth = $dbh->prepare("SELECT bit, id FROM groups WHERE bit > 0");
+ $sth->execute();
+ while (my ($bit, $gid) = $sth->fetchrow_array) {
+
+ # Create user_group_map membership grants for old groupsets.
+ # Get each user with the old groupset bit set
+ my $sth2 = $dbh->prepare(
+ "SELECT userid FROM profiles
+ WHERE (groupset & $bit) != 0"
+ );
+ $sth2->execute();
+ while (my ($uid) = $sth2->fetchrow_array) {
+
+ # Check to see if the user is already a member of the group
+ # and, if not, insert a new record.
+ my $query = "SELECT user_id FROM user_group_map
WHERE group_id = $gid AND user_id = $uid
AND isbless = 0";
- my $sth3 = $dbh->prepare($query);
- $sth3->execute();
- if ( !$sth3->fetchrow_array() ) {
- $dbh->do("INSERT INTO user_group_map
+ my $sth3 = $dbh->prepare($query);
+ $sth3->execute();
+ if (!$sth3->fetchrow_array()) {
+ $dbh->do(
+ "INSERT INTO user_group_map
(user_id, group_id, isbless, grant_type)
- VALUES ($uid, $gid, 0, " . GRANT_DIRECT . ")");
- }
- }
- # Create user can bless group grants for old groupsets, but only
- # if we're upgrading from a Bugzilla that had blessing.
- if($dbh->bz_column_info('profiles', 'blessgroupset')) {
- # Get each user with the old blessgroupset bit set
- $sth2 = $dbh->prepare("SELECT userid FROM profiles
- WHERE (blessgroupset & $bit) != 0");
- $sth2->execute();
- while (my ($uid) = $sth2->fetchrow_array) {
- $dbh->do("INSERT INTO user_group_map
+ VALUES ($uid, $gid, 0, " . GRANT_DIRECT . ")"
+ );
+ }
+ }
+
+ # Create user can bless group grants for old groupsets, but only
+ # if we're upgrading from a Bugzilla that had blessing.
+ if ($dbh->bz_column_info('profiles', 'blessgroupset')) {
+
+ # Get each user with the old blessgroupset bit set
+ $sth2 = $dbh->prepare(
+ "SELECT userid FROM profiles
+ WHERE (blessgroupset & $bit) != 0"
+ );
+ $sth2->execute();
+ while (my ($uid) = $sth2->fetchrow_array) {
+ $dbh->do(
+ "INSERT INTO user_group_map
(user_id, group_id, isbless, grant_type)
- VALUES ($uid, $gid, 1, " . GRANT_DIRECT . ")");
- }
- }
- # Create bug_group_map records for old groupsets.
- # Get each bug with the old group bit set.
- $sth2 = $dbh->prepare("SELECT bug_id FROM bugs
- WHERE (groupset & $bit) != 0");
- $sth2->execute();
- while (my ($bug_id) = $sth2->fetchrow_array) {
- # Insert the bug, group pair into the bug_group_map.
- $dbh->do("INSERT INTO bug_group_map (bug_id, group_id)
- VALUES ($bug_id, $gid)");
- }
+ VALUES ($uid, $gid, 1, " . GRANT_DIRECT . ")"
+ );
}
- # Replace old activity log groupset records with lists of names
- # of groups.
- $sth = $dbh->prepare("SELECT id FROM fielddefs
- WHERE name = " . $dbh->quote('bug_group'));
- $sth->execute();
- my ($bgfid) = $sth->fetchrow_array;
- # Get the field id for the old groupset field
- $sth = $dbh->prepare("SELECT id FROM fielddefs
- WHERE name = " . $dbh->quote('groupset'));
- $sth->execute();
- my ($gsid) = $sth->fetchrow_array;
- # Get all bugs_activity records from groupset changes
- if ($gsid) {
- $sth = $dbh->prepare("SELECT bug_id, bug_when, who, added, removed
- FROM bugs_activity WHERE fieldid = $gsid");
- $sth->execute();
- while (my ($bug_id, $bug_when, $who, $added, $removed) =
- $sth->fetchrow_array)
- {
- $added ||= 0;
- $removed ||= 0;
- # Get names of groups added.
- my $sth2 = $dbh->prepare("SELECT name FROM groups
- WHERE (bit & $added) != 0
- AND (bit & $removed) = 0");
- $sth2->execute();
- my @logadd;
- while (my ($n) = $sth2->fetchrow_array) {
- push @logadd, $n;
- }
- # Get names of groups removed.
- $sth2 = $dbh->prepare("SELECT name FROM groups
- WHERE (bit & $removed) != 0
- AND (bit & $added) = 0");
- $sth2->execute();
- my @logrem;
- while (my ($n) = $sth2->fetchrow_array) {
- push @logrem, $n;
- }
- # Get list of group bits added that correspond to
- # missing groups.
- $sth2 = $dbh->prepare("SELECT ($added & ~BIT_OR(bit))
- FROM groups");
- $sth2->execute();
- my ($miss) = $sth2->fetchrow_array;
- if ($miss) {
- push @logadd, _list_bits($miss);
- print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id",
- " CONTAINS DELETED GROUPS\n";
- }
- # Get list of group bits deleted that correspond to
- # missing groups.
- $sth2 = $dbh->prepare("SELECT ($removed & ~BIT_OR(bit))
- FROM groups");
- $sth2->execute();
- ($miss) = $sth2->fetchrow_array;
- if ($miss) {
- push @logrem, _list_bits($miss);
- print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id",
- " CONTAINS DELETED GROUPS\n";
- }
- my $logr = "";
- my $loga = "";
- $logr = join(", ", @logrem) . '?' if @logrem;
- $loga = join(", ", @logadd) . '?' if @logadd;
- # Replace to old activity record with the converted data.
- $dbh->do("UPDATE bugs_activity SET fieldid = $bgfid, added = " .
- $dbh->quote($loga) . ", removed = " .
- $dbh->quote($logr) .
- " WHERE bug_id = $bug_id AND bug_when = " .
- $dbh->quote($bug_when) .
- " AND who = $who AND fieldid = $gsid");
- }
- # Replace groupset changes with group name changes in
- # profiles_activity. Get profiles_activity records for groupset.
- $sth = $dbh->prepare(
- "SELECT userid, profiles_when, who, newvalue, oldvalue " .
- "FROM profiles_activity " .
- "WHERE fieldid = $gsid");
- $sth->execute();
- while (my ($uid, $uwhen, $uwho, $added, $removed) =
- $sth->fetchrow_array)
- {
- $added ||= 0;
- $removed ||= 0;
- # Get names of groups added.
- my $sth2 = $dbh->prepare("SELECT name FROM groups
+ }
+
+ # Create bug_group_map records for old groupsets.
+ # Get each bug with the old group bit set.
+ $sth2 = $dbh->prepare(
+ "SELECT bug_id FROM bugs
+ WHERE (groupset & $bit) != 0"
+ );
+ $sth2->execute();
+ while (my ($bug_id) = $sth2->fetchrow_array) {
+
+ # Insert the bug, group pair into the bug_group_map.
+ $dbh->do(
+ "INSERT INTO bug_group_map (bug_id, group_id)
+ VALUES ($bug_id, $gid)"
+ );
+ }
+ }
+
+ # Replace old activity log groupset records with lists of names
+ # of groups.
+ $sth = $dbh->prepare(
+ "SELECT id FROM fielddefs
+ WHERE name = " . $dbh->quote('bug_group')
+ );
+ $sth->execute();
+ my ($bgfid) = $sth->fetchrow_array;
+
+ # Get the field id for the old groupset field
+ $sth = $dbh->prepare(
+ "SELECT id FROM fielddefs
+ WHERE name = " . $dbh->quote('groupset')
+ );
+ $sth->execute();
+ my ($gsid) = $sth->fetchrow_array;
+
+ # Get all bugs_activity records from groupset changes
+ if ($gsid) {
+ $sth = $dbh->prepare(
+ "SELECT bug_id, bug_when, who, added, removed
+ FROM bugs_activity WHERE fieldid = $gsid"
+ );
+ $sth->execute();
+ while (my ($bug_id, $bug_when, $who, $added, $removed) = $sth->fetchrow_array) {
+ $added ||= 0;
+ $removed ||= 0;
+
+ # Get names of groups added.
+ my $sth2 = $dbh->prepare(
+ "SELECT name FROM groups
WHERE (bit & $added) != 0
- AND (bit & $removed) = 0");
- $sth2->execute();
- my @logadd;
- while (my ($n) = $sth2->fetchrow_array) {
- push @logadd, $n;
- }
- # Get names of groups removed.
- $sth2 = $dbh->prepare("SELECT name FROM groups
+ AND (bit & $removed) = 0"
+ );
+ $sth2->execute();
+ my @logadd;
+ while (my ($n) = $sth2->fetchrow_array) {
+ push @logadd, $n;
+ }
+
+ # Get names of groups removed.
+ $sth2 = $dbh->prepare(
+ "SELECT name FROM groups
WHERE (bit & $removed) != 0
- AND (bit & $added) = 0");
- $sth2->execute();
- my @logrem;
- while (my ($n) = $sth2->fetchrow_array) {
- push @logrem, $n;
- }
- my $ladd = "";
- my $lrem = "";
- $ladd = join(", ", @logadd) . '?' if @logadd;
- $lrem = join(", ", @logrem) . '?' if @logrem;
- # Replace profiles_activity record for groupset change
- # with group list.
- $dbh->do("UPDATE profiles_activity " .
- "SET fieldid = $bgfid, newvalue = " .
- $dbh->quote($ladd) . ", oldvalue = " .
- $dbh->quote($lrem) .
- " WHERE userid = $uid AND profiles_when = " .
- $dbh->quote($uwhen) .
- " AND who = $uwho AND fieldid = $gsid");
- }
+ AND (bit & $added) = 0"
+ );
+ $sth2->execute();
+ my @logrem;
+ while (my ($n) = $sth2->fetchrow_array) {
+ push @logrem, $n;
}
- # Identify admin group.
- my ($admin_gid) = $dbh->selectrow_array(
- "SELECT id FROM groups WHERE name = 'admin'");
- if (!$admin_gid) {
- $dbh->do(q{INSERT INTO groups (name, description)
- VALUES ('admin', 'Administrators')});
- $admin_gid = $dbh->bz_last_key('groups', 'id');
+ # Get list of group bits added that correspond to
+ # missing groups.
+ $sth2 = $dbh->prepare(
+ "SELECT ($added & ~BIT_OR(bit))
+ FROM groups"
+ );
+ $sth2->execute();
+ my ($miss) = $sth2->fetchrow_array;
+ if ($miss) {
+ push @logadd, _list_bits($miss);
+ print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id",
+ " CONTAINS DELETED GROUPS\n";
}
- # Find current admins
- my @admins;
- # Don't lose admins from DBs where Bug 157704 applies
- $sth = $dbh->prepare(
- "SELECT userid, (groupset & 65536), login_name " .
- "FROM profiles " .
- "WHERE (groupset | 65536) = 9223372036854775807");
- $sth->execute();
- while ( my ($userid, $iscomplete, $login_name)
- = $sth->fetchrow_array() )
- {
- # existing administrators are made members of group "admin"
- print "\nWARNING - $login_name IS AN ADMIN IN SPITE OF BUG",
- " 157704\n\n" if (!$iscomplete);
- push(@admins, $userid) unless grep($_ eq $userid, @admins);
+
+ # Get list of group bits deleted that correspond to
+ # missing groups.
+ $sth2 = $dbh->prepare(
+ "SELECT ($removed & ~BIT_OR(bit))
+ FROM groups"
+ );
+ $sth2->execute();
+ ($miss) = $sth2->fetchrow_array;
+ if ($miss) {
+ push @logrem, _list_bits($miss);
+ print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id",
+ " CONTAINS DELETED GROUPS\n";
}
- # Now make all those users admins directly. They were already
- # added to every other group, above, because of their groupset.
- foreach my $admin_id (@admins) {
- $dbh->do("INSERT INTO user_group_map
- (user_id, group_id, isbless, grant_type)
- VALUES (?, ?, ?, ?)",
- undef, $admin_id, $admin_gid, $_, GRANT_DIRECT)
- foreach (0, 1);
+ my $logr = "";
+ my $loga = "";
+ $logr = join(", ", @logrem) . '?' if @logrem;
+ $loga = join(", ", @logadd) . '?' if @logadd;
+
+ # Replace to old activity record with the converted data.
+ $dbh->do("UPDATE bugs_activity SET fieldid = $bgfid, added = "
+ . $dbh->quote($loga)
+ . ", removed = "
+ . $dbh->quote($logr)
+ . " WHERE bug_id = $bug_id AND bug_when = "
+ . $dbh->quote($bug_when)
+ . " AND who = $who AND fieldid = $gsid");
+ }
+
+ # Replace groupset changes with group name changes in
+ # profiles_activity. Get profiles_activity records for groupset.
+ $sth
+ = $dbh->prepare("SELECT userid, profiles_when, who, newvalue, oldvalue "
+ . "FROM profiles_activity "
+ . "WHERE fieldid = $gsid");
+ $sth->execute();
+ while (my ($uid, $uwhen, $uwho, $added, $removed) = $sth->fetchrow_array) {
+ $added ||= 0;
+ $removed ||= 0;
+
+ # Get names of groups added.
+ my $sth2 = $dbh->prepare(
+ "SELECT name FROM groups
+ WHERE (bit & $added) != 0
+ AND (bit & $removed) = 0"
+ );
+ $sth2->execute();
+ my @logadd;
+ while (my ($n) = $sth2->fetchrow_array) {
+ push @logadd, $n;
}
- $dbh->bz_drop_column('profiles','groupset');
- $dbh->bz_drop_column('profiles','blessgroupset');
- $dbh->bz_drop_column('bugs','groupset');
- $dbh->bz_drop_column('groups','bit');
- $dbh->do("DELETE FROM fielddefs WHERE name = "
- . $dbh->quote('groupset'));
+ # Get names of groups removed.
+ $sth2 = $dbh->prepare(
+ "SELECT name FROM groups
+ WHERE (bit & $removed) != 0
+ AND (bit & $added) = 0"
+ );
+ $sth2->execute();
+ my @logrem;
+ while (my ($n) = $sth2->fetchrow_array) {
+ push @logrem, $n;
+ }
+ my $ladd = "";
+ my $lrem = "";
+ $ladd = join(", ", @logadd) . '?' if @logadd;
+ $lrem = join(", ", @logrem) . '?' if @logrem;
+
+ # Replace profiles_activity record for groupset change
+ # with group list.
+ $dbh->do("UPDATE profiles_activity "
+ . "SET fieldid = $bgfid, newvalue = "
+ . $dbh->quote($ladd)
+ . ", oldvalue = "
+ . $dbh->quote($lrem)
+ . " WHERE userid = $uid AND profiles_when = "
+ . $dbh->quote($uwhen)
+ . " AND who = $uwho AND fieldid = $gsid");
+ }
}
+
+ # Identify admin group.
+ my ($admin_gid)
+ = $dbh->selectrow_array("SELECT id FROM groups WHERE name = 'admin'");
+ if (!$admin_gid) {
+ $dbh->do(q{INSERT INTO groups (name, description)
+ VALUES ('admin', 'Administrators')}
+ );
+ $admin_gid = $dbh->bz_last_key('groups', 'id');
+ }
+
+ # Find current admins
+ my @admins;
+
+ # Don't lose admins from DBs where Bug 157704 applies
+ $sth
+ = $dbh->prepare("SELECT userid, (groupset & 65536), login_name "
+ . "FROM profiles "
+ . "WHERE (groupset | 65536) = 9223372036854775807");
+ $sth->execute();
+ while (my ($userid, $iscomplete, $login_name) = $sth->fetchrow_array()) {
+
+ # existing administrators are made members of group "admin"
+ print "\nWARNING - $login_name IS AN ADMIN IN SPITE OF BUG", " 157704\n\n"
+ if (!$iscomplete);
+ push(@admins, $userid) unless grep($_ eq $userid, @admins);
+ }
+
+ # Now make all those users admins directly. They were already
+ # added to every other group, above, because of their groupset.
+ foreach my $admin_id (@admins) {
+ $dbh->do(
+ "INSERT INTO user_group_map
+ (user_id, group_id, isbless, grant_type)
+ VALUES (?, ?, ?, ?)", undef, $admin_id, $admin_gid, $_,
+ GRANT_DIRECT
+ ) foreach (0, 1);
+ }
+
+ $dbh->bz_drop_column('profiles', 'groupset');
+ $dbh->bz_drop_column('profiles', 'blessgroupset');
+ $dbh->bz_drop_column('bugs', 'groupset');
+ $dbh->bz_drop_column('groups', 'bit');
+ $dbh->do("DELETE FROM fielddefs WHERE name = " . $dbh->quote('groupset'));
+ }
}
sub _convert_attachment_statuses_to_flags {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
+
+ # September 2002 myk@mozilla.org bug 98801
+ # Convert the attachment statuses tables into flags tables.
+ if ( $dbh->bz_table_info("attachstatuses")
+ && $dbh->bz_table_info("attachstatusdefs"))
+ {
+ print "Converting attachment statuses to flags...\n";
+
+ # Get IDs for the old attachment status and new flag fields.
+ my ($old_field_id)
+ = $dbh->selectrow_array(
+ "SELECT id FROM fielddefs WHERE name='attachstatusdefs.name'")
+ || 0;
+ my ($new_field_id)
+ = $dbh->selectrow_array(
+ "SELECT id FROM fielddefs WHERE name = 'flagtypes.name'");
+
+ # Convert attachment status definitions to flag types. If more than one
+ # status has the same name and description, it is merged into a single
+ # status with multiple inclusion records.
- # September 2002 myk@mozilla.org bug 98801
- # Convert the attachment statuses tables into flags tables.
- if ($dbh->bz_table_info("attachstatuses")
- && $dbh->bz_table_info("attachstatusdefs"))
- {
- print "Converting attachment statuses to flags...\n";
-
- # Get IDs for the old attachment status and new flag fields.
- my ($old_field_id) = $dbh->selectrow_array(
- "SELECT id FROM fielddefs WHERE name='attachstatusdefs.name'")
- || 0;
- my ($new_field_id) = $dbh->selectrow_array(
- "SELECT id FROM fielddefs WHERE name = 'flagtypes.name'");
-
- # Convert attachment status definitions to flag types. If more than one
- # status has the same name and description, it is merged into a single
- # status with multiple inclusion records.
-
- my $sth = $dbh->prepare(
- "SELECT id, name, description, sortkey, product_id
- FROM attachstatusdefs");
-
- # status definition IDs indexed by name/description
- my $def_ids = {};
-
- # merged IDs and the IDs they were merged into. The key is the old ID,
- # the value is the new one. This allows us to give statuses the right
- # ID when we convert them over to flags. This map includes IDs that
- # weren't merged (in this case the old and new IDs are the same), since
- # it makes the code simpler.
- my $def_id_map = {};
-
- $sth->execute();
- while (my ($id, $name, $desc, $sortkey, $prod_id) =
- $sth->fetchrow_array())
- {
- my $key = $name . $desc;
- if (!$def_ids->{$key}) {
- $def_ids->{$key} = $id;
- my $quoted_name = $dbh->quote($name);
- my $quoted_desc = $dbh->quote($desc);
- $dbh->do("INSERT INTO flagtypes (id, name, description,
+ my $sth = $dbh->prepare(
+ "SELECT id, name, description, sortkey, product_id
+ FROM attachstatusdefs"
+ );
+
+ # status definition IDs indexed by name/description
+ my $def_ids = {};
+
+ # merged IDs and the IDs they were merged into. The key is the old ID,
+ # the value is the new one. This allows us to give statuses the right
+ # ID when we convert them over to flags. This map includes IDs that
+ # weren't merged (in this case the old and new IDs are the same), since
+ # it makes the code simpler.
+ my $def_id_map = {};
+
+ $sth->execute();
+ while (my ($id, $name, $desc, $sortkey, $prod_id) = $sth->fetchrow_array()) {
+ my $key = $name . $desc;
+ if (!$def_ids->{$key}) {
+ $def_ids->{$key} = $id;
+ my $quoted_name = $dbh->quote($name);
+ my $quoted_desc = $dbh->quote($desc);
+ $dbh->do(
+ "INSERT INTO flagtypes (id, name, description,
sortkey, target_type)
VALUES ($id, $quoted_name, $quoted_desc,
- $sortkey,'a')");
- }
- $def_id_map->{$id} = $def_ids->{$key};
- $dbh->do("INSERT INTO flaginclusions (type_id, product_id)
- VALUES ($def_id_map->{$id}, $prod_id)");
- }
+ $sortkey,'a')"
+ );
+ }
+ $def_id_map->{$id} = $def_ids->{$key};
+ $dbh->do(
+ "INSERT INTO flaginclusions (type_id, product_id)
+ VALUES ($def_id_map->{$id}, $prod_id)"
+ );
+ }
- # Note: even though we've converted status definitions, we still
- # can't drop the table because we need it to convert the statuses
- # themselves.
-
- # Convert attachment statuses to flags. To do this we select
- # the statuses from the status table and then, for each one,
- # figure out who set it and when they set it from the bugs
- # activity table.
- my $id = 0;
- $sth = $dbh->prepare(
- "SELECT attachstatuses.attach_id, attachstatusdefs.id,
+ # Note: even though we've converted status definitions, we still
+ # can't drop the table because we need it to convert the statuses
+ # themselves.
+
+ # Convert attachment statuses to flags. To do this we select
+ # the statuses from the status table and then, for each one,
+ # figure out who set it and when they set it from the bugs
+ # activity table.
+ my $id = 0;
+ $sth = $dbh->prepare(
+ "SELECT attachstatuses.attach_id, attachstatusdefs.id,
attachstatusdefs.name, attachments.bug_id
FROM attachstatuses, attachstatusdefs, attachments
WHERE attachstatuses.statusid = attachstatusdefs.id
- AND attachstatuses.attach_id = attachments.attach_id");
+ AND attachstatuses.attach_id = attachments.attach_id"
+ );
- # a query to determine when the attachment status was set and who set it
- my $sth2 = $dbh->prepare("SELECT added, who, bug_when
+ # a query to determine when the attachment status was set and who set it
+ my $sth2 = $dbh->prepare(
+ "SELECT added, who, bug_when
FROM bugs_activity
WHERE bug_id = ? AND attach_id = ?
AND fieldid = $old_field_id
- ORDER BY bug_when DESC");
-
- $sth->execute();
- while (my ($attach_id, $def_id, $status, $bug_id) =
- $sth->fetchrow_array())
- {
- ++$id;
-
- # Determine when the attachment status was set and who set it.
- # We should always be able to find out this info from the bug
- # activity, but we fall back to default values just in case.
- $sth2->execute($bug_id, $attach_id);
- my ($added, $who, $when);
- while (($added, $who, $when) = $sth2->fetchrow_array()) {
- last if $added =~ /(^|[, ]+)\Q$status\E([, ]+|$)/;
- }
- $who = $dbh->quote($who); # "NULL" by default if $who is undefined
- $when = $when ? $dbh->quote($when) : "NOW()";
-
+ ORDER BY bug_when DESC"
+ );
- $dbh->do("INSERT INTO flags (id, type_id, status, bug_id,
+ $sth->execute();
+ while (my ($attach_id, $def_id, $status, $bug_id) = $sth->fetchrow_array()) {
+ ++$id;
+
+ # Determine when the attachment status was set and who set it.
+ # We should always be able to find out this info from the bug
+ # activity, but we fall back to default values just in case.
+ $sth2->execute($bug_id, $attach_id);
+ my ($added, $who, $when);
+ while (($added, $who, $when) = $sth2->fetchrow_array()) {
+ last if $added =~ /(^|[, ]+)\Q$status\E([, ]+|$)/;
+ }
+ $who = $dbh->quote($who); # "NULL" by default if $who is undefined
+ $when = $when ? $dbh->quote($when) : "NOW()";
+
+
+ $dbh->do(
+ "INSERT INTO flags (id, type_id, status, bug_id,
attach_id, creation_date, modification_date,
requestee_id, setter_id)
VALUES ($id, $def_id_map->{$def_id}, '+', $bug_id,
- $attach_id, $when, $when, NULL, $who)");
- }
+ $attach_id, $when, $when, NULL, $who)"
+ );
+ }
- # Now that we've converted both tables we can drop them.
- $dbh->bz_drop_table("attachstatuses");
- $dbh->bz_drop_table("attachstatusdefs");
+ # Now that we've converted both tables we can drop them.
+ $dbh->bz_drop_table("attachstatuses");
+ $dbh->bz_drop_table("attachstatusdefs");
- # Convert activity records for attachment statuses into records
- # for flags.
- $sth = $dbh->prepare("SELECT attach_id, who, bug_when, added,
+ # Convert activity records for attachment statuses into records
+ # for flags.
+ $sth = $dbh->prepare(
+ "SELECT attach_id, who, bug_when, added,
removed
FROM bugs_activity
- WHERE fieldid = $old_field_id");
- $sth->execute();
- while (my ($attach_id, $who, $when, $old_added, $old_removed) =
- $sth->fetchrow_array())
- {
- my @additions = split(/[, ]+/, $old_added);
- @additions = map("$_+", @additions);
- my $new_added = $dbh->quote(join(", ", @additions));
-
- my @removals = split(/[, ]+/, $old_removed);
- @removals = map("$_+", @removals);
- my $new_removed = $dbh->quote(join(", ", @removals));
-
- $old_added = $dbh->quote($old_added);
- $old_removed = $dbh->quote($old_removed);
- $who = $dbh->quote($who);
- $when = $dbh->quote($when);
-
- $dbh->do("UPDATE bugs_activity SET fieldid = $new_field_id, " .
- "added = $new_added, removed = $new_removed " .
- "WHERE attach_id = $attach_id AND who = $who " .
- "AND bug_when = $when AND fieldid = $old_field_id " .
- "AND added = $old_added AND removed = $old_removed");
- }
+ WHERE fieldid = $old_field_id"
+ );
+ $sth->execute();
+ while (my ($attach_id, $who, $when, $old_added, $old_removed)
+ = $sth->fetchrow_array())
+ {
+ my @additions = split(/[, ]+/, $old_added);
+ @additions = map("$_+", @additions);
+ my $new_added = $dbh->quote(join(", ", @additions));
+
+ my @removals = split(/[, ]+/, $old_removed);
+ @removals = map("$_+", @removals);
+ my $new_removed = $dbh->quote(join(", ", @removals));
+
+ $old_added = $dbh->quote($old_added);
+ $old_removed = $dbh->quote($old_removed);
+ $who = $dbh->quote($who);
+ $when = $dbh->quote($when);
+
+ $dbh->do("UPDATE bugs_activity SET fieldid = $new_field_id, "
+ . "added = $new_added, removed = $new_removed "
+ . "WHERE attach_id = $attach_id AND who = $who "
+ . "AND bug_when = $when AND fieldid = $old_field_id "
+ . "AND added = $old_added AND removed = $old_removed");
+ }
- # Remove the attachment status field from the field definitions.
- $dbh->do("DELETE FROM fielddefs WHERE name='attachstatusdefs.name'");
+ # Remove the attachment status field from the field definitions.
+ $dbh->do("DELETE FROM fielddefs WHERE name='attachstatusdefs.name'");
- print "done.\n";
- }
+ print "done.\n";
+ }
}
sub _remove_spaces_and_commas_from_flagtypes {
- my $dbh = Bugzilla->dbh;
- # Get all names and IDs, to find broken ones and to
- # check for collisions when renaming.
- my $sth = $dbh->prepare("SELECT name, id FROM flagtypes");
- $sth->execute();
-
- my %flagtypes;
- my @badflagnames;
- # find broken flagtype names, and populate a hash table
- # to check for collisions.
- while (my ($name, $id) = $sth->fetchrow_array()) {
- $flagtypes{$name} = $id;
- if ($name =~ /[ ,]/) {
- push(@badflagnames, $name);
- }
+ my $dbh = Bugzilla->dbh;
+
+ # Get all names and IDs, to find broken ones and to
+ # check for collisions when renaming.
+ my $sth = $dbh->prepare("SELECT name, id FROM flagtypes");
+ $sth->execute();
+
+ my %flagtypes;
+ my @badflagnames;
+
+ # find broken flagtype names, and populate a hash table
+ # to check for collisions.
+ while (my ($name, $id) = $sth->fetchrow_array()) {
+ $flagtypes{$name} = $id;
+ if ($name =~ /[ ,]/) {
+ push(@badflagnames, $name);
}
- if (@badflagnames) {
- print "Removing spaces and commas from flag names...\n";
- my ($flagname, $tryflagname);
- my $sth = $dbh->prepare("UPDATE flagtypes SET name = ? WHERE id = ?");
- foreach $flagname (@badflagnames) {
- print " Bad flag type name \"$flagname\" ...\n";
- # find a new name for this flagtype.
- ($tryflagname = $flagname) =~ tr/ ,/__/;
- # avoid collisions with existing flagtype names.
- while (defined($flagtypes{$tryflagname})) {
- print " ... can't rename as \"$tryflagname\" ...\n";
- $tryflagname .= "'";
- if (length($tryflagname) > 50) {
- my $lastchanceflagname = (substr $tryflagname, 0, 47) . '...';
- if (defined($flagtypes{$lastchanceflagname})) {
- print " ... last attempt as \"$lastchanceflagname\" still failed.'\n";
- die install_string('update_flags_bad_name',
- { flag => $flagname }), "\n";
- }
- $tryflagname = $lastchanceflagname;
- }
- }
- $sth->execute($tryflagname, $flagtypes{$flagname});
- print " renamed flag type \"$flagname\" as \"$tryflagname\"\n";
- $flagtypes{$tryflagname} = $flagtypes{$flagname};
- delete $flagtypes{$flagname};
+ }
+ if (@badflagnames) {
+ print "Removing spaces and commas from flag names...\n";
+ my ($flagname, $tryflagname);
+ my $sth = $dbh->prepare("UPDATE flagtypes SET name = ? WHERE id = ?");
+ foreach $flagname (@badflagnames) {
+ print " Bad flag type name \"$flagname\" ...\n";
+
+ # find a new name for this flagtype.
+ ($tryflagname = $flagname) =~ tr/ ,/__/;
+
+ # avoid collisions with existing flagtype names.
+ while (defined($flagtypes{$tryflagname})) {
+ print " ... can't rename as \"$tryflagname\" ...\n";
+ $tryflagname .= "'";
+ if (length($tryflagname) > 50) {
+ my $lastchanceflagname = (substr $tryflagname, 0, 47) . '...';
+ if (defined($flagtypes{$lastchanceflagname})) {
+ print " ... last attempt as \"$lastchanceflagname\" still failed.'\n";
+ die install_string('update_flags_bad_name', {flag => $flagname}), "\n";
+ }
+ $tryflagname = $lastchanceflagname;
}
- print "... done.\n";
+ }
+ $sth->execute($tryflagname, $flagtypes{$flagname});
+ print " renamed flag type \"$flagname\" as \"$tryflagname\"\n";
+ $flagtypes{$tryflagname} = $flagtypes{$flagname};
+ delete $flagtypes{$flagname};
}
+ print "... done.\n";
+ }
}
sub _setup_usebuggroups_backward_compatibility {
- my $dbh = Bugzilla->dbh;
-
- # Don't run this on newer Bugzillas. This is a reliable test because
- # the longdescs table existed in 2.16 (which had usebuggroups)
- # but not in 2.18, and this code happens between 2.16 and 2.18.
- return if $dbh->bz_column_info('longdescs', 'already_wrapped');
-
- # 2002-11-24 - bugreport@peshkin.net - bug 147275
- #
- # If group_control_map is empty, backward-compatibility
- # usebuggroups-equivalent records should be created.
- my ($maps_exist) = $dbh->selectrow_array(
- "SELECT DISTINCT 1 FROM group_control_map");
- if (!$maps_exist) {
- print "Converting old usebuggroups controls...\n";
- # Initially populate group_control_map.
- # First, get all the existing products and their groups.
- my $sth = $dbh->prepare("SELECT groups.id, products.id, groups.name,
+ my $dbh = Bugzilla->dbh;
+
+ # Don't run this on newer Bugzillas. This is a reliable test because
+ # the longdescs table existed in 2.16 (which had usebuggroups)
+ # but not in 2.18, and this code happens between 2.16 and 2.18.
+ return if $dbh->bz_column_info('longdescs', 'already_wrapped');
+
+ # 2002-11-24 - bugreport@peshkin.net - bug 147275
+ #
+ # If group_control_map is empty, backward-compatibility
+ # usebuggroups-equivalent records should be created.
+ my ($maps_exist)
+ = $dbh->selectrow_array("SELECT DISTINCT 1 FROM group_control_map");
+ if (!$maps_exist) {
+ print "Converting old usebuggroups controls...\n";
+
+ # Initially populate group_control_map.
+ # First, get all the existing products and their groups.
+ my $sth = $dbh->prepare(
+ "SELECT groups.id, products.id, groups.name,
products.name
FROM groups, products
- WHERE isbuggroup != 0");
- $sth->execute();
- while (my ($groupid, $productid, $groupname, $productname)
- = $sth->fetchrow_array())
- {
- if ($groupname eq $productname) {
- # Product and group have same name.
- $dbh->do("INSERT INTO group_control_map " .
- "(group_id, product_id, membercontrol, othercontrol) " .
- "VALUES (?, ?, ?, ?)", undef,
- ($groupid, $productid, CONTROLMAPDEFAULT, CONTROLMAPNA));
- } else {
- # See if this group is a product group at all.
- my $sth2 = $dbh->prepare("SELECT id FROM products
- WHERE name = " .$dbh->quote($groupname));
- $sth2->execute();
- my ($id) = $sth2->fetchrow_array();
- if (!$id) {
- # If there is no product with the same name as this
- # group, then it is permitted for all products.
- $dbh->do("INSERT INTO group_control_map " .
- "(group_id, product_id, membercontrol, othercontrol) " .
- "VALUES (?, ?, ?, ?)", undef,
- ($groupid, $productid, CONTROLMAPSHOWN, CONTROLMAPNA));
- }
- }
+ WHERE isbuggroup != 0"
+ );
+ $sth->execute();
+ while (my ($groupid, $productid, $groupname, $productname)
+ = $sth->fetchrow_array())
+ {
+ if ($groupname eq $productname) {
+
+ # Product and group have same name.
+ $dbh->do(
+ "INSERT INTO group_control_map "
+ . "(group_id, product_id, membercontrol, othercontrol) "
+ . "VALUES (?, ?, ?, ?)",
+ undef,
+ ($groupid, $productid, CONTROLMAPDEFAULT, CONTROLMAPNA)
+ );
+ }
+ else {
+ # See if this group is a product group at all.
+ my $sth2 = $dbh->prepare(
+ "SELECT id FROM products
+ WHERE name = " . $dbh->quote($groupname)
+ );
+ $sth2->execute();
+ my ($id) = $sth2->fetchrow_array();
+ if (!$id) {
+
+ # If there is no product with the same name as this
+ # group, then it is permitted for all products.
+ $dbh->do(
+ "INSERT INTO group_control_map "
+ . "(group_id, product_id, membercontrol, othercontrol) "
+ . "VALUES (?, ?, ?, ?)",
+ undef,
+ ($groupid, $productid, CONTROLMAPSHOWN, CONTROLMAPNA)
+ );
}
+ }
}
+ }
}
sub _remove_user_series_map {
- my $dbh = Bugzilla->dbh;
- # 2004-07-17 GRM - Remove "subscriptions" concept from charting, and add
- # group-based security instead.
- if ($dbh->bz_table_info("user_series_map")) {
- # Oracle doesn't like "date" as a column name, and apparently some DBs
- # don't like 'value' either. We use the changes to subscriptions as
- # something to hang these renamings off.
- $dbh->bz_rename_column('series_data', 'date', 'series_date');
- $dbh->bz_rename_column('series_data', 'value', 'series_value');
-
- # series_categories.category_id produces a too-long column name for the
- # auto-incrementing sequence (Oracle again).
- $dbh->bz_rename_column('series_categories', 'category_id', 'id');
-
- $dbh->bz_add_column("series", "public",
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
-
- # Migrate public-ness across from user_series_map to new field
- my $sth = $dbh->prepare("SELECT series_id from user_series_map " .
- "WHERE user_id = 0");
- $sth->execute();
- while (my ($public_series_id) = $sth->fetchrow_array()) {
- $dbh->do("UPDATE series SET public = 1 " .
- "WHERE series_id = $public_series_id");
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 2004-07-17 GRM - Remove "subscriptions" concept from charting, and add
+ # group-based security instead.
+ if ($dbh->bz_table_info("user_series_map")) {
+
+ # Oracle doesn't like "date" as a column name, and apparently some DBs
+ # don't like 'value' either. We use the changes to subscriptions as
+ # something to hang these renamings off.
+ $dbh->bz_rename_column('series_data', 'date', 'series_date');
+ $dbh->bz_rename_column('series_data', 'value', 'series_value');
- $dbh->bz_drop_table("user_series_map");
+ # series_categories.category_id produces a too-long column name for the
+ # auto-incrementing sequence (Oracle again).
+ $dbh->bz_rename_column('series_categories', 'category_id', 'id');
+
+ $dbh->bz_add_column("series", "public",
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+
+ # Migrate public-ness across from user_series_map to new field
+ my $sth = $dbh->prepare(
+ "SELECT series_id from user_series_map " . "WHERE user_id = 0");
+ $sth->execute();
+ while (my ($public_series_id) = $sth->fetchrow_array()) {
+ $dbh->do(
+ "UPDATE series SET public = 1 " . "WHERE series_id = $public_series_id");
}
+
+ $dbh->bz_drop_table("user_series_map");
+ }
}
sub _copy_old_charts_into_database {
- my $dbh = Bugzilla->dbh;
- my $datadir = bz_locations()->{'datadir'};
- # 2003-06-26 Copy the old charting data into the database, and create the
- # queries that will keep it all running. When the old charting system goes
- # away, if this code ever runs, it'll just find no files and do nothing.
- my $series_exists = $dbh->selectrow_array("SELECT 1 FROM series " .
- $dbh->sql_limit(1));
- if (!$series_exists && -d "$datadir/mining" && -e "$datadir/mining/-All-") {
- print "Migrating old chart data into database...\n";
-
- # We prepare the handle to insert the series data
- my $seriesdatasth = $dbh->prepare(
- "INSERT INTO series_data (series_id, series_date, series_value)
- VALUES (?, ?, ?)");
-
- my $deletesth = $dbh->prepare(
- "DELETE FROM series_data WHERE series_id = ? AND series_date = ?");
-
- my $groupmapsth = $dbh->prepare(
- "INSERT INTO category_group_map (category_id, group_id)
- VALUES (?, ?)");
-
- # Fields in the data file (matches the current collectstats.pl)
- my @statuses =
- qw(NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED VERIFIED CLOSED);
- my @resolutions =
- qw(FIXED INVALID WONTFIX LATER REMIND DUPLICATE WORKSFORME MOVED);
- my @fields = (@statuses, @resolutions);
-
- # We have a localization problem here. Where do we get these values?
- my $all_name = "-All-";
- my $open_name = "All Open";
-
- $dbh->bz_start_transaction();
- my $products = $dbh->selectall_arrayref("SELECT name FROM products");
-
- foreach my $product ((map { $_->[0] } @$products), "-All-") {
- print "$product:\n";
- # First, create the series
- my %queries;
- my %seriesids;
-
- my $query_prod = "";
- if ($product ne "-All-") {
- $query_prod = "product=" . html_quote($product) . "&";
- }
+ my $dbh = Bugzilla->dbh;
+ my $datadir = bz_locations()->{'datadir'};
+
+ # 2003-06-26 Copy the old charting data into the database, and create the
+ # queries that will keep it all running. When the old charting system goes
+ # away, if this code ever runs, it'll just find no files and do nothing.
+ my $series_exists
+ = $dbh->selectrow_array("SELECT 1 FROM series " . $dbh->sql_limit(1));
+ if (!$series_exists && -d "$datadir/mining" && -e "$datadir/mining/-All-") {
+ print "Migrating old chart data into database...\n";
+
+ # We prepare the handle to insert the series data
+ my $seriesdatasth = $dbh->prepare(
+ "INSERT INTO series_data (series_id, series_date, series_value)
+ VALUES (?, ?, ?)"
+ );
- # The query for statuses is different to that for resolutions.
- $queries{$_} = ($query_prod . "bug_status=$_") foreach (@statuses);
- $queries{$_} = ($query_prod . "resolution=$_")
- foreach (@resolutions);
-
- foreach my $field (@fields) {
- # Create a Series for each field in this product.
- my $series = new Bugzilla::Series(undef, $product, $all_name,
- $field, undef, 1,
- $queries{$field}, 1);
- $series->writeToDatabase();
- $seriesids{$field} = $series->{'series_id'};
- }
+ my $deletesth = $dbh->prepare(
+ "DELETE FROM series_data WHERE series_id = ? AND series_date = ?");
- # We also add a new query for "Open", so that migrated products get
- # the same set as new products (see editproducts.cgi.)
- my @openedstatuses = ("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED");
- my $query = join("&", map { "bug_status=$_" } @openedstatuses);
- my $series = new Bugzilla::Series(undef, $product, $all_name,
- $open_name, undef, 1,
- $query_prod . $query, 1);
- $series->writeToDatabase();
- $seriesids{$open_name} = $series->{'series_id'};
-
- # Now, we attempt to read in historical data, if any
- # Convert the name in the same way that collectstats.pl does
- my $product_file = $product;
- $product_file =~ s/\//-/gs;
- $product_file = "$datadir/mining/$product_file";
-
- # There are many reasons that this might fail (e.g. no stats
- # for this product), so we don't worry if it does.
- my $in = new IO::File($product_file) or next;
-
- # The data files should be in a standard format, even for old
- # Bugzillas, because of the conversion code further up this file.
- my %data;
- my $last_date = "";
-
- my @lines = <$in>;
- while (my $line = shift @lines) {
- if ($line =~ /^(\d+\|.*)/) {
- my @numbers = split(/\||\r/, $1);
-
- # Only take the first line for each date; it was possible to
- # run collectstats.pl more than once in a day.
- next if $numbers[0] eq $last_date;
-
- for my $i (0 .. $#fields) {
- # $numbers[0] is the date
- $data{$fields[$i]}{$numbers[0]} = $numbers[$i + 1];
-
- # Keep a total of the number of open bugs for this day
- if (grep { $_ eq $fields[$i] } @openedstatuses) {
- $data{$open_name}{$numbers[0]} += $numbers[$i + 1];
- }
- }
-
- $last_date = $numbers[0];
- }
- }
+ my $groupmapsth = $dbh->prepare(
+ "INSERT INTO category_group_map (category_id, group_id)
+ VALUES (?, ?)"
+ );
- $in->close;
-
- my $total_items = (scalar(@fields) + 1)
- * scalar(keys %{ $data{'NEW'} });
- my $count = 0;
- foreach my $field (@fields, $open_name) {
- # Insert values into series_data: series_id, date, value
- my %fielddata = %{$data{$field}};
- foreach my $date (keys %fielddata) {
- # We need to delete in case the text file had duplicate
- # entries in it.
- $deletesth->execute($seriesids{$field}, $date);
-
- # We prepared this above
- $seriesdatasth->execute($seriesids{$field},
- $date, $fielddata{$date} || 0);
- indicate_progress({ total => $total_items,
- current => ++$count, every => 100 });
- }
- }
+ # Fields in the data file (matches the current collectstats.pl)
+ my @statuses = qw(NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED VERIFIED CLOSED);
+ my @resolutions
+ = qw(FIXED INVALID WONTFIX LATER REMIND DUPLICATE WORKSFORME MOVED);
+ my @fields = (@statuses, @resolutions);
+
+ # We have a localization problem here. Where do we get these values?
+ my $all_name = "-All-";
+ my $open_name = "All Open";
- # Create the groupsets for the category
- my $category_id =
- $dbh->selectrow_array("SELECT id FROM series_categories " .
- "WHERE name = " . $dbh->quote($product));
- my $product_id =
- $dbh->selectrow_array("SELECT id FROM products " .
- "WHERE name = " . $dbh->quote($product));
-
- if (defined($category_id) && defined($product_id)) {
-
- # Get all the mandatory groups for this product
- my $group_ids =
- $dbh->selectcol_arrayref("SELECT group_id " .
- "FROM group_control_map " .
- "WHERE product_id = $product_id " .
- "AND (membercontrol = " . CONTROLMAPMANDATORY .
- " OR othercontrol = " . CONTROLMAPMANDATORY . ")");
-
- foreach my $group_id (@$group_ids) {
- $groupmapsth->execute($category_id, $group_id);
- }
+ $dbh->bz_start_transaction();
+ my $products = $dbh->selectall_arrayref("SELECT name FROM products");
+
+ foreach my $product ((map { $_->[0] } @$products), "-All-") {
+ print "$product:\n";
+
+ # First, create the series
+ my %queries;
+ my %seriesids;
+
+ my $query_prod = "";
+ if ($product ne "-All-") {
+ $query_prod = "product=" . html_quote($product) . "&";
+ }
+
+ # The query for statuses is different to that for resolutions.
+ $queries{$_} = ($query_prod . "bug_status=$_") foreach (@statuses);
+ $queries{$_} = ($query_prod . "resolution=$_") foreach (@resolutions);
+
+ foreach my $field (@fields) {
+
+ # Create a Series for each field in this product.
+ my $series = new Bugzilla::Series(undef, $product, $all_name, $field, undef, 1,
+ $queries{$field}, 1);
+ $series->writeToDatabase();
+ $seriesids{$field} = $series->{'series_id'};
+ }
+
+ # We also add a new query for "Open", so that migrated products get
+ # the same set as new products (see editproducts.cgi.)
+ my @openedstatuses = ("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED");
+ my $query = join("&", map {"bug_status=$_"} @openedstatuses);
+ my $series
+ = new Bugzilla::Series(undef, $product, $all_name, $open_name, undef, 1,
+ $query_prod . $query, 1);
+ $series->writeToDatabase();
+ $seriesids{$open_name} = $series->{'series_id'};
+
+ # Now, we attempt to read in historical data, if any
+ # Convert the name in the same way that collectstats.pl does
+ my $product_file = $product;
+ $product_file =~ s/\//-/gs;
+ $product_file = "$datadir/mining/$product_file";
+
+ # There are many reasons that this might fail (e.g. no stats
+ # for this product), so we don't worry if it does.
+ my $in = new IO::File($product_file) or next;
+
+ # The data files should be in a standard format, even for old
+ # Bugzillas, because of the conversion code further up this file.
+ my %data;
+ my $last_date = "";
+
+ my @lines = <$in>;
+ while (my $line = shift @lines) {
+ if ($line =~ /^(\d+\|.*)/) {
+ my @numbers = split(/\||\r/, $1);
+
+ # Only take the first line for each date; it was possible to
+ # run collectstats.pl more than once in a day.
+ next if $numbers[0] eq $last_date;
+
+ for my $i (0 .. $#fields) {
+
+ # $numbers[0] is the date
+ $data{$fields[$i]}{$numbers[0]} = $numbers[$i + 1];
+
+ # Keep a total of the number of open bugs for this day
+ if (grep { $_ eq $fields[$i] } @openedstatuses) {
+ $data{$open_name}{$numbers[0]} += $numbers[$i + 1];
}
+ }
+
+ $last_date = $numbers[0];
}
+ }
+
+ $in->close;
+
+ my $total_items = (scalar(@fields) + 1) * scalar(keys %{$data{'NEW'}});
+ my $count = 0;
+ foreach my $field (@fields, $open_name) {
- $dbh->bz_commit_transaction();
+ # Insert values into series_data: series_id, date, value
+ my %fielddata = %{$data{$field}};
+ foreach my $date (keys %fielddata) {
+
+ # We need to delete in case the text file had duplicate
+ # entries in it.
+ $deletesth->execute($seriesids{$field}, $date);
+
+ # We prepared this above
+ $seriesdatasth->execute($seriesids{$field}, $date, $fielddata{$date} || 0);
+ indicate_progress({total => $total_items, current => ++$count, every => 100});
+ }
+ }
+
+ # Create the groupsets for the category
+ my $category_id = $dbh->selectrow_array(
+ "SELECT id FROM series_categories " . "WHERE name = " . $dbh->quote($product));
+ my $product_id = $dbh->selectrow_array(
+ "SELECT id FROM products " . "WHERE name = " . $dbh->quote($product));
+
+ if (defined($category_id) && defined($product_id)) {
+
+ # Get all the mandatory groups for this product
+ my $group_ids
+ = $dbh->selectcol_arrayref("SELECT group_id "
+ . "FROM group_control_map "
+ . "WHERE product_id = $product_id "
+ . "AND (membercontrol = "
+ . CONTROLMAPMANDATORY
+ . " OR othercontrol = "
+ . CONTROLMAPMANDATORY
+ . ")");
+
+ foreach my $group_id (@$group_ids) {
+ $groupmapsth->execute($category_id, $group_id);
+ }
+ }
}
+
+ $dbh->bz_commit_transaction();
+ }
}
sub _add_user_group_map_grant_type {
- my $dbh = Bugzilla->dbh;
- # 2004-04-12 - Keep regexp-based group permissions up-to-date - Bug 240325
- if ($dbh->bz_column_info("user_group_map", "isderived")) {
- $dbh->bz_add_column('user_group_map', 'grant_type',
- {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'});
- $dbh->do("DELETE FROM user_group_map WHERE isderived != 0");
- $dbh->do("UPDATE user_group_map SET grant_type = " . GRANT_DIRECT);
- $dbh->bz_drop_column("user_group_map", "isderived");
-
- $dbh->bz_drop_index('user_group_map', 'user_group_map_user_id_idx');
- $dbh->bz_add_index('user_group_map', 'user_group_map_user_id_idx',
- {TYPE => 'UNIQUE',
- FIELDS => [qw(user_id group_id grant_type isbless)]});
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 2004-04-12 - Keep regexp-based group permissions up-to-date - Bug 240325
+ if ($dbh->bz_column_info("user_group_map", "isderived")) {
+ $dbh->bz_add_column('user_group_map', 'grant_type',
+ {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'});
+ $dbh->do("DELETE FROM user_group_map WHERE isderived != 0");
+ $dbh->do("UPDATE user_group_map SET grant_type = " . GRANT_DIRECT);
+ $dbh->bz_drop_column("user_group_map", "isderived");
+
+ $dbh->bz_drop_index('user_group_map', 'user_group_map_user_id_idx');
+ $dbh->bz_add_index('user_group_map', 'user_group_map_user_id_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(user_id group_id grant_type isbless)]});
+ }
}
sub _add_group_group_map_grant_type {
- my $dbh = Bugzilla->dbh;
- # 2004-07-16 - Make it possible to have group-group relationships other than
- # membership and bless.
- if ($dbh->bz_column_info("group_group_map", "isbless")) {
- $dbh->bz_add_column('group_group_map', 'grant_type',
- {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'});
- $dbh->do("UPDATE group_group_map SET grant_type = " .
- "IF(isbless, " . GROUP_BLESS . ", " .
- GROUP_MEMBERSHIP . ")");
- $dbh->bz_drop_index('group_group_map', 'group_group_map_member_id_idx');
- $dbh->bz_drop_column("group_group_map", "isbless");
- $dbh->bz_add_index('group_group_map', 'group_group_map_member_id_idx',
- {TYPE => 'UNIQUE',
- FIELDS => [qw(member_id grantor_id grant_type)]});
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 2004-07-16 - Make it possible to have group-group relationships other than
+ # membership and bless.
+ if ($dbh->bz_column_info("group_group_map", "isbless")) {
+ $dbh->bz_add_column('group_group_map', 'grant_type',
+ {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'});
+ $dbh->do("UPDATE group_group_map SET grant_type = "
+ . "IF(isbless, "
+ . GROUP_BLESS . ", "
+ . GROUP_MEMBERSHIP
+ . ")");
+ $dbh->bz_drop_index('group_group_map', 'group_group_map_member_id_idx');
+ $dbh->bz_drop_column("group_group_map", "isbless");
+ $dbh->bz_add_index(
+ 'group_group_map',
+ 'group_group_map_member_id_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(member_id grantor_id grant_type)]}
+ );
+ }
}
sub _add_longdescs_already_wrapped {
- my $dbh = Bugzilla->dbh;
- # 2005-01-29 - mkanat@bugzilla.org
- if (!$dbh->bz_column_info('longdescs', 'already_wrapped')) {
- # Old, pre-wrapped comments should not be auto-wrapped
- $dbh->bz_add_column('longdescs', 'already_wrapped',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}, 1);
- # If an old comment doesn't have a newline in the first 81 characters,
- # (or doesn't contain a newline at all) and it contains a space,
- # then it's probably a mis-wrapped comment and we should wrap it
- # at display-time.
- print "Fixing old, mis-wrapped comments...\n";
- $dbh->do(q{UPDATE longdescs SET already_wrapped = 0
+ my $dbh = Bugzilla->dbh;
+
+ # 2005-01-29 - mkanat@bugzilla.org
+ if (!$dbh->bz_column_info('longdescs', 'already_wrapped')) {
+
+ # Old, pre-wrapped comments should not be auto-wrapped
+ $dbh->bz_add_column('longdescs', 'already_wrapped',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}, 1);
+
+ # If an old comment doesn't have a newline in the first 81 characters,
+ # (or doesn't contain a newline at all) and it contains a space,
+ # then it's probably a mis-wrapped comment and we should wrap it
+ # at display-time.
+ print "Fixing old, mis-wrapped comments...\n";
+ $dbh->do(
+ q{UPDATE longdescs SET already_wrapped = 0
WHERE (} . $dbh->sql_position(q{'\n'}, 'thetext') . q{ > 81
OR } . $dbh->sql_position(q{'\n'}, 'thetext') . q{ = 0)
- AND SUBSTRING(thetext FROM 1 FOR 80) LIKE '% %'});
- }
+ AND SUBSTRING(thetext FROM 1 FOR 80) LIKE '% %'}
+ );
+ }
}
sub _convert_attachments_filename_from_mediumtext {
- my $dbh = Bugzilla->dbh;
- # 2002 November, myk@mozilla.org, bug 178841:
- #
- # Convert the "attachments.filename" column from a ridiculously large
- # "mediumtext" to a much more sensible "varchar(100)". Also takes
- # the opportunity to remove paths from existing filenames, since they
- # shouldn't be there for security. Buggy browsers include them,
- # and attachment.cgi now takes them out, but old ones need converting.
- my $ref = $dbh->bz_column_info("attachments", "filename");
- if ($ref->{TYPE} ne 'varchar(100)' && $ref->{TYPE} ne 'varchar(255)') {
- print "Removing paths from filenames in attachments table...";
-
- my $sth = $dbh->prepare("SELECT attach_id, filename FROM attachments " .
- "WHERE " . $dbh->sql_position(q{'/'}, 'filename') . " > 0 OR " .
- $dbh->sql_position(q{'\\\\'}, 'filename') . " > 0");
- $sth->execute;
-
- while (my ($attach_id, $filename) = $sth->fetchrow_array) {
- $filename =~ s/^.*[\/\\]//;
- my $quoted_filename = $dbh->quote($filename);
- $dbh->do("UPDATE attachments SET filename = $quoted_filename " .
- "WHERE attach_id = $attach_id");
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 2002 November, myk@mozilla.org, bug 178841:
+ #
+ # Convert the "attachments.filename" column from a ridiculously large
+ # "mediumtext" to a much more sensible "varchar(100)". Also takes
+ # the opportunity to remove paths from existing filenames, since they
+ # shouldn't be there for security. Buggy browsers include them,
+ # and attachment.cgi now takes them out, but old ones need converting.
+ my $ref = $dbh->bz_column_info("attachments", "filename");
+ if ($ref->{TYPE} ne 'varchar(100)' && $ref->{TYPE} ne 'varchar(255)') {
+ print "Removing paths from filenames in attachments table...";
+
+ my $sth
+ = $dbh->prepare("SELECT attach_id, filename FROM attachments "
+ . "WHERE "
+ . $dbh->sql_position(q{'/'}, 'filename')
+ . " > 0 OR "
+ . $dbh->sql_position(q{'\\\\'}, 'filename')
+ . " > 0");
+ $sth->execute;
+
+ while (my ($attach_id, $filename) = $sth->fetchrow_array) {
+ $filename =~ s/^.*[\/\\]//;
+ my $quoted_filename = $dbh->quote($filename);
+ $dbh->do("UPDATE attachments SET filename = $quoted_filename "
+ . "WHERE attach_id = $attach_id");
+ }
- print "Done.\n";
+ print "Done.\n";
- $dbh->bz_alter_column("attachments", "filename",
- {TYPE => 'varchar(100)', NOTNULL => 1});
- }
+ $dbh->bz_alter_column("attachments", "filename",
+ {TYPE => 'varchar(100)', NOTNULL => 1});
+ }
}
sub _rename_votes_count_and_force_group_refresh {
- my $dbh = Bugzilla->dbh;
- # 2003-04-27 - bugzilla@chimpychompy.org (GavinS)
- #
- # Bug 180086 (http://bugzilla.mozilla.org/show_bug.cgi?id=180086)
- #
- # Renaming the 'count' column in the votes table because Sybase doesn't
- # like it
- return if !$dbh->bz_table_info('votes');
- return if $dbh->bz_column_info('votes', 'count');
- $dbh->bz_rename_column('votes', 'count', 'vote_count');
+ my $dbh = Bugzilla->dbh;
+
+ # 2003-04-27 - bugzilla@chimpychompy.org (GavinS)
+ #
+ # Bug 180086 (http://bugzilla.mozilla.org/show_bug.cgi?id=180086)
+ #
+ # Renaming the 'count' column in the votes table because Sybase doesn't
+ # like it
+ return if !$dbh->bz_table_info('votes');
+ return if $dbh->bz_column_info('votes', 'count');
+ $dbh->bz_rename_column('votes', 'count', 'vote_count');
}
sub _fix_group_with_empty_name {
- my $dbh = Bugzilla->dbh;
- # 2005-01-12 Nick Barnes <nb@ravenbrook.com> bug 278010
- # Rename any group which has an empty name.
- # Note that there can be at most one such group (because of
- # the SQL index on the name column).
- my ($emptygroupid) = $dbh->selectrow_array(
- "SELECT id FROM groups where name = ''");
- if ($emptygroupid) {
- # There is a group with an empty name; find a name to rename it
- # as. Must avoid collisions with existing names. Start with
- # group_$gid and add _<n> if necessary.
- my $trycount = 0;
- my $trygroupname;
- my $sth = $dbh->prepare("SELECT 1 FROM groups where name = ?");
- my $name_exists = 1;
-
- while ($name_exists) {
- $trygroupname = "group_$emptygroupid";
- if ($trycount > 0) {
- $trygroupname .= "_$trycount";
- }
- $name_exists = $dbh->selectrow_array($sth, undef, $trygroupname);
- $trycount++;
- }
- $dbh->do("UPDATE groups SET name = ? WHERE id = ?",
- undef, $trygroupname, $emptygroupid);
- print "Group $emptygroupid had an empty name; renamed as",
- " '$trygroupname'.\n";
+ my $dbh = Bugzilla->dbh;
+
+ # 2005-01-12 Nick Barnes <nb@ravenbrook.com> bug 278010
+ # Rename any group which has an empty name.
+ # Note that there can be at most one such group (because of
+ # the SQL index on the name column).
+ my ($emptygroupid)
+ = $dbh->selectrow_array("SELECT id FROM groups where name = ''");
+ if ($emptygroupid) {
+
+ # There is a group with an empty name; find a name to rename it
+ # as. Must avoid collisions with existing names. Start with
+ # group_$gid and add _<n> if necessary.
+ my $trycount = 0;
+ my $trygroupname;
+ my $sth = $dbh->prepare("SELECT 1 FROM groups where name = ?");
+ my $name_exists = 1;
+
+ while ($name_exists) {
+ $trygroupname = "group_$emptygroupid";
+ if ($trycount > 0) {
+ $trygroupname .= "_$trycount";
+ }
+ $name_exists = $dbh->selectrow_array($sth, undef, $trygroupname);
+ $trycount++;
}
+ $dbh->do("UPDATE groups SET name = ? WHERE id = ?",
+ undef, $trygroupname, $emptygroupid);
+ print "Group $emptygroupid had an empty name; renamed as",
+ " '$trygroupname'.\n";
+ }
}
# A helper for the emailprefs subs below
sub _clone_email_event {
- my ($source, $target) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($source, $target) = @_;
+ my $dbh = Bugzilla->dbh;
- $dbh->do("INSERT INTO email_setting (user_id, relationship, event)
+ $dbh->do(
+ "INSERT INTO email_setting (user_id, relationship, event)
SELECT user_id, relationship, $target FROM email_setting
- WHERE event = $source");
+ WHERE event = $source"
+ );
}
sub _migrate_email_prefs_to_new_table {
- my $dbh = Bugzilla->dbh;
- # 2005-03-29 - gerv@gerv.net - bug 73665.
- # Migrate email preferences to new email prefs table.
- if ($dbh->bz_column_info("profiles", "emailflags")) {
- print "Migrating email preferences to new table...\n";
-
- # These are the "roles" and "reasons" from the original code, mapped to
- # the new terminology of relationships and events.
- my %relationships = ("Owner" => REL_ASSIGNEE,
- "Reporter" => REL_REPORTER,
- "QAcontact" => REL_QA,
- "CClist" => REL_CC,
- # REL_VOTER was "4" before it was moved to an
- # extension.
- "Voter" => 4);
-
- my %events = ("Removeme" => EVT_ADDED_REMOVED,
- "Comments" => EVT_COMMENT,
- "Attachments" => EVT_ATTACHMENT,
- "Status" => EVT_PROJ_MANAGEMENT,
- "Resolved" => EVT_OPENED_CLOSED,
- "Keywords" => EVT_KEYWORD,
- "CC" => EVT_CC,
- "Other" => EVT_OTHER,
- "Unconfirmed" => EVT_UNCONFIRMED);
-
- # Request preferences
- my %requestprefs = ("FlagRequestee" => EVT_FLAG_REQUESTED,
- "FlagRequester" => EVT_REQUESTED_FLAG);
-
- # We run the below code in a transaction to speed things up.
- $dbh->bz_start_transaction();
-
- # Select all emailflags flag strings
- my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM profiles');
- my $sth = $dbh->prepare("SELECT userid, emailflags FROM profiles");
- $sth->execute();
- my $i = 0;
-
- while (my ($userid, $flagstring) = $sth->fetchrow_array()) {
- $i++;
- indicate_progress({ total => $total, current => $i, every => 10 });
- # If the user has never logged in since emailprefs arrived, and the
- # temporary code to give them a default string never ran, then
- # $flagstring will be null. In this case, they just get all mail.
- $flagstring ||= "";
-
- # The 255 param is here, because without a third param, split will
- # trim any trailing null fields, which causes Perl to eject lots of
- # warnings. Any suitably large number would do.
- my %emailflags = split(/~/, $flagstring, 255);
-
- my $sth2 = $dbh->prepare("INSERT into email_setting " .
- "(user_id, relationship, event) VALUES (" .
- "$userid, ?, ?)");
- foreach my $relationship (keys %relationships) {
- foreach my $event (keys %events) {
- my $key = "email$relationship$event";
- if (!exists($emailflags{$key})
- || $emailflags{$key} eq 'on')
- {
- $sth2->execute($relationships{$relationship},
- $events{$event});
- }
- }
- }
- # Note that in the old system, the value of "excludeself" is
- # assumed to be off if the preference does not exist in the
- # user's list, unlike other preferences whose value is
- # assumed to be on if they do not exist.
- #
- # This preference has changed from global to per-relationship.
- if (!exists($emailflags{'ExcludeSelf'})
- || $emailflags{'ExcludeSelf'} ne 'on')
- {
- foreach my $relationship (keys %relationships) {
- $dbh->do("INSERT into email_setting " .
- "(user_id, relationship, event) VALUES (" .
- $userid . ", " .
- $relationships{$relationship}. ", " .
- EVT_CHANGED_BY_ME . ")");
- }
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 2005-03-29 - gerv@gerv.net - bug 73665.
+ # Migrate email preferences to new email prefs table.
+ if ($dbh->bz_column_info("profiles", "emailflags")) {
+ print "Migrating email preferences to new table...\n";
+
+ # These are the "roles" and "reasons" from the original code, mapped to
+ # the new terminology of relationships and events.
+ my %relationships = (
+ "Owner" => REL_ASSIGNEE,
+ "Reporter" => REL_REPORTER,
+ "QAcontact" => REL_QA,
+ "CClist" => REL_CC,
+
+ # REL_VOTER was "4" before it was moved to an
+ # extension.
+ "Voter" => 4
+ );
- foreach my $key (keys %requestprefs) {
- if (!exists($emailflags{$key}) || $emailflags{$key} eq 'on') {
- $dbh->do("INSERT into email_setting " .
- "(user_id, relationship, event) VALUES (" .
- $userid . ", " . REL_ANY . ", " .
- $requestprefs{$key} . ")");
- }
- }
- }
- print "\n";
+ my %events = (
+ "Removeme" => EVT_ADDED_REMOVED,
+ "Comments" => EVT_COMMENT,
+ "Attachments" => EVT_ATTACHMENT,
+ "Status" => EVT_PROJ_MANAGEMENT,
+ "Resolved" => EVT_OPENED_CLOSED,
+ "Keywords" => EVT_KEYWORD,
+ "CC" => EVT_CC,
+ "Other" => EVT_OTHER,
+ "Unconfirmed" => EVT_UNCONFIRMED
+ );
- # EVT_ATTACHMENT_DATA should initially have identical settings to
- # EVT_ATTACHMENT.
- _clone_email_event(EVT_ATTACHMENT, EVT_ATTACHMENT_DATA);
+ # Request preferences
+ my %requestprefs = (
+ "FlagRequestee" => EVT_FLAG_REQUESTED,
+ "FlagRequester" => EVT_REQUESTED_FLAG
+ );
+
+ # We run the below code in a transaction to speed things up.
+ $dbh->bz_start_transaction();
- $dbh->bz_commit_transaction();
- $dbh->bz_drop_column("profiles", "emailflags");
+ # Select all emailflags flag strings
+ my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM profiles');
+ my $sth = $dbh->prepare("SELECT userid, emailflags FROM profiles");
+ $sth->execute();
+ my $i = 0;
+
+ while (my ($userid, $flagstring) = $sth->fetchrow_array()) {
+ $i++;
+ indicate_progress({total => $total, current => $i, every => 10});
+
+ # If the user has never logged in since emailprefs arrived, and the
+ # temporary code to give them a default string never ran, then
+ # $flagstring will be null. In this case, they just get all mail.
+ $flagstring ||= "";
+
+ # The 255 param is here, because without a third param, split will
+ # trim any trailing null fields, which causes Perl to eject lots of
+ # warnings. Any suitably large number would do.
+ my %emailflags = split(/~/, $flagstring, 255);
+
+ my $sth2
+ = $dbh->prepare("INSERT into email_setting "
+ . "(user_id, relationship, event) VALUES ("
+ . "$userid, ?, ?)");
+ foreach my $relationship (keys %relationships) {
+ foreach my $event (keys %events) {
+ my $key = "email$relationship$event";
+ if (!exists($emailflags{$key}) || $emailflags{$key} eq 'on') {
+ $sth2->execute($relationships{$relationship}, $events{$event});
+ }
+ }
+ }
+
+ # Note that in the old system, the value of "excludeself" is
+ # assumed to be off if the preference does not exist in the
+ # user's list, unlike other preferences whose value is
+ # assumed to be on if they do not exist.
+ #
+ # This preference has changed from global to per-relationship.
+ if (!exists($emailflags{'ExcludeSelf'}) || $emailflags{'ExcludeSelf'} ne 'on') {
+ foreach my $relationship (keys %relationships) {
+ $dbh->do("INSERT into email_setting "
+ . "(user_id, relationship, event) VALUES ("
+ . $userid . ", "
+ . $relationships{$relationship} . ", "
+ . EVT_CHANGED_BY_ME
+ . ")");
+ }
+ }
+
+ foreach my $key (keys %requestprefs) {
+ if (!exists($emailflags{$key}) || $emailflags{$key} eq 'on') {
+ $dbh->do("INSERT into email_setting "
+ . "(user_id, relationship, event) VALUES ("
+ . $userid . ", "
+ . REL_ANY . ", "
+ . $requestprefs{$key}
+ . ")");
+ }
+ }
}
+ print "\n";
+
+ # EVT_ATTACHMENT_DATA should initially have identical settings to
+ # EVT_ATTACHMENT.
+ _clone_email_event(EVT_ATTACHMENT, EVT_ATTACHMENT_DATA);
+
+ $dbh->bz_commit_transaction();
+ $dbh->bz_drop_column("profiles", "emailflags");
+ }
}
sub _initialize_new_email_prefs {
- my $dbh = Bugzilla->dbh;
- # Check for any "new" email settings that wouldn't have been ported over
- # during the block above. Since these settings would have otherwise
- # fallen under EVT_OTHER, we'll just clone those settings. That way if
- # folks have already disabled all of that mail, there won't be any change.
- my %events = (
- "Dependency Tree Changes" => EVT_DEPEND_BLOCK,
- "Product/Component Changes" => EVT_COMPONENT,
- );
-
- foreach my $desc (keys %events) {
- my $event = $events{$desc};
- my $have_events = $dbh->selectrow_array(
- "SELECT 1 FROM email_setting WHERE event = $event "
- . $dbh->sql_limit(1));
-
- if (!$have_events) {
- # No settings in the table yet, so we assume that this is the
- # first time it's being set.
- print "Initializing \"$desc\" email_setting ...\n";
- _clone_email_event(EVT_OTHER, $event);
- }
+ my $dbh = Bugzilla->dbh;
+
+ # Check for any "new" email settings that wouldn't have been ported over
+ # during the block above. Since these settings would have otherwise
+ # fallen under EVT_OTHER, we'll just clone those settings. That way if
+ # folks have already disabled all of that mail, there won't be any change.
+ my %events = (
+ "Dependency Tree Changes" => EVT_DEPEND_BLOCK,
+ "Product/Component Changes" => EVT_COMPONENT,
+ );
+
+ foreach my $desc (keys %events) {
+ my $event = $events{$desc};
+ my $have_events = $dbh->selectrow_array(
+ "SELECT 1 FROM email_setting WHERE event = $event " . $dbh->sql_limit(1));
+
+ if (!$have_events) {
+
+ # No settings in the table yet, so we assume that this is the
+ # first time it's being set.
+ print "Initializing \"$desc\" email_setting ...\n";
+ _clone_email_event(EVT_OTHER, $event);
}
+ }
}
sub _change_all_mysql_booleans_to_tinyint {
- my $dbh = Bugzilla->dbh;
- # 2005-03-27: Standardize all boolean fields to plain "tinyint"
- if ( $dbh->isa('Bugzilla::DB::Mysql') ) {
- # This is a change to make things consistent with Schema, so we use
- # direct-database access methods.
- my $quip_info_sth = $dbh->column_info(undef, undef, 'quips', '%');
- my $quips_cols = $quip_info_sth->fetchall_hashref("COLUMN_NAME");
- my $approved_col = $quips_cols->{'approved'};
- if ( $approved_col->{TYPE_NAME} eq 'TINYINT'
- and $approved_col->{COLUMN_SIZE} == 1 )
- {
- # series.public could have been renamed to series.is_public,
- # and so wouldn't need to be fixed manually.
- if ($dbh->bz_column_info('series', 'public')) {
- $dbh->bz_alter_column_raw('series', 'public',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '0'});
- }
- $dbh->bz_alter_column_raw('bug_status', 'isactive',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
- $dbh->bz_alter_column_raw('rep_platform', 'isactive',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
- $dbh->bz_alter_column_raw('resolution', 'isactive',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
- $dbh->bz_alter_column_raw('op_sys', 'isactive',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
- $dbh->bz_alter_column_raw('bug_severity', 'isactive',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
- $dbh->bz_alter_column_raw('priority', 'isactive',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
- $dbh->bz_alter_column_raw('quips', 'approved',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
- }
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 2005-03-27: Standardize all boolean fields to plain "tinyint"
+ if ($dbh->isa('Bugzilla::DB::Mysql')) {
+
+ # This is a change to make things consistent with Schema, so we use
+ # direct-database access methods.
+ my $quip_info_sth = $dbh->column_info(undef, undef, 'quips', '%');
+ my $quips_cols = $quip_info_sth->fetchall_hashref("COLUMN_NAME");
+ my $approved_col = $quips_cols->{'approved'};
+ if ( $approved_col->{TYPE_NAME} eq 'TINYINT'
+ and $approved_col->{COLUMN_SIZE} == 1)
+ {
+ # series.public could have been renamed to series.is_public,
+ # and so wouldn't need to be fixed manually.
+ if ($dbh->bz_column_info('series', 'public')) {
+ $dbh->bz_alter_column_raw('series', 'public',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '0'});
+ }
+ $dbh->bz_alter_column_raw('bug_status', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
+ $dbh->bz_alter_column_raw('rep_platform', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
+ $dbh->bz_alter_column_raw('resolution', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
+ $dbh->bz_alter_column_raw('op_sys', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
+ $dbh->bz_alter_column_raw('bug_severity', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
+ $dbh->bz_alter_column_raw('priority', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
+ $dbh->bz_alter_column_raw('quips', 'approved',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'});
+ }
+ }
}
# A helper for the below function.
sub _de_dup_version {
- my ($product_id, $version) = @_;
- my $dbh = Bugzilla->dbh;
- print "Fixing duplicate version $version in product_id $product_id...\n";
- $dbh->do('DELETE FROM versions WHERE product_id = ? AND value = ?',
- undef, $product_id, $version);
- $dbh->do('INSERT INTO versions (product_id, value) VALUES (?,?)',
- undef, $product_id, $version);
+ my ($product_id, $version) = @_;
+ my $dbh = Bugzilla->dbh;
+ print "Fixing duplicate version $version in product_id $product_id...\n";
+ $dbh->do('DELETE FROM versions WHERE product_id = ? AND value = ?',
+ undef, $product_id, $version);
+ $dbh->do('INSERT INTO versions (product_id, value) VALUES (?,?)',
+ undef, $product_id, $version);
}
sub _add_versions_product_id_index {
- my $dbh = Bugzilla->dbh;
- if (!$dbh->bz_index_info('versions', 'versions_product_id_idx')) {
- my $dup_versions = $dbh->selectall_arrayref(
- 'SELECT product_id, value FROM versions
- GROUP BY product_id, value HAVING COUNT(value) > 1', {Slice=>{}});
- foreach my $dup_version (@$dup_versions) {
- _de_dup_version($dup_version->{product_id}, $dup_version->{value});
- }
-
- $dbh->bz_add_index('versions', 'versions_product_id_idx',
- {TYPE => 'UNIQUE', FIELDS => [qw(product_id value)]});
+ my $dbh = Bugzilla->dbh;
+ if (!$dbh->bz_index_info('versions', 'versions_product_id_idx')) {
+ my $dup_versions = $dbh->selectall_arrayref(
+ 'SELECT product_id, value FROM versions
+ GROUP BY product_id, value HAVING COUNT(value) > 1', {Slice => {}}
+ );
+ foreach my $dup_version (@$dup_versions) {
+ _de_dup_version($dup_version->{product_id}, $dup_version->{value});
}
+
+ $dbh->bz_add_index('versions', 'versions_product_id_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(product_id value)]});
+ }
}
sub _fix_whine_queries_title_and_op_sys_value {
- my $dbh = Bugzilla->dbh;
- if (!exists $dbh->bz_column_info('whine_queries', 'title')->{DEFAULT}) {
- # The below change actually has nothing to do with the whine_queries
- # change, it just has to be contained within a schema change so that
- # it doesn't run every time we run checksetup.
-
- # Old Bugzillas have "other" as an OS choice, new ones have "Other"
- # (capital O).
- print "Setting any 'other' op_sys to 'Other'...\n";
- $dbh->do('UPDATE op_sys SET value = ? WHERE value = ?',
- undef, "Other", "other");
- $dbh->do('UPDATE bugs SET op_sys = ? WHERE op_sys = ?',
- undef, "Other", "other");
- if (Bugzilla->params->{'defaultopsys'} eq 'other') {
- # We can't actually fix the param here, because WriteParams() will
- # make $datadir/params.json unwriteable to the webservergroup.
- # It's too much of an ugly hack to copy the permission-fixing code
- # down to here. (It would create more potential future bugs than
- # it would solve problems.)
- print "WARNING: Your 'defaultopsys' param is set to 'other', but"
- . " Bugzilla now\n"
- . " uses 'Other' (capital O).\n";
- }
-
- # Add a DEFAULT to whine_queries stuff so that editwhines.cgi
- # works on PostgreSQL.
- $dbh->bz_alter_column('whine_queries', 'title', {TYPE => 'varchar(128)',
- NOTNULL => 1, DEFAULT => "''"});
+ my $dbh = Bugzilla->dbh;
+ if (!exists $dbh->bz_column_info('whine_queries', 'title')->{DEFAULT}) {
+
+ # The below change actually has nothing to do with the whine_queries
+ # change, it just has to be contained within a schema change so that
+ # it doesn't run every time we run checksetup.
+
+ # Old Bugzillas have "other" as an OS choice, new ones have "Other"
+ # (capital O).
+ print "Setting any 'other' op_sys to 'Other'...\n";
+ $dbh->do('UPDATE op_sys SET value = ? WHERE value = ?', undef, "Other",
+ "other");
+ $dbh->do('UPDATE bugs SET op_sys = ? WHERE op_sys = ?', undef, "Other",
+ "other");
+ if (Bugzilla->params->{'defaultopsys'} eq 'other') {
+
+ # We can't actually fix the param here, because WriteParams() will
+ # make $datadir/params.json unwriteable to the webservergroup.
+ # It's too much of an ugly hack to copy the permission-fixing code
+ # down to here. (It would create more potential future bugs than
+ # it would solve problems.)
+ print "WARNING: Your 'defaultopsys' param is set to 'other', but"
+ . " Bugzilla now\n"
+ . " uses 'Other' (capital O).\n";
}
+
+ # Add a DEFAULT to whine_queries stuff so that editwhines.cgi
+ # works on PostgreSQL.
+ $dbh->bz_alter_column('whine_queries', 'title',
+ {TYPE => 'varchar(128)', NOTNULL => 1, DEFAULT => "''"});
+ }
}
sub _fix_attachments_submitter_id_idx {
- my $dbh = Bugzilla->dbh;
- # 2005-06-29 bugreport@peshkin.net, bug 299156
- if ($dbh->bz_index_info('attachments', 'attachments_submitter_id_idx')
- && (scalar(@{$dbh->bz_index_info('attachments',
- 'attachments_submitter_id_idx'
- )->{FIELDS}}) < 2))
- {
- $dbh->bz_drop_index('attachments', 'attachments_submitter_id_idx');
- }
- $dbh->bz_add_index('attachments', 'attachments_submitter_id_idx',
- [qw(submitter_id bug_id)]);
+ my $dbh = Bugzilla->dbh;
+
+ # 2005-06-29 bugreport@peshkin.net, bug 299156
+ if (
+ $dbh->bz_index_info('attachments', 'attachments_submitter_id_idx')
+ && (
+ scalar(@{
+ $dbh->bz_index_info('attachments', 'attachments_submitter_id_idx')->{FIELDS}
+ }) < 2
+ )
+ )
+ {
+ $dbh->bz_drop_index('attachments', 'attachments_submitter_id_idx');
+ }
+ $dbh->bz_add_index('attachments', 'attachments_submitter_id_idx',
+ [qw(submitter_id bug_id)]);
}
sub _copy_attachments_thedata_to_attach_data {
- my $dbh = Bugzilla->dbh;
- # 2005-08-25 - bugreport@peshkin.net - Bug 305333
- if ($dbh->bz_column_info("attachments", "thedata")) {
- print "Migrating attachment data to its own table...\n";
- print "(This may take a very long time)\n";
- $dbh->do("INSERT INTO attach_data (id, thedata)
- SELECT attach_id, thedata FROM attachments");
- $dbh->bz_drop_column("attachments", "thedata");
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 2005-08-25 - bugreport@peshkin.net - Bug 305333
+ if ($dbh->bz_column_info("attachments", "thedata")) {
+ print "Migrating attachment data to its own table...\n";
+ print "(This may take a very long time)\n";
+ $dbh->do(
+ "INSERT INTO attach_data (id, thedata)
+ SELECT attach_id, thedata FROM attachments"
+ );
+ $dbh->bz_drop_column("attachments", "thedata");
+ }
}
sub _fix_broken_all_closed_series {
- my $dbh = Bugzilla->dbh;
-
- # 2005-11-26 - wurblzap@gmail.com - Bug 300473
- # Repair broken automatically generated series queries for non-open bugs.
- my $broken_series_indicator =
- 'field0-0-0=resolution&type0-0-0=notequals&value0-0-0=---';
- my $broken_nonopen_series =
- $dbh->selectall_arrayref("SELECT series_id, query FROM series
- WHERE query LIKE '$broken_series_indicator%'");
- if (@$broken_nonopen_series) {
- print 'Repairing broken series...';
- my $sth_nuke =
- $dbh->prepare('DELETE FROM series_data WHERE series_id = ?');
- # This statement is used to repair a series by replacing the broken
- # query with the correct one.
- my $sth_repair =
- $dbh->prepare('UPDATE series SET query = ? WHERE series_id = ?');
- # The corresponding series for open bugs look like one of these two
- # variations (bug 225687 changed the order of bug states).
- # This depends on the set of bug states representing open bugs not
- # to have changed since series creation.
- my $open_bugs_query_base_old =
- join("&", map { "bug_status=" . url_quote($_) }
- ('UNCONFIRMED', 'NEW', 'ASSIGNED', 'REOPENED'));
- my $open_bugs_query_base_new =
- join("&", map { "bug_status=" . url_quote($_) }
- ('NEW', 'REOPENED', 'ASSIGNED', 'UNCONFIRMED'));
- my $sth_openbugs_series =
- $dbh->prepare("SELECT series_id FROM series WHERE query IN (?, ?)");
- # Statement to find the series which has collected the most data.
- my $sth_data_collected =
- $dbh->prepare('SELECT count(*) FROM series_data
- WHERE series_id = ?');
- # Statement to select a broken non-open bugs count data entry.
- my $sth_select_broken_nonopen_data =
- $dbh->prepare('SELECT series_date, series_value FROM series_data' .
- ' WHERE series_id = ?');
- # Statement to select an open bugs count data entry.
- my $sth_select_open_data =
- $dbh->prepare('SELECT series_value FROM series_data' .
- ' WHERE series_id = ? AND series_date = ?');
- # Statement to fix a broken non-open bugs count data entry.
- my $sth_fix_broken_nonopen_data =
- $dbh->prepare('UPDATE series_data SET series_value = ?' .
- ' WHERE series_id = ? AND series_date = ?');
- # Statement to delete an unfixable broken non-open bugs count data
- # entry.
- my $sth_delete_broken_nonopen_data =
- $dbh->prepare('DELETE FROM series_data' .
- ' WHERE series_id = ? AND series_date = ?');
- foreach (@$broken_nonopen_series) {
- my ($broken_series_id, $nonopen_bugs_query) = @$_;
-
- # Determine the product-and-component part of the query.
- if ($nonopen_bugs_query =~ /^$broken_series_indicator(.*)$/) {
- my $prodcomp = $1;
-
- # If there is more than one series for the corresponding
- # open-bugs series, we pick the one with the most data,
- # which should be the one which was generated on creation.
- # It's a pity we can't do subselects.
- $sth_openbugs_series->execute(
- $open_bugs_query_base_old . $prodcomp,
- $open_bugs_query_base_new . $prodcomp);
-
- my ($found_open_series_id, $datacount) = (undef, -1);
- foreach my $open_ser_id ($sth_openbugs_series->fetchrow_array) {
- $sth_data_collected->execute($open_ser_id);
- my ($this_datacount) = $sth_data_collected->fetchrow_array;
- if ($this_datacount > $datacount) {
- $datacount = $this_datacount;
- $found_open_series_id = $open_ser_id;
- }
- }
-
- if ($found_open_series_id) {
- # Move along corrupted series data and correct it. The
- # corruption consists of it being the number of all bugs
- # instead of the number of non-open bugs, so we calculate
- # the correct count by subtracting the number of open bugs.
- # If there is no corresponding open-bugs count for some
- # reason (shouldn't happen), we drop the data entry.
- print " $broken_series_id...";
- $sth_select_broken_nonopen_data->execute($broken_series_id);
- while (my $rowref =
- $sth_select_broken_nonopen_data->fetchrow_arrayref)
- {
- my ($date, $broken_value) = @$rowref;
- my ($openbugs_value) =
- $dbh->selectrow_array($sth_select_open_data, undef,
- $found_open_series_id, $date);
- if (defined($openbugs_value)) {
- $sth_fix_broken_nonopen_data->execute
- ($broken_value - $openbugs_value,
- $broken_series_id, $date);
- }
- else {
- print <<EOT;
+ my $dbh = Bugzilla->dbh;
+
+ # 2005-11-26 - wurblzap@gmail.com - Bug 300473
+ # Repair broken automatically generated series queries for non-open bugs.
+ my $broken_series_indicator
+ = 'field0-0-0=resolution&type0-0-0=notequals&value0-0-0=---';
+ my $broken_nonopen_series = $dbh->selectall_arrayref(
+ "SELECT series_id, query FROM series
+ WHERE query LIKE '$broken_series_indicator%'"
+ );
+ if (@$broken_nonopen_series) {
+ print 'Repairing broken series...';
+ my $sth_nuke = $dbh->prepare('DELETE FROM series_data WHERE series_id = ?');
+
+ # This statement is used to repair a series by replacing the broken
+ # query with the correct one.
+ my $sth_repair
+ = $dbh->prepare('UPDATE series SET query = ? WHERE series_id = ?');
+
+ # The corresponding series for open bugs look like one of these two
+ # variations (bug 225687 changed the order of bug states).
+ # This depends on the set of bug states representing open bugs not
+ # to have changed since series creation.
+ my $open_bugs_query_base_old = join("&",
+ map { "bug_status=" . url_quote($_) }
+ ('UNCONFIRMED', 'NEW', 'ASSIGNED', 'REOPENED'));
+ my $open_bugs_query_base_new = join("&",
+ map { "bug_status=" . url_quote($_) }
+ ('NEW', 'REOPENED', 'ASSIGNED', 'UNCONFIRMED'));
+ my $sth_openbugs_series
+ = $dbh->prepare("SELECT series_id FROM series WHERE query IN (?, ?)");
+
+ # Statement to find the series which has collected the most data.
+ my $sth_data_collected = $dbh->prepare(
+ 'SELECT count(*) FROM series_data
+ WHERE series_id = ?'
+ );
+
+ # Statement to select a broken non-open bugs count data entry.
+ my $sth_select_broken_nonopen_data = $dbh->prepare(
+ 'SELECT series_date, series_value FROM series_data' . ' WHERE series_id = ?');
+
+ # Statement to select an open bugs count data entry.
+ my $sth_select_open_data = $dbh->prepare('SELECT series_value FROM series_data'
+ . ' WHERE series_id = ? AND series_date = ?');
+
+ # Statement to fix a broken non-open bugs count data entry.
+ my $sth_fix_broken_nonopen_data
+ = $dbh->prepare('UPDATE series_data SET series_value = ?'
+ . ' WHERE series_id = ? AND series_date = ?');
+
+ # Statement to delete an unfixable broken non-open bugs count data
+ # entry.
+ my $sth_delete_broken_nonopen_data = $dbh->prepare(
+ 'DELETE FROM series_data' . ' WHERE series_id = ? AND series_date = ?');
+ foreach (@$broken_nonopen_series) {
+ my ($broken_series_id, $nonopen_bugs_query) = @$_;
+
+ # Determine the product-and-component part of the query.
+ if ($nonopen_bugs_query =~ /^$broken_series_indicator(.*)$/) {
+ my $prodcomp = $1;
+
+ # If there is more than one series for the corresponding
+ # open-bugs series, we pick the one with the most data,
+ # which should be the one which was generated on creation.
+ # It's a pity we can't do subselects.
+ $sth_openbugs_series->execute($open_bugs_query_base_old . $prodcomp,
+ $open_bugs_query_base_new . $prodcomp);
+
+ my ($found_open_series_id, $datacount) = (undef, -1);
+ foreach my $open_ser_id ($sth_openbugs_series->fetchrow_array) {
+ $sth_data_collected->execute($open_ser_id);
+ my ($this_datacount) = $sth_data_collected->fetchrow_array;
+ if ($this_datacount > $datacount) {
+ $datacount = $this_datacount;
+ $found_open_series_id = $open_ser_id;
+ }
+ }
+
+ if ($found_open_series_id) {
+
+ # Move along corrupted series data and correct it. The
+ # corruption consists of it being the number of all bugs
+ # instead of the number of non-open bugs, so we calculate
+ # the correct count by subtracting the number of open bugs.
+ # If there is no corresponding open-bugs count for some
+ # reason (shouldn't happen), we drop the data entry.
+ print " $broken_series_id...";
+ $sth_select_broken_nonopen_data->execute($broken_series_id);
+ while (my $rowref = $sth_select_broken_nonopen_data->fetchrow_arrayref) {
+ my ($date, $broken_value) = @$rowref;
+ my ($openbugs_value)
+ = $dbh->selectrow_array($sth_select_open_data, undef, $found_open_series_id,
+ $date);
+ if (defined($openbugs_value)) {
+ $sth_fix_broken_nonopen_data->execute($broken_value - $openbugs_value,
+ $broken_series_id, $date);
+ }
+ else {
+ print <<EOT;
WARNING - During repairs of series $broken_series_id, the irreparable data
entry for date $date was encountered and is being deleted.
Continuing repairs...
EOT
- $sth_delete_broken_nonopen_data->execute
- ($broken_series_id, $date);
- }
- }
-
- # Fix the broken query so that it collects correct data
- # in the future.
- $nonopen_bugs_query =~
- s/^$broken_series_indicator/field0-0-0=resolution&type0-0-0=regexp&value0-0-0=./;
- $sth_repair->execute($nonopen_bugs_query,
- $broken_series_id);
- }
- else {
- print <<EOT;
+ $sth_delete_broken_nonopen_data->execute($broken_series_id, $date);
+ }
+ }
+
+ # Fix the broken query so that it collects correct data
+ # in the future.
+ $nonopen_bugs_query
+ =~ s/^$broken_series_indicator/field0-0-0=resolution&type0-0-0=regexp&value0-0-0=./;
+ $sth_repair->execute($nonopen_bugs_query, $broken_series_id);
+ }
+ else {
+ print <<EOT;
WARNING - Series $broken_series_id was meant to collect non-open bug
counts, but it has counted all bugs instead. It cannot be repaired
@@ -2733,518 +2935,575 @@ series $broken_series_id manually
Continuing repairs...
EOT
- } # if ($found_open_series_id)
- } # if ($nonopen_bugs_query =~
- } # foreach (@$broken_nonopen_series)
- print " done.\n";
- } # if (@$broken_nonopen_series)
+ } # if ($found_open_series_id)
+ } # if ($nonopen_bugs_query =~
+ } # foreach (@$broken_nonopen_series)
+ print " done.\n";
+ } # if (@$broken_nonopen_series)
}
-# This needs to happen at two times: when we upgrade from 2.16 (thus creating
+# This needs to happen at two times: when we upgrade from 2.16 (thus creating
# user_group_map), and when we kill derived gruops in the DB.
sub _rederive_regex_groups {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- my $regex_groups_exist = $dbh->selectrow_array(
- "SELECT 1 FROM groups WHERE userregexp = '' " . $dbh->sql_limit(1));
- return if !$regex_groups_exist;
+ my $regex_groups_exist = $dbh->selectrow_array(
+ "SELECT 1 FROM groups WHERE userregexp = '' " . $dbh->sql_limit(1));
+ return if !$regex_groups_exist;
- my $regex_derivations = $dbh->selectrow_array(
- 'SELECT 1 FROM user_group_map WHERE grant_type = ' . GRANT_REGEXP
- . ' ' . $dbh->sql_limit(1));
- return if $regex_derivations;
+ my $regex_derivations
+ = $dbh->selectrow_array('SELECT 1 FROM user_group_map WHERE grant_type = '
+ . GRANT_REGEXP . ' '
+ . $dbh->sql_limit(1));
+ return if $regex_derivations;
- print "Deriving regex group memberships...\n";
+ print "Deriving regex group memberships...\n";
- # Re-evaluate all regexps, to keep them up-to-date.
- my $sth = $dbh->prepare(
- "SELECT profiles.userid, profiles.login_name, groups.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");
+ 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_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
+ my $sth_del = $dbh->prepare(
+ "DELETE FROM user_group_map
WHERE user_id = ? AND group_id = ? AND isbless = 0
- AND grant_type = " . GRANT_REGEXP);
+ 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;
- }
+ $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;
+ }
+ }
}
sub _clean_control_characters_from_short_desc {
- my $dbh = Bugzilla->dbh;
-
- # Fixup for Bug 101380
- # "Newlines, nulls, leading/trailing spaces are getting into summaries"
-
- my $controlchar_bugs =
- $dbh->selectall_arrayref("SELECT short_desc, bug_id FROM bugs WHERE " .
- $dbh->sql_regexp('short_desc', "'[[:cntrl:]]'"));
- if (scalar(@$controlchar_bugs)) {
- my $msg = 'Cleaning control characters from bug summaries...';
- my $found = 0;
- foreach (@$controlchar_bugs) {
- my ($short_desc, $bug_id) = @$_;
- my $clean_short_desc = clean_text($short_desc);
- if ($clean_short_desc ne $short_desc) {
- print $msg if !$found;
- $found = 1;
- print " $bug_id...";
- $dbh->do("UPDATE bugs SET short_desc = ? WHERE bug_id = ?",
- undef, $clean_short_desc, $bug_id);
- }
- }
- print " done.\n" if $found;
+ my $dbh = Bugzilla->dbh;
+
+ # Fixup for Bug 101380
+ # "Newlines, nulls, leading/trailing spaces are getting into summaries"
+
+ my $controlchar_bugs
+ = $dbh->selectall_arrayref("SELECT short_desc, bug_id FROM bugs WHERE "
+ . $dbh->sql_regexp('short_desc', "'[[:cntrl:]]'"));
+ if (scalar(@$controlchar_bugs)) {
+ my $msg = 'Cleaning control characters from bug summaries...';
+ my $found = 0;
+ foreach (@$controlchar_bugs) {
+ my ($short_desc, $bug_id) = @$_;
+ my $clean_short_desc = clean_text($short_desc);
+ if ($clean_short_desc ne $short_desc) {
+ print $msg if !$found;
+ $found = 1;
+ print " $bug_id...";
+ $dbh->do("UPDATE bugs SET short_desc = ? WHERE bug_id = ?",
+ undef, $clean_short_desc, $bug_id);
+ }
}
+ print " done.\n" if $found;
+ }
}
sub _stop_storing_inactive_flags {
- my $dbh = Bugzilla->dbh;
- # 2006-03-02 LpSolit@gmail.com - Bug 322285
- # Do not store inactive flags in the DB anymore.
- if ($dbh->bz_column_info('flags', 'id')->{'TYPE'} eq 'INT3') {
- # We first have to remove all existing inactive flags.
- if ($dbh->bz_column_info('flags', 'is_active')) {
- $dbh->do('DELETE FROM flags WHERE is_active = 0');
- }
+ my $dbh = Bugzilla->dbh;
- # Now we convert the id column to the auto_increment format.
- $dbh->bz_alter_column('flags', 'id',
- {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ # 2006-03-02 LpSolit@gmail.com - Bug 322285
+ # Do not store inactive flags in the DB anymore.
+ if ($dbh->bz_column_info('flags', 'id')->{'TYPE'} eq 'INT3') {
- # And finally, we remove the is_active column.
- $dbh->bz_drop_column('flags', 'is_active');
+ # We first have to remove all existing inactive flags.
+ if ($dbh->bz_column_info('flags', 'is_active')) {
+ $dbh->do('DELETE FROM flags WHERE is_active = 0');
}
+
+ # Now we convert the id column to the auto_increment format.
+ $dbh->bz_alter_column('flags', 'id',
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+
+ # And finally, we remove the is_active column.
+ $dbh->bz_drop_column('flags', 'is_active');
+ }
}
sub _change_short_desc_from_mediumtext_to_varchar {
- my $dbh = Bugzilla->dbh;
- # short_desc should not be a mediumtext, fix anything longer than 255 chars.
- if($dbh->bz_column_info('bugs', 'short_desc')->{TYPE} eq 'MEDIUMTEXT') {
- # Move extremely long summaries into a comment ("from" the Reporter),
- # and then truncate the summary.
- my $long_summary_bugs = $dbh->selectall_arrayref(
- 'SELECT bug_id, short_desc, reporter
- FROM bugs WHERE CHAR_LENGTH(short_desc) > 255');
-
- if (@$long_summary_bugs) {
- print "\n", install_string('update_summary_truncated');
- my $comment_sth = $dbh->prepare(
- 'INSERT INTO longdescs (bug_id, who, thetext, bug_when)
- VALUES (?, ?, ?, NOW())');
- my $desc_sth = $dbh->prepare('UPDATE bugs SET short_desc = ?
- WHERE bug_id = ?');
- my @affected_bugs;
- foreach my $bug (@$long_summary_bugs) {
- my ($bug_id, $summary, $reporter_id) = @$bug;
- my $summary_comment =
- install_string('update_summary_truncate_comment',
- { summary => $summary });
- $comment_sth->execute($bug_id, $reporter_id, $summary_comment);
- my $short_summary = substr($summary, 0, 252) . "...";
- $desc_sth->execute($short_summary, $bug_id);
- push(@affected_bugs, $bug_id);
- }
- print join(', ', @affected_bugs) . "\n\n";
- }
+ my $dbh = Bugzilla->dbh;
+
+ # short_desc should not be a mediumtext, fix anything longer than 255 chars.
+ if ($dbh->bz_column_info('bugs', 'short_desc')->{TYPE} eq 'MEDIUMTEXT') {
+
+ # Move extremely long summaries into a comment ("from" the Reporter),
+ # and then truncate the summary.
+ my $long_summary_bugs = $dbh->selectall_arrayref(
+ 'SELECT bug_id, short_desc, reporter
+ FROM bugs WHERE CHAR_LENGTH(short_desc) > 255'
+ );
- $dbh->bz_alter_column('bugs', 'short_desc', {TYPE => 'varchar(255)',
- NOTNULL => 1});
+ if (@$long_summary_bugs) {
+ print "\n", install_string('update_summary_truncated');
+ my $comment_sth = $dbh->prepare(
+ 'INSERT INTO longdescs (bug_id, who, thetext, bug_when)
+ VALUES (?, ?, ?, NOW())'
+ );
+ my $desc_sth = $dbh->prepare(
+ 'UPDATE bugs SET short_desc = ?
+ WHERE bug_id = ?'
+ );
+ my @affected_bugs;
+ foreach my $bug (@$long_summary_bugs) {
+ my ($bug_id, $summary, $reporter_id) = @$bug;
+ my $summary_comment
+ = install_string('update_summary_truncate_comment', {summary => $summary});
+ $comment_sth->execute($bug_id, $reporter_id, $summary_comment);
+ my $short_summary = substr($summary, 0, 252) . "...";
+ $desc_sth->execute($short_summary, $bug_id);
+ push(@affected_bugs, $bug_id);
+ }
+ print join(', ', @affected_bugs) . "\n\n";
}
+
+ $dbh->bz_alter_column('bugs', 'short_desc',
+ {TYPE => 'varchar(255)', NOTNULL => 1});
+ }
}
sub _move_namedqueries_linkinfooter_to_its_own_table {
- my $dbh = Bugzilla->dbh;
- if ($dbh->bz_column_info("namedqueries", "linkinfooter")) {
- # Move link-in-footer information into a table of its own.
- my $sth_read = $dbh->prepare('SELECT id, userid
+ my $dbh = Bugzilla->dbh;
+ if ($dbh->bz_column_info("namedqueries", "linkinfooter")) {
+
+ # Move link-in-footer information into a table of its own.
+ my $sth_read = $dbh->prepare(
+ 'SELECT id, userid
FROM namedqueries
- WHERE linkinfooter = 1');
- my $sth_write = $dbh->prepare('INSERT INTO namedqueries_link_in_footer
- (namedquery_id, user_id) VALUES (?, ?)');
- $sth_read->execute();
- while (my ($id, $userid) = $sth_read->fetchrow_array()) {
- $sth_write->execute($id, $userid);
- }
- $dbh->bz_drop_column("namedqueries", "linkinfooter");
+ WHERE linkinfooter = 1'
+ );
+ my $sth_write = $dbh->prepare(
+ 'INSERT INTO namedqueries_link_in_footer
+ (namedquery_id, user_id) VALUES (?, ?)'
+ );
+ $sth_read->execute();
+ while (my ($id, $userid) = $sth_read->fetchrow_array()) {
+ $sth_write->execute($id, $userid);
}
+ $dbh->bz_drop_column("namedqueries", "linkinfooter");
+ }
}
sub _add_classifications_sortkey {
- my $dbh = Bugzilla->dbh;
- # 2006-07-07 olav@bkor.dhs.org - Bug 277377
- # Add a sortkey to the classifications
- if (!$dbh->bz_column_info('classifications', 'sortkey')) {
- $dbh->bz_add_column('classifications', 'sortkey',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
-
- my $class_ids = $dbh->selectcol_arrayref(
- 'SELECT id FROM classifications ORDER BY name');
- my $sth = $dbh->prepare('UPDATE classifications SET sortkey = ? ' .
- 'WHERE id = ?');
- my $sortkey = 0;
- foreach my $class_id (@$class_ids) {
- $sth->execute($sortkey, $class_id);
- $sortkey += 100;
- }
+ my $dbh = Bugzilla->dbh;
+
+ # 2006-07-07 olav@bkor.dhs.org - Bug 277377
+ # Add a sortkey to the classifications
+ if (!$dbh->bz_column_info('classifications', 'sortkey')) {
+ $dbh->bz_add_column('classifications', 'sortkey',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+
+ my $class_ids
+ = $dbh->selectcol_arrayref('SELECT id FROM classifications ORDER BY name');
+ my $sth
+ = $dbh->prepare('UPDATE classifications SET sortkey = ? ' . 'WHERE id = ?');
+ my $sortkey = 0;
+ foreach my $class_id (@$class_ids) {
+ $sth->execute($sortkey, $class_id);
+ $sortkey += 100;
}
+ }
}
sub _move_data_nomail_into_db {
- my $dbh = Bugzilla->dbh;
- my $datadir = bz_locations()->{'datadir'};
- # 2006-07-14 karl@kornel.name - Bug 100953
- # If a nomail file exists, move its contents into the DB
- $dbh->bz_add_column('profiles', 'disable_mail',
- { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' });
- if (-e "$datadir/nomail") {
- # We have a data/nomail file, read it in and delete it
- my %nomail;
- print "Found a data/nomail file. Moving nomail entries into DB...\n";
- my $nomail_file = new IO::File("$datadir/nomail", 'r');
- while (<$nomail_file>) {
- $nomail{trim($_)} = 1;
- }
- $nomail_file->close;
+ my $dbh = Bugzilla->dbh;
+ my $datadir = bz_locations()->{'datadir'};
+
+ # 2006-07-14 karl@kornel.name - Bug 100953
+ # If a nomail file exists, move its contents into the DB
+ $dbh->bz_add_column('profiles', 'disable_mail',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ if (-e "$datadir/nomail") {
+
+ # We have a data/nomail file, read it in and delete it
+ my %nomail;
+ print "Found a data/nomail file. Moving nomail entries into DB...\n";
+ my $nomail_file = new IO::File("$datadir/nomail", 'r');
+ while (<$nomail_file>) {
+ $nomail{trim($_)} = 1;
+ }
+ $nomail_file->close;
- # Go through each entry read. If a user exists, set disable_mail.
- my $query = $dbh->prepare('UPDATE profiles
+ # Go through each entry read. If a user exists, set disable_mail.
+ my $query = $dbh->prepare(
+ 'UPDATE profiles
SET disable_mail = 1
- WHERE userid = ?');
- foreach my $user_to_check (keys %nomail) {
- my $uid = $dbh->selectrow_array(
- 'SELECT userid FROM profiles WHERE login_name = ?',
- undef, $user_to_check);
- next if !$uid;
- print "\tDisabling email for user $user_to_check\n";
- $query->execute($uid);
- delete $nomail{$user_to_check};
- }
-
- # If there are any nomail entries remaining, move them to nomail.bad
- # and say something to the user.
- if (scalar(keys %nomail)) {
- print "\n", install_string('update_nomail_bad',
- { data => $datadir }), "\n";
- my $nomail_bad = new IO::File("$datadir/nomail.bad", '>>');
- foreach my $unknown_user (keys %nomail) {
- print "\t$unknown_user\n";
- print $nomail_bad "$unknown_user\n";
- delete $nomail{$unknown_user};
- }
- $nomail_bad->close;
- print "\n";
- }
+ WHERE userid = ?'
+ );
+ foreach my $user_to_check (keys %nomail) {
+ my $uid
+ = $dbh->selectrow_array('SELECT userid FROM profiles WHERE login_name = ?',
+ undef, $user_to_check);
+ next if !$uid;
+ print "\tDisabling email for user $user_to_check\n";
+ $query->execute($uid);
+ delete $nomail{$user_to_check};
+ }
- # Now that we don't need it, get rid of the nomail file.
- unlink "$datadir/nomail";
+ # If there are any nomail entries remaining, move them to nomail.bad
+ # and say something to the user.
+ if (scalar(keys %nomail)) {
+ print "\n", install_string('update_nomail_bad', {data => $datadir}), "\n";
+ my $nomail_bad = new IO::File("$datadir/nomail.bad", '>>');
+ foreach my $unknown_user (keys %nomail) {
+ print "\t$unknown_user\n";
+ print $nomail_bad "$unknown_user\n";
+ delete $nomail{$unknown_user};
+ }
+ $nomail_bad->close;
+ print "\n";
}
+
+ # Now that we don't need it, get rid of the nomail file.
+ unlink "$datadir/nomail";
+ }
}
sub _update_longdescs_who_index {
- my $dbh = Bugzilla->dbh;
- # When doing a search on who posted a comment, longdescs is joined
- # against the bugs table. So we need an index on both of these,
- # not just on "who".
- my $who_index = $dbh->bz_index_info('longdescs', 'longdescs_who_idx');
- if (!$who_index || scalar @{$who_index->{FIELDS}} == 1) {
- # If the index doesn't exist, this will harmlessly do nothing.
- $dbh->bz_drop_index('longdescs', 'longdescs_who_idx');
- $dbh->bz_add_index('longdescs', 'longdescs_who_idx', [qw(who bug_id)]);
- }
+ my $dbh = Bugzilla->dbh;
+
+ # When doing a search on who posted a comment, longdescs is joined
+ # against the bugs table. So we need an index on both of these,
+ # not just on "who".
+ my $who_index = $dbh->bz_index_info('longdescs', 'longdescs_who_idx');
+ if (!$who_index || scalar @{$who_index->{FIELDS}} == 1) {
+
+ # If the index doesn't exist, this will harmlessly do nothing.
+ $dbh->bz_drop_index('longdescs', 'longdescs_who_idx');
+ $dbh->bz_add_index('longdescs', 'longdescs_who_idx', [qw(who bug_id)]);
+ }
}
sub _fix_uppercase_custom_field_names {
- # Before the final release of 3.0, custom fields could be
- # created with mixed-case names.
- my $dbh = Bugzilla->dbh;
- my $fields = $dbh->selectall_arrayref(
- 'SELECT name, type FROM fielddefs WHERE custom = 1');
- foreach my $row (@$fields) {
- my ($name, $type) = @$row;
- if ($name ne lc($name)) {
- $dbh->bz_rename_column('bugs', $name, lc($name));
- $dbh->bz_rename_table($name, lc($name))
- if $type == FIELD_TYPE_SINGLE_SELECT;
- $dbh->do('UPDATE fielddefs SET name = ? WHERE name = ?',
- undef, lc($name), $name);
- }
+
+ # Before the final release of 3.0, custom fields could be
+ # created with mixed-case names.
+ my $dbh = Bugzilla->dbh;
+ my $fields = $dbh->selectall_arrayref(
+ 'SELECT name, type FROM fielddefs WHERE custom = 1');
+ foreach my $row (@$fields) {
+ my ($name, $type) = @$row;
+ if ($name ne lc($name)) {
+ $dbh->bz_rename_column('bugs', $name, lc($name));
+ $dbh->bz_rename_table($name, lc($name)) if $type == FIELD_TYPE_SINGLE_SELECT;
+ $dbh->do('UPDATE fielddefs SET name = ? WHERE name = ?',
+ undef, lc($name), $name);
}
+ }
}
sub _fix_uppercase_index_names {
- # We forgot to fix indexes in the above code.
- my $dbh = Bugzilla->dbh;
- my $fields = $dbh->selectcol_arrayref(
- 'SELECT name FROM fielddefs WHERE type = ? AND custom = 1',
- undef, FIELD_TYPE_SINGLE_SELECT);
- foreach my $field (@$fields) {
- my $indexes = $dbh->bz_table_indexes($field);
- foreach my $name (keys %$indexes) {
- next if $name eq lc($name);
- my $index = $indexes->{$name};
- # Lowercase the name and everything in the definition.
- my $new_name = lc($name);
- my @new_fields = map {lc($_)} @{$index->{FIELDS}};
- my $new_def = {FIELDS => \@new_fields, TYPE => $index->{TYPE}};
- $new_def = \@new_fields if !$index->{TYPE};
- $dbh->bz_drop_index($field, $name);
- $dbh->bz_add_index($field, $new_name, $new_def);
- }
+
+ # We forgot to fix indexes in the above code.
+ my $dbh = Bugzilla->dbh;
+ my $fields
+ = $dbh->selectcol_arrayref(
+ 'SELECT name FROM fielddefs WHERE type = ? AND custom = 1',
+ undef, FIELD_TYPE_SINGLE_SELECT);
+ foreach my $field (@$fields) {
+ my $indexes = $dbh->bz_table_indexes($field);
+ foreach my $name (keys %$indexes) {
+ next if $name eq lc($name);
+ my $index = $indexes->{$name};
+
+ # Lowercase the name and everything in the definition.
+ my $new_name = lc($name);
+ my @new_fields = map { lc($_) } @{$index->{FIELDS}};
+ my $new_def = {FIELDS => \@new_fields, TYPE => $index->{TYPE}};
+ $new_def = \@new_fields if !$index->{TYPE};
+ $dbh->bz_drop_index($field, $name);
+ $dbh->bz_add_index($field, $new_name, $new_def);
}
+ }
}
sub _initialize_workflow_for_upgrade {
- my $old_params = shift;
- my $dbh = Bugzilla->dbh;
-
- $dbh->bz_add_column('bug_status', 'is_open',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
-
- # Till now, bug statuses were not customizable. Nevertheless, local
- # changes are possible and so we will try to respect these changes.
- # This means: get the status of bugs having a resolution different from ''
- # and mark these statuses as 'closed', even if some of these statuses are
- # expected to be open statuses. Bug statuses we have no information about
- # are left as 'open'.
- #
- # We append the default list of closed statuses *unless* we detect at least
- # one closed state in the DB (i.e. with is_open = 0). This would mean that
- # the DB has already been updated at least once and maybe the admin decided
- # that e.g. 'RESOLVED' is now an open state, in which case we don't want to
- # override this attribute. At least one bug status has to be a closed state
- # anyway (due to the 'duplicate_or_move_bug_status' parameter) so it's safe
- # to use this criteria.
- my $num_closed_states = $dbh->selectrow_array('SELECT COUNT(*) FROM bug_status
- WHERE is_open = 0');
-
- if (!$num_closed_states) {
- my @closed_statuses =
- @{$dbh->selectcol_arrayref('SELECT DISTINCT bug_status FROM bugs
- WHERE resolution != ?', undef, '')};
- @closed_statuses =
- map {$dbh->quote($_)} (@closed_statuses, qw(RESOLVED VERIFIED CLOSED));
-
- print "Marking closed bug statuses as such...\n";
- $dbh->do('UPDATE bug_status SET is_open = 0 WHERE value IN (' .
- join(', ', @closed_statuses) . ')');
- }
-
- # We only populate the workflow here if we're upgrading from a version
- # before 4.0 (which is where init_workflow was added). This was the
- # first schema change done for 4.0, so we check this.
- return if $dbh->bz_column_info('bugs_activity', 'comment_id');
-
- # Populate the status_workflow table. We do nothing if the table already
- # has entries. If all bug status transitions have been deleted, the
- # workflow will be restored to its default schema.
- my $count = $dbh->selectrow_array('SELECT COUNT(*) FROM status_workflow');
-
- if (!$count) {
- # Make sure the variables below are defined as
- # status_workflow.require_comment cannot be NULL.
- my $create = $old_params->{'commentoncreate'} || 0;
- my $confirm = $old_params->{'commentonconfirm'} || 0;
- my $accept = $old_params->{'commentonaccept'} || 0;
- my $resolve = $old_params->{'commentonresolve'} || 0;
- my $verify = $old_params->{'commentonverify'} || 0;
- my $close = $old_params->{'commentonclose'} || 0;
- my $reopen = $old_params->{'commentonreopen'} || 0;
- # This was till recently the only way to get back to NEW for
- # confirmed bugs, so we use this parameter here.
- my $reassign = $old_params->{'commentonreassign'} || 0;
-
- # This is the default workflow for upgrading installations.
- my @workflow = ([undef, 'UNCONFIRMED', $create],
- [undef, 'NEW', $create],
- [undef, 'ASSIGNED', $create],
- ['UNCONFIRMED', 'NEW', $confirm],
- ['UNCONFIRMED', 'ASSIGNED', $accept],
- ['UNCONFIRMED', 'RESOLVED', $resolve],
- ['NEW', 'ASSIGNED', $accept],
- ['NEW', 'RESOLVED', $resolve],
- ['ASSIGNED', 'NEW', $reassign],
- ['ASSIGNED', 'RESOLVED', $resolve],
- ['REOPENED', 'NEW', $reassign],
- ['REOPENED', 'ASSIGNED', $accept],
- ['REOPENED', 'RESOLVED', $resolve],
- ['RESOLVED', 'UNCONFIRMED', $reopen],
- ['RESOLVED', 'REOPENED', $reopen],
- ['RESOLVED', 'VERIFIED', $verify],
- ['RESOLVED', 'CLOSED', $close],
- ['VERIFIED', 'UNCONFIRMED', $reopen],
- ['VERIFIED', 'REOPENED', $reopen],
- ['VERIFIED', 'CLOSED', $close],
- ['CLOSED', 'UNCONFIRMED', $reopen],
- ['CLOSED', 'REOPENED', $reopen]);
-
- print "Now filling the 'status_workflow' table with valid bug status transitions...\n";
- my $sth_select = $dbh->prepare('SELECT id FROM bug_status WHERE value = ?');
- my $sth = $dbh->prepare('INSERT INTO status_workflow (old_status, new_status,
- require_comment) VALUES (?, ?, ?)');
-
- foreach my $transition (@workflow) {
- my ($from, $to);
- # If it's an initial state, there is no "old" value.
- $from = $dbh->selectrow_array($sth_select, undef, $transition->[0])
- if $transition->[0];
- $to = $dbh->selectrow_array($sth_select, undef, $transition->[1]);
- # If one of the bug statuses doesn't exist, the transition is invalid.
- next if (($transition->[0] && !$from) || !$to);
-
- $sth->execute($from, $to, $transition->[2] ? 1 : 0);
- }
- }
+ my $old_params = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_add_column('bug_status', 'is_open',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+
+ # Till now, bug statuses were not customizable. Nevertheless, local
+ # changes are possible and so we will try to respect these changes.
+ # This means: get the status of bugs having a resolution different from ''
+ # and mark these statuses as 'closed', even if some of these statuses are
+ # expected to be open statuses. Bug statuses we have no information about
+ # are left as 'open'.
+ #
+ # We append the default list of closed statuses *unless* we detect at least
+ # one closed state in the DB (i.e. with is_open = 0). This would mean that
+ # the DB has already been updated at least once and maybe the admin decided
+ # that e.g. 'RESOLVED' is now an open state, in which case we don't want to
+ # override this attribute. At least one bug status has to be a closed state
+ # anyway (due to the 'duplicate_or_move_bug_status' parameter) so it's safe
+ # to use this criteria.
+ my $num_closed_states = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM bug_status
+ WHERE is_open = 0'
+ );
+
+ if (!$num_closed_states) {
+ my @closed_statuses = @{
+ $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT bug_status FROM bugs
+ WHERE resolution != ?', undef, ''
+ )
+ };
+ @closed_statuses
+ = map { $dbh->quote($_) } (@closed_statuses, qw(RESOLVED VERIFIED CLOSED));
+
+ print "Marking closed bug statuses as such...\n";
+ $dbh->do('UPDATE bug_status SET is_open = 0 WHERE value IN ('
+ . join(', ', @closed_statuses)
+ . ')');
+ }
+
+ # We only populate the workflow here if we're upgrading from a version
+ # before 4.0 (which is where init_workflow was added). This was the
+ # first schema change done for 4.0, so we check this.
+ return if $dbh->bz_column_info('bugs_activity', 'comment_id');
+
+ # Populate the status_workflow table. We do nothing if the table already
+ # has entries. If all bug status transitions have been deleted, the
+ # workflow will be restored to its default schema.
+ my $count = $dbh->selectrow_array('SELECT COUNT(*) FROM status_workflow');
+
+ if (!$count) {
+
+ # Make sure the variables below are defined as
+ # status_workflow.require_comment cannot be NULL.
+ my $create = $old_params->{'commentoncreate'} || 0;
+ my $confirm = $old_params->{'commentonconfirm'} || 0;
+ my $accept = $old_params->{'commentonaccept'} || 0;
+ my $resolve = $old_params->{'commentonresolve'} || 0;
+ my $verify = $old_params->{'commentonverify'} || 0;
+ my $close = $old_params->{'commentonclose'} || 0;
+ my $reopen = $old_params->{'commentonreopen'} || 0;
+
+ # This was till recently the only way to get back to NEW for
+ # confirmed bugs, so we use this parameter here.
+ my $reassign = $old_params->{'commentonreassign'} || 0;
+
+ # This is the default workflow for upgrading installations.
+ my @workflow = (
+ [undef, 'UNCONFIRMED', $create],
+ [undef, 'NEW', $create],
+ [undef, 'ASSIGNED', $create],
+ ['UNCONFIRMED', 'NEW', $confirm],
+ ['UNCONFIRMED', 'ASSIGNED', $accept],
+ ['UNCONFIRMED', 'RESOLVED', $resolve],
+ ['NEW', 'ASSIGNED', $accept],
+ ['NEW', 'RESOLVED', $resolve],
+ ['ASSIGNED', 'NEW', $reassign],
+ ['ASSIGNED', 'RESOLVED', $resolve],
+ ['REOPENED', 'NEW', $reassign],
+ ['REOPENED', 'ASSIGNED', $accept],
+ ['REOPENED', 'RESOLVED', $resolve],
+ ['RESOLVED', 'UNCONFIRMED', $reopen],
+ ['RESOLVED', 'REOPENED', $reopen],
+ ['RESOLVED', 'VERIFIED', $verify],
+ ['RESOLVED', 'CLOSED', $close],
+ ['VERIFIED', 'UNCONFIRMED', $reopen],
+ ['VERIFIED', 'REOPENED', $reopen],
+ ['VERIFIED', 'CLOSED', $close],
+ ['CLOSED', 'UNCONFIRMED', $reopen],
+ ['CLOSED', 'REOPENED', $reopen]
+ );
- # Make sure the bug status used by the 'duplicate_or_move_bug_status'
- # parameter has all the required transitions set.
- my $dup_status = Bugzilla->params->{'duplicate_or_move_bug_status'};
- my $status_id = $dbh->selectrow_array(
- 'SELECT id FROM bug_status WHERE value = ?', undef, $dup_status);
- # There's a minor chance that this status isn't in the DB.
- $status_id || return;
+ print
+ "Now filling the 'status_workflow' table with valid bug status transitions...\n";
+ my $sth_select = $dbh->prepare('SELECT id FROM bug_status WHERE value = ?');
+ my $sth = $dbh->prepare(
+ 'INSERT INTO status_workflow (old_status, new_status,
+ require_comment) VALUES (?, ?, ?)'
+ );
- 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, $status_id);
+ foreach my $transition (@workflow) {
+ my ($from, $to);
+
+ # If it's an initial state, there is no "old" value.
+ $from = $dbh->selectrow_array($sth_select, undef, $transition->[0])
+ if $transition->[0];
+ $to = $dbh->selectrow_array($sth_select, undef, $transition->[1]);
- my $sth = $dbh->prepare('INSERT INTO status_workflow
- (old_status, new_status) VALUES (?, ?)');
+ # If one of the bug statuses doesn't exist, the transition is invalid.
+ next if (($transition->[0] && !$from) || !$to);
- foreach my $old_status_id (@$missing_statuses) {
- next if ($old_status_id == $status_id);
- $sth->execute($old_status_id, $status_id);
+ $sth->execute($from, $to, $transition->[2] ? 1 : 0);
}
+ }
+
+ # Make sure the bug status used by the 'duplicate_or_move_bug_status'
+ # parameter has all the required transitions set.
+ my $dup_status = Bugzilla->params->{'duplicate_or_move_bug_status'};
+ my $status_id
+ = $dbh->selectrow_array('SELECT id FROM bug_status WHERE value = ?',
+ undef, $dup_status);
+
+ # There's a minor chance that this status isn't in the DB.
+ $status_id || 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, $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 == $status_id);
+ $sth->execute($old_status_id, $status_id);
+ }
}
sub _make_lang_setting_dynamic {
- my $dbh = Bugzilla->dbh;
- my $count = $dbh->selectrow_array(q{SELECT 1 FROM setting
+ my $dbh = Bugzilla->dbh;
+ my $count = $dbh->selectrow_array(q{SELECT 1 FROM setting
WHERE name = 'lang'
- AND subclass IS NULL});
- if ($count) {
- $dbh->do(q{UPDATE setting SET subclass = 'Lang' WHERE name = 'lang'});
- $dbh->do(q{DELETE FROM setting_value WHERE name = 'lang'});
- }
+ AND subclass IS NULL}
+ );
+ if ($count) {
+ $dbh->do(q{UPDATE setting SET subclass = 'Lang' WHERE name = 'lang'});
+ $dbh->do(q{DELETE FROM setting_value WHERE name = 'lang'});
+ }
}
sub _fix_attachment_modification_date {
- my $dbh = Bugzilla->dbh;
- if (!$dbh->bz_column_info('attachments', 'modification_time')) {
- # Allow NULL values till the modification time has been set.
- $dbh->bz_add_column('attachments', 'modification_time', {TYPE => 'DATETIME'});
+ my $dbh = Bugzilla->dbh;
+ if (!$dbh->bz_column_info('attachments', 'modification_time')) {
- print "Setting the modification time for attachments...\n";
- $dbh->do('UPDATE attachments SET modification_time = creation_ts');
+ # Allow NULL values till the modification time has been set.
+ $dbh->bz_add_column('attachments', 'modification_time', {TYPE => 'DATETIME'});
- # Now force values to be always defined.
- $dbh->bz_alter_column('attachments', 'modification_time',
- {TYPE => 'DATETIME', NOTNULL => 1});
+ print "Setting the modification time for attachments...\n";
+ $dbh->do('UPDATE attachments SET modification_time = creation_ts');
- # Update the modification time for attachments which have been modified.
- my $attachments =
- $dbh->selectall_arrayref('SELECT attach_id, MAX(bug_when) FROM bugs_activity
- WHERE attach_id IS NOT NULL ' .
- $dbh->sql_group_by('attach_id'));
+ # Now force values to be always defined.
+ $dbh->bz_alter_column('attachments', 'modification_time',
+ {TYPE => 'DATETIME', NOTNULL => 1});
- my $sth = $dbh->prepare('UPDATE attachments SET modification_time = ?
- WHERE attach_id = ?');
- $sth->execute($_->[1], $_->[0]) foreach (@$attachments);
- }
- # We add this here to be sure to have the index being added, due to the original
- # patch omitting it.
- $dbh->bz_add_index('attachments', 'attachments_modification_time_idx',
- [qw(modification_time)]);
+ # Update the modification time for attachments which have been modified.
+ my $attachments = $dbh->selectall_arrayref(
+ 'SELECT attach_id, MAX(bug_when) FROM bugs_activity
+ WHERE attach_id IS NOT NULL '
+ . $dbh->sql_group_by('attach_id')
+ );
+
+ my $sth = $dbh->prepare(
+ 'UPDATE attachments SET modification_time = ?
+ WHERE attach_id = ?'
+ );
+ $sth->execute($_->[1], $_->[0]) foreach (@$attachments);
+ }
+
+ # We add this here to be sure to have the index being added, due to the original
+ # patch omitting it.
+ $dbh->bz_add_index('attachments', 'attachments_modification_time_idx',
+ [qw(modification_time)]);
}
sub _change_text_types {
- my $dbh = Bugzilla->dbh;
- return if
- $dbh->bz_column_info('namedqueries', 'query')->{TYPE} eq 'LONGTEXT';
- _check_content_length('attachments', 'mimetype', 255, 'attach_id');
- _check_content_length('fielddefs', 'description', 255, 'id');
- _check_content_length('attachments', 'description', 255, 'attach_id');
-
- $dbh->bz_alter_column('bugs', 'bug_file_loc',
- { TYPE => 'MEDIUMTEXT'});
- $dbh->bz_alter_column('longdescs', 'thetext',
- { TYPE => 'LONGTEXT', NOTNULL => 1 });
- $dbh->bz_alter_column('attachments', 'description',
- { TYPE => 'TINYTEXT', NOTNULL => 1 });
- $dbh->bz_alter_column('attachments', 'mimetype',
- { TYPE => 'TINYTEXT', NOTNULL => 1 });
- # This also changes NULL to NOT NULL.
- $dbh->bz_alter_column('flagtypes', 'description',
- { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }, '');
- $dbh->bz_alter_column('fielddefs', 'description',
- { TYPE => 'TINYTEXT', NOTNULL => 1 });
- $dbh->bz_alter_column('groups', 'description',
- { TYPE => 'MEDIUMTEXT', NOTNULL => 1 });
- $dbh->bz_alter_column('namedqueries', 'query',
- { TYPE => 'LONGTEXT', NOTNULL => 1 });
-
-}
+ my $dbh = Bugzilla->dbh;
+ return if $dbh->bz_column_info('namedqueries', 'query')->{TYPE} eq 'LONGTEXT';
+ _check_content_length('attachments', 'mimetype', 255, 'attach_id');
+ _check_content_length('fielddefs', 'description', 255, 'id');
+ _check_content_length('attachments', 'description', 255, 'attach_id');
+
+ $dbh->bz_alter_column('bugs', 'bug_file_loc', {TYPE => 'MEDIUMTEXT'});
+ $dbh->bz_alter_column('longdescs', 'thetext',
+ {TYPE => 'LONGTEXT', NOTNULL => 1});
+ $dbh->bz_alter_column('attachments', 'description',
+ {TYPE => 'TINYTEXT', NOTNULL => 1});
+ $dbh->bz_alter_column('attachments', 'mimetype',
+ {TYPE => 'TINYTEXT', NOTNULL => 1});
+
+ # This also changes NULL to NOT NULL.
+ $dbh->bz_alter_column('flagtypes', 'description',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
+ $dbh->bz_alter_column('fielddefs', 'description',
+ {TYPE => 'TINYTEXT', NOTNULL => 1});
+ $dbh->bz_alter_column('groups', 'description',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1});
+ $dbh->bz_alter_column('namedqueries', 'query',
+ {TYPE => 'LONGTEXT', NOTNULL => 1});
+
+}
sub _check_content_length {
- my ($table_name, $field_name, $max_length, $id_field) = @_;
- my $dbh = Bugzilla->dbh;
- my %contents = @{ $dbh->selectcol_arrayref(
- "SELECT $id_field, $field_name FROM $table_name
- WHERE CHAR_LENGTH($field_name) > ?", {Columns=>[1,2]}, $max_length) };
-
- if (scalar keys %contents) {
- my $error = install_string('install_data_too_long',
- { column => $field_name,
- id_column => $id_field,
- table => $table_name,
- max_length => $max_length });
- foreach my $id (keys %contents) {
- my $string = $contents{$id};
- # Don't dump the whole string--it could be 16MB.
- if (length($string) > 80) {
- $string = substr($string, 0, 30) . "..."
- . substr($string, -30) . "\n";
- }
- $error .= "$id: $string\n";
- }
- die $error;
+ my ($table_name, $field_name, $max_length, $id_field) = @_;
+ my $dbh = Bugzilla->dbh;
+ my %contents = @{
+ $dbh->selectcol_arrayref(
+ "SELECT $id_field, $field_name FROM $table_name
+ WHERE CHAR_LENGTH($field_name) > ?", {Columns => [1, 2]}, $max_length
+ )
+ };
+
+ if (scalar keys %contents) {
+ my $error = install_string(
+ 'install_data_too_long',
+ {
+ column => $field_name,
+ id_column => $id_field,
+ table => $table_name,
+ max_length => $max_length
+ }
+ );
+ foreach my $id (keys %contents) {
+ my $string = $contents{$id};
+
+ # Don't dump the whole string--it could be 16MB.
+ if (length($string) > 80) {
+ $string = substr($string, 0, 30) . "..." . substr($string, -30) . "\n";
+ }
+ $error .= "$id: $string\n";
}
+ die $error;
+ }
}
sub _add_foreign_keys_to_multiselects {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- my $names = $dbh->selectcol_arrayref(
- 'SELECT name
+ my $names = $dbh->selectcol_arrayref(
+ 'SELECT name
FROM fielddefs
- WHERE type = ' . FIELD_TYPE_MULTI_SELECT);
+ WHERE type = ' . FIELD_TYPE_MULTI_SELECT
+ );
- foreach my $name (@$names) {
- $dbh->bz_add_fk("bug_$name", "bug_id",
- {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'});
-
- $dbh->bz_add_fk("bug_$name", "value",
- {TABLE => $name, COLUMN => 'value', DELETE => 'RESTRICT'});
- }
+ foreach my $name (@$names) {
+ $dbh->bz_add_fk("bug_$name", "bug_id",
+ {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'});
+
+ $dbh->bz_add_fk("bug_$name", "value",
+ {TABLE => $name, COLUMN => 'value', DELETE => 'RESTRICT'});
+ }
}
# This subroutine is used in multiple places (for times when we update
@@ -3252,693 +3511,735 @@ sub _add_foreign_keys_to_multiselects {
# it to update bugs_fulltext for those bug_ids instead of populating the
# whole table.
sub _populate_bugs_fulltext {
- my $bug_ids = shift;
- my $dbh = Bugzilla->dbh;
- my $fulltext = $dbh->selectrow_array('SELECT 1 FROM bugs_fulltext '
- . $dbh->sql_limit(1));
- # We only populate the table if it's empty or if we've been given a
- # set of bug ids.
- if ($bug_ids or !$fulltext) {
- $bug_ids ||= $dbh->selectcol_arrayref('SELECT bug_id FROM bugs');
- # If there are no bugs in the bugs table, there's nothing to populate.
- return if !@$bug_ids;
- my $num_bugs = scalar @$bug_ids;
-
- my $command = "INSERT";
- my $where = "";
- if ($fulltext) {
- print "Updating bugs_fulltext for $num_bugs bugs...\n";
- $where = "WHERE " . $dbh->sql_in('bugs.bug_id', $bug_ids);
- # It turns out that doing a REPLACE INTO is up to 10x faster
- # than any other possible method of updating the table, in MySQL,
- # which matters a LOT for large installations.
- if ($dbh->isa('Bugzilla::DB::Mysql')) {
- $command = "REPLACE";
- }
- else {
- $dbh->do("DELETE FROM bugs_fulltext WHERE "
- . $dbh->sql_in('bug_id', $bug_ids));
- }
- }
- else {
- print "Populating bugs_fulltext with $num_bugs entries...";
- print " (this can take a long time.)\n";
- }
- my $newline = $dbh->quote("\n");
- $dbh->do(
- qq{$command INTO bugs_fulltext (bug_id, short_desc, comments,
+ my $bug_ids = shift;
+ my $dbh = Bugzilla->dbh;
+ my $fulltext
+ = $dbh->selectrow_array('SELECT 1 FROM bugs_fulltext ' . $dbh->sql_limit(1));
+
+ # We only populate the table if it's empty or if we've been given a
+ # set of bug ids.
+ if ($bug_ids or !$fulltext) {
+ $bug_ids ||= $dbh->selectcol_arrayref('SELECT bug_id FROM bugs');
+
+ # If there are no bugs in the bugs table, there's nothing to populate.
+ return if !@$bug_ids;
+ my $num_bugs = scalar @$bug_ids;
+
+ my $command = "INSERT";
+ my $where = "";
+ if ($fulltext) {
+ print "Updating bugs_fulltext for $num_bugs bugs...\n";
+ $where = "WHERE " . $dbh->sql_in('bugs.bug_id', $bug_ids);
+
+ # It turns out that doing a REPLACE INTO is up to 10x faster
+ # than any other possible method of updating the table, in MySQL,
+ # which matters a LOT for large installations.
+ if ($dbh->isa('Bugzilla::DB::Mysql')) {
+ $command = "REPLACE";
+ }
+ else {
+ $dbh->do("DELETE FROM bugs_fulltext WHERE " . $dbh->sql_in('bug_id', $bug_ids));
+ }
+ }
+ else {
+ print "Populating bugs_fulltext with $num_bugs entries...";
+ print " (this can take a long time.)\n";
+ }
+ my $newline = $dbh->quote("\n");
+ $dbh->do(
+ qq{$command INTO bugs_fulltext (bug_id, short_desc, comments,
comments_noprivate)
SELECT bugs.bug_id, bugs.short_desc, }
- . $dbh->sql_group_concat('longdescs.thetext', $newline, 0)
- . ', ' . $dbh->sql_group_concat('nopriv.thetext', $newline, 0) .
- qq{ FROM bugs
+ . $dbh->sql_group_concat('longdescs.thetext', $newline, 0) . ', '
+ . $dbh->sql_group_concat('nopriv.thetext', $newline, 0)
+ . qq{ FROM bugs
LEFT JOIN longdescs
ON bugs.bug_id = longdescs.bug_id
LEFT JOIN longdescs AS nopriv
ON longdescs.comment_id = nopriv.comment_id
AND nopriv.isprivate = 0
$where }
- . $dbh->sql_group_by('bugs.bug_id', 'bugs.short_desc'));
- }
+ . $dbh->sql_group_by('bugs.bug_id', 'bugs.short_desc')
+ );
+ }
}
sub _fix_illegal_flag_modification_dates {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- my $rows = $dbh->do('UPDATE flags SET modification_date = creation_date
- WHERE modification_date < creation_date');
- # If no rows are affected, $dbh->do returns 0E0 instead of 0.
- print "$rows flags had an illegal modification date. Fixed!\n" if ($rows =~ /^\d+$/);
+ my $rows = $dbh->do(
+ 'UPDATE flags SET modification_date = creation_date
+ WHERE modification_date < creation_date'
+ );
+
+ # If no rows are affected, $dbh->do returns 0E0 instead of 0.
+ print "$rows flags had an illegal modification date. Fixed!\n"
+ if ($rows =~ /^\d+$/);
}
sub _add_visiblity_value_to_value_tables {
- my $dbh = Bugzilla->dbh;
- my @standard_fields =
- qw(bug_status resolution priority bug_severity op_sys rep_platform);
- my $custom_fields = $dbh->selectcol_arrayref(
- 'SELECT name FROM fielddefs WHERE custom = 1 AND type IN(?,?)',
- undef, FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT);
- foreach my $field (@standard_fields, @$custom_fields) {
- $dbh->bz_add_column($field, 'visibility_value_id', {TYPE => 'INT2'});
- $dbh->bz_add_index($field, "${field}_visibility_value_id_idx",
- ['visibility_value_id']);
- }
+ my $dbh = Bugzilla->dbh;
+ my @standard_fields
+ = qw(bug_status resolution priority bug_severity op_sys rep_platform);
+ my $custom_fields
+ = $dbh->selectcol_arrayref(
+ 'SELECT name FROM fielddefs WHERE custom = 1 AND type IN(?,?)',
+ undef, FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT);
+ foreach my $field (@standard_fields, @$custom_fields) {
+ $dbh->bz_add_column($field, 'visibility_value_id', {TYPE => 'INT2'});
+ $dbh->bz_add_index($field, "${field}_visibility_value_id_idx",
+ ['visibility_value_id']);
+ }
}
sub _add_extern_id_index {
- my $dbh = Bugzilla->dbh;
- if (!$dbh->bz_index_info('profiles', 'profiles_extern_id_idx')) {
- # Some Bugzillas have a multiple empty strings in extern_id,
- # which need to be converted to NULLs before we add the index.
- $dbh->do("UPDATE profiles SET extern_id = NULL WHERE extern_id = ''");
- $dbh->bz_add_index('profiles', 'profiles_extern_id_idx',
- {TYPE => 'UNIQUE', FIELDS => [qw(extern_id)]});
- }
+ my $dbh = Bugzilla->dbh;
+ if (!$dbh->bz_index_info('profiles', 'profiles_extern_id_idx')) {
+
+ # Some Bugzillas have a multiple empty strings in extern_id,
+ # which need to be converted to NULLs before we add the index.
+ $dbh->do("UPDATE profiles SET extern_id = NULL WHERE extern_id = ''");
+ $dbh->bz_add_index('profiles', 'profiles_extern_id_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(extern_id)]});
+ }
}
sub _convert_disallownew_to_isactive {
- my $dbh = Bugzilla->dbh;
- if ($dbh->bz_column_info('products', 'disallownew')){
- $dbh->bz_add_column('products', 'isactive',
- { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
-
- # isactive is the boolean reverse of disallownew.
- $dbh->do('UPDATE products SET isactive = 0 WHERE disallownew = 1');
- $dbh->do('UPDATE products SET isactive = 1 WHERE disallownew = 0');
-
- $dbh->bz_drop_column('products','disallownew');
- }
+ my $dbh = Bugzilla->dbh;
+ if ($dbh->bz_column_info('products', 'disallownew')) {
+ $dbh->bz_add_column('products', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+
+ # isactive is the boolean reverse of disallownew.
+ $dbh->do('UPDATE products SET isactive = 0 WHERE disallownew = 1');
+ $dbh->do('UPDATE products SET isactive = 1 WHERE disallownew = 0');
+
+ $dbh->bz_drop_column('products', 'disallownew');
+ }
}
sub _fix_logincookies_ipaddr {
- my $dbh = Bugzilla->dbh;
- return if !$dbh->bz_column_info('logincookies', 'ipaddr')->{NOTNULL};
+ my $dbh = Bugzilla->dbh;
+ return if !$dbh->bz_column_info('logincookies', 'ipaddr')->{NOTNULL};
- $dbh->bz_alter_column('logincookies', 'ipaddr', {TYPE => 'varchar(40)'});
- $dbh->do('UPDATE logincookies SET ipaddr = NULL WHERE ipaddr = ?',
- undef, '0.0.0.0');
+ $dbh->bz_alter_column('logincookies', 'ipaddr', {TYPE => 'varchar(40)'});
+ $dbh->do('UPDATE logincookies SET ipaddr = NULL WHERE ipaddr = ?',
+ undef, '0.0.0.0');
}
sub _fix_invalid_custom_field_names {
- my $fields = Bugzilla->fields({ custom => 1 });
+ my $fields = Bugzilla->fields({custom => 1});
- foreach my $field (@$fields) {
- next if $field->name =~ /^[a-zA-Z0-9_]+$/;
- # The field name is illegal and can break the DB. Kill the field!
- $field->set_obsolete(1);
- print install_string('update_cf_invalid_name',
- { field => $field->name }), "\n";
- eval { $field->remove_from_db(); };
- warn $@ if $@;
- }
+ foreach my $field (@$fields) {
+ next if $field->name =~ /^[a-zA-Z0-9_]+$/;
+
+ # The field name is illegal and can break the DB. Kill the field!
+ $field->set_obsolete(1);
+ print install_string('update_cf_invalid_name', {field => $field->name}), "\n";
+ eval { $field->remove_from_db(); };
+ warn $@ if $@;
+ }
}
sub _set_attachment_comment_type {
- my ($type, $string) = @_;
- my $dbh = Bugzilla->dbh;
- # We check if there are any comments of this type already, first,
- # because this is faster than a full LIKE search on the comments,
- # and currently this will run every time we run checksetup.
- my $test = $dbh->selectrow_array(
- "SELECT 1 FROM longdescs WHERE type = $type " . $dbh->sql_limit(1));
- return [] if $test;
- my %comments = @{ $dbh->selectcol_arrayref(
- "SELECT comment_id, thetext FROM longdescs
- WHERE thetext LIKE '$string%'",
- {Columns=>[1,2]}) };
- my @comment_ids = keys %comments;
- return [] if !scalar @comment_ids;
- my $what = "update";
+ my ($type, $string) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # We check if there are any comments of this type already, first,
+ # because this is faster than a full LIKE search on the comments,
+ # and currently this will run every time we run checksetup.
+ my $test = $dbh->selectrow_array(
+ "SELECT 1 FROM longdescs WHERE type = $type " . $dbh->sql_limit(1));
+ return [] if $test;
+ my %comments = @{
+ $dbh->selectcol_arrayref(
+ "SELECT comment_id, thetext FROM longdescs
+ WHERE thetext LIKE '$string%'", {Columns => [1, 2]}
+ )
+ };
+ my @comment_ids = keys %comments;
+ return [] if !scalar @comment_ids;
+ my $what = "update";
+ if ($type == CMT_ATTACHMENT_CREATED) {
+ $what = "creation";
+ }
+ print "Setting the type field on attachment $what comments...\n";
+ my $sth = $dbh->prepare(
+ 'UPDATE longdescs SET thetext = ?, type = ?, extra_data = ?
+ WHERE comment_id = ?'
+ );
+ my $count = 0;
+ my $total = scalar @comment_ids;
+ foreach my $id (@comment_ids) {
+ $count++;
+ my $text = $comments{$id};
+ next if $text !~ /^\Q$string\E(\d+)/;
+ my $attachment_id = $1;
+ my @lines = split("\n", $text);
if ($type == CMT_ATTACHMENT_CREATED) {
- $what = "creation";
+
+ # Now we have to remove the text up until we find a line that's
+ # just a single newline, because the old "Created an attachment"
+ # text included the attachment description underneath it, and in
+ # Bugzillas before 2.20, that could be wrapped into multiple lines,
+ # in the database.
+ while (1) {
+ my $line = shift @lines;
+ last if (!defined $line or trim($line) eq '');
+ }
}
- print "Setting the type field on attachment $what comments...\n";
- my $sth = $dbh->prepare(
- 'UPDATE longdescs SET thetext = ?, type = ?, extra_data = ?
- WHERE comment_id = ?');
- my $count = 0;
- my $total = scalar @comment_ids;
- foreach my $id (@comment_ids) {
- $count++;
- my $text = $comments{$id};
- next if $text !~ /^\Q$string\E(\d+)/;
- my $attachment_id = $1;
- my @lines = split("\n", $text);
- if ($type == CMT_ATTACHMENT_CREATED) {
- # Now we have to remove the text up until we find a line that's
- # just a single newline, because the old "Created an attachment"
- # text included the attachment description underneath it, and in
- # Bugzillas before 2.20, that could be wrapped into multiple lines,
- # in the database.
- while (1) {
- my $line = shift @lines;
- last if (!defined $line or trim($line) eq '');
- }
- }
- else {
- # However, the "From update of attachment" line is always just
- # one line--the first line of the comment.
- shift @lines;
- }
- $text = join("\n", @lines);
- $sth->execute($text, $type, $attachment_id, $id);
- indicate_progress({ total => $total, current => $count,
- every => 25 });
+ else {
+ # However, the "From update of attachment" line is always just
+ # one line--the first line of the comment.
+ shift @lines;
}
- return \@comment_ids;
+ $text = join("\n", @lines);
+ $sth->execute($text, $type, $attachment_id, $id);
+ indicate_progress({total => $total, current => $count, every => 25});
+ }
+ return \@comment_ids;
}
sub _set_attachment_comment_types {
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- my $created_ids = _set_attachment_comment_type(
- CMT_ATTACHMENT_CREATED, 'Created an attachment (id=');
- my $updated_ids = _set_attachment_comment_type(
- CMT_ATTACHMENT_UPDATED, '(From update of attachment ');
- $dbh->bz_commit_transaction();
- return unless (@$created_ids or @$updated_ids);
-
- my @comment_ids = (@$created_ids, @$updated_ids);
-
- my $bug_ids = $dbh->selectcol_arrayref(
- 'SELECT DISTINCT bug_id FROM longdescs WHERE '
- . $dbh->sql_in('comment_id', \@comment_ids));
- _populate_bugs_fulltext($bug_ids);
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ my $created_ids = _set_attachment_comment_type(CMT_ATTACHMENT_CREATED,
+ 'Created an attachment (id=');
+ my $updated_ids = _set_attachment_comment_type(CMT_ATTACHMENT_UPDATED,
+ '(From update of attachment ');
+ $dbh->bz_commit_transaction();
+ return unless (@$created_ids or @$updated_ids);
+
+ my @comment_ids = (@$created_ids, @$updated_ids);
+
+ my $bug_ids
+ = $dbh->selectcol_arrayref('SELECT DISTINCT bug_id FROM longdescs WHERE '
+ . $dbh->sql_in('comment_id', \@comment_ids));
+ _populate_bugs_fulltext($bug_ids);
}
sub _add_allows_unconfirmed_to_product_table {
- my $dbh = Bugzilla->dbh;
- if (!$dbh->bz_column_info('products', 'allows_unconfirmed')) {
- $dbh->bz_add_column('products', 'allows_unconfirmed',
- { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' });
- if ($dbh->bz_column_info('products', 'votestoconfirm')) {
- $dbh->do('UPDATE products SET allows_unconfirmed = 1
- WHERE votestoconfirm > 0');
- }
+ my $dbh = Bugzilla->dbh;
+ if (!$dbh->bz_column_info('products', 'allows_unconfirmed')) {
+ $dbh->bz_add_column('products', 'allows_unconfirmed',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ if ($dbh->bz_column_info('products', 'votestoconfirm')) {
+ $dbh->do(
+ 'UPDATE products SET allows_unconfirmed = 1
+ WHERE votestoconfirm > 0'
+ );
}
+ }
}
sub _convert_flagtypes_fks_to_set_null {
- my $dbh = Bugzilla->dbh;
- foreach my $column (qw(request_group_id grant_group_id)) {
- my $fk = $dbh->bz_fk_info('flagtypes', $column);
- if ($fk and !defined $fk->{DELETE}) {
- $fk->{DELETE} = 'SET NULL';
- $dbh->bz_alter_fk('flagtypes', $column, $fk);
- }
+ my $dbh = Bugzilla->dbh;
+ foreach my $column (qw(request_group_id grant_group_id)) {
+ my $fk = $dbh->bz_fk_info('flagtypes', $column);
+ if ($fk and !defined $fk->{DELETE}) {
+ $fk->{DELETE} = 'SET NULL';
+ $dbh->bz_alter_fk('flagtypes', $column, $fk);
}
+ }
}
sub _fix_decimal_types {
- my $dbh = Bugzilla->dbh;
- my $type = {TYPE => 'decimal(7,2)', NOTNULL => 1, DEFAULT => '0'};
- $dbh->bz_alter_column('bugs', 'estimated_time', $type);
- $dbh->bz_alter_column('bugs', 'remaining_time', $type);
- $dbh->bz_alter_column('longdescs', 'work_time', $type);
+ my $dbh = Bugzilla->dbh;
+ my $type = {TYPE => 'decimal(7,2)', NOTNULL => 1, DEFAULT => '0'};
+ $dbh->bz_alter_column('bugs', 'estimated_time', $type);
+ $dbh->bz_alter_column('bugs', 'remaining_time', $type);
+ $dbh->bz_alter_column('longdescs', 'work_time', $type);
}
sub _fix_series_creator_fk {
- my $dbh = Bugzilla->dbh;
- my $fk = $dbh->bz_fk_info('series', 'creator');
- if ($fk and $fk->{DELETE} eq 'SET NULL') {
- $fk->{DELETE} = 'CASCADE';
- $dbh->bz_alter_fk('series', 'creator', $fk);
- }
+ my $dbh = Bugzilla->dbh;
+ my $fk = $dbh->bz_fk_info('series', 'creator');
+ if ($fk and $fk->{DELETE} eq 'SET NULL') {
+ $fk->{DELETE} = 'CASCADE';
+ $dbh->bz_alter_fk('series', 'creator', $fk);
+ }
}
sub _remove_attachment_isurl {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- if ($dbh->bz_column_info('attachments', 'isurl')) {
- # Now all attachments must have a filename.
- $dbh->do('UPDATE attachments SET filename = ? WHERE isurl = 1',
- undef, 'url.txt');
- $dbh->bz_drop_column('attachments', 'isurl');
- $dbh->do("DELETE FROM fielddefs WHERE name='attachments.isurl'");
- }
+ if ($dbh->bz_column_info('attachments', 'isurl')) {
+
+ # Now all attachments must have a filename.
+ $dbh->do('UPDATE attachments SET filename = ? WHERE isurl = 1',
+ undef, 'url.txt');
+ $dbh->bz_drop_column('attachments', 'isurl');
+ $dbh->do("DELETE FROM fielddefs WHERE name='attachments.isurl'");
+ }
}
sub _add_isactive_to_product_fields {
- my $dbh = Bugzilla->dbh;
-
- # If we add the isactive column all values should start off as active
- if (!$dbh->bz_column_info('components', 'isactive')) {
- $dbh->bz_add_column('components', 'isactive',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
- }
-
- if (!$dbh->bz_column_info('versions', 'isactive')) {
- $dbh->bz_add_column('versions', 'isactive',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
- }
-
- if (!$dbh->bz_column_info('milestones', 'isactive')) {
- $dbh->bz_add_column('milestones', 'isactive',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
- }
+ my $dbh = Bugzilla->dbh;
+
+ # If we add the isactive column all values should start off as active
+ if (!$dbh->bz_column_info('components', 'isactive')) {
+ $dbh->bz_add_column('components', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ }
+
+ if (!$dbh->bz_column_info('versions', 'isactive')) {
+ $dbh->bz_add_column('versions', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ }
+
+ if (!$dbh->bz_column_info('milestones', 'isactive')) {
+ $dbh->bz_add_column('milestones', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ }
}
sub _migrate_field_visibility_value {
- my $dbh = Bugzilla->dbh;
-
- if ($dbh->bz_column_info('fielddefs', 'visibility_value_id')) {
- print "Populating new field_visibility table...\n";
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ if ($dbh->bz_column_info('fielddefs', 'visibility_value_id')) {
+ print "Populating new field_visibility table...\n";
- my %results =
- @{ $dbh->selectcol_arrayref(
- "SELECT id, visibility_value_id FROM fielddefs
- WHERE visibility_value_id IS NOT NULL",
- { Columns => [1,2] }) };
+ $dbh->bz_start_transaction();
- my $insert_sth =
- $dbh->prepare("INSERT INTO field_visibility (field_id, value_id)
- VALUES (?, ?)");
+ my %results = @{
+ $dbh->selectcol_arrayref(
+ "SELECT id, visibility_value_id FROM fielddefs
+ WHERE visibility_value_id IS NOT NULL", {Columns => [1, 2]}
+ )
+ };
- foreach my $id (keys %results) {
- $insert_sth->execute($id, $results{$id});
- }
+ my $insert_sth = $dbh->prepare(
+ "INSERT INTO field_visibility (field_id, value_id)
+ VALUES (?, ?)"
+ );
- $dbh->bz_commit_transaction();
- $dbh->bz_drop_column('fielddefs', 'visibility_value_id');
+ foreach my $id (keys %results) {
+ $insert_sth->execute($id, $results{$id});
}
+
+ $dbh->bz_commit_transaction();
+ $dbh->bz_drop_column('fielddefs', 'visibility_value_id');
+ }
}
sub _fix_series_indexes {
- my $dbh = Bugzilla->dbh;
- return if $dbh->bz_index_info('series', 'series_category_idx');
+ my $dbh = Bugzilla->dbh;
+ return if $dbh->bz_index_info('series', 'series_category_idx');
- $dbh->bz_drop_index('series', 'series_creator_idx');
+ $dbh->bz_drop_index('series', 'series_creator_idx');
- # Fix duplicated names under the same category/subcategory before
- # adding the more restrictive index.
- my $duplicated_series = $dbh->selectall_arrayref(
- 'SELECT s1.series_id, s1.category, s1.subcategory, s1.name
+ # Fix duplicated names under the same category/subcategory before
+ # adding the more restrictive index.
+ my $duplicated_series = $dbh->selectall_arrayref(
+ 'SELECT s1.series_id, s1.category, s1.subcategory, s1.name
FROM series AS s1
INNER JOIN series AS s2
ON s1.category = s2.category
AND s1.subcategory = s2.subcategory
AND s1.name = s2.name
- WHERE s1.series_id != s2.series_id');
- my $sth_series_update = $dbh->prepare('UPDATE series SET name = ? WHERE series_id = ?');
- my $sth_series_query = $dbh->prepare('SELECT 1 FROM series WHERE name = ?
- AND category = ? AND subcategory = ?');
-
- my %renamed_series;
- foreach my $series (@$duplicated_series) {
- my ($series_id, $category, $subcategory, $name) = @$series;
- # Leave the first series alone, then rename duplicated ones.
- if ($renamed_series{"${category}_${subcategory}_${name}"}++) {
- print "Renaming series ${category}/${subcategory}/${name}...\n";
- my $c = 0;
- my $exists = 1;
- while ($exists) {
- $sth_series_query->execute($name . ++$c, $category, $subcategory);
- $exists = $sth_series_query->fetchrow_array;
- }
- $sth_series_update->execute($name . $c, $series_id);
- }
+ WHERE s1.series_id != s2.series_id'
+ );
+ my $sth_series_update
+ = $dbh->prepare('UPDATE series SET name = ? WHERE series_id = ?');
+ my $sth_series_query = $dbh->prepare(
+ 'SELECT 1 FROM series WHERE name = ?
+ AND category = ? AND subcategory = ?'
+ );
+
+ my %renamed_series;
+ foreach my $series (@$duplicated_series) {
+ my ($series_id, $category, $subcategory, $name) = @$series;
+
+ # Leave the first series alone, then rename duplicated ones.
+ if ($renamed_series{"${category}_${subcategory}_${name}"}++) {
+ print "Renaming series ${category}/${subcategory}/${name}...\n";
+ my $c = 0;
+ my $exists = 1;
+ while ($exists) {
+ $sth_series_query->execute($name . ++$c, $category, $subcategory);
+ $exists = $sth_series_query->fetchrow_array;
+ }
+ $sth_series_update->execute($name . $c, $series_id);
}
+ }
- $dbh->bz_add_index('series', 'series_creator_idx', ['creator']);
- $dbh->bz_add_index('series', 'series_category_idx',
- {FIELDS => [qw(category subcategory name)], TYPE => 'UNIQUE'});
+ $dbh->bz_add_index('series', 'series_creator_idx', ['creator']);
+ $dbh->bz_add_index('series', 'series_category_idx',
+ {FIELDS => [qw(category subcategory name)], TYPE => 'UNIQUE'});
}
sub _migrate_user_tags {
- my $dbh = Bugzilla->dbh;
- return unless $dbh->bz_column_info('namedqueries', 'query_type');
+ my $dbh = Bugzilla->dbh;
+ return unless $dbh->bz_column_info('namedqueries', 'query_type');
- my $tags = $dbh->selectall_arrayref('SELECT id, userid, name, query
+ my $tags = $dbh->selectall_arrayref(
+ 'SELECT id, userid, name, query
FROM namedqueries
- WHERE query_type != 0');
-
- my $sth_tags = $dbh->prepare(
- 'INSERT INTO tag (user_id, name) VALUES (?, ?)');
- my $sth_tag_id = $dbh->prepare(
- 'SELECT id FROM tag WHERE user_id = ? AND name = ?');
- my $sth_bug_tag = $dbh->prepare('INSERT INTO bug_tag (bug_id, tag_id)
- VALUES (?, ?)');
- my $sth_nq = $dbh->prepare('UPDATE namedqueries SET query = ?
- WHERE id = ?');
-
- if (scalar @$tags) {
- print install_string('update_queries_to_tags'), "\n";
+ WHERE query_type != 0'
+ );
+
+ my $sth_tags = $dbh->prepare('INSERT INTO tag (user_id, name) VALUES (?, ?)');
+ my $sth_tag_id
+ = $dbh->prepare('SELECT id FROM tag WHERE user_id = ? AND name = ?');
+ my $sth_bug_tag = $dbh->prepare(
+ 'INSERT INTO bug_tag (bug_id, tag_id)
+ VALUES (?, ?)'
+ );
+ my $sth_nq = $dbh->prepare(
+ 'UPDATE namedqueries SET query = ?
+ WHERE id = ?'
+ );
+
+ if (scalar @$tags) {
+ print install_string('update_queries_to_tags'), "\n";
+ }
+
+ my $total = scalar(@$tags);
+ my $current = 0;
+
+ $dbh->bz_start_transaction();
+ foreach my $tag (@$tags) {
+ my ($query_id, $user_id, $name, $query) = @$tag;
+
+ # Tags are all lowercase.
+ my $tag_name = lc($name);
+
+ $sth_tags->execute($user_id, $tag_name);
+
+ my $tag_id = $dbh->selectrow_array($sth_tag_id, undef, $user_id, $tag_name);
+
+ indicate_progress({current => ++$current, total => $total, every => 25});
+
+ my $uri = URI->new("buglist.cgi?$query", 'http');
+ my $bug_id_list = $uri->query_param_delete('bug_id');
+ if (!$bug_id_list) {
+ warn "No bug_id param for tag $name from user $user_id: $query";
+ next;
}
+ my @bug_ids = split(/[\s,]+/, $bug_id_list);
- my $total = scalar(@$tags);
- my $current = 0;
-
- $dbh->bz_start_transaction();
- foreach my $tag (@$tags) {
- my ($query_id, $user_id, $name, $query) = @$tag;
- # Tags are all lowercase.
- my $tag_name = lc($name);
-
- $sth_tags->execute($user_id, $tag_name);
+ # Make sure that things like "001" get converted to "1"
+ @bug_ids = map { int($_) } @bug_ids;
- my $tag_id = $dbh->selectrow_array($sth_tag_id,
- undef, $user_id, $tag_name);
+ # And remove duplicates
+ @bug_ids = uniq @bug_ids;
+ foreach my $bug_id (@bug_ids) {
- indicate_progress({ current => ++$current, total => $total,
- every => 25 });
-
- my $uri = URI->new("buglist.cgi?$query", 'http');
- my $bug_id_list = $uri->query_param_delete('bug_id');
- if (!$bug_id_list) {
- warn "No bug_id param for tag $name from user $user_id: $query";
- next;
- }
- my @bug_ids = split(/[\s,]+/, $bug_id_list);
- # Make sure that things like "001" get converted to "1"
- @bug_ids = map { int($_) } @bug_ids;
- # And remove duplicates
- @bug_ids = uniq @bug_ids;
- foreach my $bug_id (@bug_ids) {
- # If "int" above failed this might be undef. We also
- # don't want to accept bug 0.
- next if !$bug_id;
- $sth_bug_tag->execute($bug_id, $tag_id);
- }
-
- # Existing tags may be used in whines, or shared with
- # other users. So we convert them rather than delete them.
- $uri->query_param('tag', $tag_name);
- $sth_nq->execute($uri->query, $query_id);
+ # If "int" above failed this might be undef. We also
+ # don't want to accept bug 0.
+ next if !$bug_id;
+ $sth_bug_tag->execute($bug_id, $tag_id);
}
- $dbh->bz_commit_transaction();
+ # Existing tags may be used in whines, or shared with
+ # other users. So we convert them rather than delete them.
+ $uri->query_param('tag', $tag_name);
+ $sth_nq->execute($uri->query, $query_id);
+ }
- $dbh->bz_drop_column('namedqueries', 'query_type');
+ $dbh->bz_commit_transaction();
+
+ $dbh->bz_drop_column('namedqueries', 'query_type');
}
sub _populate_bug_see_also_class {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- if ($dbh->bz_column_info('bug_see_also', 'class')) {
- # The length was incorrectly set to 64 instead of 255.
- $dbh->bz_alter_column('bug_see_also', 'class',
- {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
- return;
- }
+ if ($dbh->bz_column_info('bug_see_also', 'class')) {
- $dbh->bz_add_column('bug_see_also', 'class',
- {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}, '');
+ # The length was incorrectly set to 64 instead of 255.
+ $dbh->bz_alter_column('bug_see_also', 'class',
+ {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
+ return;
+ }
- my $result = $dbh->selectall_arrayref(
- "SELECT id, value FROM bug_see_also");
+ $dbh->bz_add_column('bug_see_also', 'class',
+ {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}, '');
- my $update_sth =
- $dbh->prepare("UPDATE bug_see_also SET class = ? WHERE id = ?");
-
- $dbh->bz_start_transaction();
- foreach my $see_also (@$result) {
- my ($id, $value) = @$see_also;
- my $class = Bugzilla::BugUrl->class_for($value);
- $update_sth->execute($class, $id);
- }
- $dbh->bz_commit_transaction();
+ my $result = $dbh->selectall_arrayref("SELECT id, value FROM bug_see_also");
+
+ my $update_sth
+ = $dbh->prepare("UPDATE bug_see_also SET class = ? WHERE id = ?");
+
+ $dbh->bz_start_transaction();
+ foreach my $see_also (@$result) {
+ my ($id, $value) = @$see_also;
+ my $class = Bugzilla::BugUrl->class_for($value);
+ $update_sth->execute($class, $id);
+ }
+ $dbh->bz_commit_transaction();
}
sub _migrate_disabledtext_boolean {
- my $dbh = Bugzilla->dbh;
- if (!$dbh->bz_column_info('profiles', 'is_enabled')) {
- $dbh->bz_add_column("profiles", 'is_enabled',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
- $dbh->do("UPDATE profiles SET is_enabled = 0
- WHERE disabledtext != ''");
- }
+ my $dbh = Bugzilla->dbh;
+ if (!$dbh->bz_column_info('profiles', 'is_enabled')) {
+ $dbh->bz_add_column("profiles", 'is_enabled',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ $dbh->do(
+ "UPDATE profiles SET is_enabled = 0
+ WHERE disabledtext != ''"
+ );
+ }
}
sub _rename_tags_to_tag {
- my $dbh = Bugzilla->dbh;
- if ($dbh->bz_table_info('tags')) {
- # If we get here, it's because the schema created "tag" as an empty
- # table while "tags" still exists. We get rid of the empty
- # tag table so we can do the rename over the top of it.
- $dbh->bz_drop_table('tag');
- $dbh->bz_drop_index('tags', 'tags_user_id_idx');
- $dbh->bz_rename_table('tags','tag');
- $dbh->bz_add_index('tag', 'tag_user_id_idx',
- {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'});
- }
- if (my $bug_tag_fk = $dbh->bz_fk_info('bug_tag', 'tag_id')) {
- # bz_rename_table() didn't handle FKs correctly.
- if ($bug_tag_fk->{TABLE} eq 'tags') {
- $bug_tag_fk->{TABLE} = 'tag';
- $dbh->bz_alter_fk('bug_tag', 'tag_id', $bug_tag_fk);
- }
+ my $dbh = Bugzilla->dbh;
+ if ($dbh->bz_table_info('tags')) {
+
+ # If we get here, it's because the schema created "tag" as an empty
+ # table while "tags" still exists. We get rid of the empty
+ # tag table so we can do the rename over the top of it.
+ $dbh->bz_drop_table('tag');
+ $dbh->bz_drop_index('tags', 'tags_user_id_idx');
+ $dbh->bz_rename_table('tags', 'tag');
+ $dbh->bz_add_index('tag', 'tag_user_id_idx',
+ {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'});
+ }
+ if (my $bug_tag_fk = $dbh->bz_fk_info('bug_tag', 'tag_id')) {
+
+ # bz_rename_table() didn't handle FKs correctly.
+ if ($bug_tag_fk->{TABLE} eq 'tags') {
+ $bug_tag_fk->{TABLE} = 'tag';
+ $dbh->bz_alter_fk('bug_tag', 'tag_id', $bug_tag_fk);
}
+ }
}
sub _on_delete_set_null_for_audit_log_userid {
- my $dbh = Bugzilla->dbh;
- my $fk = $dbh->bz_fk_info('audit_log', 'user_id');
- if ($fk and !defined $fk->{DELETE}) {
- $fk->{DELETE} = 'SET NULL';
- $dbh->bz_alter_fk('audit_log', 'user_id', $fk);
- }
+ my $dbh = Bugzilla->dbh;
+ my $fk = $dbh->bz_fk_info('audit_log', 'user_id');
+ if ($fk and !defined $fk->{DELETE}) {
+ $fk->{DELETE} = 'SET NULL';
+ $dbh->bz_alter_fk('audit_log', 'user_id', $fk);
+ }
}
sub _fix_notnull_defaults {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_alter_column('bugs', 'bug_file_loc',
- {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
- DEFAULT => "''"}, '');
+ $dbh->bz_alter_column('bugs', 'bug_file_loc',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}, '');
- my $custom_fields = Bugzilla::Field->match({
- custom => 1, type => [ FIELD_TYPE_FREETEXT, FIELD_TYPE_TEXTAREA ]
- });
+ my $custom_fields = Bugzilla::Field->match(
+ {custom => 1, type => [FIELD_TYPE_FREETEXT, FIELD_TYPE_TEXTAREA]});
- foreach my $field (@$custom_fields) {
- if ($field->type == FIELD_TYPE_FREETEXT) {
- $dbh->bz_alter_column('bugs', $field->name,
- {TYPE => 'varchar(255)', NOTNULL => 1,
- DEFAULT => "''"}, '');
- }
- if ($field->type == FIELD_TYPE_TEXTAREA) {
- $dbh->bz_alter_column('bugs', $field->name,
- {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
- DEFAULT => "''"}, '');
- }
+ foreach my $field (@$custom_fields) {
+ if ($field->type == FIELD_TYPE_FREETEXT) {
+ $dbh->bz_alter_column('bugs', $field->name,
+ {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}, '');
+ }
+ if ($field->type == FIELD_TYPE_TEXTAREA) {
+ $dbh->bz_alter_column('bugs', $field->name,
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}, '');
}
+ }
}
sub _fix_longdescs_primary_key {
- my $dbh = Bugzilla->dbh;
- if ($dbh->bz_column_info('longdescs', 'comment_id')->{TYPE} ne 'INTSERIAL') {
- $dbh->bz_drop_related_fks('longdescs', 'comment_id');
- $dbh->bz_alter_column('bugs_activity', 'comment_id', {TYPE => 'INT4'});
- $dbh->bz_alter_column('longdescs', 'comment_id',
- {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- }
+ my $dbh = Bugzilla->dbh;
+ if ($dbh->bz_column_info('longdescs', 'comment_id')->{TYPE} ne 'INTSERIAL') {
+ $dbh->bz_drop_related_fks('longdescs', 'comment_id');
+ $dbh->bz_alter_column('bugs_activity', 'comment_id', {TYPE => 'INT4'});
+ $dbh->bz_alter_column('longdescs', 'comment_id',
+ {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ }
}
sub _fix_longdescs_indexes {
- my $dbh = Bugzilla->dbh;
- my $bug_id_idx = $dbh->bz_index_info('longdescs', 'longdescs_bug_id_idx');
- if ($bug_id_idx && scalar @{$bug_id_idx->{'FIELDS'}} < 2) {
- $dbh->bz_drop_index('longdescs', 'longdescs_bug_id_idx');
- $dbh->bz_add_index('longdescs', 'longdescs_bug_id_idx', [qw(bug_id work_time)]);
- }
+ my $dbh = Bugzilla->dbh;
+ my $bug_id_idx = $dbh->bz_index_info('longdescs', 'longdescs_bug_id_idx');
+ if ($bug_id_idx && scalar @{$bug_id_idx->{'FIELDS'}} < 2) {
+ $dbh->bz_drop_index('longdescs', 'longdescs_bug_id_idx');
+ $dbh->bz_add_index('longdescs', 'longdescs_bug_id_idx', [qw(bug_id work_time)]);
+ }
}
sub _fix_dependencies_dupes {
- my $dbh = Bugzilla->dbh;
- my $blocked_idx = $dbh->bz_index_info('dependencies', 'dependencies_blocked_idx');
- if ($blocked_idx && scalar @{$blocked_idx->{'FIELDS'}} < 2) {
- # Remove duplicated entries
- my $dupes = $dbh->selectall_arrayref("
+ my $dbh = Bugzilla->dbh;
+ my $blocked_idx
+ = $dbh->bz_index_info('dependencies', 'dependencies_blocked_idx');
+ if ($blocked_idx && scalar @{$blocked_idx->{'FIELDS'}} < 2) {
+
+ # Remove duplicated entries
+ my $dupes = $dbh->selectall_arrayref("
SELECT blocked, dependson, COUNT(*) AS count
- FROM dependencies " .
- $dbh->sql_group_by('blocked, dependson') . "
- HAVING COUNT(*) > 1",
- { Slice => {} });
- print "Removing duplicated entries from the 'dependencies' table...\n" if @$dupes;
- foreach my $dupe (@$dupes) {
- $dbh->do("DELETE FROM dependencies
- WHERE blocked = ? AND dependson = ?",
- undef, $dupe->{blocked}, $dupe->{dependson});
- $dbh->do("INSERT INTO dependencies (blocked, dependson) VALUES (?, ?)",
- undef, $dupe->{blocked}, $dupe->{dependson});
- }
- $dbh->bz_drop_index('dependencies', 'dependencies_blocked_idx');
- $dbh->bz_add_index('dependencies', 'dependencies_blocked_idx',
- { FIELDS => [qw(blocked dependson)], TYPE => 'UNIQUE' });
- }
+ FROM dependencies " . $dbh->sql_group_by('blocked, dependson') . "
+ HAVING COUNT(*) > 1", {Slice => {}});
+ print "Removing duplicated entries from the 'dependencies' table...\n"
+ if @$dupes;
+ foreach my $dupe (@$dupes) {
+ $dbh->do(
+ "DELETE FROM dependencies
+ WHERE blocked = ? AND dependson = ?", undef, $dupe->{blocked},
+ $dupe->{dependson}
+ );
+ $dbh->do("INSERT INTO dependencies (blocked, dependson) VALUES (?, ?)",
+ undef, $dupe->{blocked}, $dupe->{dependson});
+ }
+ $dbh->bz_drop_index('dependencies', 'dependencies_blocked_idx');
+ $dbh->bz_add_index('dependencies', 'dependencies_blocked_idx',
+ {FIELDS => [qw(blocked dependson)], TYPE => 'UNIQUE'});
+ }
}
sub _shorten_long_quips {
- my $dbh = Bugzilla->dbh;
- my $quips = $dbh->selectall_arrayref("SELECT quipid, quip FROM quips
- WHERE CHAR_LENGTH(quip) > 512");
-
- if (@$quips) {
- print "Shortening quips longer than 512 characters:";
-
- my $query = $dbh->prepare("UPDATE quips SET quip = ? WHERE quipid = ?");
-
- foreach my $quip (@$quips) {
- my ($quipid, $quip_str) = @$quip;
- $quip_str = substr($quip_str, 0, 509) . "...";
- print " $quipid";
- $query->execute($quip_str, $quipid);
- }
- print "\n";
+ my $dbh = Bugzilla->dbh;
+ my $quips = $dbh->selectall_arrayref(
+ "SELECT quipid, quip FROM quips
+ WHERE CHAR_LENGTH(quip) > 512"
+ );
+
+ if (@$quips) {
+ print "Shortening quips longer than 512 characters:";
+
+ my $query = $dbh->prepare("UPDATE quips SET quip = ? WHERE quipid = ?");
+
+ foreach my $quip (@$quips) {
+ my ($quipid, $quip_str) = @$quip;
+ $quip_str = substr($quip_str, 0, 509) . "...";
+ print " $quipid";
+ $query->execute($quip_str, $quipid);
}
- $dbh->bz_alter_column('quips', 'quip', { TYPE => 'varchar(512)', NOTNULL => 1});
+ print "\n";
+ }
+ $dbh->bz_alter_column('quips', 'quip', {TYPE => 'varchar(512)', NOTNULL => 1});
}
sub _add_password_salt_separator {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- my $profiles = $dbh->selectall_arrayref("SELECT userid, cryptpassword FROM profiles WHERE ("
- . $dbh->sql_regexp("cryptpassword", "'^[^,]+{'") . ")");
+ my $profiles
+ = $dbh->selectall_arrayref(
+ "SELECT userid, cryptpassword FROM profiles WHERE ("
+ . $dbh->sql_regexp("cryptpassword", "'^[^,]+{'")
+ . ")");
- if (@$profiles) {
- say "Adding salt separator to password hashes...";
+ if (@$profiles) {
+ say "Adding salt separator to password hashes...";
- my $query = $dbh->prepare("UPDATE profiles SET cryptpassword = ? WHERE userid = ?");
- my %algo_sizes;
+ my $query
+ = $dbh->prepare("UPDATE profiles SET cryptpassword = ? WHERE userid = ?");
+ my %algo_sizes;
- foreach my $profile (@$profiles) {
- my ($userid, $hash) = @$profile;
- my ($algorithm) = $hash =~ /{([^}]+)}$/;
+ foreach my $profile (@$profiles) {
+ my ($userid, $hash) = @$profile;
+ my ($algorithm) = $hash =~ /{([^}]+)}$/;
- $algo_sizes{$algorithm} ||= length(Digest->new($algorithm)->b64digest);
+ $algo_sizes{$algorithm} ||= length(Digest->new($algorithm)->b64digest);
- # Calculate the salt length by taking the stored hash and
- # subtracting the combined lengths of the hash size, the
- # algorithm name, and 2 for the {} surrounding the name.
- my $not_salt_len = $algo_sizes{$algorithm} + length($algorithm) + 2;
- my $salt_len = length($hash) - $not_salt_len;
+ # Calculate the salt length by taking the stored hash and
+ # subtracting the combined lengths of the hash size, the
+ # algorithm name, and 2 for the {} surrounding the name.
+ my $not_salt_len = $algo_sizes{$algorithm} + length($algorithm) + 2;
+ my $salt_len = length($hash) - $not_salt_len;
- substr($hash, $salt_len, 0, ',');
- $query->execute($hash, $userid);
- }
+ substr($hash, $salt_len, 0, ',');
+ $query->execute($hash, $userid);
}
- $dbh->bz_commit_transaction();
+ }
+ $dbh->bz_commit_transaction();
}
sub _fix_flagclusions_indexes {
- my $dbh = Bugzilla->dbh;
- foreach my $table ('flaginclusions', 'flagexclusions') {
- my $index = $table . '_type_id_idx';
- my $idx_info = $dbh->bz_index_info($table, $index);
- if ($idx_info && $idx_info->{'TYPE'} ne 'UNIQUE') {
- # Remove duplicated entries
- my $dupes = $dbh->selectall_arrayref("
+ my $dbh = Bugzilla->dbh;
+ foreach my $table ('flaginclusions', 'flagexclusions') {
+ my $index = $table . '_type_id_idx';
+ my $idx_info = $dbh->bz_index_info($table, $index);
+ if ($idx_info && $idx_info->{'TYPE'} ne 'UNIQUE') {
+
+ # Remove duplicated entries
+ my $dupes = $dbh->selectall_arrayref("
SELECT type_id, product_id, component_id, COUNT(*) AS count
- FROM $table " .
- $dbh->sql_group_by('type_id, product_id, component_id') . "
- HAVING COUNT(*) > 1",
- { Slice => {} });
- say "Removing duplicated entries from the '$table' table..." if @$dupes;
- foreach my $dupe (@$dupes) {
- $dbh->do("DELETE FROM $table
+ FROM $table "
+ . $dbh->sql_group_by('type_id, product_id, component_id') . "
+ HAVING COUNT(*) > 1", {Slice => {}});
+ say "Removing duplicated entries from the '$table' table..." if @$dupes;
+ foreach my $dupe (@$dupes) {
+ $dbh->do(
+ "DELETE FROM $table
WHERE type_id = ? AND product_id = ? AND component_id = ?",
- undef, $dupe->{type_id}, $dupe->{product_id}, $dupe->{component_id});
- $dbh->do("INSERT INTO $table (type_id, product_id, component_id) VALUES (?, ?, ?)",
- undef, $dupe->{type_id}, $dupe->{product_id}, $dupe->{component_id});
- }
- $dbh->bz_drop_index($table, $index);
- $dbh->bz_add_index($table, $index,
- { FIELDS => [qw(type_id product_id component_id)],
- TYPE => 'UNIQUE' });
- }
+ undef, $dupe->{type_id}, $dupe->{product_id}, $dupe->{component_id}
+ );
+ $dbh->do(
+ "INSERT INTO $table (type_id, product_id, component_id) VALUES (?, ?, ?)",
+ undef, $dupe->{type_id}, $dupe->{product_id}, $dupe->{component_id});
+ }
+ $dbh->bz_drop_index($table, $index);
+ $dbh->bz_add_index($table, $index,
+ {FIELDS => [qw(type_id product_id component_id)], TYPE => 'UNIQUE'});
}
+ }
}
sub _fix_components_primary_key {
- my $dbh = Bugzilla->dbh;
- if ($dbh->bz_column_info('components', 'id')->{TYPE} ne 'MEDIUMSERIAL') {
- $dbh->bz_drop_related_fks('components', 'id');
- $dbh->bz_alter_column("components", "id",
- {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
- $dbh->bz_alter_column("flaginclusions", "component_id",
- {TYPE => 'INT3'});
- $dbh->bz_alter_column("flagexclusions", "component_id",
- {TYPE => 'INT3'});
- $dbh->bz_alter_column("bugs", "component_id",
- {TYPE => 'INT3', NOTNULL => 1});
- $dbh->bz_alter_column("component_cc", "component_id",
- {TYPE => 'INT3', NOTNULL => 1});
- }
+ my $dbh = Bugzilla->dbh;
+ if ($dbh->bz_column_info('components', 'id')->{TYPE} ne 'MEDIUMSERIAL') {
+ $dbh->bz_drop_related_fks('components', 'id');
+ $dbh->bz_alter_column("components", "id",
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+ $dbh->bz_alter_column("flaginclusions", "component_id", {TYPE => 'INT3'});
+ $dbh->bz_alter_column("flagexclusions", "component_id", {TYPE => 'INT3'});
+ $dbh->bz_alter_column("bugs", "component_id", {TYPE => 'INT3', NOTNULL => 1});
+ $dbh->bz_alter_column("component_cc", "component_id",
+ {TYPE => 'INT3', NOTNULL => 1});
+ }
}
sub _fix_user_api_keys_indexes {
- my $dbh = Bugzilla->dbh;
-
- if ($dbh->bz_index_info('user_api_keys', 'user_api_keys_key')) {
- $dbh->bz_drop_index('user_api_keys', 'user_api_keys_key');
- $dbh->bz_add_index('user_api_keys', 'user_api_keys_api_key_idx',
- { FIELDS => ['api_key'], TYPE => 'UNIQUE' });
- }
- if ($dbh->bz_index_info('user_api_keys', 'user_api_keys_user_id')) {
- $dbh->bz_drop_index('user_api_keys', 'user_api_keys_user_id');
- $dbh->bz_add_index('user_api_keys', 'user_api_keys_user_id_idx', ['user_id']);
- }
+ my $dbh = Bugzilla->dbh;
+
+ if ($dbh->bz_index_info('user_api_keys', 'user_api_keys_key')) {
+ $dbh->bz_drop_index('user_api_keys', 'user_api_keys_key');
+ $dbh->bz_add_index('user_api_keys', 'user_api_keys_api_key_idx',
+ {FIELDS => ['api_key'], TYPE => 'UNIQUE'});
+ }
+ if ($dbh->bz_index_info('user_api_keys', 'user_api_keys_user_id')) {
+ $dbh->bz_drop_index('user_api_keys', 'user_api_keys_user_id');
+ $dbh->bz_add_index('user_api_keys', 'user_api_keys_user_id_idx', ['user_id']);
+ }
}
sub _update_alias {
- my $dbh = Bugzilla->dbh;
- return unless $dbh->bz_column_info('bugs', 'alias');
+ my $dbh = Bugzilla->dbh;
+ return unless $dbh->bz_column_info('bugs', 'alias');
- # We need to move the aliases from the bugs table to the bugs_aliases table
- $dbh->do(q{
+ # We need to move the aliases from the bugs table to the bugs_aliases table
+ $dbh->do(q{
INSERT INTO bugs_aliases (bug_id, alias)
SELECT bug_id, alias FROM bugs WHERE alias IS NOT NULL
- });
+ }
+ );
- $dbh->bz_drop_column('bugs', 'alias');
+ $dbh->bz_drop_column('bugs', 'alias');
}
sub _sanitize_audit_log_table {
- my $dbh = Bugzilla->dbh;
-
- # Replace hashed passwords by a generic comment.
- my $class = 'Bugzilla::User';
- my $field = 'cryptpassword';
-
- my $hashed_passwd =
- $dbh->selectcol_arrayref('SELECT added FROM audit_log WHERE class = ? AND field = ?
- AND ' . $dbh->sql_not_ilike('hashed_with_', 'added'),
- undef, ($class, $field));
- if (@$hashed_passwd) {
- say "Sanitizing hashed passwords stored in the 'audit_log' table...";
- my $sth = $dbh->prepare('UPDATE audit_log SET added = ?
- WHERE class = ? AND field = ? AND added = ?');
-
- foreach my $passwd (@$hashed_passwd) {
- my (undef, $sanitized_passwd) =
- Bugzilla::Object::_sanitize_audit_log($class, $field, [undef, $passwd]);
- $sth->execute($sanitized_passwd, $class, $field, $passwd);
- }
+ my $dbh = Bugzilla->dbh;
+
+ # Replace hashed passwords by a generic comment.
+ my $class = 'Bugzilla::User';
+ my $field = 'cryptpassword';
+
+ my $hashed_passwd = $dbh->selectcol_arrayref(
+ 'SELECT added FROM audit_log WHERE class = ? AND field = ?
+ AND '
+ . $dbh->sql_not_ilike('hashed_with_', 'added'), undef, ($class, $field)
+ );
+ if (@$hashed_passwd) {
+ say "Sanitizing hashed passwords stored in the 'audit_log' table...";
+ my $sth = $dbh->prepare(
+ 'UPDATE audit_log SET added = ?
+ WHERE class = ? AND field = ? AND added = ?'
+ );
+
+ foreach my $passwd (@$hashed_passwd) {
+ my (undef, $sanitized_passwd)
+ = Bugzilla::Object::_sanitize_audit_log($class, $field, [undef, $passwd]);
+ $sth->execute($sanitized_passwd, $class, $field, $passwd);
}
+ }
}
1;
diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm
index a96fd59aa..79118de59 100644
--- a/Bugzilla/Install/Filesystem.pm
+++ b/Bugzilla/Install/Filesystem.pm
@@ -36,11 +36,11 @@ use POSIX ();
use parent qw(Exporter);
our @EXPORT = qw(
- update_filesystem
- create_htaccess
- fix_all_file_permissions
- fix_dir_permissions
- fix_file_permissions
+ update_filesystem
+ create_htaccess
+ fix_all_file_permissions
+ fix_dir_permissions
+ fix_file_permissions
);
use constant HT_DEFAULT_DENY => <<EOT;
@@ -64,52 +64,60 @@ EOT
###############
# Used by the permissions "constants" below.
-sub _suexec { Bugzilla->localconfig->{'use_suexec'} };
-sub _group { Bugzilla->localconfig->{'webservergroup'} };
+sub _suexec { Bugzilla->localconfig->{'use_suexec'} }
+sub _group { Bugzilla->localconfig->{'webservergroup'} }
# Writeable by the owner only.
use constant OWNER_WRITE => 0600;
+
# Executable by the owner only.
use constant OWNER_EXECUTE => 0700;
+
# A directory which is only writeable by the owner.
use constant DIR_OWNER_WRITE => 0700;
# A cgi script that the webserver can execute.
-sub WS_EXECUTE { _group() ? 0750 : 0755 };
+sub WS_EXECUTE { _group() ? 0750 : 0755 }
+
# A file that is read by cgi scripts, but is not ever read
# directly by the webserver.
-sub CGI_READ { _group() ? 0640 : 0644 };
+sub CGI_READ { _group() ? 0640 : 0644 }
+
# A file that is written to by cgi scripts, but is not ever
# read or written directly by the webserver.
-sub CGI_WRITE { _group() ? 0660 : 0666 };
+sub CGI_WRITE { _group() ? 0660 : 0666 }
+
# A file that is served directly by the web server.
-sub WS_SERVE { (_group() and !_suexec()) ? 0640 : 0644 };
+sub WS_SERVE { (_group() and !_suexec()) ? 0640 : 0644 }
# A directory whose contents can be read or served by the
# webserver (so even directories containing cgi scripts
# would have this permission).
-sub DIR_WS_SERVE { (_group() and !_suexec()) ? 0750 : 0755 };
+sub DIR_WS_SERVE { (_group() and !_suexec()) ? 0750 : 0755 }
+
# A directory that is read by cgi scripts, but is never accessed
# directly by the webserver
-sub DIR_CGI_READ { _group() ? 0750 : 0755 };
+sub DIR_CGI_READ { _group() ? 0750 : 0755 }
+
# A directory that is written to by cgi scripts, but where the
# scripts never needs to overwrite files created by other
# users.
-sub DIR_CGI_WRITE { _group() ? 0770 : 01777 };
+sub DIR_CGI_WRITE { _group() ? 0770 : 01777 }
+
# A directory that is written to by cgi scripts, where the
# scripts need to overwrite files created by other users.
-sub DIR_CGI_OVERWRITE { _group() ? 0770 : 0777 };
+sub DIR_CGI_OVERWRITE { _group() ? 0770 : 0777 }
-# This can be combined (using "|") with other permissions for
+# This can be combined (using "|") with other permissions for
# directories that, in addition to their normal permissions (such
# as DIR_CGI_WRITE) also have content served directly from them
# (or their subdirectories) to the user, via the webserver.
-sub DIR_ALSO_WS_SERVE { _suexec() ? 0001 : 0 };
+sub DIR_ALSO_WS_SERVE { _suexec() ? 0001 : 0 }
# 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
+# 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 there is a group the
@@ -117,196 +125,175 @@ sub DIR_ALSO_WS_SERVE { _suexec() ? 0001 : 0 };
# by this group. 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 $localconfig = bz_locations()->{'localconfig'};
- my $template_cache = bz_locations()->{'template_cache'};
- my $graphsdir = bz_locations()->{'graphsdir'};
- my $assetsdir = bz_locations()->{'assetsdir'};
-
- # We want to set the permissions the same for all localconfig files
- # across all PROJECTs, so we do something special with $localconfig,
- # lower down in the permissions section.
- if ($ENV{PROJECT}) {
- $localconfig =~ s/\.\Q$ENV{PROJECT}\E$//;
- }
-
- # 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 => OWNER_WRITE },
- # Some .pl files are WS_EXECUTE because we want
- # users to be able to cron them or otherwise run
- # them as a secure user, like the webserver owner.
- '*.cgi' => { perms => WS_EXECUTE },
- 'whineatnews.pl' => { perms => WS_EXECUTE },
- 'collectstats.pl' => { perms => WS_EXECUTE },
- 'importxml.pl' => { perms => WS_EXECUTE },
- 'testserver.pl' => { perms => WS_EXECUTE },
- 'whine.pl' => { perms => WS_EXECUTE },
- 'email_in.pl' => { perms => WS_EXECUTE },
- 'sanitycheck.pl' => { perms => WS_EXECUTE },
- 'checksetup.pl' => { perms => OWNER_EXECUTE },
- 'runtests.pl' => { perms => OWNER_EXECUTE },
- 'jobqueue.pl' => { perms => WS_EXECUTE },
- 'migrate.pl' => { perms => OWNER_EXECUTE },
- 'install-module.pl' => { perms => OWNER_EXECUTE },
- 'clean-bug-user-last-visit.pl' => { perms => WS_EXECUTE },
-
- 'Bugzilla.pm' => { perms => CGI_READ },
- "$localconfig*" => { perms => CGI_READ },
- 'bugzilla.dtd' => { perms => WS_SERVE },
- 'mod_perl.pl' => { perms => WS_SERVE },
- 'robots.txt' => { perms => WS_SERVE },
- '.htaccess' => { perms => WS_SERVE },
-
- 'contrib/README' => { perms => OWNER_WRITE },
- 'contrib/*/README' => { perms => OWNER_WRITE },
- 'contrib/Bugzilla.pm' => { perms => OWNER_WRITE },
- 'docs/bugzilla.ent' => { perms => OWNER_WRITE },
- 'docs/makedocs.pl' => { perms => OWNER_EXECUTE },
- 'docs/style.css' => { perms => WS_SERVE },
- 'docs/*/rel_notes.txt' => { perms => WS_SERVE },
- 'docs/*/README.docs' => { perms => OWNER_WRITE },
- "$datadir/params.json" => { perms => CGI_WRITE },
- "$datadir/old-params.txt" => { perms => OWNER_WRITE },
- "$extensionsdir/create.pl" => { perms => OWNER_EXECUTE },
- "$extensionsdir/*/*.pl" => { perms => WS_EXECUTE },
- );
-
- # 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 = (
- '.' => DIR_WS_SERVE,
- docs => DIR_WS_SERVE,
- );
-
- # 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
- $template_cache => { files => CGI_READ,
- dirs => DIR_CGI_OVERWRITE },
- $attachdir => { files => CGI_WRITE,
- dirs => DIR_CGI_WRITE },
- $webdotdir => { files => WS_SERVE,
- dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE },
- $graphsdir => { files => WS_SERVE,
- dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE },
- "$datadir/db" => { files => CGI_WRITE,
- dirs => DIR_CGI_WRITE },
- $assetsdir => { files => WS_SERVE,
- dirs => DIR_CGI_OVERWRITE | DIR_ALSO_WS_SERVE },
-
- # Readable directories
- "$datadir/mining" => { files => CGI_READ,
- dirs => DIR_CGI_READ },
- "$libdir/Bugzilla" => { files => CGI_READ,
- dirs => DIR_CGI_READ },
- $extlib => { files => CGI_READ,
- dirs => DIR_CGI_READ },
- $templatedir => { files => CGI_READ,
- dirs => DIR_CGI_READ },
- # Directories in the extensions/ dir are WS_SERVE so that
- # the web/ directories can be served by the web server.
- # But, for extra security, we deny direct webserver access to
- # the lib/ and template/ directories of extensions.
- $extensionsdir => { files => CGI_READ,
- dirs => DIR_WS_SERVE },
- "$extensionsdir/*/lib" => { files => CGI_READ,
- dirs => DIR_CGI_READ },
- "$extensionsdir/*/template" => { files => CGI_READ,
- dirs => DIR_CGI_READ },
-
- # Content served directly by the webserver
- images => { files => WS_SERVE,
- dirs => DIR_WS_SERVE },
- js => { files => WS_SERVE,
- dirs => DIR_WS_SERVE },
- $skinsdir => { files => WS_SERVE,
- dirs => DIR_WS_SERVE },
- 'docs/*/html' => { files => WS_SERVE,
- dirs => DIR_WS_SERVE },
- 'docs/*/pdf' => { files => WS_SERVE,
- dirs => DIR_WS_SERVE },
- 'docs/*/txt' => { files => WS_SERVE,
- dirs => DIR_WS_SERVE },
- 'docs/*/images' => { files => WS_SERVE,
- dirs => DIR_WS_SERVE },
- "$extensionsdir/*/web" => { files => WS_SERVE,
- dirs => DIR_WS_SERVE },
-
- # Directories only for the owner, not for the webserver.
- '.bzr' => { files => OWNER_WRITE,
- dirs => DIR_OWNER_WRITE },
- t => { files => OWNER_WRITE,
- dirs => DIR_OWNER_WRITE },
- xt => { files => OWNER_WRITE,
- dirs => DIR_OWNER_WRITE },
- 'docs/lib' => { files => OWNER_WRITE,
- dirs => DIR_OWNER_WRITE },
- 'docs/*/xml' => { files => OWNER_WRITE,
- dirs => DIR_OWNER_WRITE },
- 'contrib' => { files => OWNER_EXECUTE,
- dirs => DIR_OWNER_WRITE, },
- );
-
- # --- FILES TO CREATE --- #
-
- # The name of each directory that we should actually *create*,
- # pointing at its default permissions.
- my %create_dirs = (
- # This is DIR_ALSO_WS_SERVE because it contains $webdotdir and
- # $assetsdir.
- $datadir => DIR_CGI_OVERWRITE | DIR_ALSO_WS_SERVE,
- # Directories that are read-only for cgi scripts
- "$datadir/mining" => DIR_CGI_READ,
- "$datadir/extensions" => DIR_CGI_READ,
- $extensionsdir => DIR_CGI_READ,
- # Directories that cgi scripts can write to.
- "$datadir/db" => DIR_CGI_WRITE,
- $attachdir => DIR_CGI_WRITE,
- $graphsdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE,
- $webdotdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE,
- $assetsdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE,
- # Directories that contain content served directly by the web server.
- "$skinsdir/custom" => DIR_WS_SERVE,
- "$skinsdir/contrib" => DIR_WS_SERVE,
- );
-
- # The name of each file, pointing at its default permissions and
- # default contents.
- my %create_files = (
- "$datadir/extensions/additional" => { perms => CGI_READ,
- contents => '' },
- # We create this file so that it always has the right owner
- # and permissions. Otherwise, the webserver creates it as
- # owned by itself, which can cause problems if jobqueue.pl
- # or something else is not running as the webserver or root.
- "$datadir/mailer.testfile" => { perms => CGI_WRITE,
- contents => '' },
- );
-
- # 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_SERVE, contents => <<EOT
+ 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 $localconfig = bz_locations()->{'localconfig'};
+ my $template_cache = bz_locations()->{'template_cache'};
+ my $graphsdir = bz_locations()->{'graphsdir'};
+ my $assetsdir = bz_locations()->{'assetsdir'};
+
+ # We want to set the permissions the same for all localconfig files
+ # across all PROJECTs, so we do something special with $localconfig,
+ # lower down in the permissions section.
+ if ($ENV{PROJECT}) {
+ $localconfig =~ s/\.\Q$ENV{PROJECT}\E$//;
+ }
+
+ # 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 => OWNER_WRITE},
+
+ # Some .pl files are WS_EXECUTE because we want
+ # users to be able to cron them or otherwise run
+ # them as a secure user, like the webserver owner.
+ '*.cgi' => {perms => WS_EXECUTE},
+ 'whineatnews.pl' => {perms => WS_EXECUTE},
+ 'collectstats.pl' => {perms => WS_EXECUTE},
+ 'importxml.pl' => {perms => WS_EXECUTE},
+ 'testserver.pl' => {perms => WS_EXECUTE},
+ 'whine.pl' => {perms => WS_EXECUTE},
+ 'email_in.pl' => {perms => WS_EXECUTE},
+ 'sanitycheck.pl' => {perms => WS_EXECUTE},
+ 'checksetup.pl' => {perms => OWNER_EXECUTE},
+ 'runtests.pl' => {perms => OWNER_EXECUTE},
+ 'jobqueue.pl' => {perms => WS_EXECUTE},
+ 'migrate.pl' => {perms => OWNER_EXECUTE},
+ 'install-module.pl' => {perms => OWNER_EXECUTE},
+ 'clean-bug-user-last-visit.pl' => {perms => WS_EXECUTE},
+
+ 'Bugzilla.pm' => {perms => CGI_READ},
+ "$localconfig*" => {perms => CGI_READ},
+ 'bugzilla.dtd' => {perms => WS_SERVE},
+ 'mod_perl.pl' => {perms => WS_SERVE},
+ 'robots.txt' => {perms => WS_SERVE},
+ '.htaccess' => {perms => WS_SERVE},
+
+ 'contrib/README' => {perms => OWNER_WRITE},
+ 'contrib/*/README' => {perms => OWNER_WRITE},
+ 'contrib/Bugzilla.pm' => {perms => OWNER_WRITE},
+ 'docs/bugzilla.ent' => {perms => OWNER_WRITE},
+ 'docs/makedocs.pl' => {perms => OWNER_EXECUTE},
+ 'docs/style.css' => {perms => WS_SERVE},
+ 'docs/*/rel_notes.txt' => {perms => WS_SERVE},
+ 'docs/*/README.docs' => {perms => OWNER_WRITE},
+ "$datadir/params.json" => {perms => CGI_WRITE},
+ "$datadir/old-params.txt" => {perms => OWNER_WRITE},
+ "$extensionsdir/create.pl" => {perms => OWNER_EXECUTE},
+ "$extensionsdir/*/*.pl" => {perms => WS_EXECUTE},
+ );
+
+ # 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 = ('.' => DIR_WS_SERVE, docs => DIR_WS_SERVE,);
+
+ # 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
+ $template_cache => {files => CGI_READ, dirs => DIR_CGI_OVERWRITE},
+ $attachdir => {files => CGI_WRITE, dirs => DIR_CGI_WRITE},
+ $webdotdir => {files => WS_SERVE, dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE},
+ $graphsdir => {files => WS_SERVE, dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE},
+ "$datadir/db" => {files => CGI_WRITE, dirs => DIR_CGI_WRITE},
+ $assetsdir =>
+ {files => WS_SERVE, dirs => DIR_CGI_OVERWRITE | DIR_ALSO_WS_SERVE},
+
+ # Readable directories
+ "$datadir/mining" => {files => CGI_READ, dirs => DIR_CGI_READ},
+ "$libdir/Bugzilla" => {files => CGI_READ, dirs => DIR_CGI_READ},
+ $extlib => {files => CGI_READ, dirs => DIR_CGI_READ},
+ $templatedir => {files => CGI_READ, dirs => DIR_CGI_READ},
+
+ # Directories in the extensions/ dir are WS_SERVE so that
+ # the web/ directories can be served by the web server.
+ # But, for extra security, we deny direct webserver access to
+ # the lib/ and template/ directories of extensions.
+ $extensionsdir => {files => CGI_READ, dirs => DIR_WS_SERVE},
+ "$extensionsdir/*/lib" => {files => CGI_READ, dirs => DIR_CGI_READ},
+ "$extensionsdir/*/template" => {files => CGI_READ, dirs => DIR_CGI_READ},
+
+ # Content served directly by the webserver
+ images => {files => WS_SERVE, dirs => DIR_WS_SERVE},
+ js => {files => WS_SERVE, dirs => DIR_WS_SERVE},
+ $skinsdir => {files => WS_SERVE, dirs => DIR_WS_SERVE},
+ 'docs/*/html' => {files => WS_SERVE, dirs => DIR_WS_SERVE},
+ 'docs/*/pdf' => {files => WS_SERVE, dirs => DIR_WS_SERVE},
+ 'docs/*/txt' => {files => WS_SERVE, dirs => DIR_WS_SERVE},
+ 'docs/*/images' => {files => WS_SERVE, dirs => DIR_WS_SERVE},
+ "$extensionsdir/*/web" => {files => WS_SERVE, dirs => DIR_WS_SERVE},
+
+ # Directories only for the owner, not for the webserver.
+ '.bzr' => {files => OWNER_WRITE, dirs => DIR_OWNER_WRITE},
+ t => {files => OWNER_WRITE, dirs => DIR_OWNER_WRITE},
+ xt => {files => OWNER_WRITE, dirs => DIR_OWNER_WRITE},
+ 'docs/lib' => {files => OWNER_WRITE, dirs => DIR_OWNER_WRITE},
+ 'docs/*/xml' => {files => OWNER_WRITE, dirs => DIR_OWNER_WRITE},
+ 'contrib' => {files => OWNER_EXECUTE, dirs => DIR_OWNER_WRITE,},
+ );
+
+ # --- FILES TO CREATE --- #
+
+ # The name of each directory that we should actually *create*,
+ # pointing at its default permissions.
+ my %create_dirs = (
+
+ # This is DIR_ALSO_WS_SERVE because it contains $webdotdir and
+ # $assetsdir.
+ $datadir => DIR_CGI_OVERWRITE | DIR_ALSO_WS_SERVE,
+
+ # Directories that are read-only for cgi scripts
+ "$datadir/mining" => DIR_CGI_READ,
+ "$datadir/extensions" => DIR_CGI_READ,
+ $extensionsdir => DIR_CGI_READ,
+
+ # Directories that cgi scripts can write to.
+ "$datadir/db" => DIR_CGI_WRITE,
+ $attachdir => DIR_CGI_WRITE,
+ $graphsdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE,
+ $webdotdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE,
+ $assetsdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE,
+
+ # Directories that contain content served directly by the web server.
+ "$skinsdir/custom" => DIR_WS_SERVE,
+ "$skinsdir/contrib" => DIR_WS_SERVE,
+ );
+
+ # The name of each file, pointing at its default permissions and
+ # default contents.
+ my %create_files = (
+ "$datadir/extensions/additional" => {perms => CGI_READ, contents => ''},
+
+ # We create this file so that it always has the right owner
+ # and permissions. Otherwise, the webserver creates it as
+ # owned by itself, which can cause problems if jobqueue.pl
+ # or something else is not running as the webserver or root.
+ "$datadir/mailer.testfile" => {perms => CGI_WRITE, contents => ''},
+ );
+
+ # 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_SERVE,
+ contents => <<EOT
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
@@ -317,35 +304,30 @@ sub FILESYSTEM {
</body>
</html>
EOT
- }
- );
-
- # Because checksetup controls the .htaccess creation separately
- # by a localconfig variable, these go in a separate variable from
- # %create_files.
- #
- # Note that these get WS_SERVE as their permission
- # because they're *read* by the webserver, even though they're not
- # actually, themselves, served.
- my %htaccess = (
- "$attachdir/.htaccess" => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- "$libdir/Bugzilla/.htaccess" => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- "$extlib/.htaccess" => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- "$templatedir/.htaccess" => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- 'contrib/.htaccess' => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- 't/.htaccess' => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- 'xt/.htaccess' => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
- "$datadir/.htaccess" => { perms => WS_SERVE,
- contents => HT_DEFAULT_DENY },
-
- "$graphsdir/.htaccess" => { perms => WS_SERVE, contents => <<EOT
+ }
+ );
+
+ # Because checksetup controls the .htaccess creation separately
+ # by a localconfig variable, these go in a separate variable from
+ # %create_files.
+ #
+ # Note that these get WS_SERVE as their permission
+ # because they're *read* by the webserver, even though they're not
+ # actually, themselves, served.
+ my %htaccess = (
+ "$attachdir/.htaccess" => {perms => WS_SERVE, contents => HT_DEFAULT_DENY},
+ "$libdir/Bugzilla/.htaccess" =>
+ {perms => WS_SERVE, contents => HT_DEFAULT_DENY},
+ "$extlib/.htaccess" => {perms => WS_SERVE, contents => HT_DEFAULT_DENY},
+ "$templatedir/.htaccess" => {perms => WS_SERVE, contents => HT_DEFAULT_DENY},
+ 'contrib/.htaccess' => {perms => WS_SERVE, contents => HT_DEFAULT_DENY},
+ 't/.htaccess' => {perms => WS_SERVE, contents => HT_DEFAULT_DENY},
+ 'xt/.htaccess' => {perms => WS_SERVE, contents => HT_DEFAULT_DENY},
+ "$datadir/.htaccess" => {perms => WS_SERVE, contents => HT_DEFAULT_DENY},
+
+ "$graphsdir/.htaccess" => {
+ perms => WS_SERVE,
+ contents => <<EOT
# Allow access to .png and .gif files.
<FilesMatch (\\.gif|\\.png)\$>
<IfModule mod_version.c>
@@ -374,9 +356,11 @@ EOT
Deny from all
</IfModule>
EOT
- },
+ },
- "$webdotdir/.htaccess" => { perms => WS_SERVE, contents => <<EOT
+ "$webdotdir/.htaccess" => {
+ perms => WS_SERVE,
+ 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
@@ -425,9 +409,11 @@ EOT
Deny from all
</IfModule>
EOT
- },
+ },
- "$assetsdir/.htaccess" => { perms => WS_SERVE, contents => <<EOT
+ "$assetsdir/.htaccess" => {
+ perms => WS_SERVE,
+ contents => <<EOT
# Allow access to .css files
<FilesMatch \\.(css|js)\$>
<IfModule mod_version.c>
@@ -456,97 +442,102 @@ EOT
Deny from all
</IfModule>
EOT
- },
-
- );
-
- Bugzilla::Hook::process('install_filesystem', {
- files => \%files,
- create_dirs => \%create_dirs,
- non_recurse_dirs => \%non_recurse_dirs,
- recurse_dirs => \%recurse_dirs,
- create_files => \%create_files,
- htaccess => \%htaccess,
- });
-
- 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'};
- my $graphsdir = bz_locations->{'graphsdir'};
- my $assetsdir = bz_locations->{'assetsdir'};
- # 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 $graphsdir) {
- _update_old_charts($datadir);
- }
+ );
- # If there is a file named '-All-' in $datadir/mining, then we're still
- # having mining files named by product name, and we need to convert them to
- # files named by product ID.
- if (-e File::Spec->catfile($datadir, 'mining', '-All-')) {
- _update_old_mining_filenames(File::Spec->catdir($datadir, 'mining'));
+ Bugzilla::Hook::process(
+ 'install_filesystem',
+ {
+ files => \%files,
+ create_dirs => \%create_dirs,
+ non_recurse_dirs => \%non_recurse_dirs,
+ recurse_dirs => \%recurse_dirs,
+ create_files => \%create_files,
+ htaccess => \%htaccess,
}
+ );
- # 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 or die "mkdir $dir failed: $!";
- # For some reason, passing in the permissions to "mkdir"
- # doesn't work right, but doing a "chmod" does.
- chmod $dirs{$dir}, $dir or warn "Cannot chmod $dir: $!";
- }
- }
+ my %all_files = (%create_files, %htaccess, %index_html, %files);
+ my %all_dirs = (%create_dirs, %non_recurse_dirs);
- # Move the testfile if we can't write to it, so that we can re-create
- # it with the correct permissions below.
- my $testfile = "$datadir/mailer.testfile";
- if (-e $testfile and !-w $testfile) {
- _rename_file($testfile, "$testfile.old");
- }
+ return {
+ create_dirs => \%create_dirs,
+ recurse_dirs => \%recurse_dirs,
+ all_dirs => \%all_dirs,
- # If old-params.txt exists in the root directory, move it to datadir.
- my $oldparamsfile = "old_params.txt";
- if (-e $oldparamsfile) {
- _rename_file($oldparamsfile, "$datadir/$oldparamsfile");
- }
+ create_files => \%create_files,
+ htaccess => \%htaccess,
+ index_html => \%index_html,
+ all_files => \%all_files,
+ };
+}
- # Remove old assets htaccess file to force recreation with correct values.
- if (-e "$assetsdir/.htaccess") {
- if (read_text("$assetsdir/.htaccess") =~ /<FilesMatch \\\.css\$>/) {
- unlink("$assetsdir/.htaccess");
- }
+sub update_filesystem {
+ my ($params) = @_;
+ my $fs = FILESYSTEM();
+ my %dirs = %{$fs->{create_dirs}};
+ my %files = %{$fs->{create_files}};
+
+ my $datadir = bz_locations->{'datadir'};
+ my $graphsdir = bz_locations->{'graphsdir'};
+ my $assetsdir = bz_locations->{'assetsdir'};
+
+ # 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 $graphsdir) {
+ _update_old_charts($datadir);
+ }
+
+ # If there is a file named '-All-' in $datadir/mining, then we're still
+ # having mining files named by product name, and we need to convert them to
+ # files named by product ID.
+ if (-e File::Spec->catfile($datadir, 'mining', '-All-')) {
+ _update_old_mining_filenames(File::Spec->catdir($datadir, 'mining'));
+ }
+
+ # 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 or die "mkdir $dir failed: $!";
+
+ # For some reason, passing in the permissions to "mkdir"
+ # doesn't work right, but doing a "chmod" does.
+ chmod $dirs{$dir}, $dir or warn "Cannot chmod $dir: $!";
}
-
- _create_files(%files);
- if ($params->{index_html}) {
- _create_files(%{$fs->{index_html}});
+ }
+
+ # Move the testfile if we can't write to it, so that we can re-create
+ # it with the correct permissions below.
+ my $testfile = "$datadir/mailer.testfile";
+ if (-e $testfile and !-w $testfile) {
+ _rename_file($testfile, "$testfile.old");
+ }
+
+ # If old-params.txt exists in the root directory, move it to datadir.
+ my $oldparamsfile = "old_params.txt";
+ if (-e $oldparamsfile) {
+ _rename_file($oldparamsfile, "$datadir/$oldparamsfile");
+ }
+
+ # Remove old assets htaccess file to force recreation with correct values.
+ if (-e "$assetsdir/.htaccess") {
+ if (read_text("$assetsdir/.htaccess") =~ /<FilesMatch \\\.css\$>/) {
+ unlink("$assetsdir/.htaccess");
}
- elsif (-e 'index.html') {
- my $templatedir = bz_locations()->{'templatedir'};
- print <<EOT;
+ }
+
+ _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
@@ -554,445 +545,477 @@ sub update_filesystem {
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";
- }
-
- if (-e "$datadir/duplicates.rdf") {
- print "Removing duplicates.rdf...\n";
- unlink "$datadir/duplicates.rdf";
- unlink "$datadir/duplicates-old.rdf";
- }
-
- if (-e "$datadir/duplicates") {
- print "Removing duplicates directory...\n";
- rmtree("$datadir/duplicates");
- }
-
- _remove_empty_css_files();
- _convert_single_file_skins();
- _remove_dynamic_assets();
+ }
+
+ # 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";
+ }
+
+ if (-e "$datadir/duplicates.rdf") {
+ print "Removing duplicates.rdf...\n";
+ unlink "$datadir/duplicates.rdf";
+ unlink "$datadir/duplicates-old.rdf";
+ }
+
+ if (-e "$datadir/duplicates") {
+ print "Removing duplicates directory...\n";
+ rmtree("$datadir/duplicates");
+ }
+
+ _remove_empty_css_files();
+ _convert_single_file_skins();
+ _remove_dynamic_assets();
}
sub _remove_empty_css_files {
- my $skinsdir = bz_locations()->{'skinsdir'};
- foreach my $css_file (glob("$skinsdir/custom/*.css"),
- glob("$skinsdir/contrib/*/*.css"))
- {
- _remove_empty_css($css_file);
- }
+ my $skinsdir = bz_locations()->{'skinsdir'};
+ foreach my $css_file (glob("$skinsdir/custom/*.css"),
+ glob("$skinsdir/contrib/*/*.css"))
+ {
+ _remove_empty_css($css_file);
+ }
}
# A simple helper for the update code that removes "empty" CSS files.
sub _remove_empty_css {
- my ($file) = @_;
- my $basename = basename($file);
- my $empty_contents = <<EOT;
+ my ($file) = @_;
+ my $basename = basename($file);
+ my $empty_contents = <<EOT;
/*
* Custom rules for $basename.
* The rules you put here override rules in that stylesheet.
*/
EOT
- if (length($empty_contents) == -s $file) {
- open(my $fh, '<', $file) or warn "$file: $!";
- my $file_contents;
- { local $/; $file_contents = <$fh>; }
- if ($file_contents eq $empty_contents) {
- print install_string('file_remove', { name => $file }), "\n";
- unlink $file or warn "$file: $!";
- }
- };
+ if (length($empty_contents) == -s $file) {
+ open(my $fh, '<', $file) or warn "$file: $!";
+ my $file_contents;
+ { local $/; $file_contents = <$fh>; }
+ if ($file_contents eq $empty_contents) {
+ print install_string('file_remove', {name => $file}), "\n";
+ unlink $file or warn "$file: $!";
+ }
+ }
}
# We used to allow a single css file in the skins/contrib/ directory
# to be a whole skin.
sub _convert_single_file_skins {
- my $skinsdir = bz_locations()->{'skinsdir'};
- foreach my $skin_file (glob "$skinsdir/contrib/*.css") {
- my $dir_name = $skin_file;
- $dir_name =~ s/\.css$//;
- mkdir $dir_name or warn "$dir_name: $!";
- _rename_file($skin_file, "$dir_name/global.css");
- }
+ my $skinsdir = bz_locations()->{'skinsdir'};
+ foreach my $skin_file (glob "$skinsdir/contrib/*.css") {
+ my $dir_name = $skin_file;
+ $dir_name =~ s/\.css$//;
+ mkdir $dir_name or warn "$dir_name: $!";
+ _rename_file($skin_file, "$dir_name/global.css");
+ }
}
# delete all automatically generated css/js files to force recreation at the
# next request.
sub _remove_dynamic_assets {
- my @files = (
- glob(bz_locations()->{assetsdir} . '/*.css'),
- glob(bz_locations()->{assetsdir} . '/*.js'),
- );
- foreach my $file (@files) {
- unlink($file);
- }
-
- # remove old skins/assets directory
- my $old_path = bz_locations()->{skinsdir} . '/assets';
- if (-d $old_path) {
- foreach my $file (glob("$old_path/*.css")) {
- unlink($file);
- }
- rmdir($old_path);
+ my @files = (
+ glob(bz_locations()->{assetsdir} . '/*.css'),
+ glob(bz_locations()->{assetsdir} . '/*.js'),
+ );
+ foreach my $file (@files) {
+ unlink($file);
+ }
+
+ # remove old skins/assets directory
+ my $old_path = bz_locations()->{skinsdir} . '/assets';
+ if (-d $old_path) {
+ foreach my $file (glob("$old_path/*.css")) {
+ unlink($file);
}
+ rmdir($old_path);
+ }
}
sub create_htaccess {
- _create_files(%{FILESYSTEM()->{htaccess}});
-
- # Repair old .htaccess files
-
- 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>; }
+ _create_files(%{FILESYSTEM()->{htaccess}});
+
+ # Repair old .htaccess files
+
+ 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;
- 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;
- }
+ }
}
sub _rename_file {
- my ($from, $to) = @_;
- print install_string('file_rename', { from => $from, to => $to }), "\n";
- if (-e $to) {
- warn "$to already exists, not moving\n";
- }
- else {
- move($from, $to) or warn $!;
- }
+ my ($from, $to) = @_;
+ print install_string('file_rename', {from => $from, to => $to}), "\n";
+ if (-e $to) {
+ warn "$to already exists, not moving\n";
+ }
+ else {
+ move($from, $to) or warn $!;
+ }
}
# 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;
- }
+ 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
+# 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;
- }
+ 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";
}
- close(IN);
- close(OUT);
- }
+ print OUT join('|', map { defined($data{$_}) ? ($data{$_}) : "" } @out_fields),
+ "\n";
+ }
+ else {
+ print OUT;
+ }
+ }
+
+ close(IN);
+ close(OUT);
+ }
}
# The old naming scheme has product names as mining file names; we rename them
# to product IDs.
sub _update_old_mining_filenames {
- my ($miningdir) = @_;
- my $dbh = Bugzilla->dbh;
- my @conversion_errors;
-
- # We use a dummy product instance with ID 0, representing all products
- my $product_all = {id => 0, name => '-All-'};
-
- print "Updating old charting data file names...";
- my @products = @{ $dbh->selectall_arrayref('SELECT id, name FROM products
- ORDER BY name', {Slice=>{}}) };
- push(@products, $product_all);
- foreach my $product (@products) {
- if (-e File::Spec->catfile($miningdir, $product->{id})) {
- push(@conversion_errors,
- { product => $product,
- message => 'A file named "' . $product->{id} .
- '" already exists.' });
+ my ($miningdir) = @_;
+ my $dbh = Bugzilla->dbh;
+ my @conversion_errors;
+
+ # We use a dummy product instance with ID 0, representing all products
+ my $product_all = {id => 0, name => '-All-'};
+
+ print "Updating old charting data file names...";
+ my @products = @{
+ $dbh->selectall_arrayref(
+ 'SELECT id, name FROM products
+ ORDER BY name', {Slice => {}}
+ )
+ };
+ push(@products, $product_all);
+ foreach my $product (@products) {
+ if (-e File::Spec->catfile($miningdir, $product->{id})) {
+ push(
+ @conversion_errors,
+ {
+ product => $product,
+ message => 'A file named "' . $product->{id} . '" already exists.'
}
+ );
}
+ }
- if (! @conversion_errors) {
- # Renaming mining files should work now without a hitch.
- foreach my $product (@products) {
- if (! rename(File::Spec->catfile($miningdir, $product->{name}),
- File::Spec->catfile($miningdir, $product->{id}))) {
- push(@conversion_errors,
- { product => $product,
- message => $! });
- }
- }
- }
+ if (!@conversion_errors) {
- # Error reporting
- if (! @conversion_errors) {
- print " done.\n";
+ # Renaming mining files should work now without a hitch.
+ foreach my $product (@products) {
+ if (
+ !rename(
+ File::Spec->catfile($miningdir, $product->{name}),
+ File::Spec->catfile($miningdir, $product->{id})
+ )
+ )
+ {
+ push(@conversion_errors, {product => $product, message => $!});
+ }
}
- else {
- print " FAILED:\n";
- foreach my $error (@conversion_errors) {
- printf "Cannot rename charting data file for product %d (%s): %s\n",
- $error->{product}->{id}, $error->{product}->{name},
- $error->{message};
- }
- print "You need to empty the \"$miningdir\" directory, then run\n",
- " collectstats.pl --regenerate\n",
- "in order to clean this up.\n";
+ }
+
+ # Error reporting
+ if (!@conversion_errors) {
+ print " done.\n";
+ }
+ else {
+ print " FAILED:\n";
+ foreach my $error (@conversion_errors) {
+ printf "Cannot rename charting data file for product %d (%s): %s\n",
+ $error->{product}->{id}, $error->{product}->{name}, $error->{message};
}
+ print "You need to empty the \"$miningdir\" directory, then run\n",
+ " collectstats.pl --regenerate\n", "in order to clean this up.\n";
+ }
}
sub fix_dir_permissions {
- my ($dir) = @_;
- return if ON_WINDOWS;
- # Note that _get_owner_and_group is always silent here.
- my ($owner_id, $group_id) = _get_owner_and_group();
-
- my $perms;
- my $fs = FILESYSTEM();
- if ($perms = $fs->{recurse_dirs}->{$dir}) {
- _fix_perms_recursively($dir, $owner_id, $group_id, $perms);
- }
- elsif ($perms = $fs->{all_dirs}->{$dir}) {
- _fix_perms($dir, $owner_id, $group_id, $perms);
- }
- else {
- # Do nothing. We know nothing about this directory.
- warn "Unknown directory $dir";
- }
+ my ($dir) = @_;
+ return if ON_WINDOWS;
+
+ # Note that _get_owner_and_group is always silent here.
+ my ($owner_id, $group_id) = _get_owner_and_group();
+
+ my $perms;
+ my $fs = FILESYSTEM();
+ if ($perms = $fs->{recurse_dirs}->{$dir}) {
+ _fix_perms_recursively($dir, $owner_id, $group_id, $perms);
+ }
+ elsif ($perms = $fs->{all_dirs}->{$dir}) {
+ _fix_perms($dir, $owner_id, $group_id, $perms);
+ }
+ else {
+ # Do nothing. We know nothing about this directory.
+ warn "Unknown directory $dir";
+ }
}
sub fix_file_permissions {
- my ($file) = @_;
- return if ON_WINDOWS;
- my $perms = FILESYSTEM()->{all_files}->{$file}->{perms};
- # Note that _get_owner_and_group is always silent here.
- my ($owner_id, $group_id) = _get_owner_and_group();
- _fix_perms($file, $owner_id, $group_id, $perms);
+ my ($file) = @_;
+ return if ON_WINDOWS;
+ my $perms = FILESYSTEM()->{all_files}->{$file}->{perms};
+
+ # Note that _get_owner_and_group is always silent here.
+ my ($owner_id, $group_id) = _get_owner_and_group();
+ _fix_perms($file, $owner_id, $group_id, $perms);
}
sub fix_all_file_permissions {
- my ($output) = @_;
+ my ($output) = @_;
- # _get_owner_and_group also checks that the webservergroup is valid.
- my ($owner_id, $group_id) = _get_owner_and_group($output);
+ # _get_owner_and_group also checks that the webservergroup is valid.
+ my ($owner_id, $group_id) = _get_owner_and_group($output);
- return if ON_WINDOWS;
+ return if ON_WINDOWS;
- my $fs = FILESYSTEM();
- my %files = %{$fs->{all_files}};
- my %dirs = %{$fs->{all_dirs}};
- my %recurse_dirs = %{$fs->{recurse_dirs}};
+ 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;
+ print get_text('install_file_perms_fix') . "\n" if $output;
- foreach my $dir (sort keys %dirs) {
- next unless -d $dir;
- _fix_perms($dir, $owner_id, $group_id, $dirs{$dir});
- }
+ foreach my $dir (sort keys %dirs) {
+ next unless -d $dir;
+ _fix_perms($dir, $owner_id, $group_id, $dirs{$dir});
+ }
- foreach my $pattern (sort keys %recurse_dirs) {
- my $perms = $recurse_dirs{$pattern};
- # %recurse_dirs supports globs
- foreach my $dir (glob $pattern) {
- next unless -d $dir;
- _fix_perms_recursively($dir, $owner_id, $group_id, $perms);
- }
+ foreach my $pattern (sort keys %recurse_dirs) {
+ my $perms = $recurse_dirs{$pattern};
+
+ # %recurse_dirs supports globs
+ foreach my $dir (glob $pattern) {
+ next unless -d $dir;
+ _fix_perms_recursively($dir, $owner_id, $group_id, $perms);
}
+ }
- 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});
- }
+ 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, '.');
+ _fix_cvs_dirs($owner_id, '.');
}
sub _get_owner_and_group {
- my ($output) = @_;
- my $group_id = _check_web_server_group($output);
- return () if ON_WINDOWS;
+ my ($output) = @_;
+ my $group_id = _check_web_server_group($output);
+ return () if ON_WINDOWS;
- my $owner_id = POSIX::getuid();
- $group_id = POSIX::getgid() unless defined $group_id;
- return ($owner_id, $group_id);
+ my $owner_id = POSIX::getuid();
+ $group_id = POSIX::getgid() unless defined $group_id;
+ return ($owner_id, $group_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 ($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 && $_ =~ /CVS$/))
+ if ( $File::Find::dir =~ /\/CVS/
+ || $_ eq '.cvsignore'
+ || (-d $name && $_ =~ /CVS$/))
{
- my $perms = 0600;
- if (-d $name) {
- $perms = 0700;
- }
- _fix_perms($name, $owner_id, $owner_gid, $perms);
+ my $perms = 0600;
+ if (-d $name) {
+ $perms = 0700;
+ }
+ _fix_perms($name, $owner_id, $owner_gid, $perms);
}
- }}, $dir);
+ }
+ },
+ $dir
+ );
}
sub _fix_perms {
- my ($name, $owner, $group, $perms) = @_;
- #printf ("Changing $name to %o\n", $perms);
-
- # The webserver should never try to chown files.
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- chown $owner, $group, $name
- or warn install_string('chown_failed', { path => $name,
- error => $! }) . "\n";
- }
- chmod $perms, $name
- or warn install_string('chmod_failed', { path => $name,
- error => $! }) . "\n";
+ my ($name, $owner, $group, $perms) = @_;
+
+ #printf ("Changing $name to %o\n", $perms);
+
+ # The webserver should never try to chown files.
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ chown $owner, $group, $name
+ or warn install_string('chown_failed', {path => $name, error => $!}) . "\n";
+ }
+ chmod $perms, $name
+ or warn install_string('chmod_failed', {path => $name, error => $!}) . "\n";
}
sub _fix_perms_recursively {
- my ($dir, $owner_id, $group_id, $perms) = @_;
- # Set permissions on the directory itself.
- _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 ($dir, $owner_id, $group_id, $perms) = @_;
+
+ # Set permissions on the directory itself.
+ _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});
+ _fix_perms($name, $owner_id, $group_id, $perms->{dirs});
}
else {
- _fix_perms($name, $owner_id, $group_id, $perms->{files});
+ _fix_perms($name, $owner_id, $group_id, $perms->{files});
}
- }}, $dir);
+ }
+ },
+ $dir
+ );
}
sub _check_web_server_group {
- my ($output) = @_;
-
- my $group = Bugzilla->localconfig->{'webservergroup'};
- 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";
- }
+ my ($output) = @_;
+
+ my $group = Bugzilla->localconfig->{'webservergroup'};
+ 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;
+ return $group_id;
}
1;
diff --git a/Bugzilla/Install/Localconfig.pm b/Bugzilla/Install/Localconfig.pm
index 7f473cc77..bc4557309 100644
--- a/Bugzilla/Install/Localconfig.pm
+++ b/Bugzilla/Install/Localconfig.pm
@@ -31,151 +31,104 @@ use Term::ANSIColor;
use parent qw(Exporter);
our @EXPORT_OK = qw(
- read_localconfig
- update_localconfig
+ read_localconfig
+ update_localconfig
);
use constant LOCALCONFIG_VARS => (
- {
- name => 'create_htaccess',
- default => 1,
- },
- {
- name => 'webservergroup',
- default => ON_WINDOWS ? '' : 'apache',
- },
- {
- name => 'use_suexec',
- default => 0,
- },
- {
- name => 'db_driver',
- default => 'mysql',
- },
- {
- name => 'db_host',
- default => 'localhost',
- },
- {
- name => 'db_name',
- default => 'bugs',
- },
- {
- name => 'db_user',
- default => 'bugs',
- },
- {
- name => 'db_pass',
- default => '',
- },
- {
- name => 'db_port',
- default => 0,
- },
- {
- name => 'db_sock',
- default => '',
- },
- {
- name => 'db_check',
- default => 1,
- },
- {
- name => 'db_mysql_ssl_ca_file',
- default => '',
- },
- {
- name => 'db_mysql_ssl_ca_path',
- default => '',
- },
- {
- name => 'db_mysql_ssl_client_cert',
- default => '',
- },
- {
- name => 'db_mysql_ssl_client_key',
- default => '',
- },
- {
- name => 'index_html',
- default => 0,
- },
- {
- name => 'interdiffbin',
- default => sub { bin_loc('interdiff') },
- },
- {
- name => 'diffpath',
- default => sub { dirname(bin_loc('diff')) },
- },
- {
- name => 'site_wide_secret',
- # 64 characters is roughly the equivalent of a 384-bit key, which
- # is larger than anybody would ever be able to brute-force.
- default => sub { generate_random_password(64) },
- },
+ {name => 'create_htaccess', default => 1,},
+ {name => 'webservergroup', default => ON_WINDOWS ? '' : 'apache',},
+ {name => 'use_suexec', default => 0,},
+ {name => 'db_driver', default => 'mysql',},
+ {name => 'db_host', default => 'localhost',},
+ {name => 'db_name', default => 'bugs',},
+ {name => 'db_user', default => 'bugs',},
+ {name => 'db_pass', default => '',},
+ {name => 'db_port', default => 0,},
+ {name => 'db_sock', default => '',},
+ {name => 'db_check', default => 1,},
+ {name => 'db_mysql_ssl_ca_file', default => '',},
+ {name => 'db_mysql_ssl_ca_path', default => '',},
+ {name => 'db_mysql_ssl_client_cert', default => '',},
+ {name => 'db_mysql_ssl_client_key', default => '',},
+ {name => 'index_html', default => 0,},
+ {name => 'interdiffbin', default => sub { bin_loc('interdiff') },},
+ {name => 'diffpath', default => sub { dirname(bin_loc('diff')) },},
+ {
+ name => 'site_wide_secret',
+
+ # 64 characters is roughly the equivalent of a 384-bit key, which
+ # is larger than anybody would ever be able to brute-force.
+ default => sub { generate_random_password(64) },
+ },
);
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 install_string('error_localconfig_read',
- { error => $err_msg, localconfig => $filename }), "\n";
- }
+ 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 install_string(
+ 'error_localconfig_read', {error => $err_msg, localconfig => $filename}
+ ),
+ "\n";
+ }
- my @read_symbols;
- if ($include_deprecated) {
- # First we have to get the whole symbol table
- my $safe_root = $s->root;
- my %safe_package;
- { no strict 'refs'; %safe_package = %{$safe_root . "::"}; }
- # And now we read the contents of every var in the symbol table.
- # However:
- # * We only include symbols that start with an alphanumeric
- # character. This excludes symbols like "_<./localconfig"
- # that show up in some perls.
- # * We ignore the INC symbol, which exists in every package.
- # * Perl 5.10 imports a lot of random symbols that all
- # contain "::", and we want to ignore those.
- @read_symbols = grep { /^[A-Za-z0-1]/ and !/^INC$/ and !/::/ }
- (keys %safe_package);
- }
- else {
- @read_symbols = map($_->{name}, LOCALCONFIG_VARS);
- }
- foreach my $var (@read_symbols) {
- 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 (@$glob) {
- $localconfig{$var} = \@$glob;
- }
- elsif (%$glob) {
- $localconfig{$var} = \%$glob;
- }
- }
+ my @read_symbols;
+ if ($include_deprecated) {
+
+ # First we have to get the whole symbol table
+ my $safe_root = $s->root;
+ my %safe_package;
+ { no strict 'refs'; %safe_package = %{$safe_root . "::"}; }
+
+ # And now we read the contents of every var in the symbol table.
+ # However:
+ # * We only include symbols that start with an alphanumeric
+ # character. This excludes symbols like "_<./localconfig"
+ # that show up in some perls.
+ # * We ignore the INC symbol, which exists in every package.
+ # * Perl 5.10 imports a lot of random symbols that all
+ # contain "::", and we want to ignore those.
+ @read_symbols
+ = grep { /^[A-Za-z0-1]/ and !/^INC$/ and !/::/ } (keys %safe_package);
}
+ else {
+ @read_symbols = map($_->{name}, LOCALCONFIG_VARS);
+ }
+ foreach my $var (@read_symbols) {
+ 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 (@$glob) {
+ $localconfig{$var} = \@$glob;
+ }
+ elsif (%$glob) {
+ $localconfig{$var} = \%$glob;
+ }
+ }
+ }
- return \%localconfig;
+ return \%localconfig;
}
@@ -204,94 +157,97 @@ sub read_localconfig {
# 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};
- my $value = $localconfig->{$name};
- # Regenerate site_wide_secret if it was made by our old, weak
- # generate_random_password. Previously we used to generate
- # a 256-character string for site_wide_secret.
- $value = undef if ($name eq 'site_wide_secret' and defined $value
- and length($value) == 256);
-
- if (!defined $value) {
- $var->{default} = &{$var->{default}} if ref($var->{default}) eq 'CODE';
- if (exists $answer->{$name}) {
- $localconfig->{$name} = $answer->{$name};
- }
- else {
- # If the user did not supply an answers file, then they get
- # notified about every variable that gets added. If there was
- # an answer file, then we don't notify about site_wide_secret
- # because we assume the intent was to auto-generate it anyway.
- if (!scalar(keys %$answer) || $name ne 'site_wide_secret') {
- push(@new_vars, $name);
- }
- $localconfig->{$name} = $var->{default};
- }
+ 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};
+ my $value = $localconfig->{$name};
+
+ # Regenerate site_wide_secret if it was made by our old, weak
+ # generate_random_password. Previously we used to generate
+ # a 256-character string for site_wide_secret.
+ $value = undef
+ if ($name eq 'site_wide_secret' and defined $value and length($value) == 256);
+
+ if (!defined $value) {
+ $var->{default} = &{$var->{default}} if ref($var->{default}) eq 'CODE';
+ if (exists $answer->{$name}) {
+ $localconfig->{$name} = $answer->{$name};
+ }
+ else {
+ # If the user did not supply an answers file, then they get
+ # notified about every variable that gets added. If there was
+ # an answer file, then we don't notify about site_wide_secret
+ # because we assume the intent was to auto-generate it anyway.
+ if (!scalar(keys %$answer) || $name ne 'site_wide_secret') {
+ push(@new_vars, $name);
}
+ $localconfig->{$name} = $var->{default};
+ }
}
-
- if (!$localconfig->{'interdiffbin'} && $output) {
- print "\n", install_string('patchutils_missing'), "\n";
+ }
+
+ if (!$localconfig->{'interdiffbin'} && $output) {
+ print "\n", install_string('patchutils_missing'), "\n";
+ }
+
+ my @old_vars;
+ foreach my $var (keys %$localconfig) {
+ push(@old_vars, $var) if !grep($_->{name} eq $var, LOCALCONFIG_VARS);
+ }
+
+ my $filename = bz_locations->{'localconfig'};
+
+ # Move any custom or old variables into a separate file.
+ if (scalar @old_vars) {
+ my $filename_old = "$filename.old";
+ open(my $old_file, ">>:utf8", $filename_old) or die "$filename_old: $!";
+ local $Data::Dumper::Purity = 1;
+ foreach my $var (@old_vars) {
+ print $old_file Data::Dumper->Dump([$localconfig->{$var}], ["*$var"]) . "\n\n";
}
-
- my @old_vars;
- foreach my $var (keys %$localconfig) {
- push(@old_vars, $var) if !grep($_->{name} eq $var, LOCALCONFIG_VARS);
- }
-
- my $filename = bz_locations->{'localconfig'};
-
- # Move any custom or old variables into a separate file.
- if (scalar @old_vars) {
- my $filename_old = "$filename.old";
- open(my $old_file, ">>:utf8", $filename_old)
- or die "$filename_old: $!";
- local $Data::Dumper::Purity = 1;
- foreach my $var (@old_vars) {
- print $old_file Data::Dumper->Dump([$localconfig->{$var}],
- ["*$var"]) . "\n\n";
- }
- close $old_file;
- my $oldstuff = join(', ', @old_vars);
- print install_string('lc_old_vars',
- { localconfig => $filename, old_file => $filename_old,
- vars => $oldstuff }), "\n";
- }
-
- # Re-write localconfig
- open(my $fh, ">:utf8", $filename) or die "$filename: $!";
- foreach my $var (LOCALCONFIG_VARS) {
- my $name = $var->{name};
- my $desc = install_string("localconfig_$name", { root => ROOT_USER });
- chomp($desc);
- # Make the description into a comment.
- $desc =~ s/^/# /mg;
- print $fh $desc, "\n",
- Data::Dumper->Dump([$localconfig->{$name}],
- ["*$name"]), "\n";
- }
-
- if (@new_vars) {
- my $newstuff = join(', ', @new_vars);
- print "\n";
- print colored(install_string('lc_new_vars', { localconfig => $filename,
- new_vars => wrap_hard($newstuff, 70) }),
- COLOR_ERROR), "\n";
- 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 };
+ close $old_file;
+ my $oldstuff = join(', ', @old_vars);
+ print install_string('lc_old_vars',
+ {localconfig => $filename, old_file => $filename_old, vars => $oldstuff}),
+ "\n";
+ }
+
+ # Re-write localconfig
+ open(my $fh, ">:utf8", $filename) or die "$filename: $!";
+ foreach my $var (LOCALCONFIG_VARS) {
+ my $name = $var->{name};
+ my $desc = install_string("localconfig_$name", {root => ROOT_USER});
+ chomp($desc);
+
+ # Make the description into a comment.
+ $desc =~ s/^/# /mg;
+ print $fh $desc, "\n", Data::Dumper->Dump([$localconfig->{$name}], ["*$name"]),
+ "\n";
+ }
+
+ if (@new_vars) {
+ my $newstuff = join(', ', @new_vars);
+ print "\n";
+ print colored(
+ install_string(
+ 'lc_new_vars', {localconfig => $filename, new_vars => wrap_hard($newstuff, 70)}
+ ),
+ COLOR_ERROR
+ ),
+ "\n";
+ 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};
}
1;
diff --git a/Bugzilla/Install/Requirements.pm b/Bugzilla/Install/Requirements.pm
index 61496d843..852f7f78e 100644
--- a/Bugzilla/Install/Requirements.pm
+++ b/Bugzilla/Install/Requirements.pm
@@ -19,21 +19,21 @@ use warnings;
use Bugzilla::Constants;
use Bugzilla::Install::Util qw(install_string bin_loc
- extension_requirement_packages);
+ extension_requirement_packages);
use List::Util qw(max);
use Term::ANSIColor;
use parent qw(Exporter);
our @EXPORT = qw(
- REQUIRED_MODULES
- OPTIONAL_MODULES
- FEATURE_FILES
-
- check_requirements
- check_graphviz
- have_vers
- install_command
- map_files_to_features
+ REQUIRED_MODULES
+ OPTIONAL_MODULES
+ FEATURE_FILES
+
+ check_requirements
+ check_graphviz
+ have_vers
+ install_command
+ map_files_to_features
);
# This is how many *'s are in the top of each "box" message printed
@@ -45,12 +45,12 @@ use constant TABLE_WIDTH => 71;
#
# The keys are the names of the modules, the values are what the module
# is called in the output of "apachectl -t -D DUMP_MODULES".
-use constant APACHE_MODULES => {
- mod_headers => 'headers_module',
- mod_env => 'env_module',
- mod_expires => 'expires_module',
- mod_rewrite => 'rewrite_module',
- mod_version => 'version_module'
+use constant APACHE_MODULES => {
+ mod_headers => 'headers_module',
+ mod_env => 'env_module',
+ mod_expires => 'expires_module',
+ mod_rewrite => 'rewrite_module',
+ mod_version => 'version_module'
};
# These are all of the binaries that we could possibly use that can
@@ -64,11 +64,11 @@ use constant APACHE => qw(apachectl httpd apache2 apache);
# If we don't find any of the above binaries in the normal PATH,
# these are extra places we look.
use constant APACHE_PATH => [qw(
- /usr/sbin
+ /usr/sbin
/usr/local/sbin
/usr/libexec
/usr/local/libexec
-)];
+ )];
# The below two constants are subroutines so that they can implement
# a hook. Other than that they are actually constants.
@@ -82,737 +82,757 @@ use constant APACHE_PATH => [qw(
# 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 @modules = (
+ my @modules = (
{
- package => 'CGI.pm',
- module => 'CGI',
- # 3.51 fixes a security problem that affects Bugzilla.
- # (bug 591165)
- version => '3.51',
- },
- {
- package => 'Digest-SHA',
- module => 'Digest::SHA',
- version => 0
+ package => 'CGI.pm',
+ module => 'CGI',
+
+ # 3.51 fixes a security problem that affects Bugzilla.
+ # (bug 591165)
+ version => '3.51',
},
+ {package => 'Digest-SHA', module => 'Digest::SHA', version => 0},
+
# 0.23 fixes incorrect handling of 1/2 & 3/4 timezones.
- {
- package => 'TimeDate',
- module => 'Date::Format',
- version => '2.23'
- },
+ {package => 'TimeDate', module => 'Date::Format', version => '2.23'},
+
# 0.75 fixes a warning thrown with Perl 5.17 and newer.
- {
- package => 'DateTime',
- module => 'DateTime',
- version => '0.75'
- },
+ {package => 'DateTime', module => 'DateTime', version => '0.75'},
+
# 1.64 fixes a taint issue preventing the local timezone from
# being determined on some systems.
{
- package => 'DateTime-TimeZone',
- module => 'DateTime::TimeZone',
- version => '1.64'
+ package => 'DateTime-TimeZone',
+ module => 'DateTime::TimeZone',
+ version => '1.64'
},
+
# 1.54 is required for Perl 5.10+. It also makes DBD::Oracle happy.
{
- package => 'DBI',
- module => 'DBI',
- version => ($^V >= v5.13.3) ? '1.614' : '1.54'
+ package => 'DBI',
+ module => 'DBI',
+ version => ($^V >= v5.13.3) ? '1.614' : '1.54'
},
+
# 2.24 contains several useful text virtual methods.
- {
- package => 'Template-Toolkit',
- module => 'Template',
- version => '2.24'
- },
+ {package => 'Template-Toolkit', module => 'Template', version => '2.24'},
+
# 1.300011 has a debug mode for SMTP and automatically pass -i to sendmail.
+ {package => 'Email-Sender', module => 'Email::Sender', version => '1.300011',},
{
- package => 'Email-Sender',
- module => 'Email::Sender',
- version => '1.300011',
- },
- {
- package => 'Email-MIME',
- module => 'Email::MIME',
- # This fixes a memory leak in walk_parts that affected jobqueue.pl.
- version => '1.904'
+ package => 'Email-MIME',
+ module => 'Email::MIME',
+
+ # This fixes a memory leak in walk_parts that affected jobqueue.pl.
+ version => '1.904'
},
{
- package => 'URI',
- module => 'URI',
- # Follows RFC 3986 to escape characters in URI::Escape.
- version => '1.55',
+ package => 'URI',
+ module => 'URI',
+
+ # Follows RFC 3986 to escape characters in URI::Escape.
+ version => '1.55',
},
+
# 0.32 fixes several memory leaks in the XS version of some functions.
+ {package => 'List-MoreUtils', module => 'List::MoreUtils', version => 0.32,},
{
- package => 'List-MoreUtils',
- module => 'List::MoreUtils',
- version => 0.32,
+ package => 'Math-Random-ISAAC',
+ module => 'Math::Random::ISAAC',
+ version => '1.0.1',
},
{
- package => 'Math-Random-ISAAC',
- module => 'Math::Random::ISAAC',
- version => '1.0.1',
- },
- {
- package => 'JSON-XS',
- module => 'JSON::XS',
- # 2.0 is the first version that will work with JSON::RPC.
- version => '2.01',
+ package => 'JSON-XS',
+ module => 'JSON::XS',
+
+ # 2.0 is the first version that will work with JSON::RPC.
+ version => '2.01',
},
- );
+ );
- if (ON_WINDOWS) {
- push(@modules,
- {
- package => 'Win32',
- module => 'Win32',
- # 0.35 fixes a memory leak in GetOSVersion, which we use.
- version => 0.35,
- },
- {
- package => 'Win32-API',
- module => 'Win32::API',
- # 0.55 fixes a bug with char* that might affect Bugzilla::RNG.
- version => '0.55',
- },
- {
- package => 'DateTime-TimeZone-Local-Win32',
- module => 'DateTime::TimeZone::Local::Win32',
- # We require DateTime::TimeZone 1.64, so this version must match.
- version => '1.64',
- }
- );
- }
+ if (ON_WINDOWS) {
+ push(
+ @modules,
+ {
+ package => 'Win32',
+ module => 'Win32',
- my $extra_modules = _get_extension_requirements('REQUIRED_MODULES');
- push(@modules, @$extra_modules);
- return \@modules;
-};
+ # 0.35 fixes a memory leak in GetOSVersion, which we use.
+ version => 0.35,
+ },
+ {
+ package => 'Win32-API',
+ module => 'Win32::API',
+
+ # 0.55 fixes a bug with char* that might affect Bugzilla::RNG.
+ version => '0.55',
+ },
+ {
+ package => 'DateTime-TimeZone-Local-Win32',
+ module => 'DateTime::TimeZone::Local::Win32',
+
+ # We require DateTime::TimeZone 1.64, so this version must match.
+ version => '1.64',
+ }
+ );
+ }
+
+ my $extra_modules = _get_extension_requirements('REQUIRED_MODULES');
+ push(@modules, @$extra_modules);
+ return \@modules;
+}
sub OPTIONAL_MODULES {
- my @modules = (
+ my @modules = (
{
- package => 'GD',
- module => 'GD',
- version => '1.20',
- feature => [qw(graphical_reports new_charts old_charts)],
+ package => 'GD',
+ module => 'GD',
+ version => '1.20',
+ feature => [qw(graphical_reports new_charts old_charts)],
},
{
- package => 'Chart',
- module => 'Chart::Lines',
- # Versions below 2.4.1 cannot be compared accurately, see
- # https://rt.cpan.org/Public/Bug/Display.html?id=28218.
- version => '2.4.1',
- feature => [qw(new_charts old_charts)],
+ package => 'Chart',
+ module => 'Chart::Lines',
+
+ # Versions below 2.4.1 cannot be compared accurately, see
+ # https://rt.cpan.org/Public/Bug/Display.html?id=28218.
+ version => '2.4.1',
+ feature => [qw(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 => '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 => 'GDTextUtil',
+ module => 'GD::Text',
+ version => 0,
+ feature => ['graphical_reports'],
},
{
- package => 'GDGraph',
- module => 'GD::Graph',
- version => 0,
- feature => ['graphical_reports'],
+ package => 'GDGraph',
+ module => 'GD::Graph',
+ version => 0,
+ feature => ['graphical_reports'],
},
{
- package => 'MIME-tools',
- # MIME::Parser is packaged as MIME::Tools on ActiveState Perl
- module => ON_WINDOWS ? 'MIME::Tools' : 'MIME::Parser',
- version => '5.406',
- feature => ['moving'],
+ package => 'MIME-tools',
+
+ # MIME::Parser is packaged as MIME::Tools on ActiveState Perl
+ module => ON_WINDOWS ? 'MIME::Tools' : 'MIME::Parser',
+ version => '5.406',
+ feature => ['moving'],
},
{
- package => 'libwww-perl',
- module => 'LWP::UserAgent',
- version => 0,
- feature => ['updates'],
+ package => 'libwww-perl',
+ module => 'LWP::UserAgent',
+ version => 0,
+ feature => ['updates'],
},
{
- package => 'XML-Twig',
- module => 'XML::Twig',
- version => 0,
- feature => ['moving', 'updates'],
+ package => 'XML-Twig',
+ module => 'XML::Twig',
+ version => 0,
+ feature => ['moving', 'updates'],
},
{
- package => 'PatchReader',
- module => 'PatchReader',
- # 0.9.6 fixes two notable bugs and significantly improves the UX.
- version => '0.9.6',
- feature => ['patch_viewer'],
+ package => 'PatchReader',
+ module => 'PatchReader',
+
+ # 0.9.6 fixes two notable bugs and significantly improves the UX.
+ version => '0.9.6',
+ feature => ['patch_viewer'],
},
{
- package => 'perl-ldap',
- module => 'Net::LDAP',
- version => 0,
- feature => ['auth_ldap'],
+ package => 'perl-ldap',
+ module => 'Net::LDAP',
+ version => 0,
+ feature => ['auth_ldap'],
},
{
- package => 'Authen-SASL',
- module => 'Authen::SASL',
- version => 0,
- feature => ['smtp_auth'],
+ package => 'Authen-SASL',
+ module => 'Authen::SASL',
+ version => 0,
+ feature => ['smtp_auth'],
},
{
- package => 'Net-SMTP-SSL',
- module => 'Net::SMTP::SSL',
- version => 1.01,
- feature => ['smtp_ssl'],
+ package => 'Net-SMTP-SSL',
+ module => 'Net::SMTP::SSL',
+ version => 1.01,
+ feature => ['smtp_ssl'],
},
{
- package => 'RadiusPerl',
- module => 'Authen::Radius',
- version => 0,
- feature => ['auth_radius'],
+ package => 'RadiusPerl',
+ module => 'Authen::Radius',
+ version => 0,
+ feature => ['auth_radius'],
},
+
# XXX - Once we require XMLRPC::Lite 0.717 or higher, we can
# remove SOAP::Lite from the list.
{
- package => 'SOAP-Lite',
- module => 'SOAP::Lite',
- # Fixes various bugs, including 542931 and 552353 + stops
- # throwing warnings with Perl 5.12.
- version => '0.712',
- # SOAP::Transport::HTTP 1.12 is bogus.
- blacklist => ['^1\.12$'],
- feature => ['xmlrpc'],
+ package => 'SOAP-Lite',
+ module => 'SOAP::Lite',
+
+ # Fixes various bugs, including 542931 and 552353 + stops
+ # throwing warnings with Perl 5.12.
+ version => '0.712',
+
+ # SOAP::Transport::HTTP 1.12 is bogus.
+ blacklist => ['^1\.12$'],
+ feature => ['xmlrpc'],
},
+
# Since SOAP::Lite 1.0, XMLRPC::Lite is no longer included
# and so it must be checked separately.
{
- package => 'XMLRPC-Lite',
- module => 'XMLRPC::Lite',
- version => '0.712',
- feature => ['xmlrpc'],
+ package => 'XMLRPC-Lite',
+ module => 'XMLRPC::Lite',
+ version => '0.712',
+ feature => ['xmlrpc'],
},
{
- package => 'JSON-RPC',
- module => 'JSON::RPC',
- version => 0,
- feature => ['jsonrpc', 'rest'],
+ package => 'JSON-RPC',
+ module => 'JSON::RPC',
+ version => 0,
+ feature => ['jsonrpc', 'rest'],
},
{
- package => 'Test-Taint',
- module => 'Test::Taint',
- # 1.06 no longer throws warnings with Perl 5.10+.
- version => 1.06,
- feature => ['jsonrpc', 'xmlrpc', 'rest'],
+ package => 'Test-Taint',
+ module => 'Test::Taint',
+
+ # 1.06 no longer throws warnings with Perl 5.10+.
+ version => 1.06,
+ feature => ['jsonrpc', 'xmlrpc', 'rest'],
},
{
- # We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
- package => 'HTML-Parser',
- module => 'HTML::Parser',
- version => ($^V >= v5.13.3) ? '3.67' : '3.40',
- feature => ['html_desc'],
+ # We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
+ package => 'HTML-Parser',
+ module => 'HTML::Parser',
+ version => ($^V >= v5.13.3) ? '3.67' : '3.40',
+ feature => ['html_desc'],
},
{
- package => 'HTML-Scrubber',
- module => 'HTML::Scrubber',
- version => 0,
- feature => ['html_desc'],
+ package => 'HTML-Scrubber',
+ module => 'HTML::Scrubber',
+ version => 0,
+ feature => ['html_desc'],
},
{
- # we need version 2.21 of Encode for mime_name
- package => 'Encode',
- module => 'Encode',
- version => 2.21,
- feature => ['detect_charset'],
+ # we need version 2.21 of Encode for mime_name
+ package => 'Encode',
+ module => 'Encode',
+ version => 2.21,
+ feature => ['detect_charset'],
},
{
- package => 'Encode-Detect',
- module => 'Encode::Detect',
- version => 0,
- feature => ['detect_charset'],
+ package => 'Encode-Detect',
+ module => 'Encode::Detect',
+ version => 0,
+ feature => ['detect_charset'],
},
# Inbound Email
{
- package => 'Email-Reply',
- module => 'Email::Reply',
- version => 0,
- feature => ['inbound_email'],
+ package => 'Email-Reply',
+ module => 'Email::Reply',
+ version => 0,
+ feature => ['inbound_email'],
},
{
- package => 'HTML-FormatText-WithLinks',
- module => 'HTML::FormatText::WithLinks',
- # We need 0.13 to set the "bold" marker to "*".
- version => '0.13',
- feature => ['inbound_email'],
+ package => 'HTML-FormatText-WithLinks',
+ module => 'HTML::FormatText::WithLinks',
+
+ # We need 0.13 to set the "bold" marker to "*".
+ version => '0.13',
+ feature => ['inbound_email'],
},
# Mail Queueing
{
- package => 'TheSchwartz',
- module => 'TheSchwartz',
- # 1.07 supports the prioritization of jobs.
- version => 1.07,
- feature => ['jobqueue'],
+ package => 'TheSchwartz',
+ module => 'TheSchwartz',
+
+ # 1.07 supports the prioritization of jobs.
+ version => 1.07,
+ feature => ['jobqueue'],
},
{
- package => 'Daemon-Generic',
- module => 'Daemon::Generic',
- version => 0,
- feature => ['jobqueue'],
+ package => 'Daemon-Generic',
+ module => 'Daemon::Generic',
+ version => 0,
+ feature => ['jobqueue'],
},
# mod_perl
{
- package => 'mod_perl',
- module => 'mod_perl2',
- version => '1.999022',
- feature => ['mod_perl'],
+ package => 'mod_perl',
+ module => 'mod_perl2',
+ version => '1.999022',
+ feature => ['mod_perl'],
},
{
- package => 'Apache-SizeLimit',
- module => 'Apache2::SizeLimit',
- # 0.96 properly determines process size on Linux.
- version => '0.96',
- feature => ['mod_perl'],
+ package => 'Apache-SizeLimit',
+ module => 'Apache2::SizeLimit',
+
+ # 0.96 properly determines process size on Linux.
+ version => '0.96',
+ feature => ['mod_perl'],
},
# typesniffer
{
- package => 'File-MimeInfo',
- module => 'File::MimeInfo::Magic',
- version => '0',
- feature => ['typesniffer'],
+ package => 'File-MimeInfo',
+ module => 'File::MimeInfo::Magic',
+ version => '0',
+ feature => ['typesniffer'],
},
{
- package => 'IO-stringy',
- module => 'IO::Scalar',
- version => '0',
- feature => ['typesniffer'],
+ package => 'IO-stringy',
+ module => 'IO::Scalar',
+ version => '0',
+ feature => ['typesniffer'],
},
# memcached
{
- package => 'Cache-Memcached',
- module => 'Cache::Memcached',
- version => '0',
- feature => ['memcached'],
+ package => 'Cache-Memcached',
+ module => 'Cache::Memcached',
+ version => '0',
+ feature => ['memcached'],
},
# Documentation
{
- package => 'File-Copy-Recursive',
- module => 'File::Copy::Recursive',
- version => 0,
- feature => ['documentation'],
+ package => 'File-Copy-Recursive',
+ module => 'File::Copy::Recursive',
+ version => 0,
+ feature => ['documentation'],
},
{
- package => 'File-Which',
- module => 'File::Which',
- version => 0,
- feature => ['documentation'],
+ package => 'File-Which',
+ module => 'File::Which',
+ version => 0,
+ feature => ['documentation'],
},
- );
+ );
- my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
- push(@modules, @$extra_modules);
- return \@modules;
-};
+ my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
+ push(@modules, @$extra_modules);
+ return \@modules;
+}
# This maps features to the files that require that feature in order
# to compile. It is used by t/001compile.t and mod_perl.pl.
use constant FEATURE_FILES => (
- jsonrpc => ['Bugzilla/WebService/Server/JSONRPC.pm', 'jsonrpc.cgi'],
- xmlrpc => ['Bugzilla/WebService/Server/XMLRPC.pm', 'xmlrpc.cgi',
- 'Bugzilla/WebService.pm', 'Bugzilla/WebService/*.pm'],
- rest => ['Bugzilla/WebService/Server/REST.pm', 'rest.cgi',
- 'Bugzilla/WebService/Server/REST/Resources/*.pm'],
- moving => ['importxml.pl'],
- auth_ldap => ['Bugzilla/Auth/Verify/LDAP.pm'],
- auth_radius => ['Bugzilla/Auth/Verify/RADIUS.pm'],
- documentation => ['docs/makedocs.pl'],
- inbound_email => ['email_in.pl'],
- jobqueue => ['Bugzilla/Job/*', 'Bugzilla/JobQueue.pm',
- 'Bugzilla/JobQueue/*', 'jobqueue.pl'],
- patch_viewer => ['Bugzilla/Attachment/PatchReader.pm'],
- updates => ['Bugzilla/Update.pm'],
- memcached => ['Bugzilla/Memcache.pm'],
+ jsonrpc => ['Bugzilla/WebService/Server/JSONRPC.pm', 'jsonrpc.cgi'],
+ xmlrpc => [
+ 'Bugzilla/WebService/Server/XMLRPC.pm', 'xmlrpc.cgi',
+ 'Bugzilla/WebService.pm', 'Bugzilla/WebService/*.pm'
+ ],
+ rest => [
+ 'Bugzilla/WebService/Server/REST.pm', 'rest.cgi',
+ 'Bugzilla/WebService/Server/REST/Resources/*.pm'
+ ],
+ moving => ['importxml.pl'],
+ auth_ldap => ['Bugzilla/Auth/Verify/LDAP.pm'],
+ auth_radius => ['Bugzilla/Auth/Verify/RADIUS.pm'],
+ documentation => ['docs/makedocs.pl'],
+ inbound_email => ['email_in.pl'],
+ jobqueue => [
+ 'Bugzilla/Job/*', 'Bugzilla/JobQueue.pm',
+ 'Bugzilla/JobQueue/*', 'jobqueue.pl'
+ ],
+ patch_viewer => ['Bugzilla/Attachment/PatchReader.pm'],
+ updates => ['Bugzilla/Update.pm'],
+ memcached => ['Bugzilla/Memcache.pm'],
);
# This implements the REQUIRED_MODULES and OPTIONAL_MODULES stuff
# described in in Bugzilla::Extension.
sub _get_extension_requirements {
- my ($function) = @_;
-
- my $packages = extension_requirement_packages();
- my @modules;
- foreach my $package (@$packages) {
- if ($package->can($function)) {
- my $extra_modules = $package->$function;
- push(@modules, @$extra_modules);
- }
- }
- return \@modules;
-};
+ my ($function) = @_;
-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);
+ my $packages = extension_requirement_packages();
+ my @modules;
+ foreach my $package (@$packages) {
+ if ($package->can($function)) {
+ my $extra_modules = $package->$function;
+ push(@modules, @$extra_modules);
}
+ }
+ return \@modules;
+}
- print "\n", install_string('checking_optional'), "\n" if $output;
- my $missing_optional = _check_missing(OPTIONAL_MODULES, $output);
-
- my $missing_apache = _missing_apache_modules(APACHE_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,
- apache => $missing_apache,
- any_missing => !$pass || scalar(@$missing_optional),
- };
+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);
+
+ my $missing_apache = _missing_apache_modules(APACHE_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,
+ apache => $missing_apache,
+ any_missing => !$pass || scalar(@$missing_optional),
+ };
}
# A helper for check_requirements
sub _check_missing {
- my ($modules, $output) = @_;
+ my ($modules, $output) = @_;
- my @missing;
- foreach my $module (@$modules) {
- unless (have_vers($module, $output)) {
- push(@missing, $module);
- }
+ my @missing;
+ foreach my $module (@$modules) {
+ unless (have_vers($module, $output)) {
+ push(@missing, $module);
}
+ }
- return \@missing;
+ return \@missing;
}
sub _missing_apache_modules {
- my ($modules, $output) = @_;
- my $apachectl = _get_apachectl();
- return [] if !$apachectl;
- my $command = "$apachectl -t -D DUMP_MODULES";
- my $cmd_info = `$command 2>&1`;
- # If apachectl returned a value greater than 0, then there was an
- # error parsing Apache's configuration, and we can't check modules.
- my $retval = $?;
- if ($retval > 0) {
- print STDERR install_string('apachectl_failed',
- { command => $command, root => ROOT_USER }), "\n";
- return [];
- }
- my @missing;
- foreach my $module (sort keys %$modules) {
- my $ok = _check_apache_module($module, $modules->{$module},
- $cmd_info, $output);
- push(@missing, $module) if !$ok;
- }
- return \@missing;
+ my ($modules, $output) = @_;
+ my $apachectl = _get_apachectl();
+ return [] if !$apachectl;
+ my $command = "$apachectl -t -D DUMP_MODULES";
+ my $cmd_info = `$command 2>&1`;
+
+ # If apachectl returned a value greater than 0, then there was an
+ # error parsing Apache's configuration, and we can't check modules.
+ my $retval = $?;
+ if ($retval > 0) {
+ print STDERR install_string('apachectl_failed',
+ {command => $command, root => ROOT_USER}), "\n";
+ return [];
+ }
+ my @missing;
+ foreach my $module (sort keys %$modules) {
+ my $ok = _check_apache_module($module, $modules->{$module}, $cmd_info, $output);
+ push(@missing, $module) if !$ok;
+ }
+ return \@missing;
}
sub _get_apachectl {
- foreach my $bin_name (APACHE) {
- my $bin = bin_loc($bin_name);
- return $bin if $bin;
- }
- # Try again with a possibly different path.
- foreach my $bin_name (APACHE) {
- my $bin = bin_loc($bin_name, APACHE_PATH);
- return $bin if $bin;
- }
- return undef;
+ foreach my $bin_name (APACHE) {
+ my $bin = bin_loc($bin_name);
+ return $bin if $bin;
+ }
+
+ # Try again with a possibly different path.
+ foreach my $bin_name (APACHE) {
+ my $bin = bin_loc($bin_name, APACHE_PATH);
+ return $bin if $bin;
+ }
+ return undef;
}
sub _check_apache_module {
- my ($module, $config_name, $mod_info, $output) = @_;
- my $ok;
- if ($mod_info =~ /^\s+\Q$config_name\E\b/m) {
- $ok = 1;
- }
- if ($output) {
- _checking_for({ package => $module, ok => $ok });
- }
- return $ok;
+ my ($module, $config_name, $mod_info, $output) = @_;
+ my $ok;
+ if ($mod_info =~ /^\s+\Q$config_name\E\b/m) {
+ $ok = 1;
+ }
+ if ($output) {
+ _checking_for({package => $module, ok => $ok});
+ }
+ return $ok;
}
sub print_module_instructions {
- my ($check_results, $output) = @_;
-
- # First we print the long explanatory messages.
-
- if (scalar @{$check_results->{missing}}) {
- print install_string('modules_message_required');
- }
-
- if (!$check_results->{one_dbd}) {
- print install_string('modules_message_db');
- }
-
- if (my @missing = @{$check_results->{optional}} and $output) {
- print install_string('modules_message_optional');
- # 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 TABLE_WIDTH characters long. There are seven mandatory
- # characters (* and space) in the string. So, we have a total
- # of TABLE_WIDTH - 7 characters to work with.
- my $remaining_space = (TABLE_WIDTH - 7) - $longest_name;
- print '*' x TABLE_WIDTH . "\n";
- printf "* \%${longest_name}s * %-${remaining_space}s *\n",
- 'MODULE NAME', 'ENABLES FEATURE(S)';
- print '*' x TABLE_WIDTH . "\n";
- foreach my $package (@missing) {
- printf "* \%${longest_name}s * %-${remaining_space}s *\n",
- $package->{package},
- _translate_feature($package->{feature});
- }
- }
-
- if (my @missing = @{ $check_results->{apache} }) {
- print install_string('modules_message_apache');
- my $missing_string = join(', ', @missing);
- my $size = TABLE_WIDTH - 7;
- printf "* \%-${size}s *\n", $missing_string;
- my $spaces = TABLE_WIDTH - 2;
- print "*", (' ' x $spaces), "*\n";
- }
-
- my $need_module_instructions =
- ( (!$output and @{$check_results->{missing}})
- or ($output and $check_results->{any_missing}) ) ? 1 : 0;
-
- if ($need_module_instructions or @{ $check_results->{apache} }) {
- # If any output was required, we want to close the "table"
- print "*" x TABLE_WIDTH . "\n";
- }
-
- # And now we print the actual installation commands.
-
- if (my @missing = @{$check_results->{optional}} and $output) {
- print install_string('commands_optional') . "\n\n";
- foreach my $module (@missing) {
- my $command = install_command($module);
- printf "%15s: $command\n", $module->{package};
- }
- print "\n";
- }
-
- if (!$check_results->{one_dbd}) {
- print install_string('commands_dbd') . "\n";
- 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 "\n";
- }
-
- if (my @missing = @{$check_results->{missing}}) {
- print colored(install_string('commands_required'), COLOR_ERROR), "\n";
- foreach my $package (@missing) {
- my $command = install_command($package);
- print " $command\n";
- }
- }
-
- if ($output && $check_results->{any_missing} && !ON_ACTIVESTATE
- && !$check_results->{hide_all})
- {
- print install_string('install_all', { perl => $^X });
- }
- if (!$check_results->{pass}) {
- print colored(install_string('installation_failed'), COLOR_ERROR),
- "\n\n";
- }
+ my ($check_results, $output) = @_;
+
+ # First we print the long explanatory messages.
+
+ if (scalar @{$check_results->{missing}}) {
+ print install_string('modules_message_required');
+ }
+
+ if (!$check_results->{one_dbd}) {
+ print install_string('modules_message_db');
+ }
+
+ if (my @missing = @{$check_results->{optional}} and $output) {
+ print install_string('modules_message_optional');
+
+ # 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 TABLE_WIDTH characters long. There are seven mandatory
+ # characters (* and space) in the string. So, we have a total
+ # of TABLE_WIDTH - 7 characters to work with.
+ my $remaining_space = (TABLE_WIDTH - 7) - $longest_name;
+ print '*' x TABLE_WIDTH . "\n";
+ printf "* \%${longest_name}s * %-${remaining_space}s *\n", 'MODULE NAME',
+ 'ENABLES FEATURE(S)';
+ print '*' x TABLE_WIDTH . "\n";
+ foreach my $package (@missing) {
+ printf "* \%${longest_name}s * %-${remaining_space}s *\n", $package->{package},
+ _translate_feature($package->{feature});
+ }
+ }
+
+ if (my @missing = @{$check_results->{apache}}) {
+ print install_string('modules_message_apache');
+ my $missing_string = join(', ', @missing);
+ my $size = TABLE_WIDTH - 7;
+ printf "* \%-${size}s *\n", $missing_string;
+ my $spaces = TABLE_WIDTH - 2;
+ print "*", (' ' x $spaces), "*\n";
+ }
+
+ my $need_module_instructions = (
+ (!$output and @{$check_results->{missing}})
+ or ($output and $check_results->{any_missing})
+ ) ? 1 : 0;
+
+ if ($need_module_instructions or @{$check_results->{apache}}) {
+
+ # If any output was required, we want to close the "table"
+ print "*" x TABLE_WIDTH . "\n";
+ }
+
+ # And now we print the actual installation commands.
+
+ if (my @missing = @{$check_results->{optional}} and $output) {
+ print install_string('commands_optional') . "\n\n";
+ foreach my $module (@missing) {
+ my $command = install_command($module);
+ printf "%15s: $command\n", $module->{package};
+ }
+ print "\n";
+ }
+
+ if (!$check_results->{one_dbd}) {
+ print install_string('commands_dbd') . "\n";
+ 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 "\n";
+ }
+
+ if (my @missing = @{$check_results->{missing}}) {
+ print colored(install_string('commands_required'), COLOR_ERROR), "\n";
+ foreach my $package (@missing) {
+ my $command = install_command($package);
+ print " $command\n";
+ }
+ }
+
+ if ( $output
+ && $check_results->{any_missing}
+ && !ON_ACTIVESTATE
+ && !$check_results->{hide_all})
+ {
+ print install_string('install_all', {perl => $^X});
+ }
+ if (!$check_results->{pass}) {
+ print colored(install_string('installation_failed'), COLOR_ERROR), "\n\n";
+ }
}
sub _translate_feature {
- my $features = shift;
- my @strings;
- foreach my $feature (@$features) {
- push(@strings, install_string("feature_$feature"));
- }
- return join(', ', @strings);
+ my $features = shift;
+ my @strings;
+ foreach my $feature (@$features) {
+ push(@strings, install_string("feature_$feature"));
+ }
+ return join(', ', @strings);
}
sub check_graphviz {
- my ($output) = @_;
+ my ($output) = @_;
- my $webdotbase = Bugzilla->params->{'webdotbase'};
- return 1 if $webdotbase =~ /^https?:/;
+ my $webdotbase = Bugzilla->params->{'webdotbase'};
+ return 1 if $webdotbase =~ /^https?:/;
- my $return;
- $return = 1 if -x $webdotbase;
+ my $return;
+ $return = 1 if -x $webdotbase;
- if ($output) {
- _checking_for({ package => 'GraphViz', ok => $return });
- }
+ if ($output) {
+ _checking_for({package => 'GraphViz', ok => $return});
+ }
- if (!$return) {
- print install_string('bad_executable', { bin => $webdotbase }), "\n";
- }
+ if (!$return) {
+ print install_string('bad_executable', {bin => $webdotbase}), "\n";
+ }
+
+ my $webdotdir = bz_locations()->{'webdotdir'};
- 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 STDERR install_string('webdot_bad_htaccess',
- { dir => $webdotdir }), "\n";
- }
- $htaccess->close;
+ # 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 STDERR install_string('webdot_bad_htaccess', {dir => $webdotdir}), "\n";
}
+ $htaccess->close;
+ }
- return $return;
+ return $return;
}
# This was originally clipped from the libnet Makefile.PL, adapted here 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;";
- # Don't let loading a module change the output-encoding of STDOUT
- # or STDERR. (CGI.pm tries to set "binmode" on these file handles when
- # it's loaded, and other modules may do the same in the future.)
- Bugzilla::Install::Util::set_output_encoding();
-
- # VERSION is provided by UNIVERSAL::, and can be called even if
- # the module isn't loaded. We eval'uate ->VERSION because it can die
- # when the version is not valid (yes, this happens from time to time).
- # In that case, we use an uglier method to get the version.
- my $vnum = eval { $module->VERSION };
- if ($@) {
- no strict 'refs';
- $vnum = ${"${module}::VERSION"};
-
- # If we come here, then the version is not a valid one.
- # We try to sanitize it.
- if ($vnum =~ /^((\d+)(\.\d+)*)/) {
- $vnum = $1;
- }
- }
- $vnum ||= -1;
-
- # Must do a string comparison as $vnum may be of the form 5.10.1.
- my $vok = ($vnum ne '-1' && version->new($vnum) >= version->new($wanted)) ? 1 : 0;
- my $blacklisted;
- if ($vok && $params->{blacklist}) {
- $blacklisted = grep($vnum =~ /$_/, @{$params->{blacklist}});
- $vok = 0 if $blacklisted;
- }
-
- if ($output) {
- _checking_for({
- package => $package, ok => $vok, wanted => $wanted,
- found => $vnum, blacklisted => $blacklisted
- });
- }
-
- return $vok ? 1 : 0;
+ 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;";
+
+ # Don't let loading a module change the output-encoding of STDOUT
+ # or STDERR. (CGI.pm tries to set "binmode" on these file handles when
+ # it's loaded, and other modules may do the same in the future.)
+ Bugzilla::Install::Util::set_output_encoding();
+
+ # VERSION is provided by UNIVERSAL::, and can be called even if
+ # the module isn't loaded. We eval'uate ->VERSION because it can die
+ # when the version is not valid (yes, this happens from time to time).
+ # In that case, we use an uglier method to get the version.
+ my $vnum = eval { $module->VERSION };
+ if ($@) {
+ no strict 'refs';
+ $vnum = ${"${module}::VERSION"};
+
+ # If we come here, then the version is not a valid one.
+ # We try to sanitize it.
+ if ($vnum =~ /^((\d+)(\.\d+)*)/) {
+ $vnum = $1;
+ }
+ }
+ $vnum ||= -1;
+
+ # Must do a string comparison as $vnum may be of the form 5.10.1.
+ my $vok
+ = ($vnum ne '-1' && version->new($vnum) >= version->new($wanted)) ? 1 : 0;
+ my $blacklisted;
+ if ($vok && $params->{blacklist}) {
+ $blacklisted = grep($vnum =~ /$_/, @{$params->{blacklist}});
+ $vok = 0 if $blacklisted;
+ }
+
+ if ($output) {
+ _checking_for({
+ package => $package,
+ ok => $vok,
+ wanted => $wanted,
+ found => $vnum,
+ blacklisted => $blacklisted
+ });
+ }
+
+ return $vok ? 1 : 0;
}
sub _checking_for {
- my ($params) = @_;
- my ($package, $ok, $wanted, $blacklisted, $found) =
- @$params{qw(package ok wanted blacklisted found)};
-
- my $ok_string = $ok ? install_string('module_ok') : '';
-
- # If we're actually checking versions (like for Perl modules), then
- # we have some rather complex logic to determine what we want to
- # show. If we're not checking versions (like for GraphViz) we just
- # show "ok" or "not found".
- if (exists $params->{found}) {
- my $found_string;
- # We do a string compare in case it's non-numeric. We make sure
- # it's not a version object as negative versions are forbidden.
- if ($found && !ref($found) && $found eq '-1') {
- $found_string = install_string('module_not_found');
- }
- elsif ($found) {
- $found_string = install_string('module_found', { ver => $found });
- }
- else {
- $found_string = install_string('module_unknown_version');
- }
- $ok_string = $ok ? "$ok_string: $found_string" : $found_string;
+ my ($params) = @_;
+ my ($package, $ok, $wanted, $blacklisted, $found)
+ = @$params{qw(package ok wanted blacklisted found)};
+
+ my $ok_string = $ok ? install_string('module_ok') : '';
+
+ # If we're actually checking versions (like for Perl modules), then
+ # we have some rather complex logic to determine what we want to
+ # show. If we're not checking versions (like for GraphViz) we just
+ # show "ok" or "not found".
+ if (exists $params->{found}) {
+ my $found_string;
+
+ # We do a string compare in case it's non-numeric. We make sure
+ # it's not a version object as negative versions are forbidden.
+ if ($found && !ref($found) && $found eq '-1') {
+ $found_string = install_string('module_not_found');
+ }
+ elsif ($found) {
+ $found_string = install_string('module_found', {ver => $found});
}
- elsif (!$ok) {
- $ok_string = install_string('module_not_found');
+ else {
+ $found_string = install_string('module_unknown_version');
}
+ $ok_string = $ok ? "$ok_string: $found_string" : $found_string;
+ }
+ elsif (!$ok) {
+ $ok_string = install_string('module_not_found');
+ }
- my $black_string = $blacklisted ? install_string('blacklisted') : '';
- my $want_string = $wanted ? "v$wanted" : install_string('any');
+ my $black_string = $blacklisted ? install_string('blacklisted') : '';
+ my $want_string = $wanted ? "v$wanted" : install_string('any');
- my $str = sprintf "%s %20s %-11s $ok_string $black_string\n",
- install_string('checking_for'), $package, "($want_string)";
- print $ok ? $str : colored($str, COLOR_ERROR);
+ my $str = sprintf "%s %20s %-11s $ok_string $black_string\n",
+ install_string('checking_for'), $package, "($want_string)";
+ print $ok ? $str : colored($str, COLOR_ERROR);
}
sub install_command {
- my $module = shift;
- my ($command, $package);
-
- if (ON_ACTIVESTATE) {
- $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;
+ my $module = shift;
+ my ($command, $package);
+
+ if (ON_ACTIVESTATE) {
+ $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;
}
# This does a reverse mapping for FEATURE_FILES.
sub map_files_to_features {
- my %features = FEATURE_FILES;
- my %files;
- foreach my $feature (keys %features) {
- my @my_files = @{ $features{$feature} };
- foreach my $pattern (@my_files) {
- foreach my $file (glob $pattern) {
- $files{$file} = $feature;
- }
- }
- }
- return \%files;
+ my %features = FEATURE_FILES;
+ my %files;
+ foreach my $feature (keys %features) {
+ my @my_files = @{$features{$feature}};
+ foreach my $pattern (@my_files) {
+ foreach my $file (glob $pattern) {
+ $files{$file} = $feature;
+ }
+ }
+ }
+ return \%files;
}
1;
diff --git a/Bugzilla/Install/Util.pm b/Bugzilla/Install/Util.pm
index c05037061..617977241 100644
--- a/Bugzilla/Install/Util.pm
+++ b/Bugzilla/Install/Util.pm
@@ -27,195 +27,206 @@ use PerlIO;
use parent qw(Exporter);
our @EXPORT_OK = qw(
- bin_loc
- get_version_and_os
- extension_code_files
- extension_package_directory
- extension_requirement_packages
- extension_template_directory
- extension_web_directory
- indicate_progress
- install_string
- include_languages
- success
- template_include_path
- init_console
+ bin_loc
+ get_version_and_os
+ extension_code_files
+ extension_package_directory
+ extension_requirement_packages
+ extension_template_directory
+ extension_web_directory
+ indicate_progress
+ install_string
+ include_languages
+ success
+ template_include_path
+ init_console
);
sub bin_loc {
- my ($bin, $path) = @_;
- # This module is not needed most of the time and is a bit slow,
- # so we only load it when calling bin_loc().
- require ExtUtils::MM;
-
- # If the binary is a full path...
- if ($bin =~ m{[/\\]}) {
- return MM->maybe_command($bin) || '';
- }
-
- # Otherwise we look for it in the path in a cross-platform way.
- my @path = $path ? @$path : File::Spec->path;
- foreach my $dir (@path) {
- next if !-d $dir;
- my $full_path = File::Spec->catfile($dir, $bin);
- # MM is an alias for ExtUtils::MM. maybe_command is nice
- # because it checks .com, .bat, .exe (etc.) on Windows.
- my $command = MM->maybe_command($full_path);
- return $command if $command;
- }
-
- return '';
+ my ($bin, $path) = @_;
+
+ # This module is not needed most of the time and is a bit slow,
+ # so we only load it when calling bin_loc().
+ require ExtUtils::MM;
+
+ # If the binary is a full path...
+ if ($bin =~ m{[/\\]}) {
+ return MM->maybe_command($bin) || '';
+ }
+
+ # Otherwise we look for it in the path in a cross-platform way.
+ my @path = $path ? @$path : File::Spec->path;
+ foreach my $dir (@path) {
+ next if !-d $dir;
+ my $full_path = File::Spec->catfile($dir, $bin);
+
+ # MM is an alias for ExtUtils::MM. maybe_command is nice
+ # because it checks .com, .bat, .exe (etc.) on Windows.
+ my $command = MM->maybe_command($full_path);
+ return $command if $command;
+ }
+
+ return '';
}
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] };
+
+ # 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 _extension_paths {
- my $dir = bz_locations()->{'extensionsdir'};
- my @extension_items = glob("$dir/*");
- my @paths;
- foreach my $item (@extension_items) {
- my $basename = basename($item);
- # Skip CVS directories and any hidden files/dirs.
- next if ($basename eq 'CVS' or $basename =~ /^\./);
- if (-d $item) {
- if (!-e "$item/disabled") {
- push(@paths, $item);
- }
- }
- elsif ($item =~ /\.pm$/i) {
- push(@paths, $item);
- }
- }
- return @paths;
+ my $dir = bz_locations()->{'extensionsdir'};
+ my @extension_items = glob("$dir/*");
+ my @paths;
+ foreach my $item (@extension_items) {
+ my $basename = basename($item);
+
+ # Skip CVS directories and any hidden files/dirs.
+ next if ($basename eq 'CVS' or $basename =~ /^\./);
+ if (-d $item) {
+ if (!-e "$item/disabled") {
+ push(@paths, $item);
+ }
+ }
+ elsif ($item =~ /\.pm$/i) {
+ push(@paths, $item);
+ }
+ }
+ return @paths;
}
sub extension_code_files {
- my ($requirements_only) = @_;
- my @files;
- foreach my $path (_extension_paths()) {
- my @load_files;
- if (-d $path) {
- my $extension_file = "$path/Extension.pm";
- my $config_file = "$path/Config.pm";
- if (-e $extension_file) {
- push(@load_files, $extension_file);
- }
- if (-e $config_file) {
- push(@load_files, $config_file);
- }
-
- # Don't load Extension.pm if we just want Config.pm and
- # we found both.
- if ($requirements_only and scalar(@load_files) == 2) {
- shift(@load_files);
- }
- }
- else {
- push(@load_files, $path);
- }
- next if !scalar(@load_files);
- # We know that these paths are safe, because they came from
- # extensionsdir and we checked them specifically for their format.
- # Also, the only thing we ever do with them is pass them to "require".
- trick_taint($_) foreach @load_files;
- push(@files, \@load_files);
- }
-
- my @additional;
- my $datadir = bz_locations()->{'datadir'};
- my $addl_file = "$datadir/extensions/additional";
- if (-e $addl_file) {
- open(my $fh, '<', $addl_file) || die "$addl_file: $!";
- @additional = map { trim($_) } <$fh>;
- close($fh);
+ my ($requirements_only) = @_;
+ my @files;
+ foreach my $path (_extension_paths()) {
+ my @load_files;
+ if (-d $path) {
+ my $extension_file = "$path/Extension.pm";
+ my $config_file = "$path/Config.pm";
+ if (-e $extension_file) {
+ push(@load_files, $extension_file);
+ }
+ if (-e $config_file) {
+ push(@load_files, $config_file);
+ }
+
+ # Don't load Extension.pm if we just want Config.pm and
+ # we found both.
+ if ($requirements_only and scalar(@load_files) == 2) {
+ shift(@load_files);
+ }
}
- return (\@files, \@additional);
+ else {
+ push(@load_files, $path);
+ }
+ next if !scalar(@load_files);
+
+ # We know that these paths are safe, because they came from
+ # extensionsdir and we checked them specifically for their format.
+ # Also, the only thing we ever do with them is pass them to "require".
+ trick_taint($_) foreach @load_files;
+ push(@files, \@load_files);
+ }
+
+ my @additional;
+ my $datadir = bz_locations()->{'datadir'};
+ my $addl_file = "$datadir/extensions/additional";
+ if (-e $addl_file) {
+ open(my $fh, '<', $addl_file) || die "$addl_file: $!";
+ @additional = map { trim($_) } <$fh>;
+ close($fh);
+ }
+ return (\@files, \@additional);
}
# Used by _get_extension_requirements in Bugzilla::Install::Requirements.
sub extension_requirement_packages {
- # If we're in a .cgi script or some time that's not the requirements phase,
- # just use Bugzilla->extensions. This avoids running the below code during
- # a normal Bugzilla page, which is important because the below code
- # doesn't actually function right if it runs after
- # Bugzilla::Extension->load_all (because stuff has already been loaded).
- # (This matters because almost every page calls Bugzilla->feature, which
- # calls OPTIONAL_MODULES, which calls this method.)
- #
- # We check if Bugzilla.pm is already loaded, instead of doing a "require",
- # because we *do* want the code lower down to run during the Requirements
- # phase of checksetup.pl, instead of Bugzilla->extensions, and Bugzilla.pm
- # actually *can* be loaded during the Requirements phase if all the
- # requirements have already been installed.
- if ($INC{'Bugzilla.pm'}) {
- return Bugzilla->extensions;
- }
- my $packages = _cache()->{extension_requirement_packages};
- return $packages if $packages;
- $packages = [];
- my %package_map;
-
- my ($file_sets, $extra_packages) = extension_code_files('requirements only');
- foreach my $file_set (@$file_sets) {
- my $file = shift @$file_set;
- my $name = require $file;
- if ($name =~ /^\d+$/) {
- die install_string('extension_must_return_name',
- { file => $file, returned => $name });
- }
- my $package = "Bugzilla::Extension::$name";
- if ($package->can('package_dir')) {
- $package->package_dir($file);
- }
- else {
- extension_package_directory($package, $file);
- }
- $package_map{$file} = $package;
- push(@$packages, $package);
- }
- foreach my $package (@$extra_packages) {
- eval("require $package") || die $@;
- push(@$packages, $package);
- }
- _cache()->{extension_requirement_packages} = $packages;
- # Used by Bugzilla::Extension->load if it's called after this method
- # (which only happens during checksetup.pl, currently).
- _cache()->{extension_requirement_package_map} = \%package_map;
- return $packages;
+ # If we're in a .cgi script or some time that's not the requirements phase,
+ # just use Bugzilla->extensions. This avoids running the below code during
+ # a normal Bugzilla page, which is important because the below code
+ # doesn't actually function right if it runs after
+ # Bugzilla::Extension->load_all (because stuff has already been loaded).
+ # (This matters because almost every page calls Bugzilla->feature, which
+ # calls OPTIONAL_MODULES, which calls this method.)
+ #
+ # We check if Bugzilla.pm is already loaded, instead of doing a "require",
+ # because we *do* want the code lower down to run during the Requirements
+ # phase of checksetup.pl, instead of Bugzilla->extensions, and Bugzilla.pm
+ # actually *can* be loaded during the Requirements phase if all the
+ # requirements have already been installed.
+ if ($INC{'Bugzilla.pm'}) {
+ return Bugzilla->extensions;
+ }
+ my $packages = _cache()->{extension_requirement_packages};
+ return $packages if $packages;
+ $packages = [];
+ my %package_map;
+
+ my ($file_sets, $extra_packages) = extension_code_files('requirements only');
+ foreach my $file_set (@$file_sets) {
+ my $file = shift @$file_set;
+ my $name = require $file;
+ if ($name =~ /^\d+$/) {
+ die install_string('extension_must_return_name',
+ {file => $file, returned => $name});
+ }
+ my $package = "Bugzilla::Extension::$name";
+ if ($package->can('package_dir')) {
+ $package->package_dir($file);
+ }
+ else {
+ extension_package_directory($package, $file);
+ }
+ $package_map{$file} = $package;
+ push(@$packages, $package);
+ }
+ foreach my $package (@$extra_packages) {
+ eval("require $package") || die $@;
+ push(@$packages, $package);
+ }
+
+ _cache()->{extension_requirement_packages} = $packages;
+
+ # Used by Bugzilla::Extension->load if it's called after this method
+ # (which only happens during checksetup.pl, currently).
+ _cache()->{extension_requirement_package_map} = \%package_map;
+ return $packages;
}
# Used in this file and in Bugzilla::Extension.
sub extension_template_directory {
- my $extension = shift;
- my $class = ref($extension) || $extension;
- my $base_dir = extension_package_directory($class);
- if ($base_dir eq bz_locations->{'extensionsdir'}) {
- return bz_locations->{'templatedir'};
- }
- return "$base_dir/template";
+ my $extension = shift;
+ my $class = ref($extension) || $extension;
+ my $base_dir = extension_package_directory($class);
+ if ($base_dir eq bz_locations->{'extensionsdir'}) {
+ return bz_locations->{'templatedir'};
+ }
+ return "$base_dir/template";
}
# Used in this file and in Bugzilla::Extension.
sub extension_web_directory {
- my $extension = shift;
- my $class = ref($extension) || $extension;
- my $base_dir = extension_package_directory($class);
- return "$base_dir/web";
+ my $extension = shift;
+ my $class = ref($extension) || $extension;
+ my $base_dir = extension_package_directory($class);
+ return "$base_dir/web";
}
# For extensions that are in the extensions/ dir, this both sets and fetches
@@ -223,263 +234,271 @@ sub extension_web_directory {
# when determining the template directory for extensions (or other things
# that are relative to the extension's base directory).
sub extension_package_directory {
- my ($invocant, $file) = @_;
- my $class = ref($invocant) || $invocant;
-
- # $file is set on the first invocation, store the value in the extension's
- # package for retrieval on subsequent calls
- my $var;
- {
- no warnings 'once';
- no strict 'refs';
- $var = \${"${class}::EXTENSION_PACKAGE_DIR"};
- }
- if ($file) {
- $$var = dirname($file);
- }
- my $value = $$var;
-
- # This is for extensions loaded from data/extensions/additional.
- if (!$value) {
- my $short_path = $class;
- $short_path =~ s/::/\//g;
- $short_path .= ".pm";
- my $long_path = $INC{$short_path};
- die "$short_path is not in \%INC" if !$long_path;
- $value = $long_path;
- $value =~ s/\.pm//;
- }
- return $value;
+ my ($invocant, $file) = @_;
+ my $class = ref($invocant) || $invocant;
+
+ # $file is set on the first invocation, store the value in the extension's
+ # package for retrieval on subsequent calls
+ my $var;
+ {
+ no warnings 'once';
+ no strict 'refs';
+ $var = \${"${class}::EXTENSION_PACKAGE_DIR"};
+ }
+ if ($file) {
+ $$var = dirname($file);
+ }
+ my $value = $$var;
+
+ # This is for extensions loaded from data/extensions/additional.
+ if (!$value) {
+ my $short_path = $class;
+ $short_path =~ s/::/\//g;
+ $short_path .= ".pm";
+ my $long_path = $INC{$short_path};
+ die "$short_path is not in \%INC" if !$long_path;
+ $value = $long_path;
+ $value =~ s/\.pm//;
+ }
+ return $value;
}
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";
- }
+ 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()->{install_string_path} ||= template_include_path();
- my $path = _cache()->{install_string_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;
-
- utf8::decode($string_template) if !utf8::is_utf8($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 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;
+ my ($string_id, $vars) = @_;
+ _cache()->{install_string_path} ||= template_include_path();
+ my $path = _cache()->{install_string_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;
+
+ utf8::decode($string_template) if !utf8::is_utf8($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 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 _wanted_languages {
- my ($requested, @wanted);
-
- # Checking SERVER_SOFTWARE is the same as i_am_cgi() in Bugzilla::Util.
- if (exists $ENV{'SERVER_SOFTWARE'}) {
- my $cgi = eval { Bugzilla->cgi } || eval { require CGI; return CGI->new() };
- $requested = $cgi->http('Accept-Language') || '';
- my $lang = $cgi->cookie('LANG');
- push(@wanted, $lang) if $lang;
- }
- else {
- $requested = get_console_locale();
- }
-
- push(@wanted, _sort_accept_language($requested));
- return \@wanted;
+ my ($requested, @wanted);
+
+ # Checking SERVER_SOFTWARE is the same as i_am_cgi() in Bugzilla::Util.
+ if (exists $ENV{'SERVER_SOFTWARE'}) {
+ my $cgi = eval { Bugzilla->cgi } || eval { require CGI; return CGI->new() };
+ $requested = $cgi->http('Accept-Language') || '';
+ my $lang = $cgi->cookie('LANG');
+ push(@wanted, $lang) if $lang;
+ }
+ else {
+ $requested = get_console_locale();
+ }
+
+ push(@wanted, _sort_accept_language($requested));
+ return \@wanted;
}
sub _wanted_to_actual_languages {
- my ($wanted, $supported) = @_;
-
- my @actual;
- foreach my $lang (@$wanted) {
- # If we support the language we want, or *any version* of
- # the language we want, it gets pushed into @actual.
- #
- # 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)
- my @found = grep(/^\Q$lang\E(-.+)?$/i, @$supported);
- push(@actual, @found) if @found;
- }
+ my ($wanted, $supported) = @_;
- # We always include English at the bottom if it's not there, even if
- # it wasn't selected by the user.
- if (!grep($_ eq 'en', @actual)) {
- push(@actual, 'en');
- }
+ my @actual;
+ foreach my $lang (@$wanted) {
- return \@actual;
+ # If we support the language we want, or *any version* of
+ # the language we want, it gets pushed into @actual.
+ #
+ # 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)
+ my @found = grep(/^\Q$lang\E(-.+)?$/i, @$supported);
+ push(@actual, @found) if @found;
+ }
+
+ # We always include English at the bottom if it's not there, even if
+ # it wasn't selected by the user.
+ if (!grep($_ eq 'en', @actual)) {
+ push(@actual, 'en');
+ }
+
+ return \@actual;
}
sub supported_languages {
- my $cache = _cache();
- return $cache->{supported_languages} if $cache->{supported_languages};
-
- my @dirs = glob(bz_locations()->{'templatedir'} . "/*");
- my @languages;
- foreach my $dir (@dirs) {
- # It's a language directory only if it contains "default" or
- # "custom". This auto-excludes CVS directories as well.
- next if (!-d "$dir/default" and !-d "$dir/custom");
- my $lang = basename($dir);
- # Check for language tag format conforming to RFC 1766.
- next unless $lang =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/;
- push(@languages, $lang);
- }
+ my $cache = _cache();
+ return $cache->{supported_languages} if $cache->{supported_languages};
+
+ my @dirs = glob(bz_locations()->{'templatedir'} . "/*");
+ my @languages;
+ foreach my $dir (@dirs) {
+
+ # It's a language directory only if it contains "default" or
+ # "custom". This auto-excludes CVS directories as well.
+ next if (!-d "$dir/default" and !-d "$dir/custom");
+ my $lang = basename($dir);
+
+ # Check for language tag format conforming to RFC 1766.
+ next unless $lang =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/;
+ push(@languages, $lang);
+ }
- $cache->{supported_languages} = \@languages;
- return \@languages;
+ $cache->{supported_languages} = \@languages;
+ return \@languages;
}
sub include_languages {
- my ($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. If there is only one language installed, we take it.
- my $supported = supported_languages();
- return @$supported if @$supported == 1;
-
- my $wanted;
- if ($params->{language}) {
- # We can pass several languages at once as an arrayref
- # or a single language.
- $wanted = $params->{language};
- $wanted = [$wanted] unless ref $wanted;
- }
- else {
- $wanted = _wanted_languages();
- }
- my $actual = _wanted_to_actual_languages($wanted, $supported);
- return @$actual;
+ my ($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. If there is only one language installed, we take it.
+ my $supported = supported_languages();
+ return @$supported if @$supported == 1;
+
+ my $wanted;
+ if ($params->{language}) {
+
+ # We can pass several languages at once as an arrayref
+ # or a single language.
+ $wanted = $params->{language};
+ $wanted = [$wanted] unless ref $wanted;
+ }
+ else {
+ $wanted = _wanted_languages();
+ }
+ my $actual = _wanted_to_actual_languages($wanted, $supported);
+ return @$actual;
}
# Used by template_include_path
sub _template_lang_directories {
- my ($languages, $templatedir) = @_;
-
- my @add = qw(custom default);
- my $project = bz_locations->{'project'};
- unshift(@add, $project) if $project;
-
- my @result;
- foreach my $lang (@$languages) {
- foreach my $dir (@add) {
- my $full_dir = "$templatedir/$lang/$dir";
- if (-d $full_dir) {
- trick_taint($full_dir);
- push(@result, $full_dir);
- }
- }
- }
- return @result;
+ my ($languages, $templatedir) = @_;
+
+ my @add = qw(custom default);
+ my $project = bz_locations->{'project'};
+ unshift(@add, $project) if $project;
+
+ my @result;
+ foreach my $lang (@$languages) {
+ foreach my $dir (@add) {
+ my $full_dir = "$templatedir/$lang/$dir";
+ if (-d $full_dir) {
+ trick_taint($full_dir);
+ push(@result, $full_dir);
+ }
+ }
+ }
+ return @result;
}
# Used by template_include_path.
sub _template_base_directories {
- # 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.
- #
- # We use extension_requirement_packages instead of Bugzilla->extensions
- # because this fucntion is called during the requirements phase of
- # installation (so Bugzilla->extensions isn't available).
- my $extensions = extension_requirement_packages();
- my @template_dirs;
- foreach my $extension (@$extensions) {
- my $dir;
- # If there's a template_dir method available in the extension
- # package, then call it. Note that this has to be defined in
- # Config.pm for extensions that have a Config.pm, to be effective
- # during the Requirements phase of checksetup.pl.
- if ($extension->can('template_dir')) {
- $dir = $extension->template_dir;
- }
- else {
- $dir = extension_template_directory($extension);
- }
- if (-d $dir) {
- push(@template_dirs, $dir);
- }
+
+ # 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.
+ #
+ # We use extension_requirement_packages instead of Bugzilla->extensions
+ # because this fucntion is called during the requirements phase of
+ # installation (so Bugzilla->extensions isn't available).
+ my $extensions = extension_requirement_packages();
+ my @template_dirs;
+ foreach my $extension (@$extensions) {
+ my $dir;
+
+ # If there's a template_dir method available in the extension
+ # package, then call it. Note that this has to be defined in
+ # Config.pm for extensions that have a Config.pm, to be effective
+ # during the Requirements phase of checksetup.pl.
+ if ($extension->can('template_dir')) {
+ $dir = $extension->template_dir;
+ }
+ else {
+ $dir = extension_template_directory($extension);
}
+ if (-d $dir) {
+ push(@template_dirs, $dir);
+ }
+ }
- # Extensions may also contain *only* templates, in which case they
- # won't show up in extension_requirement_packages.
- foreach my $path (_extension_paths()) {
- next if !-d $path;
- if (!-e "$path/Extension.pm" and !-e "$path/Config.pm"
- and -d "$path/template")
- {
- push(@template_dirs, "$path/template");
- }
+ # Extensions may also contain *only* templates, in which case they
+ # won't show up in extension_requirement_packages.
+ foreach my $path (_extension_paths()) {
+ next if !-d $path;
+ if (!-e "$path/Extension.pm" and !-e "$path/Config.pm" and -d "$path/template")
+ {
+ push(@template_dirs, "$path/template");
}
+ }
- push(@template_dirs, bz_locations()->{'templatedir'});
- return \@template_dirs;
+ push(@template_dirs, bz_locations()->{'templatedir'});
+ return \@template_dirs;
}
sub template_include_path {
- my ($params) = @_;
- my @used_languages = include_languages($params);
- # Now, we add template directories in the order they will be searched:
- my $template_dirs = _template_base_directories();
-
- my @include_path;
- foreach my $template_dir (@$template_dirs) {
- my @lang_dirs = _template_lang_directories(\@used_languages,
- $template_dir);
- # Hooks get each set of extension directories separately.
- if ($params->{hook}) {
- push(@include_path, \@lang_dirs);
- }
- # Whereas everything else just gets a whole INCLUDE_PATH.
- else {
- push(@include_path, @lang_dirs);
- }
+ my ($params) = @_;
+ my @used_languages = include_languages($params);
+
+ # Now, we add template directories in the order they will be searched:
+ my $template_dirs = _template_base_directories();
+
+ my @include_path;
+ foreach my $template_dir (@$template_dirs) {
+ my @lang_dirs = _template_lang_directories(\@used_languages, $template_dir);
+
+ # Hooks get each set of extension directories separately.
+ if ($params->{hook}) {
+ push(@include_path, \@lang_dirs);
+ }
+
+ # Whereas everything else just gets a whole INCLUDE_PATH.
+ else {
+ push(@include_path, @lang_dirs);
}
- return \@include_path;
+ }
+ return \@include_path;
}
sub no_checksetup_from_cgi {
- print "Content-Type: text/html; charset=UTF-8\r\n\r\n";
- print install_string('no_checksetup_from_cgi');
- exit;
+ print "Content-Type: text/html; charset=UTF-8\r\n\r\n";
+ print install_string('no_checksetup_from_cgi');
+ exit;
}
######################
@@ -488,172 +507,183 @@ sub no_checksetup_from_cgi {
# Used by install_string
sub _get_string_from_file {
- my ($string_id, $file) = @_;
- # If we already loaded the file, then use its copy from the cache.
- if (my $strings = _cache()->{strings_from_file}->{$file}) {
- return $strings->{$string_id};
- }
-
- # This module is only needed by checksetup.pl,
- # so only load it when needed.
- require Safe;
-
- return undef if !-e $file;
- my $safe = new Safe;
- $safe->rdo($file);
- my %strings = %{$safe->varglob('strings')};
- _cache()->{strings_from_file}->{$file} = \%strings;
- return $strings{$string_id};
+ my ($string_id, $file) = @_;
+
+ # If we already loaded the file, then use its copy from the cache.
+ if (my $strings = _cache()->{strings_from_file}->{$file}) {
+ return $strings->{$string_id};
+ }
+
+ # This module is only needed by checksetup.pl,
+ # so only load it when needed.
+ require Safe;
+
+ return undef if !-e $file;
+ my $safe = new Safe;
+ $safe->rdo($file);
+ my %strings = %{$safe->varglob('strings')};
+ _cache()->{strings_from_file}->{$file} = \%strings;
+ return $strings{$string_id};
}
# 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 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;
+ 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;
}
sub set_output_encoding {
- # If we've already set an encoding layer on STDOUT, don't
- # add another one.
- my @stdout_layers = PerlIO::get_layers(STDOUT);
- return if grep(/^encoding/, @stdout_layers);
-
- my $encoding;
- if (ON_WINDOWS and eval { require Win32::Console }) {
- # Although setlocale() works on Windows, it doesn't always return
- # the current *console's* encoding. So we use OutputCP here instead,
- # when we can.
- $encoding = Win32::Console::OutputCP();
- }
- else {
- my $locale = setlocale(LC_CTYPE);
- if ($locale =~ /\.([^\.]+)$/) {
- $encoding = $1;
- }
- }
- $encoding = "cp$encoding" if ON_WINDOWS;
- $encoding = Encode::resolve_alias($encoding) if $encoding;
- if ($encoding and $encoding !~ /utf-8/i) {
- binmode STDOUT, ":encoding($encoding)";
- binmode STDERR, ":encoding($encoding)";
- }
- else {
- binmode STDOUT, ':utf8';
- binmode STDERR, ':utf8';
- }
+ # If we've already set an encoding layer on STDOUT, don't
+ # add another one.
+ my @stdout_layers = PerlIO::get_layers(STDOUT);
+ return if grep(/^encoding/, @stdout_layers);
+
+ my $encoding;
+ if (ON_WINDOWS and eval { require Win32::Console }) {
+
+ # Although setlocale() works on Windows, it doesn't always return
+ # the current *console's* encoding. So we use OutputCP here instead,
+ # when we can.
+ $encoding = Win32::Console::OutputCP();
+ }
+ else {
+ my $locale = setlocale(LC_CTYPE);
+ if ($locale =~ /\.([^\.]+)$/) {
+ $encoding = $1;
+ }
+ }
+ $encoding = "cp$encoding" if ON_WINDOWS;
+
+ $encoding = Encode::resolve_alias($encoding) if $encoding;
+ if ($encoding and $encoding !~ /utf-8/i) {
+ binmode STDOUT, ":encoding($encoding)";
+ binmode STDERR, ":encoding($encoding)";
+ }
+ else {
+ binmode STDOUT, ':utf8';
+ binmode STDERR, ':utf8';
+ }
}
sub init_console {
- eval { ON_WINDOWS && require Win32::Console::ANSI; };
- $ENV{'ANSI_COLORS_DISABLED'} = 1 if ($@ || !-t *STDOUT);
- $SIG{__DIE__} = \&_console_die;
- prevent_windows_dialog_boxes();
- set_output_encoding();
+ eval { ON_WINDOWS && require Win32::Console::ANSI; };
+ $ENV{'ANSI_COLORS_DISABLED'} = 1 if ($@ || !-t *STDOUT);
+ $SIG{__DIE__} = \&_console_die;
+ prevent_windows_dialog_boxes();
+ set_output_encoding();
}
sub _console_die {
- my ($message) = @_;
- # $^S means "we are in an eval"
- if ($^S) {
- die $message;
- }
- # Remove newlines from the message before we color it, and then
- # add them back in on display. Otherwise the ANSI escape code
- # for resetting the color comes after the newline, and Perl thinks
- # that it should put "at Bugzilla/Install.pm line 1234" after the
- # message.
- $message =~ s/\n+$//;
- # We put quotes around the message to stringify any object exceptions,
- # like Template::Exception.
- die colored("$message", COLOR_ERROR) . "\n";
+ my ($message) = @_;
+
+ # $^S means "we are in an eval"
+ if ($^S) {
+ die $message;
+ }
+
+ # Remove newlines from the message before we color it, and then
+ # add them back in on display. Otherwise the ANSI escape code
+ # for resetting the color comes after the newline, and Perl thinks
+ # that it should put "at Bugzilla/Install.pm line 1234" after the
+ # message.
+ $message =~ s/\n+$//;
+
+ # We put quotes around the message to stringify any object exceptions,
+ # like Template::Exception.
+ die colored("$message", COLOR_ERROR) . "\n";
}
sub success {
- my ($message) = @_;
- print colored($message, COLOR_SUCCESS), "\n";
+ my ($message) = @_;
+ print colored($message, COLOR_SUCCESS), "\n";
}
sub prevent_windows_dialog_boxes {
- # This code comes from http://bugs.activestate.com/show_bug.cgi?id=82183
- # and prevents Perl modules from popping up dialog boxes, particularly
- # during checksetup (since loading DBD::Oracle during checksetup when
- # Oracle isn't installed causes a scary popup and pauses checksetup).
- #
- # Win32::API ships with ActiveState by default, though there could
- # theoretically be a Windows installation without it, I suppose.
- if (ON_WINDOWS and eval { require Win32::API }) {
- # Call kernel32.SetErrorMode with arguments that mean:
- # "The system does not display the critical-error-handler message box.
- # Instead, the system sends the error to the calling process." and
- # "A child process inherits the error mode of its parent process."
- my $SetErrorMode = Win32::API->new('kernel32', 'SetErrorMode',
- 'I', 'I');
- my $SEM_FAILCRITICALERRORS = 0x0001;
- my $SEM_NOGPFAULTERRORBOX = 0x0002;
- $SetErrorMode->Call($SEM_FAILCRITICALERRORS | $SEM_NOGPFAULTERRORBOX);
- }
+
+ # This code comes from http://bugs.activestate.com/show_bug.cgi?id=82183
+ # and prevents Perl modules from popping up dialog boxes, particularly
+ # during checksetup (since loading DBD::Oracle during checksetup when
+ # Oracle isn't installed causes a scary popup and pauses checksetup).
+ #
+ # Win32::API ships with ActiveState by default, though there could
+ # theoretically be a Windows installation without it, I suppose.
+ if (ON_WINDOWS and eval { require Win32::API }) {
+
+ # Call kernel32.SetErrorMode with arguments that mean:
+ # "The system does not display the critical-error-handler message box.
+ # Instead, the system sends the error to the calling process." and
+ # "A child process inherits the error mode of its parent process."
+ my $SetErrorMode = Win32::API->new('kernel32', 'SetErrorMode', 'I', 'I');
+ my $SEM_FAILCRITICALERRORS = 0x0001;
+ my $SEM_NOGPFAULTERRORBOX = 0x0002;
+ $SetErrorMode->Call($SEM_FAILCRITICALERRORS | $SEM_NOGPFAULTERRORBOX);
+ }
}
# This is like request_cache, but it's used only by installation code
# for checksetup.pl and things like that.
our $_cache = {};
+
sub _cache {
- # If the normal request_cache is available (which happens any time
- # after the requirements phase) then we should use that.
- if (eval { Bugzilla->request_cache; }) {
- return Bugzilla->request_cache;
- }
- return $_cache;
+
+ # If the normal request_cache is available (which happens any time
+ # after the requirements phase) then we should use that.
+ if (eval { Bugzilla->request_cache; }) {
+ return Bugzilla->request_cache;
+ }
+ return $_cache;
}
###############################
@@ -661,20 +691,20 @@ sub _cache {
##############################
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]));
+ require Carp;
+ Carp::confess("Undef to trick_taint") unless defined $_[0];
+ my $match = $_[0] =~ /^(.*)$/s;
+ $_[0] = $match ? $1 : undef;
+ return (defined($_[0]));
}
sub trim {
- my ($str) = @_;
- if ($str) {
- $str =~ s/^\s+//g;
- $str =~ s/\s+$//g;
- }
- return $str;
+ my ($str) = @_;
+ if ($str) {
+ $str =~ s/^\s+//g;
+ $str =~ s/\s+$//g;
+ }
+ return $str;
}
__END__
diff --git a/Bugzilla/Job/BugMail.pm b/Bugzilla/Job/BugMail.pm
index e0b7f5448..a6deb5777 100644
--- a/Bugzilla/Job/BugMail.pm
+++ b/Bugzilla/Job/BugMail.pm
@@ -15,18 +15,18 @@ use Bugzilla::BugMail;
BEGIN { eval "use parent qw(Bugzilla::Job::Mailer)"; }
sub work {
- my ($class, $job) = @_;
- my $success = eval {
- Bugzilla::BugMail::dequeue($job->arg->{vars});
- 1;
- };
- if (!$success) {
- $job->failed($@);
- undef $@;
- }
- else {
- $job->completed;
- }
+ my ($class, $job) = @_;
+ my $success = eval {
+ Bugzilla::BugMail::dequeue($job->arg->{vars});
+ 1;
+ };
+ if (!$success) {
+ $job->failed($@);
+ undef $@;
+ }
+ else {
+ $job->completed;
+ }
}
1;
diff --git a/Bugzilla/Job/Mailer.pm b/Bugzilla/Job/Mailer.pm
index cd1c23445..544d86107 100644
--- a/Bugzilla/Job/Mailer.pm
+++ b/Bugzilla/Job/Mailer.pm
@@ -16,31 +16,33 @@ BEGIN { eval "use parent qw(TheSchwartz::Worker)"; }
# The longest we expect a job to possibly take, in seconds.
use constant grab_for => 300;
+
# We don't want email to fail permanently very easily. Retry for 30 days.
use constant max_retries => 725;
# The first few retries happen quickly, but after that we wait an hour for
# each retry.
sub retry_delay {
- my ($class, $num_retries) = @_;
- if ($num_retries < 5) {
- return (10, 30, 60, 300, 600)[$num_retries];
- }
- # One hour
- return 60*60;
+ my ($class, $num_retries) = @_;
+ if ($num_retries < 5) {
+ return (10, 30, 60, 300, 600)[$num_retries];
+ }
+
+ # One hour
+ return 60 * 60;
}
sub work {
- my ($class, $job) = @_;
- my $msg = $job->arg->{msg};
- my $success = eval { MessageToMTA($msg, 1); 1; };
- if (!$success) {
- $job->failed($@);
- undef $@;
- }
- else {
- $job->completed;
- }
+ my ($class, $job) = @_;
+ my $msg = $job->arg->{msg};
+ my $success = eval { MessageToMTA($msg, 1); 1; };
+ if (!$success) {
+ $job->failed($@);
+ undef $@;
+ }
+ else {
+ $job->completed;
+ }
}
1;
diff --git a/Bugzilla/JobQueue.pm b/Bugzilla/JobQueue.pm
index 6ff85d84f..e48182007 100644
--- a/Bugzilla/JobQueue.pm
+++ b/Bugzilla/JobQueue.pm
@@ -21,153 +21,155 @@ use fields qw(_worker_pidfile);
# This maps job names for Bugzilla::JobQueue to the appropriate modules.
# If you add new types of jobs, you should add a mapping here.
-use constant JOB_MAP => {
- send_mail => 'Bugzilla::Job::Mailer',
- bug_mail => 'Bugzilla::Job::BugMail',
-};
+use constant JOB_MAP =>
+ {send_mail => 'Bugzilla::Job::Mailer', bug_mail => 'Bugzilla::Job::BugMail',};
# Without a driver cache TheSchwartz opens a new database connection
# for each email it sends. This cached connection doesn't persist
# across requests.
-use constant DRIVER_CACHE_TIME => 300; # 5 minutes
+use constant DRIVER_CACHE_TIME => 300; # 5 minutes
# To avoid memory leak/fragmentation, a worker process won't process more than
# MAX_MESSAGES messages.
use constant MAX_MESSAGES => 1000;
sub job_map {
- if (!defined(Bugzilla->request_cache->{job_map})) {
- my $job_map = JOB_MAP;
- Bugzilla::Hook::process('job_map', { job_map => $job_map });
- Bugzilla->request_cache->{job_map} = $job_map;
- }
-
- return Bugzilla->request_cache->{job_map};
+ if (!defined(Bugzilla->request_cache->{job_map})) {
+ my $job_map = JOB_MAP;
+ Bugzilla::Hook::process('job_map', {job_map => $job_map});
+ Bugzilla->request_cache->{job_map} = $job_map;
+ }
+
+ return Bugzilla->request_cache->{job_map};
}
sub new {
- my $class = shift;
-
- if (!Bugzilla->feature('jobqueue')) {
- ThrowUserError('feature_disabled', { feature => 'jobqueue' });
- }
-
- my $lc = Bugzilla->localconfig;
- # We need to use the main DB as TheSchwartz module is going
- # to write to it.
- my $self = $class->SUPER::new(
- databases => [{
- dsn => Bugzilla->dbh_main->{private_bz_dsn},
- user => $lc->{db_user},
- pass => $lc->{db_pass},
- prefix => 'ts_',
- }],
- driver_cache_expiration => DRIVER_CACHE_TIME,
- prioritize => 1,
- );
-
- return $self;
+ my $class = shift;
+
+ if (!Bugzilla->feature('jobqueue')) {
+ ThrowUserError('feature_disabled', {feature => 'jobqueue'});
+ }
+
+ my $lc = Bugzilla->localconfig;
+
+ # We need to use the main DB as TheSchwartz module is going
+ # to write to it.
+ my $self = $class->SUPER::new(
+ databases => [{
+ dsn => Bugzilla->dbh_main->{private_bz_dsn},
+ user => $lc->{db_user},
+ pass => $lc->{db_pass},
+ prefix => 'ts_',
+ }],
+ driver_cache_expiration => DRIVER_CACHE_TIME,
+ prioritize => 1,
+ );
+
+ return $self;
}
# A way to get access to the underlying databases directly.
sub bz_databases {
- my $self = shift;
- my @hashes = keys %{ $self->{databases} };
- return map { $self->driver_for($_) } @hashes;
+ my $self = shift;
+ my @hashes = keys %{$self->{databases}};
+ return map { $self->driver_for($_) } @hashes;
}
# inserts a job into the queue to be processed and returns immediately
sub insert {
- my $self = shift;
- my $job = shift;
-
- if (!ref($job)) {
- my $mapped_job = Bugzilla::JobQueue->job_map()->{$job};
- ThrowCodeError('jobqueue_no_job_mapping', { job => $job })
- if !$mapped_job;
-
- $job = new TheSchwartz::Job(
- funcname => $mapped_job,
- arg => $_[0],
- priority => $_[1] || 5
- );
- }
-
- my $retval = $self->SUPER::insert($job);
- # XXX Need to get an error message here if insert fails, but
- # I don't see any way to do that in TheSchwartz.
- ThrowCodeError('jobqueue_insert_failed', { job => $job, errmsg => $@ })
- if !$retval;
-
- return $retval;
+ my $self = shift;
+ my $job = shift;
+
+ if (!ref($job)) {
+ my $mapped_job = Bugzilla::JobQueue->job_map()->{$job};
+ ThrowCodeError('jobqueue_no_job_mapping', {job => $job}) if !$mapped_job;
+
+ $job = new TheSchwartz::Job(
+ funcname => $mapped_job,
+ arg => $_[0],
+ priority => $_[1] || 5
+ );
+ }
+
+ my $retval = $self->SUPER::insert($job);
+
+ # XXX Need to get an error message here if insert fails, but
+ # I don't see any way to do that in TheSchwartz.
+ ThrowCodeError('jobqueue_insert_failed', {job => $job, errmsg => $@})
+ if !$retval;
+
+ return $retval;
}
# To avoid memory leaks/fragmentation which tends to happen for long running
# perl processes; check for jobs, and spawn a new process to empty the queue.
sub subprocess_worker {
- my $self = shift;
-
- my $command = "$0 -d -p '" . $self->{_worker_pidfile} . "' onepass";
-
- while (1) {
- my $time = (time);
- my @jobs = $self->list_jobs({
- funcname => $self->{all_abilities},
- run_after => $time,
- grabbed_until => $time,
- limit => 1,
- });
- if (@jobs) {
- $self->debug("Spawning queue worker process");
- # Run the worker as a daemon
- system $command;
- # And poll the PID to detect when the working has finished.
- # We do this instead of system() to allow for the INT signal to
- # interrup us and trigger kill_worker().
- my $pid = read_text($self->{_worker_pidfile}, err_mode => 'quiet');
- if ($pid) {
- sleep(3) while(kill(0, $pid));
- }
- $self->debug("Queue worker process completed");
- } else {
- $self->debug("No jobs found");
- }
- sleep(5);
+ my $self = shift;
+
+ my $command = "$0 -d -p '" . $self->{_worker_pidfile} . "' onepass";
+
+ while (1) {
+ my $time = (time);
+ my @jobs = $self->list_jobs({
+ funcname => $self->{all_abilities},
+ run_after => $time,
+ grabbed_until => $time,
+ limit => 1,
+ });
+ if (@jobs) {
+ $self->debug("Spawning queue worker process");
+
+ # Run the worker as a daemon
+ system $command;
+
+ # And poll the PID to detect when the working has finished.
+ # We do this instead of system() to allow for the INT signal to
+ # interrup us and trigger kill_worker().
+ my $pid = read_text($self->{_worker_pidfile}, err_mode => 'quiet');
+ if ($pid) {
+ sleep(3) while (kill(0, $pid));
+ }
+ $self->debug("Queue worker process completed");
}
+ else {
+ $self->debug("No jobs found");
+ }
+ sleep(5);
+ }
}
sub kill_worker {
- my $self = Bugzilla->job_queue();
- if ($self->{_worker_pidfile} && -e $self->{_worker_pidfile}) {
- my $worker_pid = read_text($self->{_worker_pidfile});
- if ($worker_pid && kill(0, $worker_pid)) {
- $self->debug("Stopping worker process");
- system "$0 -f -p '" . $self->{_worker_pidfile} . "' stop";
- }
+ my $self = Bugzilla->job_queue();
+ if ($self->{_worker_pidfile} && -e $self->{_worker_pidfile}) {
+ my $worker_pid = read_text($self->{_worker_pidfile});
+ if ($worker_pid && kill(0, $worker_pid)) {
+ $self->debug("Stopping worker process");
+ system "$0 -f -p '" . $self->{_worker_pidfile} . "' stop";
}
+ }
}
sub set_pidfile {
- my ($self, $pidfile) = @_;
- $self->{_worker_pidfile} = bz_locations->{'datadir'} .
- '/worker-' . basename($pidfile);
+ my ($self, $pidfile) = @_;
+ $self->{_worker_pidfile}
+ = bz_locations->{'datadir'} . '/worker-' . basename($pidfile);
}
# Clear the request cache at the start of each run.
sub work_once {
- my $self = shift;
- Bugzilla->clear_request_cache();
- return $self->SUPER::work_once(@_);
+ my $self = shift;
+ Bugzilla->clear_request_cache();
+ return $self->SUPER::work_once(@_);
}
# Never process more than MAX_MESSAGES in one batch, to avoid memory
# leak/fragmentation issues.
sub work_until_done {
- my $self = shift;
- my $count = 0;
- while ($count++ < MAX_MESSAGES) {
- $self->work_once or last;
- }
+ my $self = shift;
+ my $count = 0;
+ while ($count++ < MAX_MESSAGES) {
+ $self->work_once or last;
+ }
}
1;
diff --git a/Bugzilla/JobQueue/Runner.pm b/Bugzilla/JobQueue/Runner.pm
index 104a97b0b..a9eb0bca4 100644
--- a/Bugzilla/JobQueue/Runner.pm
+++ b/Bugzilla/JobQueue/Runner.pm
@@ -28,8 +28,8 @@ BEGIN { eval "use parent qw(Daemon::Generic)"; }
our $VERSION = BUGZILLA_VERSION;
# Info we need to install/uninstall the daemon.
-our $chkconfig = "/sbin/chkconfig";
-our $initd = "/etc/init.d";
+our $chkconfig = "/sbin/chkconfig";
+our $initd = "/etc/init.d";
our $initscript = "bugzilla-queue";
# The Daemon::Generic docs say that it uses all sorts of
@@ -37,187 +37,188 @@ our $initscript = "bugzilla-queue";
# only thing it uses from gd_preconfig is the "pidfile"
# config parameter.
sub gd_preconfig {
- my $self = shift;
-
- $self->{_run_command} = 'subprocess_worker';
- my $pidfile = $self->{gd_args}{pidfile};
- if (!$pidfile) {
- $pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname}
- . ".pid";
- }
- return (pidfile => $pidfile);
+ my $self = shift;
+
+ $self->{_run_command} = 'subprocess_worker';
+ my $pidfile = $self->{gd_args}{pidfile};
+ if (!$pidfile) {
+ $pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname} . ".pid";
+ }
+ return (pidfile => $pidfile);
}
# All config other than the pidfile has to be done in gd_getopt
# in order for it to be set up early enough.
sub gd_getopt {
- my $self = shift;
+ my $self = shift;
- $self->SUPER::gd_getopt();
+ $self->SUPER::gd_getopt();
- if ($self->{gd_args}{progname}) {
- $self->{gd_progname} = $self->{gd_args}{progname};
- }
- else {
- $self->{gd_progname} = basename($0);
- }
+ if ($self->{gd_args}{progname}) {
+ $self->{gd_progname} = $self->{gd_args}{progname};
+ }
+ else {
+ $self->{gd_progname} = basename($0);
+ }
- # There are places that Daemon Generic's new() uses $0 instead of
- # gd_progname, which it really shouldn't, but this hack fixes it.
- $self->{_original_zero} = $0;
- $0 = $self->{gd_progname};
+ # There are places that Daemon Generic's new() uses $0 instead of
+ # gd_progname, which it really shouldn't, but this hack fixes it.
+ $self->{_original_zero} = $0;
+ $0 = $self->{gd_progname};
}
sub gd_postconfig {
- my $self = shift;
- # See the hack above in gd_getopt. This just reverses it
- # in case anything else needs the accurate $0.
- $0 = delete $self->{_original_zero};
+ my $self = shift;
+
+ # See the hack above in gd_getopt. This just reverses it
+ # in case anything else needs the accurate $0.
+ $0 = delete $self->{_original_zero};
}
sub gd_more_opt {
- my $self = shift;
- return (
- 'pidfile=s' => \$self->{gd_args}{pidfile},
- 'n=s' => \$self->{gd_args}{progname},
- );
+ my $self = shift;
+ return (
+ 'pidfile=s' => \$self->{gd_args}{pidfile},
+ 'n=s' => \$self->{gd_args}{progname},
+ );
}
sub gd_usage {
- pod2usage({ -verbose => 0, -exitval => 'NOEXIT' });
- return 0
+ pod2usage({-verbose => 0, -exitval => 'NOEXIT'});
+ return 0;
}
sub gd_can_install {
- my $self = shift;
+ my $self = shift;
+
+ my $source_file;
+ if (-e "/etc/SuSE-release") {
+ $source_file = "contrib/$initscript.suse";
+ }
+ else {
+ $source_file = "contrib/$initscript.rhel";
+ }
+ my $dest_file = "$initd/$initscript";
+ my $sysconfig = '/etc/sysconfig';
+ my $config_file = "$sysconfig/$initscript";
+
+ if (!-x $chkconfig or !-d $initd) {
+ return $self->SUPER::gd_can_install(@_);
+ }
- my $source_file;
- if ( -e "/etc/SuSE-release" ) {
- $source_file = "contrib/$initscript.suse";
- } else {
- $source_file = "contrib/$initscript.rhel";
+ return sub {
+ if (!-w $initd) {
+ print "You must run the 'install' command as root.\n";
+ return;
}
- my $dest_file = "$initd/$initscript";
- my $sysconfig = '/etc/sysconfig';
- my $config_file = "$sysconfig/$initscript";
-
- if (!-x $chkconfig or !-d $initd) {
- return $self->SUPER::gd_can_install(@_);
+ if (-e $dest_file) {
+ print "$initscript already in $initd.\n";
+ }
+ else {
+ copy($source_file, $dest_file)
+ or die "Could not copy $source_file to $dest_file: $!";
+ chmod(0755, $dest_file) or die "Could not change permissions on $dest_file: $!";
}
- return sub {
- if (!-w $initd) {
- print "You must run the 'install' command as root.\n";
- return;
- }
- if (-e $dest_file) {
- print "$initscript already in $initd.\n";
- }
- else {
- copy($source_file, $dest_file)
- or die "Could not copy $source_file to $dest_file: $!";
- chmod(0755, $dest_file)
- or die "Could not change permissions on $dest_file: $!";
- }
-
- system($chkconfig, '--add', $initscript);
- print "$initscript installed.",
- " To start the daemon, do \"$dest_file start\" as root.\n";
-
- if (-d $sysconfig and -w $sysconfig) {
- if (-e $config_file) {
- print "$config_file already exists.\n";
- return;
- }
-
- open(my $config_fh, ">", $config_file)
- or die "Could not write to $config_file: $!";
- my $directory = abs_path(dirname($self->{_original_zero}));
- my $owner_id = (stat $self->{_original_zero})[4];
- my $owner = getpwuid($owner_id);
- print $config_fh <<END;
+ system($chkconfig, '--add', $initscript);
+ print "$initscript installed.",
+ " To start the daemon, do \"$dest_file start\" as root.\n";
+
+ if (-d $sysconfig and -w $sysconfig) {
+ if (-e $config_file) {
+ print "$config_file already exists.\n";
+ return;
+ }
+
+ open(my $config_fh, ">", $config_file)
+ or die "Could not write to $config_file: $!";
+ my $directory = abs_path(dirname($self->{_original_zero}));
+ my $owner_id = (stat $self->{_original_zero})[4];
+ my $owner = getpwuid($owner_id);
+ print $config_fh <<END;
#!/bin/sh
BUGZILLA="$directory"
# This user must have write access to Bugzilla's data/ directory.
USER=$owner
END
- close($config_fh);
- }
- else {
- print "Please edit $dest_file to configure the daemon.\n";
- }
+ close($config_fh);
+ }
+ else {
+ print "Please edit $dest_file to configure the daemon.\n";
}
+ }
}
sub gd_can_uninstall {
- my $self = shift;
-
- if (-x $chkconfig and -d $initd) {
- return sub {
- if (!-e "$initd/$initscript") {
- print "$initscript not installed.\n";
- return;
- }
- system($chkconfig, '--del', $initscript);
- print "$initscript disabled.",
- " To stop it, run: $initd/$initscript stop\n";
- }
+ my $self = shift;
+
+ if (-x $chkconfig and -d $initd) {
+ return sub {
+ if (!-e "$initd/$initscript") {
+ print "$initscript not installed.\n";
+ return;
+ }
+ system($chkconfig, '--del', $initscript);
+ print "$initscript disabled.", " To stop it, run: $initd/$initscript stop\n";
}
+ }
- return $self->SUPER::gd_can_install(@_);
+ return $self->SUPER::gd_can_install(@_);
}
sub gd_check {
- my $self = shift;
-
- # Get a count of all the jobs currently in the queue.
- my $jq = Bugzilla->job_queue();
- my @dbs = $jq->bz_databases();
- my $count = 0;
- foreach my $driver (@dbs) {
- $count += $driver->select_one('SELECT COUNT(*) FROM ts_job', []);
- }
- print get_text('job_queue_depth', { count => $count }) . "\n";
+ my $self = shift;
+
+ # Get a count of all the jobs currently in the queue.
+ my $jq = Bugzilla->job_queue();
+ my @dbs = $jq->bz_databases();
+ my $count = 0;
+ foreach my $driver (@dbs) {
+ $count += $driver->select_one('SELECT COUNT(*) FROM ts_job', []);
+ }
+ print get_text('job_queue_depth', {count => $count}) . "\n";
}
sub gd_setup_signals {
- my $self = shift;
- $self->SUPER::gd_setup_signals();
- $SIG{TERM} = sub { $self->gd_quit_event(); }
+ my $self = shift;
+ $self->SUPER::gd_setup_signals();
+ $SIG{TERM} = sub { $self->gd_quit_event(); }
}
sub gd_quit_event {
- Bugzilla->job_queue->kill_worker();
- exit(1);
+ Bugzilla->job_queue->kill_worker();
+ exit(1);
}
sub gd_other_cmd {
- my ($self, $do, $locked) = @_;
- if ($do eq "once") {
- $self->{_run_command} = 'work_once';
- } elsif ($do eq "onepass") {
- $self->{_run_command} = 'work_until_done';
- } else {
- $self->SUPER::gd_other_cmd($do, $locked);
- }
+ my ($self, $do, $locked) = @_;
+ if ($do eq "once") {
+ $self->{_run_command} = 'work_once';
+ }
+ elsif ($do eq "onepass") {
+ $self->{_run_command} = 'work_until_done';
+ }
+ else {
+ $self->SUPER::gd_other_cmd($do, $locked);
+ }
}
sub gd_run {
- my $self = shift;
- $self->_do_work($self->{_run_command});
+ my $self = shift;
+ $self->_do_work($self->{_run_command});
}
sub _do_work {
- my ($self, $fn) = @_;
-
- my $jq = Bugzilla->job_queue();
- $jq->set_verbose($self->{debug});
- $jq->set_pidfile($self->{gd_pidfile});
- foreach my $module (values %{ Bugzilla::JobQueue->job_map() }) {
- eval "use $module";
- $jq->can_do($module);
- }
- $jq->$fn;
+ my ($self, $fn) = @_;
+
+ my $jq = Bugzilla->job_queue();
+ $jq->set_verbose($self->{debug});
+ $jq->set_pidfile($self->{gd_pidfile});
+ foreach my $module (values %{Bugzilla::JobQueue->job_map()}) {
+ eval "use $module";
+ $jq->can_do($module);
+ }
+ $jq->$fn;
}
1;
diff --git a/Bugzilla/Keyword.pm b/Bugzilla/Keyword.pm
index afa93e1e9..f1cb6cadf 100644
--- a/Bugzilla/Keyword.pm
+++ b/Bugzilla/Keyword.pm
@@ -23,44 +23,42 @@ use Bugzilla::Util;
use constant IS_CONFIG => 1;
use constant DB_COLUMNS => qw(
- keyworddefs.id
- keyworddefs.name
- keyworddefs.description
+ keyworddefs.id
+ keyworddefs.name
+ keyworddefs.description
);
use constant DB_TABLE => 'keyworddefs';
-use constant VALIDATORS => {
- name => \&_check_name,
- description => \&_check_description,
-};
+use constant VALIDATORS =>
+ {name => \&_check_name, description => \&_check_description,};
use constant UPDATE_COLUMNS => qw(
- name
- description
+ name
+ description
);
###############################
#### Accessors ######
###############################
-sub description { return $_[0]->{'description'}; }
+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'};
+ 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_name { $_[0]->set('name', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); }
###############################
@@ -68,27 +66,29 @@ sub set_description { $_[0]->set('description', $_[1]); }
###############################
sub get_all_with_bug_count {
- my $class = shift;
- my $dbh = Bugzilla->dbh;
- my $keywords =
- $dbh->selectall_arrayref('SELECT '
- . join(', ', $class->_get_db_columns) . ',
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+ my $keywords = $dbh->selectall_arrayref(
+ 'SELECT ' . join(', ', $class->_get_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;
+ 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;
}
###############################
@@ -96,33 +96,33 @@ sub get_all_with_bug_count {
###############################
sub _check_name {
- my ($self, $name) = @_;
-
- $name = trim($name);
- if (!defined $name or $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) || lc($self->name) ne lc($name)) {
- my $keyword = new Bugzilla::Keyword({ name => $name });
- ThrowUserError("keyword_already_exists", { name => $name }) if $keyword;
- }
-
- return $name;
+ my ($self, $name) = @_;
+
+ $name = trim($name);
+ if (!defined $name or $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) || lc($self->name) ne lc($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);
- if (!defined $desc or $desc eq '') {
- ThrowUserError("keyword_blank_description");
- }
- return $desc;
+ my ($self, $desc) = @_;
+ $desc = trim($desc);
+ if (!defined $desc or $desc eq '') {
+ ThrowUserError("keyword_blank_description");
+ }
+ return $desc;
}
1;
diff --git a/Bugzilla/MIME.pm b/Bugzilla/MIME.pm
index 8c6c141bb..660799e66 100644
--- a/Bugzilla/MIME.pm
+++ b/Bugzilla/MIME.pm
@@ -14,91 +14,91 @@ use warnings;
use parent qw(Email::MIME);
sub new {
- my ($class, $msg) = @_;
- state $use_utf8 = Bugzilla->params->{'utf8'};
-
- # Template-Toolkit trims trailing newlines, which is problematic when
- # parsing headers.
- $msg =~ s/\n*$/\n/;
-
- # Because the encoding headers are not present in our email templates, we
- # need to treat them as binary UTF-8 when parsing.
- my ($in_header, $has_type, $has_encoding, $has_body) = (1);
- foreach my $line (split(/\n/, $msg)) {
- if ($line eq '') {
- $in_header = 0;
- next;
- }
- if (!$in_header) {
- $has_body = 1;
- last;
- }
- $has_type = 1 if $line =~ /^Content-Type:/i;
- $has_encoding = 1 if $line =~ /^Content-Transfer-Encoding:/i;
+ my ($class, $msg) = @_;
+ state $use_utf8 = Bugzilla->params->{'utf8'};
+
+ # Template-Toolkit trims trailing newlines, which is problematic when
+ # parsing headers.
+ $msg =~ s/\n*$/\n/;
+
+ # Because the encoding headers are not present in our email templates, we
+ # need to treat them as binary UTF-8 when parsing.
+ my ($in_header, $has_type, $has_encoding, $has_body) = (1);
+ foreach my $line (split(/\n/, $msg)) {
+ if ($line eq '') {
+ $in_header = 0;
+ next;
}
- if ($has_body) {
- if (!$has_type && $use_utf8) {
- $msg = qq#Content-Type: text/plain; charset="UTF-8"\n# . $msg;
- }
- if (!$has_encoding) {
- $msg = qq#Content-Transfer-Encoding: binary\n# . $msg;
- }
+ if (!$in_header) {
+ $has_body = 1;
+ last;
}
- if ($use_utf8 && utf8::is_utf8($msg)) {
- utf8::encode($msg);
+ $has_type = 1 if $line =~ /^Content-Type:/i;
+ $has_encoding = 1 if $line =~ /^Content-Transfer-Encoding:/i;
+ }
+ if ($has_body) {
+ if (!$has_type && $use_utf8) {
+ $msg = qq#Content-Type: text/plain; charset="UTF-8"\n# . $msg;
}
-
- # RFC 2822 requires us to have CRLF for our line endings and
- # Email::MIME doesn't do this for us. We use \015 (CR) and \012 (LF)
- # directly because Perl translates "\n" depending on what platform
- # you're running on. See http://perldoc.perl.org/perlport.html#Newlines
- $msg =~ s/(?:\015+)?\012/\015\012/msg;
-
- return $class->SUPER::new($msg);
+ if (!$has_encoding) {
+ $msg = qq#Content-Transfer-Encoding: binary\n# . $msg;
+ }
+ }
+ if ($use_utf8 && utf8::is_utf8($msg)) {
+ utf8::encode($msg);
+ }
+
+ # RFC 2822 requires us to have CRLF for our line endings and
+ # Email::MIME doesn't do this for us. We use \015 (CR) and \012 (LF)
+ # directly because Perl translates "\n" depending on what platform
+ # you're running on. See http://perldoc.perl.org/perlport.html#Newlines
+ $msg =~ s/(?:\015+)?\012/\015\012/msg;
+
+ return $class->SUPER::new($msg);
}
sub as_string {
- my $self = shift;
- state $use_utf8 = Bugzilla->params->{'utf8'};
-
- # We add this header to uniquely identify all email that we
- # send as coming from this Bugzilla installation.
- #
- # We don't use correct_urlbase, because we want this URL to
- # *always* be the same for this Bugzilla, in every email,
- # even if the admin changes the "ssl_redirect" parameter some day.
- $self->header_set('X-Bugzilla-URL', Bugzilla->params->{'urlbase'});
-
- # We add this header to mark the mail as "auto-generated" and
- # thus to hopefully avoid auto replies.
- $self->header_set('Auto-Submitted', 'auto-generated');
-
- # MIME-Version must be set otherwise some mailsystems ignore the charset
- $self->header_set('MIME-Version', '1.0') if !$self->header('MIME-Version');
-
- # Encode the headers correctly.
- foreach my $header ($self->header_names) {
- my @values = $self->header($header);
- map { utf8::decode($_) if defined($_) && !utf8::is_utf8($_) } @values;
-
- $self->header_str_set($header, @values);
- }
-
- # Ensure the character-set and encoding is set correctly on single part
- # emails. Multipart emails should have these already set when the parts
- # are assembled.
- if (scalar($self->parts) == 1) {
- $self->charset_set('UTF-8') if $use_utf8;
- $self->encoding_set('quoted-printable');
- }
-
- # Ensure we always return the encoded string
- my $value = $self->SUPER::as_string();
- if ($use_utf8 && utf8::is_utf8($value)) {
- utf8::encode($value);
- }
-
- return $value;
+ my $self = shift;
+ state $use_utf8 = Bugzilla->params->{'utf8'};
+
+ # We add this header to uniquely identify all email that we
+ # send as coming from this Bugzilla installation.
+ #
+ # We don't use correct_urlbase, because we want this URL to
+ # *always* be the same for this Bugzilla, in every email,
+ # even if the admin changes the "ssl_redirect" parameter some day.
+ $self->header_set('X-Bugzilla-URL', Bugzilla->params->{'urlbase'});
+
+ # We add this header to mark the mail as "auto-generated" and
+ # thus to hopefully avoid auto replies.
+ $self->header_set('Auto-Submitted', 'auto-generated');
+
+ # MIME-Version must be set otherwise some mailsystems ignore the charset
+ $self->header_set('MIME-Version', '1.0') if !$self->header('MIME-Version');
+
+ # Encode the headers correctly.
+ foreach my $header ($self->header_names) {
+ my @values = $self->header($header);
+ map { utf8::decode($_) if defined($_) && !utf8::is_utf8($_) } @values;
+
+ $self->header_str_set($header, @values);
+ }
+
+ # Ensure the character-set and encoding is set correctly on single part
+ # emails. Multipart emails should have these already set when the parts
+ # are assembled.
+ if (scalar($self->parts) == 1) {
+ $self->charset_set('UTF-8') if $use_utf8;
+ $self->encoding_set('quoted-printable');
+ }
+
+ # Ensure we always return the encoded string
+ my $value = $self->SUPER::as_string();
+ if ($use_utf8 && utf8::is_utf8($value)) {
+ utf8::encode($value);
+ }
+
+ return $value;
}
1;
diff --git a/Bugzilla/Mailer.pm b/Bugzilla/Mailer.pm
index 5ccf2d1ed..a5f79b9bc 100644
--- a/Bugzilla/Mailer.pm
+++ b/Bugzilla/Mailer.pm
@@ -28,199 +28,209 @@ use Email::Sender::Transport::SMTP::Persistent;
use Bugzilla::Sender::Transport::Sendmail;
sub generate_email {
- my ($vars, $templates) = @_;
- my ($lang, $email_format, $msg_text, $msg_html, $msg_header);
- state $use_utf8 = Bugzilla->params->{'utf8'};
-
- if ($vars->{to_user}) {
- $lang = $vars->{to_user}->setting('lang');
- $email_format = $vars->{to_user}->setting('email_format');
- } else {
- # If there are users in the CC list who don't have an account,
- # use the default language for email notifications.
- $lang = Bugzilla::User->new()->setting('lang');
- # However we cannot fall back to the default email_format, since
- # it may be HTML, and many of the includes used in the HTML
- # template require a valid user object. Instead we fall back to
- # the plaintext template.
- $email_format = 'text_only';
- }
-
- my $template = Bugzilla->template_inner($lang);
-
- $template->process($templates->{header}, $vars, \$msg_header)
- || ThrowTemplateError($template->error());
- $template->process($templates->{text}, $vars, \$msg_text)
- || ThrowTemplateError($template->error());
-
- my @parts = (
- Bugzilla::MIME->create(
- attributes => {
- content_type => 'text/plain',
- charset => $use_utf8 ? 'UTF-8' : 'iso-8859-1',
- encoding => 'quoted-printable',
- },
- body_str => $msg_text,
- )
- );
- if ($templates->{html} && $email_format eq 'html') {
- $template->process($templates->{html}, $vars, \$msg_html)
- || ThrowTemplateError($template->error());
- push @parts, Bugzilla::MIME->create(
- attributes => {
- content_type => 'text/html',
- charset => $use_utf8 ? 'UTF-8' : 'iso-8859-1',
- encoding => 'quoted-printable',
- },
- body_str => $msg_html,
- );
- }
-
- my $email = Bugzilla::MIME->new($msg_header);
- if (scalar(@parts) == 1) {
- $email->content_type_set($parts[0]->content_type);
- } else {
- $email->content_type_set('multipart/alternative');
- # Some mail clients need same encoding for each part, even empty ones.
- $email->charset_set('UTF-8') if $use_utf8;
- }
- $email->parts_set(\@parts);
- return $email;
+ my ($vars, $templates) = @_;
+ my ($lang, $email_format, $msg_text, $msg_html, $msg_header);
+ state $use_utf8 = Bugzilla->params->{'utf8'};
+
+ if ($vars->{to_user}) {
+ $lang = $vars->{to_user}->setting('lang');
+ $email_format = $vars->{to_user}->setting('email_format');
+ }
+ else {
+ # If there are users in the CC list who don't have an account,
+ # use the default language for email notifications.
+ $lang = Bugzilla::User->new()->setting('lang');
+
+ # However we cannot fall back to the default email_format, since
+ # it may be HTML, and many of the includes used in the HTML
+ # template require a valid user object. Instead we fall back to
+ # the plaintext template.
+ $email_format = 'text_only';
+ }
+
+ my $template = Bugzilla->template_inner($lang);
+
+ $template->process($templates->{header}, $vars, \$msg_header)
+ || ThrowTemplateError($template->error());
+ $template->process($templates->{text}, $vars, \$msg_text)
+ || ThrowTemplateError($template->error());
+
+ my @parts = (Bugzilla::MIME->create(
+ attributes => {
+ content_type => 'text/plain',
+ charset => $use_utf8 ? 'UTF-8' : 'iso-8859-1',
+ encoding => 'quoted-printable',
+ },
+ body_str => $msg_text,
+ ));
+ if ($templates->{html} && $email_format eq 'html') {
+ $template->process($templates->{html}, $vars, \$msg_html)
+ || ThrowTemplateError($template->error());
+ push @parts,
+ Bugzilla::MIME->create(
+ attributes => {
+ content_type => 'text/html',
+ charset => $use_utf8 ? 'UTF-8' : 'iso-8859-1',
+ encoding => 'quoted-printable',
+ },
+ body_str => $msg_html,
+ );
+ }
+
+ my $email = Bugzilla::MIME->new($msg_header);
+ if (scalar(@parts) == 1) {
+ $email->content_type_set($parts[0]->content_type);
+ }
+ else {
+ $email->content_type_set('multipart/alternative');
+
+ # Some mail clients need same encoding for each part, even empty ones.
+ $email->charset_set('UTF-8') if $use_utf8;
+ }
+ $email->parts_set(\@parts);
+ return $email;
}
sub MessageToMTA {
- my ($msg, $send_now) = (@_);
- my $method = Bugzilla->params->{'mail_delivery_method'};
- return if $method eq 'None';
-
- if (Bugzilla->params->{'use_mailer_queue'}
- && ! $send_now
- && ! Bugzilla->dbh->bz_in_transaction()
- ) {
- Bugzilla->job_queue->insert('send_mail', { msg => $msg });
- return;
- }
-
- my $dbh = Bugzilla->dbh;
-
- my $email = ref($msg) ? $msg : Bugzilla::MIME->new($msg);
-
- # If we're called from within a transaction, we don't want to send the
- # email immediately, in case the transaction is rolled back. Instead we
- # insert it into the mail_staging table, and bz_commit_transaction calls
- # send_staged_mail() after the transaction is committed.
- if (! $send_now && $dbh->bz_in_transaction()) {
- # The e-mail string may contain tainted values.
- my $string = $email->as_string;
- trick_taint($string);
-
- my $sth = $dbh->prepare("INSERT INTO mail_staging (message) VALUES (?)");
- $sth->bind_param(1, $string, $dbh->BLOB_TYPE);
- $sth->execute;
- return;
- }
-
- my $from = $email->header('From');
-
- my $hostname;
- my $transport;
- if ($method eq "Sendmail") {
- if (ON_WINDOWS) {
- $transport = Bugzilla::Sender::Transport::Sendmail->new({ sendmail => SENDMAIL_EXE });
- }
- else {
- $transport = Bugzilla::Sender::Transport::Sendmail->new();
- }
+ my ($msg, $send_now) = (@_);
+ my $method = Bugzilla->params->{'mail_delivery_method'};
+ return if $method eq 'None';
+
+ if ( Bugzilla->params->{'use_mailer_queue'}
+ && !$send_now
+ && !Bugzilla->dbh->bz_in_transaction())
+ {
+ Bugzilla->job_queue->insert('send_mail', {msg => $msg});
+ return;
+ }
+
+ my $dbh = Bugzilla->dbh;
+
+ my $email = ref($msg) ? $msg : Bugzilla::MIME->new($msg);
+
+ # If we're called from within a transaction, we don't want to send the
+ # email immediately, in case the transaction is rolled back. Instead we
+ # insert it into the mail_staging table, and bz_commit_transaction calls
+ # send_staged_mail() after the transaction is committed.
+ if (!$send_now && $dbh->bz_in_transaction()) {
+
+ # The e-mail string may contain tainted values.
+ my $string = $email->as_string;
+ trick_taint($string);
+
+ my $sth = $dbh->prepare("INSERT INTO mail_staging (message) VALUES (?)");
+ $sth->bind_param(1, $string, $dbh->BLOB_TYPE);
+ $sth->execute;
+ return;
+ }
+
+ my $from = $email->header('From');
+
+ my $hostname;
+ my $transport;
+ if ($method eq "Sendmail") {
+ if (ON_WINDOWS) {
+ $transport
+ = Bugzilla::Sender::Transport::Sendmail->new({sendmail => SENDMAIL_EXE});
}
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 || 'localhost';
- $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, %d %b %Y %T %z", time()));
- }
- }
-
- if ($method eq "SMTP") {
- my ($host, $port) = split(/:/, Bugzilla->params->{'smtpserver'}, 2);
- $transport = Bugzilla->request_cache->{smtp} //=
- Email::Sender::Transport::SMTP::Persistent->new({
- host => $host,
- defined($port) ? (port => $port) : (),
- sasl_username => Bugzilla->params->{'smtp_username'},
- sasl_password => Bugzilla->params->{'smtp_password'},
- helo => $hostname,
- ssl => Bugzilla->params->{'smtp_ssl'},
- debug => Bugzilla->params->{'smtp_debug'} });
+ $transport = Bugzilla::Sender::Transport::Sendmail->new();
}
-
- Bugzilla::Hook::process('mailer_before_send', { email => $email });
-
- return if $email->header('to') eq '';
-
- 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 {
+ # Sendmail will automatically append our hostname to the From
+ # address, but other mailers won't.
+ my $urlbase = Bugzilla->params->{'urlbase'};
+ $urlbase =~ m|//([^:/]+)[:/]?|;
+ $hostname = $1 || 'localhost';
+ $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, %d %b %Y %T %z", time()));
}
- else {
- # This is useful for Sendmail, so we put it out here.
- local $ENV{PATH} = SENDMAIL_PATH;
- eval { sendmail($email, { transport => $transport }) };
- if ($@) {
- ThrowCodeError('mail_send_error', { msg => $@->message, mail => $email });
- }
+ }
+
+ if ($method eq "SMTP") {
+ my ($host, $port) = split(/:/, Bugzilla->params->{'smtpserver'}, 2);
+ $transport = Bugzilla->request_cache->{smtp}
+ //= Email::Sender::Transport::SMTP::Persistent->new({
+ host => $host,
+ defined($port) ? (port => $port) : (),
+ sasl_username => Bugzilla->params->{'smtp_username'},
+ sasl_password => Bugzilla->params->{'smtp_password'},
+ helo => $hostname,
+ ssl => Bugzilla->params->{'smtp_ssl'},
+ debug => Bugzilla->params->{'smtp_debug'}
+ });
+ }
+
+ Bugzilla::Hook::process('mailer_before_send', {email => $email});
+
+ return if $email->header('to') eq '';
+
+ 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 Sendmail, so we put it out here.
+ local $ENV{PATH} = SENDMAIL_PATH;
+ eval { sendmail($email, {transport => $transport}) };
+ if ($@) {
+ ThrowCodeError('mail_send_error', {msg => $@->message, mail => $email});
}
+ }
}
# 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 {
- my $rand_bits = generate_random_password(10);
- $threadingmarker = "Message-ID: <bug-$bug_id-$user_id-$rand_bits$sitespec>" .
- "\nIn-Reply-To: <bug-$bug_id-$user_id$sitespec>" .
- "\nReferences: <bug-$bug_id-$user_id$sitespec>";
- }
-
- return $threadingmarker;
+ 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 {
+ my $rand_bits = generate_random_password(10);
+ $threadingmarker
+ = "Message-ID: <bug-$bug_id-$user_id-$rand_bits$sitespec>"
+ . "\nIn-Reply-To: <bug-$bug_id-$user_id$sitespec>"
+ . "\nReferences: <bug-$bug_id-$user_id$sitespec>";
+ }
+
+ return $threadingmarker;
}
sub send_staged_mail {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- my $emails = $dbh->selectall_arrayref('SELECT id, message FROM mail_staging');
- my $sth = $dbh->prepare('DELETE FROM mail_staging WHERE id = ?');
+ my $emails = $dbh->selectall_arrayref('SELECT id, message FROM mail_staging');
+ my $sth = $dbh->prepare('DELETE FROM mail_staging WHERE id = ?');
- foreach my $email (@$emails) {
- my ($id, $message) = @$email;
- MessageToMTA($message);
- $sth->execute($id);
- }
+ foreach my $email (@$emails) {
+ my ($id, $message) = @$email;
+ MessageToMTA($message);
+ $sth->execute($id);
+ }
}
1;
diff --git a/Bugzilla/Memcached.pm b/Bugzilla/Memcached.pm
index df90fef93..2f30c186a 100644
--- a/Bugzilla/Memcached.pm
+++ b/Bugzilla/Memcached.pm
@@ -20,281 +20,278 @@ use URI::Escape;
use constant MAX_KEY_LENGTH => 250;
sub _new {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $self = {};
-
- # always return an object to simplify calling code when memcached is
- # disabled.
- if (Bugzilla->feature('memcached')
- && Bugzilla->params->{memcached_servers})
- {
- require Cache::Memcached;
- $self->{namespace} = Bugzilla->params->{memcached_namespace} || '';
- $self->{memcached} =
- Cache::Memcached->new({
- servers => [ split(/[, ]+/, Bugzilla->params->{memcached_servers}) ],
- namespace => $self->{namespace},
- });
- }
- return bless($self, $class);
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $self = {};
+
+ # always return an object to simplify calling code when memcached is
+ # disabled.
+ if (Bugzilla->feature('memcached') && Bugzilla->params->{memcached_servers}) {
+ require Cache::Memcached;
+ $self->{namespace} = Bugzilla->params->{memcached_namespace} || '';
+ $self->{memcached} = Cache::Memcached->new({
+ servers => [split(/[, ]+/, Bugzilla->params->{memcached_servers})],
+ namespace => $self->{namespace},
+ });
+ }
+ return bless($self, $class);
}
sub enabled {
- return $_[0]->{memcached} ? 1 : 0;
+ return $_[0]->{memcached} ? 1 : 0;
}
sub set {
- my ($self, $args) = @_;
- return unless $self->{memcached};
-
- # { key => $key, value => $value }
- if (exists $args->{key}) {
- $self->_set($args->{key}, $args->{value});
+ my ($self, $args) = @_;
+ return unless $self->{memcached};
+
+ # { key => $key, value => $value }
+ if (exists $args->{key}) {
+ $self->_set($args->{key}, $args->{value});
+ }
+
+ # { table => $table, id => $id, name => $name, data => $data }
+ elsif (exists $args->{table} && exists $args->{id} && exists $args->{name}) {
+
+ # For caching of Bugzilla::Object, we have to be able to clear the
+ # cached values when given either the object's id or name.
+ my ($table, $id, $name, $data) = @$args{qw(table id name data)};
+ $self->_set("$table.id.$id", $data);
+ if (defined $name) {
+ $self->_set("$table.name_id.$name", $id);
+ $self->_set("$table.id_name.$id", $name);
}
+ }
- # { table => $table, id => $id, name => $name, data => $data }
- elsif (exists $args->{table} && exists $args->{id} && exists $args->{name}) {
- # For caching of Bugzilla::Object, we have to be able to clear the
- # cached values when given either the object's id or name.
- my ($table, $id, $name, $data) = @$args{qw(table id name data)};
- $self->_set("$table.id.$id", $data);
- if (defined $name) {
- $self->_set("$table.name_id.$name", $id);
- $self->_set("$table.id_name.$id", $name);
- }
- }
-
- else {
- ThrowCodeError('params_required', { function => "Bugzilla::Memcached::set",
- params => [ 'key', 'table' ] });
- }
+ else {
+ ThrowCodeError('params_required',
+ {function => "Bugzilla::Memcached::set", params => ['key', 'table']});
+ }
}
sub get {
- my ($self, $args) = @_;
- return unless $self->{memcached};
-
- # { key => $key }
- if (exists $args->{key}) {
- return $self->_get($args->{key});
- }
-
- # { table => $table, id => $id }
- elsif (exists $args->{table} && exists $args->{id}) {
- my ($table, $id) = @$args{qw(table id)};
- return $self->_get("$table.id.$id");
- }
-
- # { table => $table, name => $name }
- elsif (exists $args->{table} && exists $args->{name}) {
- my ($table, $name) = @$args{qw(table name)};
- return unless my $id = $self->_get("$table.name_id.$name");
- return $self->_get("$table.id.$id");
- }
-
- else {
- ThrowCodeError('params_required', { function => "Bugzilla::Memcached::get",
- params => [ 'key', 'table' ] });
- }
+ my ($self, $args) = @_;
+ return unless $self->{memcached};
+
+ # { key => $key }
+ if (exists $args->{key}) {
+ return $self->_get($args->{key});
+ }
+
+ # { table => $table, id => $id }
+ elsif (exists $args->{table} && exists $args->{id}) {
+ my ($table, $id) = @$args{qw(table id)};
+ return $self->_get("$table.id.$id");
+ }
+
+ # { table => $table, name => $name }
+ elsif (exists $args->{table} && exists $args->{name}) {
+ my ($table, $name) = @$args{qw(table name)};
+ return unless my $id = $self->_get("$table.name_id.$name");
+ return $self->_get("$table.id.$id");
+ }
+
+ else {
+ ThrowCodeError('params_required',
+ {function => "Bugzilla::Memcached::get", params => ['key', 'table']});
+ }
}
sub set_config {
- my ($self, $args) = @_;
- return unless $self->{memcached};
-
- if (exists $args->{key}) {
- return $self->_set($self->_config_prefix . '.' . $args->{key}, $args->{data});
- }
- else {
- ThrowCodeError('params_required', { function => "Bugzilla::Memcached::set_config",
- params => [ 'key' ] });
- }
+ my ($self, $args) = @_;
+ return unless $self->{memcached};
+
+ if (exists $args->{key}) {
+ return $self->_set($self->_config_prefix . '.' . $args->{key}, $args->{data});
+ }
+ else {
+ ThrowCodeError('params_required',
+ {function => "Bugzilla::Memcached::set_config", params => ['key']});
+ }
}
sub get_config {
- my ($self, $args) = @_;
- return unless $self->{memcached};
-
- if (exists $args->{key}) {
- return $self->_get($self->_config_prefix . '.' . $args->{key});
- }
- else {
- ThrowCodeError('params_required', { function => "Bugzilla::Memcached::get_config",
- params => [ 'key' ] });
- }
+ my ($self, $args) = @_;
+ return unless $self->{memcached};
+
+ if (exists $args->{key}) {
+ return $self->_get($self->_config_prefix . '.' . $args->{key});
+ }
+ else {
+ ThrowCodeError('params_required',
+ {function => "Bugzilla::Memcached::get_config", params => ['key']});
+ }
}
sub clear {
- my ($self, $args) = @_;
- return unless $self->{memcached};
-
- # { key => $key }
- if (exists $args->{key}) {
- $self->_delete($args->{key});
- }
-
- # { table => $table, id => $id }
- elsif (exists $args->{table} && exists $args->{id}) {
- my ($table, $id) = @$args{qw(table id)};
- my $name = $self->_get("$table.id_name.$id");
- $self->_delete("$table.id.$id");
- $self->_delete("$table.name_id.$name") if defined $name;
- $self->_delete("$table.id_name.$id");
- }
-
- # { table => $table, name => $name }
- elsif (exists $args->{table} && exists $args->{name}) {
- my ($table, $name) = @$args{qw(table name)};
- return unless my $id = $self->_get("$table.name_id.$name");
- $self->_delete("$table.id.$id");
- $self->_delete("$table.name_id.$name");
- $self->_delete("$table.id_name.$id");
- }
-
- else {
- ThrowCodeError('params_required', { function => "Bugzilla::Memcached::clear",
- params => [ 'key', 'table' ] });
- }
+ my ($self, $args) = @_;
+ return unless $self->{memcached};
+
+ # { key => $key }
+ if (exists $args->{key}) {
+ $self->_delete($args->{key});
+ }
+
+ # { table => $table, id => $id }
+ elsif (exists $args->{table} && exists $args->{id}) {
+ my ($table, $id) = @$args{qw(table id)};
+ my $name = $self->_get("$table.id_name.$id");
+ $self->_delete("$table.id.$id");
+ $self->_delete("$table.name_id.$name") if defined $name;
+ $self->_delete("$table.id_name.$id");
+ }
+
+ # { table => $table, name => $name }
+ elsif (exists $args->{table} && exists $args->{name}) {
+ my ($table, $name) = @$args{qw(table name)};
+ return unless my $id = $self->_get("$table.name_id.$name");
+ $self->_delete("$table.id.$id");
+ $self->_delete("$table.name_id.$name");
+ $self->_delete("$table.id_name.$id");
+ }
+
+ else {
+ ThrowCodeError('params_required',
+ {function => "Bugzilla::Memcached::clear", params => ['key', 'table']});
+ }
}
sub clear_all {
- my ($self) = @_;
- return unless $self->{memcached};
- $self->_inc_prefix("global");
+ my ($self) = @_;
+ return unless $self->{memcached};
+ $self->_inc_prefix("global");
}
sub clear_config {
- my ($self, $args) = @_;
- return unless $self->{memcached};
- if ($args && exists $args->{key}) {
- $self->_delete($self->_config_prefix . '.' . $args->{key});
- }
- else {
- $self->_inc_prefix("config");
- }
+ my ($self, $args) = @_;
+ return unless $self->{memcached};
+ if ($args && exists $args->{key}) {
+ $self->_delete($self->_config_prefix . '.' . $args->{key});
+ }
+ else {
+ $self->_inc_prefix("config");
+ }
}
# in order to clear all our keys, we add a prefix to all our keys. when we
# need to "clear" all current keys, we increment the prefix.
sub _prefix {
- my ($self, $name) = @_;
- # we don't want to change prefixes in the middle of a request
- my $request_cache = Bugzilla->request_cache;
- my $request_cache_key = "memcached_prefix_$name";
- if (!$request_cache->{$request_cache_key}) {
- my $memcached = $self->{memcached};
- my $prefix = $memcached->get($name);
- if (!$prefix) {
- $prefix = time();
- if (!$memcached->add($name, $prefix)) {
- # if this failed, either another process set the prefix, or
- # memcached is down. assume we lost the race, and get the new
- # value. if that fails, memcached is down so use a dummy
- # prefix for this request.
- $prefix = $memcached->get($name) || 0;
- }
- }
- $request_cache->{$request_cache_key} = $prefix;
+ my ($self, $name) = @_;
+
+ # we don't want to change prefixes in the middle of a request
+ my $request_cache = Bugzilla->request_cache;
+ my $request_cache_key = "memcached_prefix_$name";
+ if (!$request_cache->{$request_cache_key}) {
+ my $memcached = $self->{memcached};
+ my $prefix = $memcached->get($name);
+ if (!$prefix) {
+ $prefix = time();
+ if (!$memcached->add($name, $prefix)) {
+
+ # if this failed, either another process set the prefix, or
+ # memcached is down. assume we lost the race, and get the new
+ # value. if that fails, memcached is down so use a dummy
+ # prefix for this request.
+ $prefix = $memcached->get($name) || 0;
+ }
}
- return $request_cache->{$request_cache_key};
+ $request_cache->{$request_cache_key} = $prefix;
+ }
+ return $request_cache->{$request_cache_key};
}
sub _inc_prefix {
- my ($self, $name) = @_;
- my $memcached = $self->{memcached};
- if (!$memcached->incr($name, 1)) {
- $memcached->add($name, time());
- }
- delete Bugzilla->request_cache->{"memcached_prefix_$name"};
+ my ($self, $name) = @_;
+ my $memcached = $self->{memcached};
+ if (!$memcached->incr($name, 1)) {
+ $memcached->add($name, time());
+ }
+ delete Bugzilla->request_cache->{"memcached_prefix_$name"};
}
sub _global_prefix {
- return $_[0]->_prefix("global");
+ return $_[0]->_prefix("global");
}
sub _config_prefix {
- return $_[0]->_prefix("config");
+ return $_[0]->_prefix("config");
}
sub _encode_key {
- my ($self, $key) = @_;
- $key = $self->_global_prefix . '.' . uri_escape_utf8($key);
- return length($self->{namespace} . $key) > MAX_KEY_LENGTH
- ? undef
- : $key;
+ my ($self, $key) = @_;
+ $key = $self->_global_prefix . '.' . uri_escape_utf8($key);
+ return length($self->{namespace} . $key) > MAX_KEY_LENGTH ? undef : $key;
}
sub _set {
- my ($self, $key, $value) = @_;
- if (blessed($value)) {
- # we don't support blessed objects
- ThrowCodeError('param_invalid', { function => "Bugzilla::Memcached::set",
- param => "value" });
- }
+ my ($self, $key, $value) = @_;
+ if (blessed($value)) {
+
+ # we don't support blessed objects
+ ThrowCodeError('param_invalid',
+ {function => "Bugzilla::Memcached::set", param => "value"});
+ }
- $key = $self->_encode_key($key)
- or return;
- return $self->{memcached}->set($key, $value);
+ $key = $self->_encode_key($key) or return;
+ return $self->{memcached}->set($key, $value);
}
sub _get {
- my ($self, $key) = @_;
-
- $key = $self->_encode_key($key)
- or return;
- my $value = $self->{memcached}->get($key);
- return unless defined $value;
-
- # detaint returned values
- # hashes and arrays are detainted just one level deep
- if (ref($value) eq 'HASH') {
+ my ($self, $key) = @_;
+
+ $key = $self->_encode_key($key) or return;
+ my $value = $self->{memcached}->get($key);
+ return unless defined $value;
+
+ # detaint returned values
+ # hashes and arrays are detainted just one level deep
+ if (ref($value) eq 'HASH') {
+ _detaint_hashref($value);
+ }
+ elsif (ref($value) eq 'ARRAY') {
+ foreach my $value (@$value) {
+ next unless defined $value;
+
+ # arrays of hashes and arrays are common
+ if (ref($value) eq 'HASH') {
_detaint_hashref($value);
- }
- elsif (ref($value) eq 'ARRAY') {
- foreach my $value (@$value) {
- next unless defined $value;
- # arrays of hashes and arrays are common
- if (ref($value) eq 'HASH') {
- _detaint_hashref($value);
- }
- elsif (ref($value) eq 'ARRAY') {
- _detaint_arrayref($value);
- }
- elsif (!ref($value)) {
- trick_taint($value);
- }
- }
- }
- elsif (!ref($value)) {
+ }
+ elsif (ref($value) eq 'ARRAY') {
+ _detaint_arrayref($value);
+ }
+ elsif (!ref($value)) {
trick_taint($value);
+ }
}
- return $value;
+ }
+ elsif (!ref($value)) {
+ trick_taint($value);
+ }
+ return $value;
}
sub _detaint_hashref {
- my ($hashref) = @_;
- foreach my $value (values %$hashref) {
- if (defined($value) && !ref($value)) {
- trick_taint($value);
- }
+ my ($hashref) = @_;
+ foreach my $value (values %$hashref) {
+ if (defined($value) && !ref($value)) {
+ trick_taint($value);
}
+ }
}
sub _detaint_arrayref {
- my ($arrayref) = @_;
- foreach my $value (@$arrayref) {
- if (defined($value) && !ref($value)) {
- trick_taint($value);
- }
+ my ($arrayref) = @_;
+ foreach my $value (@$arrayref) {
+ if (defined($value) && !ref($value)) {
+ trick_taint($value);
}
+ }
}
sub _delete {
- my ($self, $key) = @_;
- $key = $self->_encode_key($key)
- or return;
- return $self->{memcached}->delete($key);
+ my ($self, $key) = @_;
+ $key = $self->_encode_key($key) or return;
+ return $self->{memcached}->delete($key);
}
1;
diff --git a/Bugzilla/Migrate.pm b/Bugzilla/Migrate.pm
index 7865c842d..887e553ee 100644
--- a/Bugzilla/Migrate.pm
+++ b/Bugzilla/Migrate.pm
@@ -20,7 +20,7 @@ use Bugzilla::Install::Requirements ();
use Bugzilla::Install::Util qw(indicate_progress);
use Bugzilla::Product;
use Bugzilla::Util qw(get_text trim generate_random_password);
-use Bugzilla::User ();
+use Bugzilla::User ();
use Bugzilla::Status ();
use Bugzilla::Version;
@@ -37,10 +37,10 @@ use constant REQUIRED_MODULES => [];
use constant NON_COMMENT_FIELDS => ();
use constant CONFIG_VARS => (
- {
- name => 'translate_fields',
- default => {},
- desc => <<'END',
+ {
+ name => 'translate_fields',
+ default => {},
+ desc => <<'END',
# This maps field names in your bug-tracker to Bugzilla field names. If a field
# has the same name in your bug-tracker and Bugzilla (case-insensitively), it
# doesn't need a mapping here. If a field isn't listed here and doesn't have
@@ -64,11 +64,11 @@ use constant CONFIG_VARS => (
# variable by default, then that field will be automatically created by
# the migrator and you don't have to worry about it.
END
- },
- {
- name => 'translate_values',
- default => {},
- desc => <<'END',
+ },
+ {
+ name => 'translate_values',
+ default => {},
+ desc => <<'END',
# This configuration variable allows you to say that a particular field
# value in your current bug-tracker should be translated to a different
# value when it's imported into Bugzilla.
@@ -108,22 +108,22 @@ END
#
# Values that don't get translated will be imported as-is.
END
- },
- {
- name => 'starting_bug_id',
- default => 0,
- desc => <<'END',
+ },
+ {
+ name => 'starting_bug_id',
+ default => 0,
+ desc => <<'END',
# What bug ID do you want the first imported bug to get? If you set this to
# 0, then the imported bug ids will just start right after the current
# bug ids. If you use this configuration variable, you must make sure that
# nobody else is using your Bugzilla while you run the migration, or a new
# bug filed by a user might take this ID instead.
END
- },
- {
- name => 'timezone',
- default => 'local',
- desc => <<'END',
+ },
+ {
+ name => 'timezone',
+ default => 'local',
+ desc => <<'END',
# If migrate.pl comes across any dates without timezones, while doing the
# migration, what timezone should we assume those dates are in?
# The best format for this variable is something like "America/Los Angeles".
@@ -133,7 +133,7 @@ END
# The special value "local" means "use the same timezone as the system I
# am running this script on now".
END
- },
+ },
);
use constant USER_FIELDS => qw(user assigned_to qa_contact reporter cc);
@@ -143,43 +143,46 @@ use constant USER_FIELDS => qw(user assigned_to qa_contact reporter cc);
#########################
sub do_migration {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- # On MySQL, setting serial values implicitly commits a transaction,
- # so we want to do it up here, outside of any transaction. This also
- # has the advantage of loading the config before anything else is done.
- if ($self->config('starting_bug_id')) {
- $dbh->bz_set_next_serial_value('bugs', 'bug_id',
- $self->config('starting_bug_id'));
- }
- $dbh->bz_start_transaction();
-
- $self->before_read();
- # Read Other Database
- my $users = $self->users;
- my $products = $self->products;
- my $bugs = $self->bugs;
- $self->after_read();
-
- $self->translate_all_bugs($bugs);
-
- Bugzilla->set_user(Bugzilla::User->super_user);
-
- # Insert into Bugzilla
- $self->before_insert();
- $self->insert_users($users);
- $self->insert_products($products);
- $self->create_custom_fields();
- $self->create_legal_values($bugs);
- $self->insert_bugs($bugs);
- $self->after_insert();
- if ($self->dry_run) {
- $dbh->bz_rollback_transaction();
- $self->reset_serial_values();
- }
- else {
- $dbh->bz_commit_transaction();
- }
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ # On MySQL, setting serial values implicitly commits a transaction,
+ # so we want to do it up here, outside of any transaction. This also
+ # has the advantage of loading the config before anything else is done.
+ if ($self->config('starting_bug_id')) {
+ $dbh->bz_set_next_serial_value('bugs', 'bug_id',
+ $self->config('starting_bug_id'));
+ }
+ $dbh->bz_start_transaction();
+
+ $self->before_read();
+
+ # Read Other Database
+ my $users = $self->users;
+ my $products = $self->products;
+ my $bugs = $self->bugs;
+ $self->after_read();
+
+ $self->translate_all_bugs($bugs);
+
+ Bugzilla->set_user(Bugzilla::User->super_user);
+
+ # Insert into Bugzilla
+ $self->before_insert();
+ $self->insert_users($users);
+ $self->insert_products($products);
+ $self->create_custom_fields();
+ $self->create_legal_values($bugs);
+ $self->insert_bugs($bugs);
+ $self->after_insert();
+
+ if ($self->dry_run) {
+ $dbh->bz_rollback_transaction();
+ $self->reset_serial_values();
+ }
+ else {
+ $dbh->bz_commit_transaction();
+ }
}
################
@@ -187,24 +190,23 @@ sub do_migration {
################
sub new {
- my ($class) = @_;
- my $self = { };
- bless $self, $class;
- return $self;
+ my ($class) = @_;
+ my $self = {};
+ bless $self, $class;
+ return $self;
}
sub load {
- my ($class, $from) = @_;
- my $libdir = bz_locations()->{libpath};
- my @migration_modules = glob("$libdir/Bugzilla/Migrate/*");
- my ($module) = grep { basename($_) =~ /^\Q$from\E\.pm$/i }
- @migration_modules;
- if (!$module) {
- ThrowUserError('migrate_from_invalid', { from => $from });
- }
- require $module;
- my $canonical_name = _canonical_name($module);
- return "Bugzilla::Migrate::$canonical_name"->new;
+ my ($class, $from) = @_;
+ my $libdir = bz_locations()->{libpath};
+ my @migration_modules = glob("$libdir/Bugzilla/Migrate/*");
+ my ($module) = grep { basename($_) =~ /^\Q$from\E\.pm$/i } @migration_modules;
+ if (!$module) {
+ ThrowUserError('migrate_from_invalid', {from => $from});
+ }
+ require $module;
+ my $canonical_name = _canonical_name($module);
+ return "Bugzilla::Migrate::$canonical_name"->new;
}
#############
@@ -212,67 +214,67 @@ sub load {
#############
sub name {
- my $self = shift;
- return _canonical_name(ref $self);
+ my $self = shift;
+ return _canonical_name(ref $self);
}
sub dry_run {
- my ($self, $value) = @_;
- if (scalar(@_) > 1) {
- $self->{dry_run} = $value;
- }
- return $self->{dry_run} || 0;
+ my ($self, $value) = @_;
+ if (scalar(@_) > 1) {
+ $self->{dry_run} = $value;
+ }
+ return $self->{dry_run} || 0;
}
sub verbose {
- my ($self, $value) = @_;
- if (scalar(@_) > 1) {
- $self->{verbose} = $value;
- }
- return $self->{verbose} || 0;
+ my ($self, $value) = @_;
+ if (scalar(@_) > 1) {
+ $self->{verbose} = $value;
+ }
+ return $self->{verbose} || 0;
}
sub debug {
- my ($self, $value, $level) = @_;
- $level ||= 1;
- if ($self->verbose >= $level) {
- $value = Dumper($value) if ref $value;
- print STDERR $value, "\n";
- }
+ my ($self, $value, $level) = @_;
+ $level ||= 1;
+ if ($self->verbose >= $level) {
+ $value = Dumper($value) if ref $value;
+ print STDERR $value, "\n";
+ }
}
sub bug_fields {
- my $self = shift;
- $self->{bug_fields} ||= Bugzilla->fields({ by_name => 1 });
- return $self->{bug_fields};
+ my $self = shift;
+ $self->{bug_fields} ||= Bugzilla->fields({by_name => 1});
+ return $self->{bug_fields};
}
sub users {
- my $self = shift;
- if (!exists $self->{users}) {
- say get_text('migrate_reading_users');
- $self->{users} = $self->_read_users();
- }
- return $self->{users};
+ my $self = shift;
+ if (!exists $self->{users}) {
+ say get_text('migrate_reading_users');
+ $self->{users} = $self->_read_users();
+ }
+ return $self->{users};
}
sub products {
- my $self = shift;
- if (!exists $self->{products}) {
- say get_text('migrate_reading_products');
- $self->{products} = $self->_read_products();
- }
- return $self->{products};
+ my $self = shift;
+ if (!exists $self->{products}) {
+ say get_text('migrate_reading_products');
+ $self->{products} = $self->_read_products();
+ }
+ return $self->{products};
}
sub bugs {
- my $self = shift;
- if (!exists $self->{bugs}) {
- say get_text('migrate_reading_bugs');
- $self->{bugs} = $self->_read_bugs();
- }
- return $self->{bugs};
+ my $self = shift;
+ if (!exists $self->{bugs}) {
+ say get_text('migrate_reading_bugs');
+ $self->{bugs} = $self->_read_bugs();
+ }
+ return $self->{bugs};
}
###########
@@ -280,49 +282,49 @@ sub bugs {
###########
sub check_requirements {
- my $self = shift;
- my $missing = Bugzilla::Install::Requirements::_check_missing(
- $self->REQUIRED_MODULES, 1);
- my %results = (
- apache => [],
- pass => @$missing ? 0 : 1,
- missing => $missing,
- any_missing => @$missing ? 1 : 0,
- hide_all => 1,
- # These are just for compatibility with print_module_instructions
- one_dbd => 1,
- optional => [],
- );
- Bugzilla::Install::Requirements::print_module_instructions(
- \%results, 1);
- exit(1) if @$missing;
+ my $self = shift;
+ my $missing
+ = Bugzilla::Install::Requirements::_check_missing($self->REQUIRED_MODULES, 1);
+ my %results = (
+ apache => [],
+ pass => @$missing ? 0 : 1,
+ missing => $missing,
+ any_missing => @$missing ? 1 : 0,
+ hide_all => 1,
+
+ # These are just for compatibility with print_module_instructions
+ one_dbd => 1,
+ optional => [],
+ );
+ Bugzilla::Install::Requirements::print_module_instructions(\%results, 1);
+ exit(1) if @$missing;
}
sub reset_serial_values {
- my $self = shift;
- return if $self->{serial_values_reset};
- my $dbh = Bugzilla->dbh;
- my %reset = (
- 'bugs' => 'bug_id',
- 'attachments' => 'attach_id',
- 'profiles' => 'userid',
- 'longdescs' => 'comment_id',
- 'products' => 'id',
- 'components' => 'id',
- 'versions' => 'id',
- 'milestones' => 'id',
- );
- my @select_fields = grep { $_->is_select } (values %{ $self->bug_fields });
- foreach my $field (@select_fields) {
- next if $field->is_abnormal;
- $reset{$field->name} = 'id';
- }
-
- while (my ($table, $column) = each %reset) {
- $dbh->bz_set_next_serial_value($table, $column);
- }
-
- $self->{serial_values_reset} = 1;
+ my $self = shift;
+ return if $self->{serial_values_reset};
+ my $dbh = Bugzilla->dbh;
+ my %reset = (
+ 'bugs' => 'bug_id',
+ 'attachments' => 'attach_id',
+ 'profiles' => 'userid',
+ 'longdescs' => 'comment_id',
+ 'products' => 'id',
+ 'components' => 'id',
+ 'versions' => 'id',
+ 'milestones' => 'id',
+ );
+ my @select_fields = grep { $_->is_select } (values %{$self->bug_fields});
+ foreach my $field (@select_fields) {
+ next if $field->is_abnormal;
+ $reset{$field->name} = 'id';
+ }
+
+ while (my ($table, $column) = each %reset) {
+ $dbh->bz_set_next_serial_value($table, $column);
+ }
+
+ $self->{serial_values_reset} = 1;
}
###################
@@ -330,160 +332,167 @@ sub reset_serial_values {
###################
sub translate_all_bugs {
- my ($self, $bugs) = @_;
- say get_text('migrate_translating_bugs');
- # We modify the array in place so that $self->bugs will return the
- # modified bugs, in case $self->before_insert wants them.
- my $num_bugs = scalar(@$bugs);
- for (my $i = 0; $i < $num_bugs; $i++) {
- $bugs->[$i] = $self->translate_bug($bugs->[$i]);
- }
+ my ($self, $bugs) = @_;
+ say get_text('migrate_translating_bugs');
+
+ # We modify the array in place so that $self->bugs will return the
+ # modified bugs, in case $self->before_insert wants them.
+ my $num_bugs = scalar(@$bugs);
+ for (my $i = 0; $i < $num_bugs; $i++) {
+ $bugs->[$i] = $self->translate_bug($bugs->[$i]);
+ }
}
sub translate_bug {
- my ($self, $fields) = @_;
- my (%bug, %other_fields);
- my $original_status;
- foreach my $field (keys %$fields) {
- my $value = delete $fields->{$field};
- my $bz_field = $self->translate_field($field);
- if ($bz_field) {
- $bug{$bz_field} = $self->translate_value($bz_field, $value);
- if ($bz_field eq 'bug_status') {
- $original_status = $value;
- }
- }
- else {
- $other_fields{$field} = $value;
- }
+ my ($self, $fields) = @_;
+ my (%bug, %other_fields);
+ my $original_status;
+ foreach my $field (keys %$fields) {
+ my $value = delete $fields->{$field};
+ my $bz_field = $self->translate_field($field);
+ if ($bz_field) {
+ $bug{$bz_field} = $self->translate_value($bz_field, $value);
+ if ($bz_field eq 'bug_status') {
+ $original_status = $value;
+ }
}
-
- if (defined $original_status and !defined $bug{resolution}
- and $self->map_value('bug_status_resolution', $original_status))
- {
- $bug{resolution} = $self->map_value('bug_status_resolution',
- $original_status);
+ else {
+ $other_fields{$field} = $value;
}
-
- $bug{comment} = $self->_generate_description(\%bug, \%other_fields);
-
- return wantarray ? (\%bug, \%other_fields) : \%bug;
+ }
+
+ if ( defined $original_status
+ and !defined $bug{resolution}
+ and $self->map_value('bug_status_resolution', $original_status))
+ {
+ $bug{resolution} = $self->map_value('bug_status_resolution', $original_status);
+ }
+
+ $bug{comment} = $self->_generate_description(\%bug, \%other_fields);
+
+ return wantarray ? (\%bug, \%other_fields) : \%bug;
}
sub _generate_description {
- my ($self, $bug, $fields) = @_;
-
- my $description = "";
- foreach my $field (sort keys %$fields) {
- next if grep($_ eq $field, $self->NON_COMMENT_FIELDS);
- my $value = delete $fields->{$field};
- next if $value eq '';
- $description .= "$field: $value\n";
- }
- $description .= "\n" if $description;
-
- return $description . $bug->{comment};
+ my ($self, $bug, $fields) = @_;
+
+ my $description = "";
+ foreach my $field (sort keys %$fields) {
+ next if grep($_ eq $field, $self->NON_COMMENT_FIELDS);
+ my $value = delete $fields->{$field};
+ next if $value eq '';
+ $description .= "$field: $value\n";
+ }
+ $description .= "\n" if $description;
+
+ return $description . $bug->{comment};
}
sub translate_field {
- my ($self, $field) = @_;
- my $mapped = $self->config('translate_fields')->{$field};
- return $mapped if defined $mapped;
- ($mapped) = grep { lc($_) eq lc($field) } (keys %{ $self->bug_fields });
- return $mapped;
+ my ($self, $field) = @_;
+ my $mapped = $self->config('translate_fields')->{$field};
+ return $mapped if defined $mapped;
+ ($mapped) = grep { lc($_) eq lc($field) } (keys %{$self->bug_fields});
+ return $mapped;
}
sub parse_date {
- my ($self, $date) = @_;
- my @time = strptime($date);
- # Handle times with timezones that strptime doesn't know about.
- if (!scalar @time) {
- $date =~ s/\s+\S+$//;
- @time = strptime($date);
+ my ($self, $date) = @_;
+ my @time = strptime($date);
+
+ # Handle times with timezones that strptime doesn't know about.
+ if (!scalar @time) {
+ $date =~ s/\s+\S+$//;
+ @time = strptime($date);
+ }
+ my $tz;
+ if ($time[6]) {
+ $tz = DateTime::TimeZone->offset_as_string($time[6]);
+ }
+ else {
+ $tz = $self->config('timezone');
+ $tz =~ s/\s/_/g;
+ if ($tz eq 'local') {
+ $tz = Bugzilla->local_timezone;
}
- my $tz;
- if ($time[6]) {
- $tz = DateTime::TimeZone->offset_as_string($time[6]);
- }
- else {
- $tz = $self->config('timezone');
- $tz =~ s/\s/_/g;
- if ($tz eq 'local') {
- $tz = Bugzilla->local_timezone;
- }
- }
- my $dt = DateTime->new({
- year => $time[5] + 1900,
- month => $time[4] + 1,
- day => $time[3],
- hour => $time[2],
- minute => $time[1],
- second => int($time[0]),
- time_zone => $tz,
- });
- $dt->set_time_zone(Bugzilla->local_timezone);
- return $dt->iso8601;
+ }
+ my $dt = DateTime->new({
+ year => $time[5] + 1900,
+ month => $time[4] + 1,
+ day => $time[3],
+ hour => $time[2],
+ minute => $time[1],
+ second => int($time[0]),
+ time_zone => $tz,
+ });
+ $dt->set_time_zone(Bugzilla->local_timezone);
+ return $dt->iso8601;
}
sub translate_value {
- my ($self, $field, $value) = @_;
-
- if (!defined $value) {
- warn("Got undefined value for $field\n");
- $value = '';
- }
-
- if (ref($value) eq 'ARRAY') {
- return [ map($self->translate_value($field, $_), @$value) ];
- }
+ my ($self, $field, $value) = @_;
-
- if (defined $self->map_value($field, $value)) {
- return $self->map_value($field, $value);
- }
-
- if (grep($_ eq $field, USER_FIELDS)) {
- if (defined $self->map_value('user', $value)) {
- return $self->map_value('user', $value);
- }
- }
+ if (!defined $value) {
+ warn("Got undefined value for $field\n");
+ $value = '';
+ }
+
+ if (ref($value) eq 'ARRAY') {
+ return [map($self->translate_value($field, $_), @$value)];
+ }
+
+
+ if (defined $self->map_value($field, $value)) {
+ return $self->map_value($field, $value);
+ }
- my $field_obj = $self->bug_fields->{$field};
- if ($field eq 'creation_ts'
- or $field eq 'delta_ts'
- or ($field_obj and
- ($field_obj->type == FIELD_TYPE_DATETIME
- or $field_obj->type == FIELD_TYPE_DATE)))
- {
- $value = trim($value);
- return undef if !$value;
- return $self->parse_date($value);
+ if (grep($_ eq $field, USER_FIELDS)) {
+ if (defined $self->map_value('user', $value)) {
+ return $self->map_value('user', $value);
}
-
- return $value;
+ }
+
+ my $field_obj = $self->bug_fields->{$field};
+ if (
+ $field eq 'creation_ts'
+ or $field eq 'delta_ts'
+ or (
+ $field_obj
+ and
+ ($field_obj->type == FIELD_TYPE_DATETIME or $field_obj->type == FIELD_TYPE_DATE)
+ )
+ )
+ {
+ $value = trim($value);
+ return undef if !$value;
+ return $self->parse_date($value);
+ }
+
+ return $value;
}
sub map_value {
- my ($self, $field, $value) = @_;
- return $self->_value_map->{$field}->{lc($value)};
+ my ($self, $field, $value) = @_;
+ return $self->_value_map->{$field}->{lc($value)};
}
sub _value_map {
- my $self = shift;
- if (!defined $self->{_value_map}) {
- # Lowercase all values to make them case-insensitive.
- my %map;
- my $translation = $self->config('translate_values');
- foreach my $field (keys %$translation) {
- my $value_mapping = $translation->{$field};
- foreach my $value (keys %$value_mapping) {
- $map{$field}->{lc($value)} = $value_mapping->{$value};
- }
- }
- $self->{_value_map} = \%map;
+ my $self = shift;
+ if (!defined $self->{_value_map}) {
+
+ # Lowercase all values to make them case-insensitive.
+ my %map;
+ my $translation = $self->config('translate_values');
+ foreach my $field (keys %$translation) {
+ my $value_mapping = $translation->{$field};
+ foreach my $value (keys %$value_mapping) {
+ $map{$field}->{lc($value)} = $value_mapping->{$value};
+ }
}
- return $self->{_value_map};
+ $self->{_value_map} = \%map;
+ }
+ return $self->{_value_map};
}
#################
@@ -491,387 +500,401 @@ sub _value_map {
#################
sub config {
- my ($self, $var) = @_;
- if (!exists $self->{config}) {
- $self->{config} = $self->read_config;
- }
- return $self->{config}->{$var};
+ my ($self, $var) = @_;
+ if (!exists $self->{config}) {
+ $self->{config} = $self->read_config;
+ }
+ return $self->{config}->{$var};
}
sub config_file_name {
- my $self = shift;
- my $name = $self->name;
- my $dir = bz_locations()->{datadir};
- return "$dir/migrate-$name.cfg"
+ my $self = shift;
+ my $name = $self->name;
+ my $dir = bz_locations()->{datadir};
+ return "$dir/migrate-$name.cfg";
}
sub read_config {
- my ($self) = @_;
- my $file = $self->config_file_name;
- if (!-e $file) {
- $self->write_config();
- ThrowUserError('migrate_config_created', { file => $file });
- }
- open(my $fh, "<", $file) || die "$file: $!";
- my $safe = new Safe;
- $safe->rdo($file);
- my @read_symbols = map($_->{name}, $self->CONFIG_VARS);
- my %config;
- foreach my $var (@read_symbols) {
- my $glob = $safe->varglob($var);
- $config{$var} = $$glob;
- }
- return \%config;
+ my ($self) = @_;
+ my $file = $self->config_file_name;
+ if (!-e $file) {
+ $self->write_config();
+ ThrowUserError('migrate_config_created', {file => $file});
+ }
+ open(my $fh, "<", $file) || die "$file: $!";
+ my $safe = new Safe;
+ $safe->rdo($file);
+ my @read_symbols = map($_->{name}, $self->CONFIG_VARS);
+ my %config;
+ foreach my $var (@read_symbols) {
+ my $glob = $safe->varglob($var);
+ $config{$var} = $$glob;
+ }
+ return \%config;
}
sub write_config {
- my ($self) = @_;
- my $file = $self->config_file_name;
- open(my $fh, ">", $file) || die "$file: $!";
- # Fixed indentation
- local $Data::Dumper::Indent = 1;
- local $Data::Dumper::Quotekeys = 0;
- local $Data::Dumper::Sortkeys = 1;
- foreach my $var ($self->CONFIG_VARS) {
- print $fh "\n", $var->{desc},
- Data::Dumper->Dump([$var->{default}], [$var->{name}]);
- }
- close($fh);
+ my ($self) = @_;
+ my $file = $self->config_file_name;
+ open(my $fh, ">", $file) || die "$file: $!";
+
+ # Fixed indentation
+ local $Data::Dumper::Indent = 1;
+ local $Data::Dumper::Quotekeys = 0;
+ local $Data::Dumper::Sortkeys = 1;
+ foreach my $var ($self->CONFIG_VARS) {
+ print $fh "\n", $var->{desc},
+ Data::Dumper->Dump([$var->{default}], [$var->{name}]);
+ }
+ close($fh);
}
####################################
# Default Implementations of Hooks #
####################################
-sub after_insert {}
-sub before_insert {}
-sub after_read {}
-sub before_read {}
+sub after_insert { }
+sub before_insert { }
+sub after_read { }
+sub before_read { }
#############
# Inserters #
#############
sub insert_users {
- my ($self, $users) = @_;
- foreach my $user (@$users) {
- next if new Bugzilla::User({ name => $user->{login_name} });
- my $generated_password;
- if (!defined $user->{cryptpassword}) {
- $generated_password = lc(generate_random_password());
- $user->{cryptpassword} = $generated_password;
- }
- my $created = Bugzilla::User->create($user);
- print get_text('migrate_user_created',
- { created => $created,
- password => $generated_password }), "\n";
+ my ($self, $users) = @_;
+ foreach my $user (@$users) {
+ next if new Bugzilla::User({name => $user->{login_name}});
+ my $generated_password;
+ if (!defined $user->{cryptpassword}) {
+ $generated_password = lc(generate_random_password());
+ $user->{cryptpassword} = $generated_password;
}
+ my $created = Bugzilla::User->create($user);
+ print get_text('migrate_user_created',
+ {created => $created, password => $generated_password}),
+ "\n";
+ }
}
# XXX This should also insert Classifications.
sub insert_products {
- my ($self, $products) = @_;
- foreach my $product (@$products) {
- my $components = delete $product->{components};
-
- my $created_prod = new Bugzilla::Product({ name => $product->{name} });
- if (!$created_prod) {
- $created_prod = Bugzilla::Product->create($product);
- print get_text('migrate_product_created',
- { created => $created_prod }), "\n";
- }
-
- foreach my $component (@$components) {
- next if new Bugzilla::Component({ product => $created_prod,
- name => $component->{name} });
- my $created_comp = Bugzilla::Component->create(
- { %$component, product => $created_prod });
- print ' ', get_text('migrate_component_created',
- { comp => $created_comp,
- product => $created_prod }), "\n";
- }
+ my ($self, $products) = @_;
+ foreach my $product (@$products) {
+ my $components = delete $product->{components};
+
+ my $created_prod = new Bugzilla::Product({name => $product->{name}});
+ if (!$created_prod) {
+ $created_prod = Bugzilla::Product->create($product);
+ print get_text('migrate_product_created', {created => $created_prod}), "\n";
}
+
+ foreach my $component (@$components) {
+ next
+ if new Bugzilla::Component(
+ {product => $created_prod, name => $component->{name}});
+ my $created_comp
+ = Bugzilla::Component->create({%$component, product => $created_prod});
+ print ' ',
+ get_text('migrate_component_created',
+ {comp => $created_comp, product => $created_prod}),
+ "\n";
+ }
+ }
}
sub create_custom_fields {
- my $self = shift;
- foreach my $field (keys %{ $self->CUSTOM_FIELDS }) {
- next if new Bugzilla::Field({ name => $field });
- my %values = %{ $self->CUSTOM_FIELDS->{$field} };
- # We set these all here for the dry-run case.
- my $created = { %values, name => $field, custom => 1 };
- if (!$self->dry_run) {
- $created = Bugzilla::Field->create($created);
- }
- say get_text('migrate_field_created', { field => $created });
+ my $self = shift;
+ foreach my $field (keys %{$self->CUSTOM_FIELDS}) {
+ next if new Bugzilla::Field({name => $field});
+ my %values = %{$self->CUSTOM_FIELDS->{$field}};
+
+ # We set these all here for the dry-run case.
+ my $created = {%values, name => $field, custom => 1};
+ if (!$self->dry_run) {
+ $created = Bugzilla::Field->create($created);
}
- delete $self->{bug_fields};
+ say get_text('migrate_field_created', {field => $created});
+ }
+ delete $self->{bug_fields};
}
sub create_legal_values {
- my ($self, $bugs) = @_;
- my @select_fields = grep($_->is_select, values %{ $self->bug_fields });
-
- # Get all the values in use on all the bugs we're importing.
- my (%values, %product_values);
- foreach my $bug (@$bugs) {
- foreach my $field (@select_fields) {
- my $name = $field->name;
- next if !defined $bug->{$name};
- $values{$name}->{$bug->{$name}} = 1;
- }
- foreach my $field (qw(version target_milestone)) {
- # Fix per-product bug values here, because it's easier than
- # doing it during _insert_bugs.
- if (!defined $bug->{$field} or trim($bug->{$field}) eq '') {
- my $accessor = $field;
- $accessor =~ s/^target_//; $accessor .= "s";
- my $product = Bugzilla::Product->check($bug->{product});
- $bug->{$field} = $product->$accessor->[0]->name;
- next;
- }
- $product_values{$bug->{product}}->{$field}->{$bug->{$field}} = 1;
- }
- }
-
+ my ($self, $bugs) = @_;
+ my @select_fields = grep($_->is_select, values %{$self->bug_fields});
+
+ # Get all the values in use on all the bugs we're importing.
+ my (%values, %product_values);
+ foreach my $bug (@$bugs) {
foreach my $field (@select_fields) {
- next if $field->is_abnormal;
- my $name = $field->name;
- foreach my $value (keys %{ $values{$name} }) {
- next if Bugzilla::Field::Choice->type($field)->new({ name => $value });
- Bugzilla::Field::Choice->type($field)->create({ value => $value });
- print get_text('migrate_value_created',
- { field => $field, value => $value }), "\n";
- }
+ my $name = $field->name;
+ next if !defined $bug->{$name};
+ $values{$name}->{$bug->{$name}} = 1;
}
-
- foreach my $product (keys %product_values) {
- my $prod_obj = Bugzilla::Product->check($product);
- foreach my $version (keys %{ $product_values{$product}->{version} }) {
- next if new Bugzilla::Version({ product => $prod_obj,
- name => $version });
- my $created = Bugzilla::Version->create({ product => $prod_obj,
- value => $version });
- my $field = $self->bug_fields->{version};
- print get_text('migrate_value_created', { product => $prod_obj,
- field => $field,
- value => $created->name }), "\n";
- }
- foreach my $milestone (keys %{ $product_values{$product}->{target_milestone} }) {
- next if new Bugzilla::Milestone({ product => $prod_obj,
- name => $milestone });
- my $created = Bugzilla::Milestone->create(
- { product => $prod_obj, value => $milestone });
- my $field = $self->bug_fields->{target_milestone};
- print get_text('migrate_value_created', { product => $prod_obj,
- field => $field,
- value => $created->name }), "\n";
-
- }
+ foreach my $field (qw(version target_milestone)) {
+
+ # Fix per-product bug values here, because it's easier than
+ # doing it during _insert_bugs.
+ if (!defined $bug->{$field} or trim($bug->{$field}) eq '') {
+ my $accessor = $field;
+ $accessor =~ s/^target_//;
+ $accessor .= "s";
+ my $product = Bugzilla::Product->check($bug->{product});
+ $bug->{$field} = $product->$accessor->[0]->name;
+ next;
+ }
+ $product_values{$bug->{product}}->{$field}->{$bug->{$field}} = 1;
+ }
+ }
+
+ foreach my $field (@select_fields) {
+ next if $field->is_abnormal;
+ my $name = $field->name;
+ foreach my $value (keys %{$values{$name}}) {
+ next if Bugzilla::Field::Choice->type($field)->new({name => $value});
+ Bugzilla::Field::Choice->type($field)->create({value => $value});
+ print get_text('migrate_value_created', {field => $field, value => $value}),
+ "\n";
+ }
+ }
+
+ foreach my $product (keys %product_values) {
+ my $prod_obj = Bugzilla::Product->check($product);
+ foreach my $version (keys %{$product_values{$product}->{version}}) {
+ next if new Bugzilla::Version({product => $prod_obj, name => $version});
+ my $created
+ = Bugzilla::Version->create({product => $prod_obj, value => $version});
+ my $field = $self->bug_fields->{version};
+ print get_text('migrate_value_created',
+ {product => $prod_obj, field => $field, value => $created->name}),
+ "\n";
+ }
+ foreach my $milestone (keys %{$product_values{$product}->{target_milestone}}) {
+ next if new Bugzilla::Milestone({product => $prod_obj, name => $milestone});
+ my $created
+ = Bugzilla::Milestone->create({product => $prod_obj, value => $milestone});
+ my $field = $self->bug_fields->{target_milestone};
+ print get_text('migrate_value_created',
+ {product => $prod_obj, field => $field, value => $created->name}),
+ "\n";
+
}
-
+ }
+
}
sub insert_bugs {
- my ($self, $bugs) = @_;
- my $dbh = Bugzilla->dbh;
- say get_text('migrate_creating_bugs');
-
- my $init_statuses = Bugzilla::Status->can_change_to();
- my %allowed_statuses = map { lc($_->name) => 1 } @$init_statuses;
- # Bypass the question of whether or not we can file UNCONFIRMED
- # in any product by simply picking a non-UNCONFIRMED status as our
- # default for bugs that don't have a status specified.
- my $default_status = first { $_->name ne 'UNCONFIRMED' } @$init_statuses;
- # Use the first resolution that's not blank.
- my $default_resolution =
- first { $_->name ne '' }
- @{ $self->bug_fields->{resolution}->legal_values };
-
- # Set the values of any required drop-down fields that aren't set.
- my @standard_drop_downs = grep { !$_->custom and $_->is_select }
- (values %{ $self->bug_fields });
- # Make bug_status get set before resolution.
- @standard_drop_downs = sort { $a->name cmp $b->name } @standard_drop_downs;
- # Cache all statuses for setting the resolution.
- my %statuses = map { lc($_->name) => $_ } Bugzilla::Status->get_all;
-
- my $total = scalar @$bugs;
- my $count = 1;
- foreach my $bug (@$bugs) {
- my $comments = delete $bug->{comments};
- my $history = delete $bug->{history};
- my $attachments = delete $bug->{attachments};
-
- $self->debug($bug, 3);
-
- foreach my $field (@standard_drop_downs) {
- next if $field->is_abnormal;
- my $field_name = $field->name;
- if (!defined $bug->{$field_name}) {
- # If there's a default value for this, then just let create()
- # pick it.
- next if grep($_->is_default, @{ $field->legal_values });
- # Otherwise, pick the first valid value if this is a required
- # field.
- if ($field_name eq 'bug_status') {
- $bug->{bug_status} = $default_status;
- }
- elsif ($field_name eq 'resolution') {
- my $status = $statuses{lc($bug->{bug_status})};
- if (!$status->is_open) {
- $bug->{resolution} = $default_resolution;
- }
- }
- else {
- $bug->{$field_name} = $field->legal_values->[0]->name;
- }
- }
- }
-
- my $product = Bugzilla::Product->check($bug->{product});
-
- # If this isn't a legal starting status, or if the bug has a
- # resolution, then those will have to be set after creating the bug.
- # We make them into objects so that we can normalize their names.
- my ($set_status, $set_resolution);
- if (defined $bug->{resolution}) {
- $set_resolution = Bugzilla::Field::Choice->type('resolution')
- ->new({ name => delete $bug->{resolution} });
+ my ($self, $bugs) = @_;
+ my $dbh = Bugzilla->dbh;
+ say get_text('migrate_creating_bugs');
+
+ my $init_statuses = Bugzilla::Status->can_change_to();
+ my %allowed_statuses = map { lc($_->name) => 1 } @$init_statuses;
+
+ # Bypass the question of whether or not we can file UNCONFIRMED
+ # in any product by simply picking a non-UNCONFIRMED status as our
+ # default for bugs that don't have a status specified.
+ my $default_status = first { $_->name ne 'UNCONFIRMED' } @$init_statuses;
+
+ # Use the first resolution that's not blank.
+ my $default_resolution = first { $_->name ne '' }
+ @{$self->bug_fields->{resolution}->legal_values};
+
+ # Set the values of any required drop-down fields that aren't set.
+ my @standard_drop_downs
+ = grep { !$_->custom and $_->is_select } (values %{$self->bug_fields});
+
+ # Make bug_status get set before resolution.
+ @standard_drop_downs = sort { $a->name cmp $b->name } @standard_drop_downs;
+
+ # Cache all statuses for setting the resolution.
+ my %statuses = map { lc($_->name) => $_ } Bugzilla::Status->get_all;
+
+ my $total = scalar @$bugs;
+ my $count = 1;
+ foreach my $bug (@$bugs) {
+ my $comments = delete $bug->{comments};
+ my $history = delete $bug->{history};
+ my $attachments = delete $bug->{attachments};
+
+ $self->debug($bug, 3);
+
+ foreach my $field (@standard_drop_downs) {
+ next if $field->is_abnormal;
+ my $field_name = $field->name;
+ if (!defined $bug->{$field_name}) {
+
+ # If there's a default value for this, then just let create()
+ # pick it.
+ next if grep($_->is_default, @{$field->legal_values});
+
+ # Otherwise, pick the first valid value if this is a required
+ # field.
+ if ($field_name eq 'bug_status') {
+ $bug->{bug_status} = $default_status;
}
- if (!$allowed_statuses{lc($bug->{bug_status})}) {
- $set_status = new Bugzilla::Status({ name => $bug->{bug_status} });
- # Set the starting status to some status that Bugzilla will
- # accept. We're going to overwrite it immediately afterward.
- $bug->{bug_status} = $default_status;
+ elsif ($field_name eq 'resolution') {
+ my $status = $statuses{lc($bug->{bug_status})};
+ if (!$status->is_open) {
+ $bug->{resolution} = $default_resolution;
+ }
}
-
- # If we're in dry-run mode, our custom fields haven't been created
- # yet, so we shouldn't try to set them on creation.
- if ($self->dry_run) {
- foreach my $field (keys %{ $self->CUSTOM_FIELDS }) {
- delete $bug->{$field};
- }
+ else {
+ $bug->{$field_name} = $field->legal_values->[0]->name;
}
-
- # File the bug as the reporter.
- my $super_user = Bugzilla->user;
- my $reporter = Bugzilla::User->check($bug->{reporter});
- # Allow the user to file a bug in any product, no matter their current
- # permissions.
- $reporter->{groups} = $super_user->groups;
- Bugzilla->set_user($reporter);
- my $created = Bugzilla::Bug->create($bug);
- $self->debug('Created bug ' . $created->id);
- Bugzilla->set_user($super_user);
-
- if (defined $bug->{creation_ts}) {
- $dbh->do('UPDATE bugs SET creation_ts = ?, delta_ts = ?
+ }
+ }
+
+ my $product = Bugzilla::Product->check($bug->{product});
+
+ # If this isn't a legal starting status, or if the bug has a
+ # resolution, then those will have to be set after creating the bug.
+ # We make them into objects so that we can normalize their names.
+ my ($set_status, $set_resolution);
+ if (defined $bug->{resolution}) {
+ $set_resolution = Bugzilla::Field::Choice->type('resolution')
+ ->new({name => delete $bug->{resolution}});
+ }
+ if (!$allowed_statuses{lc($bug->{bug_status})}) {
+ $set_status = new Bugzilla::Status({name => $bug->{bug_status}});
+
+ # Set the starting status to some status that Bugzilla will
+ # accept. We're going to overwrite it immediately afterward.
+ $bug->{bug_status} = $default_status;
+ }
+
+ # If we're in dry-run mode, our custom fields haven't been created
+ # yet, so we shouldn't try to set them on creation.
+ if ($self->dry_run) {
+ foreach my $field (keys %{$self->CUSTOM_FIELDS}) {
+ delete $bug->{$field};
+ }
+ }
+
+ # File the bug as the reporter.
+ my $super_user = Bugzilla->user;
+ my $reporter = Bugzilla::User->check($bug->{reporter});
+
+ # Allow the user to file a bug in any product, no matter their current
+ # permissions.
+ $reporter->{groups} = $super_user->groups;
+ Bugzilla->set_user($reporter);
+ my $created = Bugzilla::Bug->create($bug);
+ $self->debug('Created bug ' . $created->id);
+ Bugzilla->set_user($super_user);
+
+ if (defined $bug->{creation_ts}) {
+ $dbh->do(
+ 'UPDATE bugs SET creation_ts = ?, delta_ts = ?
WHERE bug_id = ?', undef, $bug->{creation_ts},
- $bug->{creation_ts}, $created->id);
- }
- if (defined $bug->{delta_ts}) {
- $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
- undef, $bug->{delta_ts}, $created->id);
- }
- # We don't need to send email for imported bugs.
- $dbh->do('UPDATE bugs SET lastdiffed = delta_ts WHERE bug_id = ?',
- undef, $created->id);
-
- # We don't use set_ and update() because that would create
- # a bugs_activity entry that we don't want.
- if ($set_status) {
- $dbh->do('UPDATE bugs SET bug_status = ? WHERE bug_id = ?',
- undef, $set_status->name, $created->id);
- }
- if ($set_resolution) {
- $dbh->do('UPDATE bugs SET resolution = ? WHERE bug_id = ?',
- undef, $set_resolution->name, $created->id);
- }
-
- $self->_insert_comments($created, $comments);
- $self->_insert_history($created, $history);
- $self->_insert_attachments($created, $attachments);
-
- # bugs_fulltext isn't transactional, so if we're in a dry-run we
- # need to delete anything that we put in there.
- if ($self->dry_run) {
- $dbh->do('DELETE FROM bugs_fulltext WHERE bug_id = ?',
- undef, $created->id);
- }
+ $bug->{creation_ts}, $created->id
+ );
+ }
+ if (defined $bug->{delta_ts}) {
+ $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+ undef, $bug->{delta_ts}, $created->id);
+ }
- if (!$self->verbose) {
- indicate_progress({ current => $count++, every => 5, total => $total });
- }
+ # We don't need to send email for imported bugs.
+ $dbh->do('UPDATE bugs SET lastdiffed = delta_ts WHERE bug_id = ?',
+ undef, $created->id);
+
+ # We don't use set_ and update() because that would create
+ # a bugs_activity entry that we don't want.
+ if ($set_status) {
+ $dbh->do('UPDATE bugs SET bug_status = ? WHERE bug_id = ?',
+ undef, $set_status->name, $created->id);
+ }
+ if ($set_resolution) {
+ $dbh->do('UPDATE bugs SET resolution = ? WHERE bug_id = ?',
+ undef, $set_resolution->name, $created->id);
}
+
+ $self->_insert_comments($created, $comments);
+ $self->_insert_history($created, $history);
+ $self->_insert_attachments($created, $attachments);
+
+ # bugs_fulltext isn't transactional, so if we're in a dry-run we
+ # need to delete anything that we put in there.
+ if ($self->dry_run) {
+ $dbh->do('DELETE FROM bugs_fulltext WHERE bug_id = ?', undef, $created->id);
+ }
+
+ if (!$self->verbose) {
+ indicate_progress({current => $count++, every => 5, total => $total});
+ }
+ }
}
sub _insert_comments {
- my ($self, $bug, $comments) = @_;
- return if !$comments;
- $self->debug(' Inserting comments:', 2);
- foreach my $comment (@$comments) {
- $self->debug($comment, 3);
- my %copy = %$comment;
- # XXX In the future, if we have a Bugzilla::Comment->create, this
- # should use it.
- my $who = Bugzilla::User->check(delete $copy{who});
- $copy{who} = $who->id;
- $copy{bug_id} = $bug->id;
- $self->_do_table_insert('longdescs', \%copy);
- $self->debug(" Inserted comment from " . $who->login, 2);
- }
- $bug->_sync_fulltext( update_comments => 1 );
+ my ($self, $bug, $comments) = @_;
+ return if !$comments;
+ $self->debug(' Inserting comments:', 2);
+ foreach my $comment (@$comments) {
+ $self->debug($comment, 3);
+ my %copy = %$comment;
+
+ # XXX In the future, if we have a Bugzilla::Comment->create, this
+ # should use it.
+ my $who = Bugzilla::User->check(delete $copy{who});
+ $copy{who} = $who->id;
+ $copy{bug_id} = $bug->id;
+ $self->_do_table_insert('longdescs', \%copy);
+ $self->debug(" Inserted comment from " . $who->login, 2);
+ }
+ $bug->_sync_fulltext(update_comments => 1);
}
sub _insert_history {
- my ($self, $bug, $history) = @_;
- return if !$history;
- $self->debug(' Inserting history:', 2);
- foreach my $item (@$history) {
- $self->debug($item, 3);
- my $who = Bugzilla::User->check($item->{who});
- LogActivityEntry($bug->id, $item->{field}, $item->{removed},
- $item->{added}, $who->id, $item->{bug_when});
- $self->debug(" $item->{field} change from " . $who->login, 2);
- }
+ my ($self, $bug, $history) = @_;
+ return if !$history;
+ $self->debug(' Inserting history:', 2);
+ foreach my $item (@$history) {
+ $self->debug($item, 3);
+ my $who = Bugzilla::User->check($item->{who});
+ LogActivityEntry($bug->id, $item->{field}, $item->{removed}, $item->{added},
+ $who->id, $item->{bug_when});
+ $self->debug(" $item->{field} change from " . $who->login, 2);
+ }
}
sub _insert_attachments {
- my ($self, $bug, $attachments) = @_;
- return if !$attachments;
- $self->debug(' Inserting attachments:', 2);
- foreach my $attachment (@$attachments) {
- $self->debug($attachment, 3);
- # Make sure that our pointer is at the beginning of the file,
- # because usually it will be at the end, having just been fully
- # written to.
- if (ref $attachment->{data}) {
- $attachment->{data}->seek(0, SEEK_SET);
- }
-
- my $submitter = Bugzilla::User->check(delete $attachment->{submitter});
- my $super_user = Bugzilla->user;
- # Make sure the submitter can attach this attachment no matter what.
- $submitter->{groups} = $super_user->groups;
- Bugzilla->set_user($submitter);
- my $created =
- Bugzilla::Attachment->create({ %$attachment, bug => $bug });
- $self->debug(' Attachment ' . $created->description . ' from '
- . $submitter->login, 2);
- Bugzilla->set_user($super_user);
+ my ($self, $bug, $attachments) = @_;
+ return if !$attachments;
+ $self->debug(' Inserting attachments:', 2);
+ foreach my $attachment (@$attachments) {
+ $self->debug($attachment, 3);
+
+ # Make sure that our pointer is at the beginning of the file,
+ # because usually it will be at the end, having just been fully
+ # written to.
+ if (ref $attachment->{data}) {
+ $attachment->{data}->seek(0, SEEK_SET);
}
+
+ my $submitter = Bugzilla::User->check(delete $attachment->{submitter});
+ my $super_user = Bugzilla->user;
+
+ # Make sure the submitter can attach this attachment no matter what.
+ $submitter->{groups} = $super_user->groups;
+ Bugzilla->set_user($submitter);
+ my $created = Bugzilla::Attachment->create({%$attachment, bug => $bug});
+ $self->debug(
+ ' Attachment ' . $created->description . ' from ' . $submitter->login, 2);
+ Bugzilla->set_user($super_user);
+ }
}
sub _do_table_insert {
- my ($self, $table, $hash) = @_;
- my @fields = keys %$hash;
- my @questions = ('?') x @fields;
- my @values = map { $hash->{$_} } @fields;
- my $field_sql = join(',', @fields);
- my $question_sql = join(',', @questions);
- Bugzilla->dbh->do("INSERT INTO $table ($field_sql) VALUES ($question_sql)",
- undef, @values);
+ my ($self, $table, $hash) = @_;
+ my @fields = keys %$hash;
+ my @questions = ('?') x @fields;
+ my @values = map { $hash->{$_} } @fields;
+ my $field_sql = join(',', @fields);
+ my $question_sql = join(',', @questions);
+ Bugzilla->dbh->do("INSERT INTO $table ($field_sql) VALUES ($question_sql)",
+ undef, @values);
}
######################
@@ -879,11 +902,11 @@ sub _do_table_insert {
######################
sub _canonical_name {
- my ($module) = @_;
- $module =~ s{::}{/}g;
- $module = basename($module);
- $module =~ s/\.pm$//g;
- return $module;
+ my ($module) = @_;
+ $module =~ s{::}{/}g;
+ $module = basename($module);
+ $module =~ s/\.pm$//g;
+ return $module;
}
1;
diff --git a/Bugzilla/Migrate/Gnats.pm b/Bugzilla/Migrate/Gnats.pm
index 5feda4b8d..a5aa642e1 100644
--- a/Bugzilla/Migrate/Gnats.pm
+++ b/Bugzilla/Migrate/Gnats.pm
@@ -25,88 +25,87 @@ use List::MoreUtils qw(firstidx);
use List::Util qw(first);
use constant REQUIRED_MODULES => [
- {
- package => 'Email-Simple-FromHandle',
- module => 'Email::Simple::FromHandle',
- # This version added seekable handles.
- version => 0.050,
- },
+ {
+ package => 'Email-Simple-FromHandle',
+ module => 'Email::Simple::FromHandle',
+
+ # This version added seekable handles.
+ version => 0.050,
+ },
];
use constant FIELD_MAP => {
- 'Number' => 'bug_id',
- 'Category' => 'product',
- 'Synopsis' => 'short_desc',
- 'Responsible' => 'assigned_to',
- 'State' => 'bug_status',
- 'Class' => 'cf_type',
- 'Classification' => '',
- 'Originator' => 'reporter',
- 'Arrival-Date' => 'creation_ts',
- 'Last-Modified' => 'delta_ts',
- 'Release' => 'version',
- 'Severity' => 'bug_severity',
- 'Description' => 'comment',
+ 'Number' => 'bug_id',
+ 'Category' => 'product',
+ 'Synopsis' => 'short_desc',
+ 'Responsible' => 'assigned_to',
+ 'State' => 'bug_status',
+ 'Class' => 'cf_type',
+ 'Classification' => '',
+ 'Originator' => 'reporter',
+ 'Arrival-Date' => 'creation_ts',
+ 'Last-Modified' => 'delta_ts',
+ 'Release' => 'version',
+ 'Severity' => 'bug_severity',
+ 'Description' => 'comment',
};
use constant VALUE_MAP => {
- bug_severity => {
- 'serious' => 'major',
- 'cosmetic' => 'trivial',
- 'new-feature' => 'enhancement',
- 'non-critical' => 'normal',
- },
- bug_status => {
- 'open' => 'CONFIRMED',
- 'analyzed' => 'IN_PROGRESS',
- 'suspended' => 'RESOLVED',
- 'feedback' => 'RESOLVED',
- 'released' => 'VERIFIED',
- },
- bug_status_resolution => {
- 'feedback' => 'FIXED',
- 'released' => 'FIXED',
- 'closed' => 'FIXED',
- 'suspended' => 'LATER',
- },
- priority => {
- 'medium' => 'Normal',
- },
+ bug_severity => {
+ 'serious' => 'major',
+ 'cosmetic' => 'trivial',
+ 'new-feature' => 'enhancement',
+ 'non-critical' => 'normal',
+ },
+ bug_status => {
+ 'open' => 'CONFIRMED',
+ 'analyzed' => 'IN_PROGRESS',
+ 'suspended' => 'RESOLVED',
+ 'feedback' => 'RESOLVED',
+ 'released' => 'VERIFIED',
+ },
+ bug_status_resolution => {
+ 'feedback' => 'FIXED',
+ 'released' => 'FIXED',
+ 'closed' => 'FIXED',
+ 'suspended' => 'LATER',
+ },
+ priority => {'medium' => 'Normal',},
};
use constant GNATS_CONFIG_VARS => (
- {
- name => 'gnats_path',
- default => '/var/lib/gnats',
- desc => <<END,
+ {
+ name => 'gnats_path',
+ default => '/var/lib/gnats',
+ desc => <<END,
# The path to the directory that contains the GNATS database.
END
- },
- {
- name => 'default_email_domain',
- default => 'example.com',
- desc => <<'END',
+ },
+ {
+ name => 'default_email_domain',
+ default => 'example.com',
+ desc => <<'END',
# Some GNATS users do not have full email addresses, but Bugzilla requires
# every user to have an email address. What domain should be appended to
# usernames that don't have emails, to make them into email addresses?
# (For example, if you leave this at the default, "unknown" would become
# "unknown@example.com".)
END
- },
- {
- name => 'component_name',
- default => 'General',
- desc => <<'END',
+ },
+ {
+ name => 'component_name',
+ default => 'General',
+ desc => <<'END',
# GNATS has only "Category" to classify bugs. However, Bugzilla has a
# multi-level system of Products that contain Components. When importing
# GNATS categories, they become a Product with one Component. What should
# the name of that Component be?
END
- },
- {
- name => 'version_regex',
- default => '',
- desc => <<'END',
+ },
+ {
+ name => 'version_regex',
+ default => '',
+ desc => <<'END',
# In GNATS, the "version" field can contain almost anything. However, in
# Bugzilla, it's a drop-down, so you don't want too many choices in there.
# If you specify a regular expression here, versions will be tested against
@@ -115,43 +114,43 @@ END
# as the version value for the bug instead of the full version value specified
# in GNATS.
END
- },
- {
- name => 'default_originator',
- default => 'gnats-admin',
- desc => <<'END',
+ },
+ {
+ name => 'default_originator',
+ default => 'gnats-admin',
+ desc => <<'END',
# Sometimes, a PR has no valid Originator, so we fall back to the From
# header of the email. If the From header also isn't a valid username
# (is just a name with spaces in it--we can't convert that to an email
# address) then this username (which can either be a GNATS username or an
# email address) will be considered to be the Originator of the PR.
END
- }
+ }
);
sub CONFIG_VARS {
- my $self = shift;
- my @vars = (GNATS_CONFIG_VARS, $self->SUPER::CONFIG_VARS);
- my $field_map = first { $_->{name} eq 'translate_fields' } @vars;
- $field_map->{default} = FIELD_MAP;
- my $value_map = first { $_->{name} eq 'translate_values' } @vars;
- $value_map->{default} = VALUE_MAP;
- return @vars;
+ my $self = shift;
+ my @vars = (GNATS_CONFIG_VARS, $self->SUPER::CONFIG_VARS);
+ my $field_map = first { $_->{name} eq 'translate_fields' } @vars;
+ $field_map->{default} = FIELD_MAP;
+ my $value_map = first { $_->{name} eq 'translate_values' } @vars;
+ $value_map->{default} = VALUE_MAP;
+ return @vars;
}
# Directories that aren't projects, or that we shouldn't be parsing
use constant SKIP_DIRECTORIES => qw(
- gnats-adm
- gnats-queue
- pending
+ gnats-adm
+ gnats-queue
+ pending
);
use constant NON_COMMENT_FIELDS => qw(
- Audit-Trail
- Closed-Date
- Confidential
- Unformatted
- attachments
+ Audit-Trail
+ Closed-Date
+ Confidential
+ Unformatted
+ attachments
);
# Certain fields can contain things that look like fields in them,
@@ -160,20 +159,16 @@ use constant NON_COMMENT_FIELDS => qw(
# and wait for the next field to consider that we actually have
# a field to parse.
use constant END_FIELD_ORDER => qw(
- Description
- How-To-Repeat
- Fix
- Release-Note
- Audit-Trail
- Unformatted
+ Description
+ How-To-Repeat
+ Fix
+ Release-Note
+ Audit-Trail
+ Unformatted
);
-use constant CUSTOM_FIELDS => {
- cf_type => {
- type => FIELD_TYPE_SINGLE_SELECT,
- description => 'Type',
- },
-};
+use constant CUSTOM_FIELDS =>
+ {cf_type => {type => FIELD_TYPE_SINGLE_SELECT, description => 'Type',},};
use constant FIELD_REGEX => qr/^>(\S+):\s*(.*)$/;
@@ -192,24 +187,24 @@ use constant LONG_VERSION_LENGTH => 32;
#########
sub before_insert {
- my $self = shift;
-
- # gnats_id isn't a valid User::create field, and we don't need it
- # anymore now.
- delete $_->{gnats_id} foreach @{ $self->users };
-
- # Grab a version out of a bug for each product, so that there is a
- # valid "version" argument for Bugzilla::Product->create.
- foreach my $product (@{ $self->products }) {
- my $bug = first { $_->{product} eq $product->{name} and $_->{version} }
- @{ $self->bugs };
- if (defined $bug) {
- $product->{version} = $bug->{version};
- }
- else {
- $product->{version} = 'unspecified';
- }
+ my $self = shift;
+
+ # gnats_id isn't a valid User::create field, and we don't need it
+ # anymore now.
+ delete $_->{gnats_id} foreach @{$self->users};
+
+ # Grab a version out of a bug for each product, so that there is a
+ # valid "version" argument for Bugzilla::Product->create.
+ foreach my $product (@{$self->products}) {
+ my $bug = first { $_->{product} eq $product->{name} and $_->{version} }
+ @{$self->bugs};
+ if (defined $bug) {
+ $product->{version} = $bug->{version};
+ }
+ else {
+ $product->{version} = 'unspecified';
}
+ }
}
#########
@@ -217,53 +212,53 @@ sub before_insert {
#########
sub _read_users {
- my $self = shift;
- my $path = $self->config('gnats_path');
- my $file = "$path/gnats-adm/responsible";
- $self->debug("Reading users from $file");
- my $default_domain = $self->config('default_email_domain');
- open(my $users_fh, '<', $file) || die "$file: $!";
- my @users;
- foreach my $line (<$users_fh>) {
- $line = trim($line);
- next if $line =~ /^#/;
- my ($id, $name, $email) = split(':', $line, 3);
- $email ||= "$id\@$default_domain";
- # We can't call our own translate_value, because that depends on
- # the existence of user_map, which doesn't exist until after
- # this method. However, we still want to translate any users found.
- $email = $self->SUPER::translate_value('user', $email);
- push(@users, { realname => $name, login_name => $email,
- gnats_id => $id });
- }
- close($users_fh);
- return \@users;
+ my $self = shift;
+ my $path = $self->config('gnats_path');
+ my $file = "$path/gnats-adm/responsible";
+ $self->debug("Reading users from $file");
+ my $default_domain = $self->config('default_email_domain');
+ open(my $users_fh, '<', $file) || die "$file: $!";
+ my @users;
+ foreach my $line (<$users_fh>) {
+ $line = trim($line);
+ next if $line =~ /^#/;
+ my ($id, $name, $email) = split(':', $line, 3);
+ $email ||= "$id\@$default_domain";
+
+ # We can't call our own translate_value, because that depends on
+ # the existence of user_map, which doesn't exist until after
+ # this method. However, we still want to translate any users found.
+ $email = $self->SUPER::translate_value('user', $email);
+ push(@users, {realname => $name, login_name => $email, gnats_id => $id});
+ }
+ close($users_fh);
+ return \@users;
}
sub user_map {
- my $self = shift;
- $self->{user_map} ||= { map { $_->{gnats_id} => $_->{login_name} }
- @{ $self->users } };
- return $self->{user_map};
+ my $self = shift;
+ $self->{user_map}
+ ||= {map { $_->{gnats_id} => $_->{login_name} } @{$self->users}};
+ return $self->{user_map};
}
sub add_user {
- my ($self, $id, $email) = @_;
- return if defined $self->user_map->{$id};
- $self->user_map->{$id} = $email;
- push(@{ $self->users }, { login_name => $email, gnats_id => $id });
+ my ($self, $id, $email) = @_;
+ return if defined $self->user_map->{$id};
+ $self->user_map->{$id} = $email;
+ push(@{$self->users}, {login_name => $email, gnats_id => $id});
}
sub user_to_email {
- my ($self, $value) = @_;
- if (defined $self->user_map->{$value}) {
- $value = $self->user_map->{$value};
- }
- elsif ($value !~ /@/) {
- my $domain = $self->config('default_email_domain');
- $value = "$value\@$domain";
- }
- return $value;
+ my ($self, $value) = @_;
+ if (defined $self->user_map->{$value}) {
+ $value = $self->user_map->{$value};
+ }
+ elsif ($value !~ /@/) {
+ my $domain = $self->config('default_email_domain');
+ $value = "$value\@$domain";
+ }
+ return $value;
}
############
@@ -271,31 +266,33 @@ sub user_to_email {
############
sub _read_products {
- my $self = shift;
- my $path = $self->config('gnats_path');
- my $file = "$path/gnats-adm/categories";
- $self->debug("Reading categories from $file");
-
- open(my $categories_fh, '<', $file) || die "$file: $!";
- my @products;
- foreach my $line (<$categories_fh>) {
- $line = trim($line);
- next if $line =~ /^#/;
- my ($name, $description, $assigned_to, $cc) = split(':', $line, 4);
- my %product = ( name => $name, description => $description );
-
- my @initial_cc = split(',', $cc);
- @initial_cc = @{ $self->translate_value('user', \@initial_cc) };
- $assigned_to = $self->translate_value('user', $assigned_to);
- my %component = ( name => $self->config('component_name'),
- description => $description,
- initialowner => $assigned_to,
- initial_cc => \@initial_cc );
- $product{components} = [\%component];
- push(@products, \%product);
- }
- close($categories_fh);
- return \@products;
+ my $self = shift;
+ my $path = $self->config('gnats_path');
+ my $file = "$path/gnats-adm/categories";
+ $self->debug("Reading categories from $file");
+
+ open(my $categories_fh, '<', $file) || die "$file: $!";
+ my @products;
+ foreach my $line (<$categories_fh>) {
+ $line = trim($line);
+ next if $line =~ /^#/;
+ my ($name, $description, $assigned_to, $cc) = split(':', $line, 4);
+ my %product = (name => $name, description => $description);
+
+ my @initial_cc = split(',', $cc);
+ @initial_cc = @{$self->translate_value('user', \@initial_cc)};
+ $assigned_to = $self->translate_value('user', $assigned_to);
+ my %component = (
+ name => $self->config('component_name'),
+ description => $description,
+ initialowner => $assigned_to,
+ initial_cc => \@initial_cc
+ );
+ $product{components} = [\%component];
+ push(@products, \%product);
+ }
+ close($categories_fh);
+ return \@products;
}
################
@@ -303,128 +300,131 @@ sub _read_products {
################
sub _read_bugs {
- my $self = shift;
- my $path = $self->config('gnats_path');
- my @directories = glob("$path/*");
- my @bugs;
- foreach my $directory (@directories) {
- next if !-d $directory;
- my $name = basename($directory);
- next if grep($_ eq $name, SKIP_DIRECTORIES);
- push(@bugs, @{ $self->_parse_project($directory) });
- }
- @bugs = sort { $a->{Number} <=> $b->{Number} } @bugs;
- return \@bugs;
+ my $self = shift;
+ my $path = $self->config('gnats_path');
+ my @directories = glob("$path/*");
+ my @bugs;
+ foreach my $directory (@directories) {
+ next if !-d $directory;
+ my $name = basename($directory);
+ next if grep($_ eq $name, SKIP_DIRECTORIES);
+ push(@bugs, @{$self->_parse_project($directory)});
+ }
+ @bugs = sort { $a->{Number} <=> $b->{Number} } @bugs;
+ return \@bugs;
}
sub _parse_project {
- my ($self, $directory) = @_;
- my @files = glob("$directory/*");
-
- $self->debug("Reading Project: $directory");
- # Sometimes other files get into gnats directories.
- @files = grep { basename($_) =~ /^\d+$/ } @files;
- my @bugs;
- my $count = 1;
- my $total = scalar @files;
- print basename($directory) . ":\n";
- foreach my $file (@files) {
- push(@bugs, $self->_parse_bug_file($file));
- if (!$self->verbose) {
- indicate_progress({ current => $count++, every => 5,
- total => $total });
- }
+ my ($self, $directory) = @_;
+ my @files = glob("$directory/*");
+
+ $self->debug("Reading Project: $directory");
+
+ # Sometimes other files get into gnats directories.
+ @files = grep { basename($_) =~ /^\d+$/ } @files;
+ my @bugs;
+ my $count = 1;
+ my $total = scalar @files;
+ print basename($directory) . ":\n";
+ foreach my $file (@files) {
+ push(@bugs, $self->_parse_bug_file($file));
+ if (!$self->verbose) {
+ indicate_progress({current => $count++, every => 5, total => $total});
}
- return \@bugs;
+ }
+ return \@bugs;
}
sub _parse_bug_file {
- my ($self, $file) = @_;
- $self->debug("Reading $file");
- open(my $fh, "<", $file) || die "$file: $!";
- my $email = Email::Simple::FromHandle->new($fh);
- my $fields = $self->_get_gnats_field_data($email);
- # We parse attachments here instead of during translate_bug,
- # because otherwise we'd be taking up huge amounts of memory storing
- # all the raw attachment data in memory.
- $fields->{attachments} = $self->_parse_attachments($fields);
- close($fh);
- return $fields;
+ my ($self, $file) = @_;
+ $self->debug("Reading $file");
+ open(my $fh, "<", $file) || die "$file: $!";
+ my $email = Email::Simple::FromHandle->new($fh);
+ my $fields = $self->_get_gnats_field_data($email);
+
+ # We parse attachments here instead of during translate_bug,
+ # because otherwise we'd be taking up huge amounts of memory storing
+ # all the raw attachment data in memory.
+ $fields->{attachments} = $self->_parse_attachments($fields);
+ close($fh);
+ return $fields;
}
sub _get_gnats_field_data {
- my ($self, $email) = @_;
- my ($current_field, @value_lines, %fields);
- $email->reset_handle();
- my $handle = $email->handle;
- foreach my $line (<$handle>) {
- # If this line starts a field name
- if ($line =~ FIELD_REGEX) {
- my ($new_field, $rest_of_line) = ($1, $2);
-
- # If this is one of the last few PR fields, then make sure
- # that we're getting our fields in the right order.
- my $new_field_valid = 1;
- my $search_for = $current_field || '';
- my $current_field_pos = firstidx { $_ eq $search_for }
- END_FIELD_ORDER;
- if ($current_field_pos > -1) {
- my $new_field_pos = firstidx { $_ eq $new_field }
- END_FIELD_ORDER;
- # We accept any field, as long as it's later than this one.
- $new_field_valid = $new_field_pos > $current_field_pos ? 1 : 0;
- }
-
- if ($new_field_valid) {
- if ($current_field) {
- $fields{$current_field} = _handle_lines(\@value_lines);
- @value_lines = ();
- }
- $current_field = $new_field;
- $line = $rest_of_line;
- }
+ my ($self, $email) = @_;
+ my ($current_field, @value_lines, %fields);
+ $email->reset_handle();
+ my $handle = $email->handle;
+ foreach my $line (<$handle>) {
+
+ # If this line starts a field name
+ if ($line =~ FIELD_REGEX) {
+ my ($new_field, $rest_of_line) = ($1, $2);
+
+ # If this is one of the last few PR fields, then make sure
+ # that we're getting our fields in the right order.
+ my $new_field_valid = 1;
+ my $search_for = $current_field || '';
+ my $current_field_pos = firstidx { $_ eq $search_for }
+ END_FIELD_ORDER;
+ if ($current_field_pos > -1) {
+ my $new_field_pos = firstidx { $_ eq $new_field }
+ END_FIELD_ORDER;
+
+ # We accept any field, as long as it's later than this one.
+ $new_field_valid = $new_field_pos > $current_field_pos ? 1 : 0;
+ }
+
+ if ($new_field_valid) {
+ if ($current_field) {
+ $fields{$current_field} = _handle_lines(\@value_lines);
+ @value_lines = ();
}
- push(@value_lines, $line) if defined $line;
+ $current_field = $new_field;
+ $line = $rest_of_line;
+ }
}
- $fields{$current_field} = _handle_lines(\@value_lines);
- $fields{cc} = [$email->header('Cc')] if $email->header('Cc');
-
- # If the Originator is invalid and we don't have a translation for it,
- # use the From header instead.
- my $originator = $self->translate_value('reporter', $fields{Originator},
- { check_only => 1 });
- if ($originator !~ Bugzilla->params->{emailregexp}) {
- # We use the raw header sometimes, because it looks like "From: user"
- # which Email::Address won't parse but we can still use.
- my $address = $email->header('From');
- my ($parsed) = Email::Address->parse($address);
- if ($parsed) {
- $address = $parsed->address;
- }
- if ($address) {
- $self->debug(
- "PR $fields{Number} had an Originator that was not a valid"
- . " user ($fields{Originator}). Using From ($address)"
- . " instead.\n");
- my $address_email = $self->translate_value('reporter', $address,
- { check_only => 1 });
- if ($address_email !~ Bugzilla->params->{emailregexp}) {
- $self->debug(" From was also invalid, using default_originator.\n");
- $address = $self->config('default_originator');
- }
- $fields{Originator} = $address;
- }
+ push(@value_lines, $line) if defined $line;
+ }
+ $fields{$current_field} = _handle_lines(\@value_lines);
+ $fields{cc} = [$email->header('Cc')] if $email->header('Cc');
+
+ # If the Originator is invalid and we don't have a translation for it,
+ # use the From header instead.
+ my $originator
+ = $self->translate_value('reporter', $fields{Originator}, {check_only => 1});
+ if ($originator !~ Bugzilla->params->{emailregexp}) {
+
+ # We use the raw header sometimes, because it looks like "From: user"
+ # which Email::Address won't parse but we can still use.
+ my $address = $email->header('From');
+ my ($parsed) = Email::Address->parse($address);
+ if ($parsed) {
+ $address = $parsed->address;
+ }
+ if ($address) {
+ $self->debug("PR $fields{Number} had an Originator that was not a valid"
+ . " user ($fields{Originator}). Using From ($address)"
+ . " instead.\n");
+ my $address_email
+ = $self->translate_value('reporter', $address, {check_only => 1});
+ if ($address_email !~ Bugzilla->params->{emailregexp}) {
+ $self->debug(" From was also invalid, using default_originator.\n");
+ $address = $self->config('default_originator');
+ }
+ $fields{Originator} = $address;
}
+ }
- $self->debug(\%fields, 3);
- return \%fields;
+ $self->debug(\%fields, 3);
+ return \%fields;
}
sub _handle_lines {
- my ($lines) = @_;
- my $value = join('', @$lines);
- $value =~ s/\s+$//;
- return $value;
+ my ($lines) = @_;
+ my $value = join('', @$lines);
+ $value =~ s/\s+$//;
+ return $value;
}
####################
@@ -432,169 +432,188 @@ sub _handle_lines {
####################
sub translate_bug {
- my ($self, $fields) = @_;
+ my ($self, $fields) = @_;
- my ($bug, $other_fields) = $self->SUPER::translate_bug($fields);
+ my ($bug, $other_fields) = $self->SUPER::translate_bug($fields);
- $bug->{attachments} = delete $other_fields->{attachments};
+ $bug->{attachments} = delete $other_fields->{attachments};
- if (defined $other_fields->{_add_to_comment}) {
- $bug->{comment} .= delete $other_fields->{_add_to_comment};
- }
+ if (defined $other_fields->{_add_to_comment}) {
+ $bug->{comment} .= delete $other_fields->{_add_to_comment};
+ }
- my ($changes, $extra_comment) =
- $self->_parse_audit_trail($bug, $other_fields->{'Audit-Trail'});
-
- my @comments;
- foreach my $change (@$changes) {
- if (exists $change->{comment}) {
- push(@comments, {
- thetext => $change->{comment},
- who => $change->{who},
- bug_when => $change->{bug_when} });
- delete $change->{comment};
- }
- }
- $bug->{history} = $changes;
+ my ($changes, $extra_comment)
+ = $self->_parse_audit_trail($bug, $other_fields->{'Audit-Trail'});
- if (trim($extra_comment)) {
- push(@comments, { thetext => $extra_comment, who => $bug->{reporter},
- bug_when => $bug->{delta_ts} || $bug->{creation_ts} });
- }
- $bug->{comments} = \@comments;
-
- $bug->{component} = $self->config('component_name');
- if (!$bug->{short_desc}) {
- $bug->{short_desc} = NO_SUBJECT;
- }
-
- foreach my $attachment (@{ $bug->{attachments} || [] }) {
- $attachment->{submitter} = $bug->{reporter};
- $attachment->{creation_ts} = $bug->{creation_ts};
+ my @comments;
+ foreach my $change (@$changes) {
+ if (exists $change->{comment}) {
+ push(
+ @comments,
+ {
+ thetext => $change->{comment},
+ who => $change->{who},
+ bug_when => $change->{bug_when}
+ }
+ );
+ delete $change->{comment};
}
-
- $self->debug($bug, 3);
- return $bug;
+ }
+ $bug->{history} = $changes;
+
+ if (trim($extra_comment)) {
+ push(
+ @comments,
+ {
+ thetext => $extra_comment,
+ who => $bug->{reporter},
+ bug_when => $bug->{delta_ts} || $bug->{creation_ts}
+ }
+ );
+ }
+ $bug->{comments} = \@comments;
+
+ $bug->{component} = $self->config('component_name');
+ if (!$bug->{short_desc}) {
+ $bug->{short_desc} = NO_SUBJECT;
+ }
+
+ foreach my $attachment (@{$bug->{attachments} || []}) {
+ $attachment->{submitter} = $bug->{reporter};
+ $attachment->{creation_ts} = $bug->{creation_ts};
+ }
+
+ $self->debug($bug, 3);
+ return $bug;
}
sub _parse_audit_trail {
- my ($self, $bug, $audit_trail) = @_;
- return [] if !trim($audit_trail);
- $self->debug(" Parsing audit trail...", 2);
-
- if ($audit_trail !~ /^\S+-Changed-\S+:/ms) {
- # This is just a comment from the bug's creator.
- $self->debug(" Audit trail is just a comment.", 2);
- return ([], $audit_trail);
- }
-
- my (@changes, %current_data, $current_column, $on_why);
- my $extra_comment = '';
- my $current_field;
- my @all_lines = split("\n", $audit_trail);
- foreach my $line (@all_lines) {
- # GNATS history looks like:
- # Status-Changed-From-To: open->closed
- # Status-Changed-By: jack
- # Status-Changed-When: Mon May 12 14:46:59 2003
- # Status-Changed-Why:
- # This is some comment here about the change.
- if ($line =~ /^(\S+)-Changed-(\S+):(.*)/) {
- my ($field, $column, $value) = ($1, $2, $3);
- my $bz_field = $self->translate_field($field);
- # If it's not a field we're importing, we don't care about
- # its history.
- next if !$bz_field;
- # GNATS doesn't track values for description changes,
- # unfortunately, and that's the only information we'd be able to
- # use in Bugzilla for the audit trail on that field.
- next if $bz_field eq 'comment';
- $current_field = $bz_field if !$current_field;
- if ($bz_field ne $current_field) {
- $self->_store_audit_change(
- \@changes, $current_field, \%current_data);
- %current_data = ();
- $current_field = $bz_field;
- }
- $value = trim($value);
- $self->debug(" $bz_field $column: $value", 3);
- if ($column eq 'From-To') {
- my ($from, $to) = split('->', $value, 2);
- # Sometimes there's just a - instead of a -> between the values.
- if (!defined($to)) {
- ($from, $to) = split('-', $value, 2);
- }
- $current_data{added} = $to;
- $current_data{removed} = $from;
- }
- elsif ($column eq 'By') {
- my $email = $self->translate_value('user', $value);
- # Sometimes we hit users in the audit trail that we haven't
- # seen anywhere else.
- $current_data{who} = $email;
- }
- elsif ($column eq 'When') {
- $current_data{bug_when} = $self->parse_date($value);
- }
- if ($column eq 'Why') {
- $value = '' if !defined $value;
- $current_data{comment} = $value;
- $on_why = 1;
- }
- else {
- $on_why = 0;
- }
- }
- elsif ($on_why) {
- # "Why" lines are indented four characters.
- $line =~ s/^\s{4}//;
- $current_data{comment} .= "$line\n";
- }
- else {
- $self->debug(
- "Extra Audit-Trail line on $bug->{product} $bug->{bug_id}:"
- . " $line\n", 2);
- $extra_comment .= "$line\n";
+ my ($self, $bug, $audit_trail) = @_;
+ return [] if !trim($audit_trail);
+ $self->debug(" Parsing audit trail...", 2);
+
+ if ($audit_trail !~ /^\S+-Changed-\S+:/ms) {
+
+ # This is just a comment from the bug's creator.
+ $self->debug(" Audit trail is just a comment.", 2);
+ return ([], $audit_trail);
+ }
+
+ my (@changes, %current_data, $current_column, $on_why);
+ my $extra_comment = '';
+ my $current_field;
+ my @all_lines = split("\n", $audit_trail);
+ foreach my $line (@all_lines) {
+
+ # GNATS history looks like:
+ # Status-Changed-From-To: open->closed
+ # Status-Changed-By: jack
+ # Status-Changed-When: Mon May 12 14:46:59 2003
+ # Status-Changed-Why:
+ # This is some comment here about the change.
+ if ($line =~ /^(\S+)-Changed-(\S+):(.*)/) {
+ my ($field, $column, $value) = ($1, $2, $3);
+ my $bz_field = $self->translate_field($field);
+
+ # If it's not a field we're importing, we don't care about
+ # its history.
+ next if !$bz_field;
+
+ # GNATS doesn't track values for description changes,
+ # unfortunately, and that's the only information we'd be able to
+ # use in Bugzilla for the audit trail on that field.
+ next if $bz_field eq 'comment';
+ $current_field = $bz_field if !$current_field;
+ if ($bz_field ne $current_field) {
+ $self->_store_audit_change(\@changes, $current_field, \%current_data);
+ %current_data = ();
+ $current_field = $bz_field;
+ }
+ $value = trim($value);
+ $self->debug(" $bz_field $column: $value", 3);
+ if ($column eq 'From-To') {
+ my ($from, $to) = split('->', $value, 2);
+
+ # Sometimes there's just a - instead of a -> between the values.
+ if (!defined($to)) {
+ ($from, $to) = split('-', $value, 2);
}
+ $current_data{added} = $to;
+ $current_data{removed} = $from;
+ }
+ elsif ($column eq 'By') {
+ my $email = $self->translate_value('user', $value);
+
+ # Sometimes we hit users in the audit trail that we haven't
+ # seen anywhere else.
+ $current_data{who} = $email;
+ }
+ elsif ($column eq 'When') {
+ $current_data{bug_when} = $self->parse_date($value);
+ }
+ if ($column eq 'Why') {
+ $value = '' if !defined $value;
+ $current_data{comment} = $value;
+ $on_why = 1;
+ }
+ else {
+ $on_why = 0;
+ }
+ }
+ elsif ($on_why) {
+
+ # "Why" lines are indented four characters.
+ $line =~ s/^\s{4}//;
+ $current_data{comment} .= "$line\n";
+ }
+ else {
+ $self->debug(
+ "Extra Audit-Trail line on $bug->{product} $bug->{bug_id}:" . " $line\n", 2);
+ $extra_comment .= "$line\n";
}
- $self->_store_audit_change(\@changes, $current_field, \%current_data);
- return (\@changes, $extra_comment);
+ }
+ $self->_store_audit_change(\@changes, $current_field, \%current_data);
+ return (\@changes, $extra_comment);
}
sub _store_audit_change {
- my ($self, $changes, $old_field, $current_data) = @_;
-
- $current_data->{field} = $old_field;
- $current_data->{removed} =
- $self->translate_value($old_field, $current_data->{removed});
- $current_data->{added} =
- $self->translate_value($old_field, $current_data->{added});
- push(@$changes, { %$current_data });
+ my ($self, $changes, $old_field, $current_data) = @_;
+
+ $current_data->{field} = $old_field;
+ $current_data->{removed}
+ = $self->translate_value($old_field, $current_data->{removed});
+ $current_data->{added}
+ = $self->translate_value($old_field, $current_data->{added});
+ push(@$changes, {%$current_data});
}
sub _parse_attachments {
- my ($self, $fields) = @_;
- my $unformatted = delete $fields->{'Unformatted'};
- my $gnats_boundary = GNATS_BOUNDARY;
- # A sanity checker to make sure that we're parsing attachments right.
- my $num_attachments = 0;
- $num_attachments++ while ($unformatted =~ /\Q$gnats_boundary\E/g);
- # Sometimes there's a GNATS_BOUNDARY that is on the same line as other data.
- $unformatted =~ s/(\S\s*)\Q$gnats_boundary\E$/$1\n$gnats_boundary/mg;
- # Often the "Unformatted" section starts with stuff before
- # ----gnatsweb-attachment---- that isn't necessary.
- $unformatted =~ s/^\s*From:.+?Reply-to:[^\n]+//s;
- $unformatted = trim($unformatted);
- return [] if !$unformatted;
- $self->debug('Reading attachments...', 2);
- my $boundary = generate_random_password(48);
- $unformatted =~ s/\Q$gnats_boundary\E/--$boundary/g;
- # Sometimes the whole Unformatted section is indented by exactly
- # one space, and needs to be fixed.
- if ($unformatted =~ /--\Q$boundary\E\n /) {
- $unformatted =~ s/^ //mg;
- }
- $unformatted = <<END;
+ my ($self, $fields) = @_;
+ my $unformatted = delete $fields->{'Unformatted'};
+ my $gnats_boundary = GNATS_BOUNDARY;
+
+ # A sanity checker to make sure that we're parsing attachments right.
+ my $num_attachments = 0;
+ $num_attachments++ while ($unformatted =~ /\Q$gnats_boundary\E/g);
+
+ # Sometimes there's a GNATS_BOUNDARY that is on the same line as other data.
+ $unformatted =~ s/(\S\s*)\Q$gnats_boundary\E$/$1\n$gnats_boundary/mg;
+
+ # Often the "Unformatted" section starts with stuff before
+ # ----gnatsweb-attachment---- that isn't necessary.
+ $unformatted =~ s/^\s*From:.+?Reply-to:[^\n]+//s;
+ $unformatted = trim($unformatted);
+ return [] if !$unformatted;
+ $self->debug('Reading attachments...', 2);
+ my $boundary = generate_random_password(48);
+ $unformatted =~ s/\Q$gnats_boundary\E/--$boundary/g;
+
+ # Sometimes the whole Unformatted section is indented by exactly
+ # one space, and needs to be fixed.
+ if ($unformatted =~ /--\Q$boundary\E\n /) {
+ $unformatted =~ s/^ //mg;
+ }
+ $unformatted = <<END;
From: nobody
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="$boundary"
@@ -607,96 +626,103 @@ Content-Transfer-Encoding: 7bit
$unformatted
--$boundary--
END
- my $email = new Email::MIME(\$unformatted);
- my @parts = $email->parts;
- # Remove the fake body.
- my $part1 = shift @parts;
- if ($part1->body) {
- $self->debug(" Additional Unformatted data found on "
- . $fields->{Category} . " bug " . $fields->{Number});
- $self->debug($part1->body, 3);
- $fields->{_add_comment} .= "\n\nUnformatted:\n" . $part1->body;
- }
+ my $email = new Email::MIME(\$unformatted);
+ my @parts = $email->parts;
+
+ # Remove the fake body.
+ my $part1 = shift @parts;
+ if ($part1->body) {
+ $self->debug(" Additional Unformatted data found on "
+ . $fields->{Category} . " bug "
+ . $fields->{Number});
+ $self->debug($part1->body, 3);
+ $fields->{_add_comment} .= "\n\nUnformatted:\n" . $part1->body;
+ }
+
+ my @attachments;
+ foreach my $part (@parts) {
+ $self->debug(' Parsing attachment: ' . $part->filename);
+ my $temp_fh = IO::File->new_tmpfile or die("Can't create tempfile: $!");
+ $temp_fh->binmode;
+ print $temp_fh $part->body;
+ my $content_type = $part->content_type;
+ $content_type =~ s/; name=.+$//;
+ my $attachment = {
+ filename => $part->filename,
+ description => $part->filename,
+ mimetype => $content_type,
+ data => $temp_fh
+ };
+ $self->debug($attachment, 3);
+ push(@attachments, $attachment);
+ }
+
+ if (scalar(@attachments) ne $num_attachments) {
+ warn "WARNING: Expected $num_attachments attachments but got "
+ . scalar(@attachments) . "\n";
+ $self->debug($unformatted, 3);
+ }
+ return \@attachments;
+}
- my @attachments;
- foreach my $part (@parts) {
- $self->debug(' Parsing attachment: ' . $part->filename);
- my $temp_fh = IO::File->new_tmpfile or die ("Can't create tempfile: $!");
- $temp_fh->binmode;
- print $temp_fh $part->body;
- my $content_type = $part->content_type;
- $content_type =~ s/; name=.+$//;
- my $attachment = { filename => $part->filename,
- description => $part->filename,
- mimetype => $content_type,
- data => $temp_fh };
- $self->debug($attachment, 3);
- push(@attachments, $attachment);
+sub translate_value {
+ my $self = shift;
+ my ($field, $value, $options) = @_;
+ my $original_value = $value;
+ $options ||= {};
+
+ if (!ref($value) and grep($_ eq $field, $self->USER_FIELDS)) {
+ if ($value =~ /(\S+\@\S+)/) {
+ $value = $1;
+ $value =~ s/^<//;
+ $value =~ s/>$//;
}
-
- if (scalar(@attachments) ne $num_attachments) {
- warn "WARNING: Expected $num_attachments attachments but got "
- . scalar(@attachments) . "\n" ;
- $self->debug($unformatted, 3);
+ else {
+ # Sometimes names have extra stuff on the end like "(Somebody's Name)"
+ $value =~ s/\s+\(.+\)$//;
+
+ # Sometimes user fields look like "(user)" instead of just "user".
+ $value =~ s/^\((.+)\)$/$1/;
+ $value = trim($value);
}
- return \@attachments;
-}
+ }
-sub translate_value {
- my $self = shift;
- my ($field, $value, $options) = @_;
- my $original_value = $value;
- $options ||= {};
-
- if (!ref($value) and grep($_ eq $field, $self->USER_FIELDS)) {
- if ($value =~ /(\S+\@\S+)/) {
- $value = $1;
- $value =~ s/^<//;
- $value =~ s/>$//;
- }
- else {
- # Sometimes names have extra stuff on the end like "(Somebody's Name)"
- $value =~ s/\s+\(.+\)$//;
- # Sometimes user fields look like "(user)" instead of just "user".
- $value =~ s/^\((.+)\)$/$1/;
- $value = trim($value);
- }
+ if ($field eq 'version' and $value ne '') {
+ my $version_re = $self->config('version_regex');
+ if ($version_re and $value =~ $version_re) {
+ $value = $1;
}
- if ($field eq 'version' and $value ne '') {
- my $version_re = $self->config('version_regex');
- if ($version_re and $value =~ $version_re) {
- $value = $1;
- }
- # In the GNATS that I tested this with, there were many extremely long
- # values for "version" that caused some import problems (they were
- # longer than the max allowed version value). So if the version value
- # is longer than 32 characters, pull out the first thing that looks
- # like a version number.
- elsif (length($value) > LONG_VERSION_LENGTH) {
- $value =~ s/^.+?\b(\d[\w\.]+)\b.+$/$1/;
- }
+ # In the GNATS that I tested this with, there were many extremely long
+ # values for "version" that caused some import problems (they were
+ # longer than the max allowed version value). So if the version value
+ # is longer than 32 characters, pull out the first thing that looks
+ # like a version number.
+ elsif (length($value) > LONG_VERSION_LENGTH) {
+ $value =~ s/^.+?\b(\d[\w\.]+)\b.+$/$1/;
}
-
- my @args = @_;
+ }
+
+ my @args = @_;
+ $args[1] = $value;
+
+ $value = $self->SUPER::translate_value(@args);
+ return $value if ref $value;
+
+ if (grep($_ eq $field, $self->USER_FIELDS)) {
+ my $from_value = $value;
+ $value = $self->user_to_email($value);
$args[1] = $value;
-
+
+ # If we got something new from user_to_email, do any necessary
+ # translation of it.
$value = $self->SUPER::translate_value(@args);
- return $value if ref $value;
-
- if (grep($_ eq $field, $self->USER_FIELDS)) {
- my $from_value = $value;
- $value = $self->user_to_email($value);
- $args[1] = $value;
- # If we got something new from user_to_email, do any necessary
- # translation of it.
- $value = $self->SUPER::translate_value(@args);
- if (!$options->{check_only}) {
- $self->add_user($from_value, $value);
- }
+ if (!$options->{check_only}) {
+ $self->add_user($from_value, $value);
}
-
- return $value;
+ }
+
+ return $value;
}
1;
diff --git a/Bugzilla/Milestone.pm b/Bugzilla/Milestone.pm
index cf7e3e35f..815a111f8 100644
--- a/Bugzilla/Milestone.pm
+++ b/Bugzilla/Milestone.pm
@@ -25,140 +25,140 @@ use Scalar::Util qw(blessed);
use constant DEFAULT_SORTKEY => 0;
-use constant DB_TABLE => 'milestones';
+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
- isactive
+ id
+ value
+ product_id
+ sortkey
+ isactive
);
-use constant REQUIRED_FIELD_MAP => {
- product_id => 'product',
-};
+use constant REQUIRED_FIELD_MAP => {product_id => 'product',};
use constant UPDATE_COLUMNS => qw(
- value
- sortkey
- isactive
+ value
+ sortkey
+ isactive
);
use constant VALIDATORS => {
- product => \&_check_product,
- sortkey => \&_check_sortkey,
- value => \&_check_value,
- isactive => \&Bugzilla::Object::check_boolean,
+ product => \&_check_product,
+ sortkey => \&_check_sortkey,
+ value => \&_check_value,
+ isactive => \&Bugzilla::Object::check_boolean,
};
-use constant VALIDATOR_DEPENDENCIES => {
- value => ['product'],
-};
+use constant VALIDATOR_DEPENDENCIES => {value => ['product'],};
################################
sub new {
- my $class = shift;
- my $param = shift;
- my $dbh = Bugzilla->dbh;
-
- my $product;
- if (ref $param and !defined $param->{id}) {
- $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 };
+ my $class = shift;
+ my $param = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $product;
+ if (ref $param and !defined $param->{id}) {
+ $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(@_);
+ 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;
- return $params;
+ my $class = shift;
+ my $params = $class->SUPER::run_create_validators(@_);
+ my $product = delete $params->{product};
+ $params->{product_id} = $product->id;
+ return $params;
}
sub update {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- $dbh->bz_start_transaction();
- my $changes = $self->SUPER::update(@_);
-
- if (exists $changes->{value}) {
- # 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]));
- Bugzilla->memcached->clear({ table => 'products', id => $self->product_id });
- }
- $dbh->bz_commit_transaction();
- Bugzilla->memcached->clear_config();
-
- return $changes;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+ my $changes = $self->SUPER::update(@_);
+
+ if (exists $changes->{value}) {
+
+ # 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])
+ );
+ Bugzilla->memcached->clear({table => 'products', id => $self->product_id});
+ }
+ $dbh->bz_commit_transaction();
+ Bugzilla->memcached->clear_config();
+
+ return $changes;
}
sub remove_from_db {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- # The default milestone cannot be deleted.
- if ($self->name eq $self->product->default_milestone) {
- ThrowUserError('milestone_is_default', { milestone => $self });
- }
+ # The default milestone cannot be deleted.
+ if ($self->name eq $self->product->default_milestone) {
+ ThrowUserError('milestone_is_default', {milestone => $self});
+ }
+
+ if ($self->bug_count) {
- 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
+ # 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);
- }
+ 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);
}
- $self->SUPER::remove_from_db();
+ }
+ $self->SUPER::remove_from_db();
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
}
################################
@@ -166,78 +166,84 @@ sub remove_from_db {
################################
sub _check_value {
- my ($invocant, $name, undef, $params) = @_;
- my $product = blessed($invocant) ? $invocant->product : $params->{product};
-
- $name = trim($name);
- $name || ThrowUserError('milestone_blank_name');
- if (length($name) > MAX_MILESTONE_SIZE) {
- ThrowUserError('milestone_name_too_long', {name => $name});
- }
-
- 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;
+ my ($invocant, $name, undef, $params) = @_;
+ my $product = blessed($invocant) ? $invocant->product : $params->{product};
+
+ $name = trim($name);
+ $name || ThrowUserError('milestone_blank_name');
+ if (length($name) > MAX_MILESTONE_SIZE) {
+ ThrowUserError('milestone_name_too_long', {name => $name});
+ }
+
+ 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;
+ 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) = @_;
- $product || ThrowCodeError('param_required',
- { function => "$invocant->create", param => "product" });
- return Bugzilla->user->check_can_admin_product($product->name);
+ my ($invocant, $product) = @_;
+ $product
+ || ThrowCodeError('param_required',
+ {function => "$invocant->create", param => "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 set_name { $_[0]->set('value', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
sub set_is_active { $_[0]->set('isactive', $_[1]); }
sub bug_count {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!defined $self->{'bug_count'}) {
- $self->{'bug_count'} = $dbh->selectrow_array(q{
+ 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'};
+ WHERE product_id = ? AND target_milestone = ?}, undef, $self->product_id,
+ $self->name
+ ) || 0;
+ }
+ return $self->{'bug_count'};
}
################################
##### Accessors ######
################################
-sub name { return $_[0]->{'value'}; }
+sub name { return $_[0]->{'value'}; }
sub product_id { return $_[0]->{'product_id'}; }
-sub sortkey { return $_[0]->{'sortkey'}; }
-sub is_active { return $_[0]->{'isactive'}; }
+sub sortkey { return $_[0]->{'sortkey'}; }
+sub is_active { return $_[0]->{'isactive'}; }
sub product {
- my $self = shift;
+ my $self = shift;
- require Bugzilla::Product;
- $self->{'product'} ||= new Bugzilla::Product($self->product_id);
- return $self->{'product'};
+ require Bugzilla::Product;
+ $self->{'product'} ||= new Bugzilla::Product($self->product_id);
+ return $self->{'product'};
}
1;
diff --git a/Bugzilla/Object.pm b/Bugzilla/Object.pm
index d43c8ca34..e38a44448 100644
--- a/Bugzilla/Object.pm
+++ b/Bugzilla/Object.pm
@@ -24,16 +24,17 @@ 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 => ();
+use constant UPDATE_VALIDATORS => {};
+use constant NUMERIC_COLUMNS => ();
+use constant DATE_COLUMNS => ();
use constant VALIDATOR_DEPENDENCIES => {};
+
# XXX At some point, this will be joined with FIELD_MAP.
-use constant REQUIRED_FIELD_MAP => {};
+use constant REQUIRED_FIELD_MAP => {};
use constant EXTRA_REQUIRED_FIELDS => ();
-use constant AUDIT_CREATES => 1;
-use constant AUDIT_UPDATES => 1;
-use constant AUDIT_REMOVES => 1;
+use constant AUDIT_CREATES => 1;
+use constant AUDIT_UPDATES => 1;
+use constant AUDIT_REMOVES => 1;
# When USE_MEMCACHED is true, the class is suitable for serialisation to
# Memcached. See documentation in Bugzilla::Memcached for more information.
@@ -47,54 +48,51 @@ use constant IS_CONFIG => 0;
# This allows the JSON-RPC interface to return Bugzilla::Object instances
# as though they were hashes. In the future, this may be modified to return
# less information.
-sub TO_JSON { return { %{ $_[0] } }; }
+sub TO_JSON { return {%{$_[0]}}; }
###############################
#### Initialization ####
###############################
sub new {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $param = shift;
-
- my $object = $class->_object_cache_get($param);
- return $object if $object;
-
- my ($data, $set_memcached);
- if (Bugzilla->memcached->enabled
- && $class->USE_MEMCACHED
- && ref($param) eq 'HASH' && $param->{cache})
- {
- if (defined $param->{id}) {
- $data = Bugzilla->memcached->get({
- table => $class->DB_TABLE,
- id => $param->{id},
- });
- }
- elsif (defined $param->{name}) {
- $data = Bugzilla->memcached->get({
- table => $class->DB_TABLE,
- name => $param->{name},
- });
- }
- $set_memcached = $data ? 0 : 1;
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $param = shift;
+
+ my $object = $class->_object_cache_get($param);
+ return $object if $object;
+
+ my ($data, $set_memcached);
+ if ( Bugzilla->memcached->enabled
+ && $class->USE_MEMCACHED
+ && ref($param) eq 'HASH'
+ && $param->{cache})
+ {
+ if (defined $param->{id}) {
+ $data
+ = Bugzilla->memcached->get({table => $class->DB_TABLE, id => $param->{id},});
}
- $data ||= $class->_load_from_db($param);
-
- if ($data && $set_memcached) {
- Bugzilla->memcached->set({
- table => $class->DB_TABLE,
- id => $data->{$class->ID_FIELD},
- name => $data->{$class->NAME_FIELD},
- data => $data,
- });
+ elsif (defined $param->{name}) {
+ $data = Bugzilla->memcached->get(
+ {table => $class->DB_TABLE, name => $param->{name},});
}
-
- $object = $class->new_from_hash($data);
- $class->_object_cache_set($param, $object);
-
- return $object;
+ $set_memcached = $data ? 0 : 1;
+ }
+ $data ||= $class->_load_from_db($param);
+
+ if ($data && $set_memcached) {
+ Bugzilla->memcached->set({
+ table => $class->DB_TABLE,
+ id => $data->{$class->ID_FIELD},
+ name => $data->{$class->NAME_FIELD},
+ data => $data,
+ });
+ }
+
+ $object = $class->new_from_hash($data);
+ $class->_object_cache_set($param, $object);
+
+ return $object;
}
# Note: Because this uses sql_istrcmp, if you make a new object use
@@ -102,326 +100,324 @@ sub new {
# in Bugzilla::DB::Pg appropriately, to add the right LOWER
# index. You can see examples already there.
sub _load_from_db {
- my $class = shift;
- my ($param) = @_;
- my $dbh = Bugzilla->dbh;
- my $columns = join(',', $class->_get_db_columns);
- my $table = $class->DB_TABLE;
- my $name_field = $class->NAME_FIELD;
- my $id_field = $class->ID_FIELD;
-
- my $id = $param;
- if (ref $param eq 'HASH') {
- $id = $param->{id};
+ my $class = shift;
+ my ($param) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $columns = join(',', $class->_get_db_columns);
+ my $table = $class->DB_TABLE;
+ my $name_field = $class->NAME_FIELD;
+ my $id_field = $class->ID_FIELD;
+
+ my $id = $param;
+ if (ref $param eq 'HASH') {
+ $id = $param->{id};
+ }
+
+ my $object_data;
+ 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 . '::_load_from_db'});
+
+ # Too large integers make PostgreSQL crash.
+ return if $id > MAX_INT_32;
+
+ $object_data = $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 $object_data;
- 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 . '::_load_from_db'});
-
- # Too large integers make PostgreSQL crash.
- return if $id > MAX_INT_32;
-
- $object_data = $dbh->selectrow_hashref(qq{
- SELECT $columns FROM $table
- WHERE $id_field = ?}, undef, $id);
- } else {
- unless (defined $param->{name} || (defined $param->{'condition'}
- && defined $param->{'values'}))
+ 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',
{
- 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'}});
+ caller => caller,
+ function => $class . '::new',
+ argument => 'condition/values'
}
-
- map { trick_taint($_) } @values;
- $object_data = $dbh->selectrow_hashref(
- "SELECT $columns FROM $table WHERE $condition", undef, @values);
+ );
+ $condition = $param->{'condition'};
+ push(@values, @{$param->{'values'}});
}
- return $object_data;
+
+ map { trick_taint($_) } @values;
+ $object_data
+ = $dbh->selectrow_hashref("SELECT $columns FROM $table WHERE $condition",
+ undef, @values);
+ }
+ return $object_data;
}
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'});
- # Too large integers make PostgreSQL crash.
- next if $id > MAX_INT_32;
- 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 });
+ 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'});
+
+ # Too large integers make PostgreSQL crash.
+ next if $id > MAX_INT_32;
+ 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});
}
sub new_from_hash {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $object_data = shift || return;
- $class->_serialisation_keys($object_data);
- bless($object_data, $class);
- $object_data->initialize();
- return $object_data;
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $object_data = shift || return;
+ $class->_serialisation_keys($object_data);
+ bless($object_data, $class);
+ $object_data->initialize();
+ return $object_data;
}
sub initialize {
- # abstract
+
+ # abstract
}
# Provides a mechanism for objects to be cached in the request_cache
sub object_cache_get {
- my ($class, $id) = @_;
- return $class->_object_cache_get(
- { id => $id, cache => 1},
- $class
- );
+ my ($class, $id) = @_;
+ return $class->_object_cache_get({id => $id, cache => 1}, $class);
}
sub object_cache_set {
- my $self = shift;
- return $self->_object_cache_set(
- { id => $self->id, cache => 1 },
- $self
- );
+ my $self = shift;
+ return $self->_object_cache_set({id => $self->id, cache => 1}, $self);
}
sub _object_cache_get {
- my $class = shift;
- my ($param) = @_;
- my $cache_key = $class->object_cache_key($param)
- || return;
- return Bugzilla->request_cache->{$cache_key};
+ my $class = shift;
+ my ($param) = @_;
+ my $cache_key = $class->object_cache_key($param) || return;
+ return Bugzilla->request_cache->{$cache_key};
}
sub _object_cache_set {
- my $class = shift;
- my ($param, $object) = @_;
- my $cache_key = $class->object_cache_key($param)
- || return;
- Bugzilla->request_cache->{$cache_key} = $object;
+ my $class = shift;
+ my ($param, $object) = @_;
+ my $cache_key = $class->object_cache_key($param) || return;
+ Bugzilla->request_cache->{$cache_key} = $object;
}
sub _object_cache_remove {
- my $class = shift;
- my ($param) = @_;
- $param->{cache} = 1;
- my $cache_key = $class->object_cache_key($param)
- || return;
- delete Bugzilla->request_cache->{$cache_key};
+ my $class = shift;
+ my ($param) = @_;
+ $param->{cache} = 1;
+ my $cache_key = $class->object_cache_key($param) || return;
+ delete Bugzilla->request_cache->{$cache_key};
}
sub object_cache_key {
- my $class = shift;
- my ($param) = @_;
- if (ref($param) && $param->{cache} && ($param->{id} || $param->{name})) {
- $class = blessed($class) if blessed($class);
- return $class . ',' . ($param->{id} || $param->{name});
- } else {
- return;
- }
+ my $class = shift;
+ my ($param) = @_;
+ if (ref($param) && $param->{cache} && ($param->{id} || $param->{name})) {
+ $class = blessed($class) if blessed($class);
+ return $class . ',' . ($param->{id} || $param->{name});
+ }
+ else {
+ return;
+ }
}
# To support serialisation, we need to capture the keys in an object's default
# hashref.
sub _serialisation_keys {
- my ($class, $object) = @_;
- my $cache = Bugzilla->request_cache->{serialisation_keys} ||= {};
- $cache->{$class} = [ keys %$object ] if $object && !exists $cache->{$class};
- return @{ $cache->{$class} };
+ my ($class, $object) = @_;
+ my $cache = Bugzilla->request_cache->{serialisation_keys} ||= {};
+ $cache->{$class} = [keys %$object] if $object && !exists $cache->{$class};
+ return @{$cache->{$class}};
}
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 };
+ 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 or ids.
+ my $check_param = exists $param->{id} ? 'id' : 'name';
+ $param->{$check_param} = trim($param->{$check_param});
+
+ # If somebody passes us "0", we want to throw an error like
+ # "there is no X with the name 0". This is true even for ids. So here,
+ # we only check if the parameter is undefined or empty.
+ if (!defined $param->{$check_param} or $param->{$check_param} eq '') {
+ ThrowUserError('object_not_specified', {class => $class});
+ }
+
+ my $obj = $class->new($param);
+ if (!$obj) {
+
+ # We don't want to override the normal template "user" object if
+ # "user" is one of the params.
+ delete $param->{user};
+ if (my $error = delete $param->{_error}) {
+ ThrowUserError($error, {%$param, class => $class});
}
-
- # Don't allow empty names or ids.
- my $check_param = exists $param->{id} ? 'id' : 'name';
- $param->{$check_param} = trim($param->{$check_param});
- # If somebody passes us "0", we want to throw an error like
- # "there is no X with the name 0". This is true even for ids. So here,
- # we only check if the parameter is undefined or empty.
- if (!defined $param->{$check_param} or $param->{$check_param} eq '') {
- ThrowUserError('object_not_specified', { class => $class });
+ else {
+ ThrowUserError('object_does_not_exist', {%$param, class => $class});
}
-
- my $obj = $class->new($param);
- if (!$obj) {
- # We don't want to override the normal template "user" object if
- # "user" is one of the params.
- delete $param->{user};
- if (my $error = delete $param->{_error}) {
- ThrowUserError($error, { %$param, class => $class });
- }
- else {
- ThrowUserError('object_does_not_exist', { %$param, class => $class });
- }
- }
- return $obj;
+ }
+ return $obj;
}
# 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, $postamble);
- foreach my $field (keys %$criteria) {
- my $value = $criteria->{$field};
-
- # allow for LIMIT and OFFSET expressions via the criteria.
- next if $field eq 'OFFSET';
- if ( $field eq 'LIMIT' ) {
- next unless defined $value;
- detaint_natural($value)
- or ThrowCodeError('param_must_be_numeric',
- { param => 'LIMIT',
- function => "${class}::match" });
- my $offset;
- if (defined $criteria->{OFFSET}) {
- $offset = $criteria->{OFFSET};
- detaint_signed($offset)
- or ThrowCodeError('param_must_be_numeric',
- { param => 'OFFSET',
- function => "${class}::match" });
- }
- $postamble = $dbh->sql_limit($value, $offset);
- next;
- }
- elsif ( $field eq 'WHERE' ) {
- # the WHERE value is a hashref where the keys are
- # "column_name operator ?" and values are the placeholder's
- # value (either a scalar or an array of values).
- foreach my $k (keys %$value) {
- push(@terms, $k);
- my @this_value = ref($value->{$k}) ? @{ $value->{$k} }
- : ($value->{$k});
- push(@values, @this_value);
- }
- next;
- }
+ my ($invocant, $criteria) = @_;
+ my $class = ref($invocant) || $invocant;
+ my $dbh = Bugzilla->dbh;
+
+ return [$class->get_all] if !$criteria;
+
+ my (@terms, @values, $postamble);
+ foreach my $field (keys %$criteria) {
+ my $value = $criteria->{$field};
+
+ # allow for LIMIT and OFFSET expressions via the criteria.
+ next if $field eq 'OFFSET';
+ if ($field eq 'LIMIT') {
+ next unless defined $value;
+ detaint_natural($value)
+ or ThrowCodeError('param_must_be_numeric',
+ {param => 'LIMIT', function => "${class}::match"});
+ my $offset;
+ if (defined $criteria->{OFFSET}) {
+ $offset = $criteria->{OFFSET};
+ detaint_signed($offset)
+ or ThrowCodeError('param_must_be_numeric',
+ {param => 'OFFSET', function => "${class}::match"});
+ }
+ $postamble = $dbh->sql_limit($value, $offset);
+ next;
+ }
+ elsif ($field eq 'WHERE') {
+
+ # the WHERE value is a hashref where the keys are
+ # "column_name operator ?" and values are the placeholder's
+ # value (either a scalar or an array of values).
+ foreach my $k (keys %$value) {
+ push(@terms, $k);
+ my @this_value = ref($value->{$k}) ? @{$value->{$k}} : ($value->{$k});
+ push(@values, @this_value);
+ }
+ next;
+ }
- # It's always safe to use the field defined by classes as being
- # their ID field. In particular, this means that new_from_list()
- # is exempted from this check.
- $class->_check_field($field, 'match') unless $field eq $class->ID_FIELD;
+ # It's always safe to use the field defined by classes as being
+ # their ID field. In particular, this means that new_from_list()
+ # is exempted from this check.
+ $class->_check_field($field, 'match') unless $field eq $class->ID_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;
+ if (ref $value eq 'ARRAY') {
- 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);
- }
+ # 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) if scalar @terms;
- return $class->_do_list_select($where, \@values, $postamble);
+ my $where = join(' AND ', @terms) if scalar @terms;
+ return $class->_do_list_select($where, \@values, $postamble);
}
sub _do_list_select {
- my ($class, $where, $values, $postamble) = @_;
- my $table = $class->DB_TABLE;
- my $cols = join(',', $class->_get_db_columns);
- my $order = $class->LIST_ORDER;
-
- # Unconditional requests for configuration data are cacheable.
- my ($objects, $set_memcached, $memcached_key);
- if (!defined $where
- && Bugzilla->memcached->enabled
- && $class->IS_CONFIG)
- {
- $memcached_key = "$class:get_all";
- $objects = Bugzilla->memcached->get_config({ key => $memcached_key });
- $set_memcached = $objects ? 0 : 1;
+ my ($class, $where, $values, $postamble) = @_;
+ my $table = $class->DB_TABLE;
+ my $cols = join(',', $class->_get_db_columns);
+ my $order = $class->LIST_ORDER;
+
+ # Unconditional requests for configuration data are cacheable.
+ my ($objects, $set_memcached, $memcached_key);
+ if (!defined $where && Bugzilla->memcached->enabled && $class->IS_CONFIG) {
+ $memcached_key = "$class:get_all";
+ $objects = Bugzilla->memcached->get_config({key => $memcached_key});
+ $set_memcached = $objects ? 0 : 1;
+ }
+
+ if (!$objects) {
+ my $sql = "SELECT $cols FROM $table";
+ if (defined $where) {
+ $sql .= " WHERE $where ";
}
+ $sql .= " ORDER BY $order";
+ $sql .= " $postamble" if $postamble;
- if (!$objects) {
- my $sql = "SELECT $cols FROM $table";
- if (defined $where) {
- $sql .= " WHERE $where ";
- }
- $sql .= " ORDER BY $order";
- $sql .= " $postamble" if $postamble;
-
- my $dbh = Bugzilla->dbh;
- # Sometimes the values are tainted, but we don't want to untaint them
- # for the caller. So we copy the array. It's safe to untaint because
- # they're only used in placeholders here.
- my @untainted = @{ $values || [] };
- trick_taint($_) foreach @untainted;
- $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @untainted);
- $class->_serialisation_keys($objects->[0]) if @$objects;
- }
-
- if ($objects && $set_memcached) {
- Bugzilla->memcached->set_config({
- key => $memcached_key,
- data => $objects
- });
- }
+ my $dbh = Bugzilla->dbh;
- foreach my $object (@$objects) {
- $object = $class->new_from_hash($object);
- }
- return $objects;
+ # Sometimes the values are tainted, but we don't want to untaint them
+ # for the caller. So we copy the array. It's safe to untaint because
+ # they're only used in placeholders here.
+ my @untainted = @{$values || []};
+ trick_taint($_) foreach @untainted;
+ $objects = $dbh->selectall_arrayref($sql, {Slice => {}}, @untainted);
+ $class->_serialisation_keys($objects->[0]) if @$objects;
+ }
+
+ if ($objects && $set_memcached) {
+ Bugzilla->memcached->set_config({key => $memcached_key, data => $objects});
+ }
+
+ foreach my $object (@$objects) {
+ $object = $class->new_from_hash($object);
+ }
+ return $objects;
}
###############################
#### Accessors ######
###############################
-sub id { return $_[0]->{$_[0]->ID_FIELD}; }
+sub id { return $_[0]->{$_[0]->ID_FIELD}; }
sub name { return $_[0]->{$_[0]->NAME_FIELD}; }
###############################
@@ -429,204 +425,214 @@ sub name { return $_[0]->{$_[0]->NAME_FIELD}; }
###############################
sub set {
- my ($self, $field, $value) = @_;
-
- # This method is protected. It's used to help implement set_ functions.
- my $caller = caller;
- $caller->isa('Bugzilla::Object') || $caller->isa('Bugzilla::Extension')
- || ThrowCodeError('protection_violation',
- { caller => caller,
- superclass => __PACKAGE__,
- function => 'Bugzilla::Object->set' });
-
- Bugzilla::Hook::process('object_before_set',
- { object => $self, field => $field,
- value => $value });
-
- my %validators = (%{$self->_get_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);
- }
+ my ($self, $field, $value) = @_;
+
+ # This method is protected. It's used to help implement set_ functions.
+ my $caller = caller;
+ $caller->isa('Bugzilla::Object')
+ || $caller->isa('Bugzilla::Extension')
+ || ThrowCodeError(
+ 'protection_violation',
+ {
+ caller => caller,
+ superclass => __PACKAGE__,
+ function => 'Bugzilla::Object->set'
}
+ );
- $self->{$field} = $value;
+ Bugzilla::Hook::process('object_before_set',
+ {object => $self, field => $field, value => $value});
- Bugzilla::Hook::process('object_end_of_set',
- { object => $self, field => $field });
+ my %validators = (%{$self->_get_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;
+
+ Bugzilla::Hook::process('object_end_of_set',
+ {object => $self, field => $field});
}
sub set_all {
- my ($self, $params) = @_;
-
- # Don't let setters modify the values in $params for the caller.
- my %field_values = %$params;
-
- my @sorted_names = $self->_sort_by_dep(keys %field_values);
-
- foreach my $key (@sorted_names) {
- # It's possible for one set_ method to delete a key from $params
- # for another set method, so if that's happened, we don't call the
- # other set method.
- next if !exists $field_values{$key};
- my $method = "set_$key";
- if (!$self->can($method)) {
- my $class = ref($self) || $self;
- ThrowCodeError("unknown_method", { method => "${class}::${method}" });
- }
- $self->$method($field_values{$key}, \%field_values);
+ my ($self, $params) = @_;
+
+ # Don't let setters modify the values in $params for the caller.
+ my %field_values = %$params;
+
+ my @sorted_names = $self->_sort_by_dep(keys %field_values);
+
+ foreach my $key (@sorted_names) {
+
+ # It's possible for one set_ method to delete a key from $params
+ # for another set method, so if that's happened, we don't call the
+ # other set method.
+ next if !exists $field_values{$key};
+ my $method = "set_$key";
+ if (!$self->can($method)) {
+ my $class = ref($self) || $self;
+ ThrowCodeError("unknown_method", {method => "${class}::${method}"});
}
- Bugzilla::Hook::process('object_end_of_set_all',
- { object => $self, params => \%field_values });
+ $self->$method($field_values{$key}, \%field_values);
+ }
+ Bugzilla::Hook::process('object_end_of_set_all',
+ {object => $self, params => \%field_values});
}
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 @all_columns = $self->UPDATE_COLUMNS;
- my @hook_columns;
- Bugzilla::Hook::process('object_update_columns',
- { object => $self, columns => \@hook_columns });
- push(@all_columns, @hook_columns);
-
- my %numeric = map { $_ => 1 } $self->NUMERIC_COLUMNS;
- my %date = map { $_ => 1 } $self->DATE_COLUMNS;
- my (@update_columns, @values, %changes);
- foreach my $column (@all_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;
- }
+ my $self = shift;
- 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 $dbh = Bugzilla->dbh;
+ my $table = $self->DB_TABLE;
+ my $id_field = $self->ID_FIELD;
- my $columns = join(', ', map {"$_ = ?"} @update_columns);
+ $dbh->bz_start_transaction();
- $dbh->do("UPDATE $table SET $columns WHERE $id_field = ?", undef,
- @values, $self->id) if @values;
+ my $old_self = $self->new($self->id);
- Bugzilla::Hook::process('object_end_of_update',
- { object => $self, old_object => $old_self,
- changes => \%changes });
+ my @all_columns = $self->UPDATE_COLUMNS;
+ my @hook_columns;
+ Bugzilla::Hook::process('object_update_columns',
+ {object => $self, columns => \@hook_columns});
+ push(@all_columns, @hook_columns);
- $self->audit_log(\%changes) if $self->AUDIT_UPDATES;
+ my %numeric = map { $_ => 1 } $self->NUMERIC_COLUMNS;
+ my %date = map { $_ => 1 } $self->DATE_COLUMNS;
+ my (@update_columns, @values, %changes);
+ foreach my $column (@all_columns) {
+ my ($old, $new) = ($old_self->{$column}, $self->{$column});
- $dbh->bz_commit_transaction();
- if ($self->USE_MEMCACHED && @values) {
- Bugzilla->memcached->clear({ table => $table, id => $self->id });
- Bugzilla->memcached->clear_config()
- if $self->IS_CONFIG;
+ # 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;
}
- $self->_object_cache_remove({ id => $self->id });
- $self->_object_cache_remove({ name => $self->name }) if $self->name;
-
- if (wantarray) {
- return (\%changes, $old_self);
+ elsif (($numeric{$column} && $old == $new)
+ || ($date{$column} && str2time($old) == str2time($new))
+ || $old eq $new)
+ {
+ next;
}
- return \%changes;
+ 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;
+
+ Bugzilla::Hook::process('object_end_of_update',
+ {object => $self, old_object => $old_self, changes => \%changes});
+
+ $self->audit_log(\%changes) if $self->AUDIT_UPDATES;
+
+ $dbh->bz_commit_transaction();
+ if ($self->USE_MEMCACHED && @values) {
+ Bugzilla->memcached->clear({table => $table, id => $self->id});
+ Bugzilla->memcached->clear_config() if $self->IS_CONFIG;
+ }
+ $self->_object_cache_remove({id => $self->id});
+ $self->_object_cache_remove({name => $self->name}) if $self->name;
+
+ if (wantarray) {
+ return (\%changes, $old_self);
+ }
+
+ return \%changes;
}
sub remove_from_db {
- my $self = shift;
- Bugzilla::Hook::process('object_before_delete', { object => $self });
- my $table = $self->DB_TABLE;
- my $id_field = $self->ID_FIELD;
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- $self->audit_log(AUDIT_REMOVE) if $self->AUDIT_REMOVES;
- $dbh->do("DELETE FROM $table WHERE $id_field = ?", undef, $self->id);
- $dbh->bz_commit_transaction();
- if ($self->USE_MEMCACHED) {
- Bugzilla->memcached->clear({ table => $table, id => $self->id });
- Bugzilla->memcached->clear_config()
- if $self->IS_CONFIG;
- }
- $self->_object_cache_remove({ id => $self->id });
- $self->_object_cache_remove({ name => $self->name }) if $self->name;
- undef $self;
+ my $self = shift;
+ Bugzilla::Hook::process('object_before_delete', {object => $self});
+ my $table = $self->DB_TABLE;
+ my $id_field = $self->ID_FIELD;
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ $self->audit_log(AUDIT_REMOVE) if $self->AUDIT_REMOVES;
+ $dbh->do("DELETE FROM $table WHERE $id_field = ?", undef, $self->id);
+ $dbh->bz_commit_transaction();
+
+ if ($self->USE_MEMCACHED) {
+ Bugzilla->memcached->clear({table => $table, id => $self->id});
+ Bugzilla->memcached->clear_config() if $self->IS_CONFIG;
+ }
+ $self->_object_cache_remove({id => $self->id});
+ $self->_object_cache_remove({name => $self->name}) if $self->name;
+ undef $self;
}
sub audit_log {
- my ($self, $changes) = @_;
- my $class = ref $self;
- my $dbh = Bugzilla->dbh;
- my $user_id = Bugzilla->user->id || undef;
- my $sth = $dbh->prepare(
- 'INSERT INTO audit_log (user_id, class, object_id, field,
+ my ($self, $changes) = @_;
+ my $class = ref $self;
+ my $dbh = Bugzilla->dbh;
+ my $user_id = Bugzilla->user->id || undef;
+ my $sth = $dbh->prepare(
+ 'INSERT INTO audit_log (user_id, class, object_id, field,
removed, added, at_time)
- VALUES (?,?,?,?,?,?,LOCALTIMESTAMP(0))');
- # During creation or removal, $changes is actually just a string
- # indicating whether we're creating or removing the object.
- if ($changes eq AUDIT_CREATE or $changes eq AUDIT_REMOVE) {
- # We put the object's name in the "added" or "removed" field.
- # We do this thing with NAME_FIELD because $self->name returns
- # the wrong thing for Bugzilla::User.
- my $name = $self->{$self->NAME_FIELD};
- my @added_removed = $changes eq AUDIT_CREATE ? (undef, $name)
- : ($name, undef);
- $sth->execute($user_id, $class, $self->id, $changes, @added_removed);
- return;
- }
-
- # During update, it's the actual %changes hash produced by update().
- foreach my $field (keys %$changes) {
- # Skip private changes.
- next if $field =~ /^_/;
- my ($from, $to) = $self->_sanitize_audit_log($field, $changes->{$field});
- $sth->execute($user_id, $class, $self->id, $field, $from, $to);
- }
+ VALUES (?,?,?,?,?,?,LOCALTIMESTAMP(0))'
+ );
+
+ # During creation or removal, $changes is actually just a string
+ # indicating whether we're creating or removing the object.
+ if ($changes eq AUDIT_CREATE or $changes eq AUDIT_REMOVE) {
+
+ # We put the object's name in the "added" or "removed" field.
+ # We do this thing with NAME_FIELD because $self->name returns
+ # the wrong thing for Bugzilla::User.
+ my $name = $self->{$self->NAME_FIELD};
+ my @added_removed = $changes eq AUDIT_CREATE ? (undef, $name) : ($name, undef);
+ $sth->execute($user_id, $class, $self->id, $changes, @added_removed);
+ return;
+ }
+
+ # During update, it's the actual %changes hash produced by update().
+ foreach my $field (keys %$changes) {
+
+ # Skip private changes.
+ next if $field =~ /^_/;
+ my ($from, $to) = $self->_sanitize_audit_log($field, $changes->{$field});
+ $sth->execute($user_id, $class, $self->id, $field, $from, $to);
+ }
}
sub _sanitize_audit_log {
- my ($self, $field, $changes) = @_;
- my $class = ref($self) || $self;
-
- # Do not store hashed passwords. Only record the algorithm used to encode them.
- if ($class eq 'Bugzilla::User' && $field eq 'cryptpassword') {
- foreach my $passwd (@$changes) {
- next unless $passwd;
- my $algorithm = 'unknown_algorithm';
- if ($passwd =~ /{([^}]+)}$/) {
- $algorithm = $1;
- }
- $passwd = "hashed_with_$algorithm";
- }
+ my ($self, $field, $changes) = @_;
+ my $class = ref($self) || $self;
+
+ # Do not store hashed passwords. Only record the algorithm used to encode them.
+ if ($class eq 'Bugzilla::User' && $field eq 'cryptpassword') {
+ foreach my $passwd (@$changes) {
+ next unless $passwd;
+ my $algorithm = 'unknown_algorithm';
+ if ($passwd =~ /{([^}]+)}$/) {
+ $algorithm = $1;
+ }
+ $passwd = "hashed_with_$algorithm";
}
- return @$changes;
+ }
+ return @$changes;
}
sub flatten_to_hash {
- my $self = shift;
- my $class = blessed($self);
- my %hash = map { $_ => $self->{$_} } $class->_serialisation_keys;
- return \%hash;
+ my $self = shift;
+ my $class = blessed($self);
+ my %hash = map { $_ => $self->{$_} } $class->_serialisation_keys;
+ return \%hash;
}
###############################
@@ -634,127 +640,125 @@ sub flatten_to_hash {
###############################
sub any_exist {
- my $class = shift;
- my $table = $class->DB_TABLE;
- my $dbh = Bugzilla->dbh;
- my $any_exist = $dbh->selectrow_array(
- "SELECT 1 FROM $table " . $dbh->sql_limit(1));
- return $any_exist ? 1 : 0;
+ my $class = shift;
+ my $table = $class->DB_TABLE;
+ my $dbh = Bugzilla->dbh;
+ my $any_exist
+ = $dbh->selectrow_array("SELECT 1 FROM $table " . $dbh->sql_limit(1));
+ return $any_exist ? 1 : 0;
}
sub create {
- my ($class, $params) = @_;
- my $dbh = Bugzilla->dbh;
+ 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();
+ $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();
- if (Bugzilla->memcached->enabled
- && $class->USE_MEMCACHED
- && $class->IS_CONFIG)
- {
- Bugzilla->memcached->clear_config();
- }
+ if (Bugzilla->memcached->enabled && $class->USE_MEMCACHED && $class->IS_CONFIG)
+ {
+ Bugzilla->memcached->clear_config();
+ }
- return $object;
+ return $object;
}
# Used to validate that a field name is in fact a valid column in the
# current table before inserting it into SQL.
sub _check_field {
- my ($invocant, $field, $function) = @_;
- my $class = ref($invocant) || $invocant;
- if (!Bugzilla->dbh->bz_column_info($class->DB_TABLE, $field)) {
- ThrowCodeError('param_invalid', { param => $field,
- function => "${class}::$function" });
- }
+ my ($invocant, $field, $function) = @_;
+ my $class = ref($invocant) || $invocant;
+ if (!Bugzilla->dbh->bz_column_info($class->DB_TABLE, $field)) {
+ ThrowCodeError('param_invalid',
+ {param => $field, function => "${class}::$function"});
+ }
}
sub check_required_create_fields {
- my ($class, $params) = @_;
+ my ($class, $params) = @_;
- # This hook happens here so that even subclasses that don't call
- # SUPER::create are still affected by the hook.
- Bugzilla::Hook::process('object_before_create', { class => $class,
- params => $params });
+ # This hook happens here so that even subclasses that don't call
+ # SUPER::create are still affected by the hook.
+ Bugzilla::Hook::process('object_before_create',
+ {class => $class, params => $params});
- my @check_fields = $class->_required_create_fields();
- foreach my $field (@check_fields) {
- $params->{$field} = undef if !exists $params->{$field};
- }
+ my @check_fields = $class->_required_create_fields();
+ foreach my $field (@check_fields) {
+ $params->{$field} = undef if !exists $params->{$field};
+ }
}
sub run_create_validators {
- my ($class, $params, $options) = @_;
+ my ($class, $params, $options) = @_;
- my $validators = $class->_get_validators;
- my %field_values = %$params;
+ my $validators = $class->_get_validators;
+ my %field_values = %$params;
- # Make a hash skiplist for easier searching later
- my %skip_list = map { $_ => 1 } @{ $options->{skip} || [] };
+ # Make a hash skiplist for easier searching later
+ my %skip_list = map { $_ => 1 } @{$options->{skip} || []};
- # Get the sorted field names
- my @sorted_names = $class->_sort_by_dep(keys %field_values);
+ # Get the sorted field names
+ my @sorted_names = $class->_sort_by_dep(keys %field_values);
- # Remove the skipped names
- my @unskipped = grep { !$skip_list{$_} } @sorted_names;
+ # Remove the skipped names
+ my @unskipped = grep { !$skip_list{$_} } @sorted_names;
- foreach my $field (@unskipped) {
- my $value;
- if (exists $validators->{$field}) {
- my $validator = $validators->{$field};
- $value = $class->$validator($field_values{$field}, $field,
- \%field_values);
- }
- else {
- $value = $field_values{$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;
+ foreach my $field (@unskipped) {
+ my $value;
+ if (exists $validators->{$field}) {
+ my $validator = $validators->{$field};
+ $value = $class->$validator($field_values{$field}, $field, \%field_values);
}
-
- Bugzilla::Hook::process('object_end_of_create_validators',
- { class => $class, params => \%field_values });
-
- 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) {
- $class->_check_field($field, 'create');
- push(@field_names, $field);
- push(@values, $value);
+ else {
+ $value = $field_values{$field};
}
- 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);
+ # 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;
+ }
- my $object = $class->new($id);
+ Bugzilla::Hook::process('object_end_of_create_validators',
+ {class => $class, params => \%field_values});
- Bugzilla::Hook::process('object_end_of_create', { class => $class,
- object => $object });
- $object->audit_log(AUDIT_CREATE) if $object->AUDIT_CREATES;
+ return \%field_values;
+}
- return $object;
+sub insert_create_data {
+ my ($class, $field_values) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my (@field_names, @values);
+ while (my ($field, $value) = each %$field_values) {
+ $class->_check_field($field, 'create');
+ 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);
+
+ my $object = $class->new($id);
+
+ Bugzilla::Hook::process('object_end_of_create',
+ {class => $class, object => $object});
+ $object->audit_log(AUDIT_CREATE) if $object->AUDIT_CREATES;
+
+ return $object;
}
sub get_all {
- my $class = shift;
- return @{ $class->_do_list_select() };
+ my $class = shift;
+ return @{$class->_do_list_select()};
}
###############################
@@ -764,20 +768,19 @@ sub get_all {
sub check_boolean { return $_[1] ? 1 : 0 }
sub check_time {
- my ($invocant, $value, $field, $params, $allow_negative) = @_;
+ my ($invocant, $value, $field, $params, $allow_negative) = @_;
- # If we don't have a current value default to zero
- my $current = blessed($invocant) ? $invocant->{$field}
- : 0;
- $current ||= 0;
+ # If we don't have a current value default to zero
+ my $current = blessed($invocant) ? $invocant->{$field} : 0;
+ $current ||= 0;
- # Get the new value or zero if it isn't defined
- $value = trim($value) || 0;
+ # Get the new value or zero if it isn't defined
+ $value = trim($value) || 0;
- # Make sure the new value is well formed
- _validate_time($value, $field, $allow_negative);
+ # Make sure the new value is well formed
+ _validate_time($value, $field, $allow_negative);
- return $value;
+ return $value;
}
@@ -786,26 +789,25 @@ sub check_time {
###################
sub _validate_time {
- my ($time, $field, $allow_negative) = @_;
-
- # regexp verifies one or more digits, optionally followed by a period and
- # zero or more digits, OR we have a period followed by one or more digits
- # (allow negatives, though, so people can back out errors in time reporting)
- if ($time !~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) {
- ThrowUserError("number_not_numeric",
- {field => $field, num => "$time"});
- }
-
- # Callers can optionally allow negative times
- if ( ($time < 0) && !$allow_negative ) {
- ThrowUserError("number_too_small",
- {field => $field, num => "$time", min_num => "0"});
- }
-
- if ($time > 99999.99) {
- ThrowUserError("number_too_large",
- {field => $field, num => "$time", max_num => "99999.99"});
- }
+ my ($time, $field, $allow_negative) = @_;
+
+ # regexp verifies one or more digits, optionally followed by a period and
+ # zero or more digits, OR we have a period followed by one or more digits
+ # (allow negatives, though, so people can back out errors in time reporting)
+ if ($time !~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) {
+ ThrowUserError("number_not_numeric", {field => $field, num => "$time"});
+ }
+
+ # Callers can optionally allow negative times
+ if (($time < 0) && !$allow_negative) {
+ ThrowUserError("number_too_small",
+ {field => $field, num => "$time", min_num => "0"});
+ }
+
+ if ($time > 99999.99) {
+ ThrowUserError("number_too_large",
+ {field => $field, num => "$time", max_num => "99999.99"});
+ }
}
# Sorts fields according to VALIDATOR_DEPENDENCIES. This is not a
@@ -813,54 +815,55 @@ sub _validate_time {
# *have* to be in the list--it just has to be earlier than its dependent
# if it *is* in the list.
sub _sort_by_dep {
- my ($invocant, @fields) = @_;
-
- my $dependencies = $invocant->VALIDATOR_DEPENDENCIES;
- my ($has_deps, $no_deps) = part { $dependencies->{$_} ? 0 : 1 } @fields;
-
- # For fields with no dependencies, we sort them alphabetically,
- # so that validation always happens in a consistent order.
- # Fields with no dependencies come at the start of the list.
- my @result = sort @{ $no_deps || [] };
-
- # Fields with dependencies all go at the end of the list, and if
- # they have dependencies on *each other*, then they have to be
- # sorted properly. We go through $has_deps in sorted order to be
- # sure that fields always validate in a consistent order.
- foreach my $field (sort @{ $has_deps || [] }) {
- if (!grep { $_ eq $field } @result) {
- _insert_dep_field($field, $has_deps, $dependencies, \@result);
- }
+ my ($invocant, @fields) = @_;
+
+ my $dependencies = $invocant->VALIDATOR_DEPENDENCIES;
+ my ($has_deps, $no_deps) = part { $dependencies->{$_} ? 0 : 1 } @fields;
+
+ # For fields with no dependencies, we sort them alphabetically,
+ # so that validation always happens in a consistent order.
+ # Fields with no dependencies come at the start of the list.
+ my @result = sort @{$no_deps || []};
+
+ # Fields with dependencies all go at the end of the list, and if
+ # they have dependencies on *each other*, then they have to be
+ # sorted properly. We go through $has_deps in sorted order to be
+ # sure that fields always validate in a consistent order.
+ foreach my $field (sort @{$has_deps || []}) {
+ if (!grep { $_ eq $field } @result) {
+ _insert_dep_field($field, $has_deps, $dependencies, \@result);
}
- return @result;
+ }
+ return @result;
}
sub _insert_dep_field {
- my ($field, $insert_me, $dependencies, $result, $loop_tracking) = @_;
+ my ($field, $insert_me, $dependencies, $result, $loop_tracking) = @_;
- if ($loop_tracking->{$field}) {
- ThrowCodeError('object_dep_sort_loop',
- { field => $field,
- considered => [keys %$loop_tracking] });
- }
- $loop_tracking->{$field} = 1;
-
- my $required_fields = $dependencies->{$field};
- # Imagine Field A requires field B...
- foreach my $required_field (@$required_fields) {
- # If our dependency is already satisfied, we're good.
- next if grep { $_ eq $required_field } @$result;
-
- # If our dependency is not in the remaining fields to insert,
- # then we're also OK.
- next if !grep { $_ eq $required_field } @$insert_me;
-
- # So, at this point, we know that Field B is in $insert_me.
- # So let's put the required field into the result.
- _insert_dep_field($required_field, $insert_me, $dependencies,
- $result, $loop_tracking);
- }
- push(@$result, $field);
+ if ($loop_tracking->{$field}) {
+ ThrowCodeError('object_dep_sort_loop',
+ {field => $field, considered => [keys %$loop_tracking]});
+ }
+ $loop_tracking->{$field} = 1;
+
+ my $required_fields = $dependencies->{$field};
+
+ # Imagine Field A requires field B...
+ foreach my $required_field (@$required_fields) {
+
+ # If our dependency is already satisfied, we're good.
+ next if grep { $_ eq $required_field } @$result;
+
+ # If our dependency is not in the remaining fields to insert,
+ # then we're also OK.
+ next if !grep { $_ eq $required_field } @$insert_me;
+
+ # So, at this point, we know that Field B is in $insert_me.
+ # So let's put the required field into the result.
+ _insert_dep_field($required_field, $insert_me, $dependencies, $result,
+ $loop_tracking);
+ }
+ push(@$result, $field);
}
####################
@@ -873,61 +876,67 @@ sub _insert_dep_field {
# page.
sub _get_db_columns {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $cache = Bugzilla->request_cache;
- my $cache_key = "object_${class}_db_columns";
- return @{ $cache->{$cache_key} } if $cache->{$cache_key};
- # Currently you can only add new columns using object_columns, not
- # remove or modify existing columns, because removing columns would
- # almost certainly cause Bugzilla to function improperly.
- my @add_columns;
- Bugzilla::Hook::process('object_columns',
- { class => $class, columns => \@add_columns });
- my @columns = ($invocant->DB_COLUMNS, @add_columns);
- $cache->{$cache_key} = \@columns;
- return @{ $cache->{$cache_key} };
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $cache = Bugzilla->request_cache;
+ my $cache_key = "object_${class}_db_columns";
+ return @{$cache->{$cache_key}} if $cache->{$cache_key};
+
+ # Currently you can only add new columns using object_columns, not
+ # remove or modify existing columns, because removing columns would
+ # almost certainly cause Bugzilla to function improperly.
+ my @add_columns;
+ Bugzilla::Hook::process('object_columns',
+ {class => $class, columns => \@add_columns});
+ my @columns = ($invocant->DB_COLUMNS, @add_columns);
+ $cache->{$cache_key} = \@columns;
+ return @{$cache->{$cache_key}};
}
# This method is private and should only be called by Bugzilla::Object.
sub _get_validators {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $cache = Bugzilla->request_cache;
- my $cache_key = "object_${class}_validators";
- return $cache->{$cache_key} if $cache->{$cache_key};
- # We copy this into a hash so that the hook doesn't modify the constant.
- # (That could be bad in mod_perl.)
- my %validators = %{ $invocant->VALIDATORS };
- Bugzilla::Hook::process('object_validators',
- { class => $class, validators => \%validators });
- $cache->{$cache_key} = \%validators;
- return $cache->{$cache_key};
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $cache = Bugzilla->request_cache;
+ my $cache_key = "object_${class}_validators";
+ return $cache->{$cache_key} if $cache->{$cache_key};
+
+ # We copy this into a hash so that the hook doesn't modify the constant.
+ # (That could be bad in mod_perl.)
+ my %validators = %{$invocant->VALIDATORS};
+ Bugzilla::Hook::process('object_validators',
+ {class => $class, validators => \%validators});
+ $cache->{$cache_key} = \%validators;
+ return $cache->{$cache_key};
}
# These are all the fields that need to be checked, always, when
# calling create(), because they have no DEFAULT and they are marked
# NOT NULL.
sub _required_create_fields {
- my $class = shift;
- my $dbh = Bugzilla->dbh;
- my $table = $class->DB_TABLE;
-
- my @columns = $dbh->bz_table_columns($table);
- my @required;
- foreach my $column (@columns) {
- my $def = $dbh->bz_column_info($table, $column);
- if ($def->{NOTNULL} and !defined $def->{DEFAULT}
- # SERIAL fields effectively have a DEFAULT, but they're not
- # listed as having a DEFAULT in DB::Schema.
- and $def->{TYPE} !~ /serial/i)
- {
- my $field = $class->REQUIRED_FIELD_MAP->{$column} || $column;
- push(@required, $field);
- }
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+ my $table = $class->DB_TABLE;
+
+ my @columns = $dbh->bz_table_columns($table);
+ my @required;
+ foreach my $column (@columns) {
+ my $def = $dbh->bz_column_info($table, $column);
+ if (
+ $def->{NOTNULL}
+ and !defined $def->{DEFAULT}
+
+ # SERIAL fields effectively have a DEFAULT, but they're not
+ # listed as having a DEFAULT in DB::Schema.
+ and $def->{TYPE} !~ /serial/i
+ )
+ {
+ my $field = $class->REQUIRED_FIELD_MAP->{$column} || $column;
+ push(@required, $field);
}
- push(@required, $class->EXTRA_REQUIRED_FIELDS);
- return @required;
+ }
+ push(@required, $class->EXTRA_REQUIRED_FIELDS);
+ return @required;
}
1;
diff --git a/Bugzilla/Product.pm b/Bugzilla/Product.pm
index 0c0cb458d..09c2c5922 100644
--- a/Bugzilla/Product.pm
+++ b/Bugzilla/Product.pm
@@ -39,32 +39,32 @@ use constant IS_CONFIG => 1;
use constant DB_TABLE => 'products';
use constant DB_COLUMNS => qw(
- id
- name
- classification_id
- description
- isactive
- defaultmilestone
- allows_unconfirmed
+ id
+ name
+ classification_id
+ description
+ isactive
+ defaultmilestone
+ allows_unconfirmed
);
use constant UPDATE_COLUMNS => qw(
- name
- description
- defaultmilestone
- isactive
- allows_unconfirmed
+ name
+ description
+ defaultmilestone
+ isactive
+ allows_unconfirmed
);
use constant VALIDATORS => {
- allows_unconfirmed => \&Bugzilla::Object::check_boolean,
- classification => \&_check_classification,
- name => \&_check_name,
- description => \&_check_description,
- version => \&_check_version,
- defaultmilestone => \&_check_default_milestone,
- isactive => \&Bugzilla::Object::check_boolean,
- create_series => \&Bugzilla::Object::check_boolean
+ allows_unconfirmed => \&Bugzilla::Object::check_boolean,
+ classification => \&_check_classification,
+ name => \&_check_name,
+ description => \&_check_description,
+ version => \&_check_version,
+ defaultmilestone => \&_check_default_milestone,
+ isactive => \&Bugzilla::Object::check_boolean,
+ create_series => \&Bugzilla::Object::check_boolean
};
###############################
@@ -72,258 +72,283 @@ use constant VALIDATORS => {
###############################
sub create {
- my $class = shift;
- my $dbh = Bugzilla->dbh;
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- $class->check_required_create_fields(@_);
+ $class->check_required_create_fields(@_);
- my $params = $class->run_create_validators(@_);
- # Some fields do not exist in the DB as is.
- if (defined $params->{classification}) {
- $params->{classification_id} = delete $params->{classification};
- }
- my $version = delete $params->{version};
- my $create_series = delete $params->{create_series};
+ my $params = $class->run_create_validators(@_);
+
+ # Some fields do not exist in the DB as is.
+ if (defined $params->{classification}) {
+ $params->{classification_id} = delete $params->{classification};
+ }
+ my $version = delete $params->{version};
+ my $create_series = delete $params->{create_series};
- my $product = $class->insert_create_data($params);
- Bugzilla->user->clear_product_cache();
+ my $product = $class->insert_create_data($params);
+ Bugzilla->user->clear_product_cache();
- # Add the new version and milestone into the DB as valid values.
- Bugzilla::Version->create({ value => $version, product => $product });
- Bugzilla::Milestone->create({ value => $product->default_milestone,
- product => $product });
+ # Add the new version and milestone into the DB as valid values.
+ Bugzilla::Version->create({value => $version, product => $product});
+ Bugzilla::Milestone->create(
+ {value => $product->default_milestone, product => $product});
- # Create groups and series for the new product, if requested.
- $product->_create_bug_group() if Bugzilla->params->{'makeproductgroups'};
- $product->_create_series() if $create_series;
+ # Create groups and series for the new product, if requested.
+ $product->_create_bug_group() if Bugzilla->params->{'makeproductgroups'};
+ $product->_create_series() if $create_series;
- Bugzilla::Hook::process('product_end_of_create', { product => $product });
+ Bugzilla::Hook::process('product_end_of_create', {product => $product});
- $dbh->bz_commit_transaction();
- Bugzilla->memcached->clear_config();
- return $product;
+ $dbh->bz_commit_transaction();
+ Bugzilla->memcached->clear_config();
+ return $product;
}
# 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, $preload_flagtypes) = @_;
- my %prods = map { $_->id => $_ } @$products;
- my @prod_ids = keys %prods;
- return unless @prod_ids;
-
- # We cannot |use| it due to a dependency loop with Bugzilla::User.
- require Bugzilla::Component;
- 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);
- }
- }
- if ($preload_flagtypes) {
- $_->flag_types foreach @$products;
+ my ($products, $preload_flagtypes) = @_;
+ my %prods = map { $_->id => $_ } @$products;
+ my @prod_ids = keys %prods;
+ return unless @prod_ids;
+
+ # We cannot |use| it due to a dependency loop with Bugzilla::User.
+ require Bugzilla::Component;
+ 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);
}
+ }
+ if ($preload_flagtypes) {
+ $_->flag_types foreach @$products;
+ }
}
sub update {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- # Don't update the DB if something goes wrong below -> transaction.
- $dbh->bz_start_transaction();
- my ($changes, $old_self) = $self->SUPER::update(@_);
-
- # Also update group settings.
- if ($self->{check_group_controls}) {
- require Bugzilla::Bug;
- import Bugzilla::Bug qw(LogActivityEntry);
-
- my $old_settings = $old_self->group_controls;
- my $new_settings = $self->group_controls;
- my $timestamp = $dbh->selectrow_array('SELECT NOW()');
-
- foreach my $gid (keys %$new_settings) {
- my $old_setting = $old_settings->{$gid} || {};
- my $new_setting = $new_settings->{$gid};
- # If all new settings are 0 for a given group, we delete the entry
- # from group_control_map, so we have to track it here.
- my $all_zero = 1;
- my @fields;
- my @values;
-
- foreach my $field ('entry', 'membercontrol', 'othercontrol', 'canedit',
- 'editcomponents', 'editbugs', 'canconfirm')
- {
- my $old_value = $old_setting->{$field};
- my $new_value = $new_setting->{$field};
- $all_zero = 0 if $new_value;
- next if (defined $old_value && $old_value == $new_value);
- push(@fields, $field);
- # The value has already been validated.
- detaint_natural($new_value);
- push(@values, $new_value);
- }
- # Is there anything to update?
- next unless scalar @fields;
-
- if ($all_zero) {
- $dbh->do('DELETE FROM group_control_map
- WHERE product_id = ? AND group_id = ?',
- undef, $self->id, $gid);
- }
- else {
- if (exists $old_setting->{group}) {
- # There is already an entry in the DB.
- my $set_fields = join(', ', map {"$_ = ?"} @fields);
- $dbh->do("UPDATE group_control_map SET $set_fields
- WHERE product_id = ? AND group_id = ?",
- undef, (@values, $self->id, $gid));
- }
- else {
- # No entry yet.
- my $fields = join(', ', @fields);
- # +2 because of the product and group IDs.
- my $qmarks = join(',', ('?') x (scalar @fields + 2));
- $dbh->do("INSERT INTO group_control_map (product_id, group_id, $fields)
- VALUES ($qmarks)", undef, ($self->id, $gid, @values));
- }
- }
-
- # If the group is mandatory, restrict all bugs to it.
- if ($new_setting->{membercontrol} == CONTROLMAPMANDATORY) {
- my $bug_ids =
- $dbh->selectcol_arrayref('SELECT bugs.bug_id
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ # Don't update the DB if something goes wrong below -> transaction.
+ $dbh->bz_start_transaction();
+ my ($changes, $old_self) = $self->SUPER::update(@_);
+
+ # Also update group settings.
+ if ($self->{check_group_controls}) {
+ require Bugzilla::Bug;
+ import Bugzilla::Bug qw(LogActivityEntry);
+
+ my $old_settings = $old_self->group_controls;
+ my $new_settings = $self->group_controls;
+ my $timestamp = $dbh->selectrow_array('SELECT NOW()');
+
+ foreach my $gid (keys %$new_settings) {
+ my $old_setting = $old_settings->{$gid} || {};
+ my $new_setting = $new_settings->{$gid};
+
+ # If all new settings are 0 for a given group, we delete the entry
+ # from group_control_map, so we have to track it here.
+ my $all_zero = 1;
+ my @fields;
+ my @values;
+
+ foreach my $field (
+ 'entry', 'membercontrol', 'othercontrol', 'canedit',
+ 'editcomponents', 'editbugs', 'canconfirm'
+ )
+ {
+ my $old_value = $old_setting->{$field};
+ my $new_value = $new_setting->{$field};
+ $all_zero = 0 if $new_value;
+ next if (defined $old_value && $old_value == $new_value);
+ push(@fields, $field);
+
+ # The value has already been validated.
+ detaint_natural($new_value);
+ push(@values, $new_value);
+ }
+
+ # Is there anything to update?
+ next unless scalar @fields;
+
+ if ($all_zero) {
+ $dbh->do(
+ 'DELETE FROM group_control_map
+ WHERE product_id = ? AND group_id = ?', undef, $self->id, $gid
+ );
+ }
+ else {
+ if (exists $old_setting->{group}) {
+
+ # There is already an entry in the DB.
+ my $set_fields = join(', ', map {"$_ = ?"} @fields);
+ $dbh->do(
+ "UPDATE group_control_map SET $set_fields
+ WHERE product_id = ? AND group_id = ?", undef,
+ (@values, $self->id, $gid)
+ );
+ }
+ else {
+ # No entry yet.
+ my $fields = join(', ', @fields);
+
+ # +2 because of the product and group IDs.
+ my $qmarks = join(',', ('?') x (scalar @fields + 2));
+ $dbh->do(
+ "INSERT INTO group_control_map (product_id, group_id, $fields)
+ VALUES ($qmarks)", undef, ($self->id, $gid, @values)
+ );
+ }
+ }
+
+ # If the group is mandatory, restrict all bugs to it.
+ if ($new_setting->{membercontrol} == CONTROLMAPMANDATORY) {
+ my $bug_ids = $dbh->selectcol_arrayref(
+ 'SELECT bugs.bug_id
FROM bugs
LEFT JOIN bug_group_map
ON bug_group_map.bug_id = bugs.bug_id
AND group_id = ?
WHERE product_id = ?
AND bug_group_map.bug_id IS NULL',
- undef, $gid, $self->id);
-
- if (scalar @$bug_ids) {
- 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);
- # Add this change to the bug history.
- LogActivityEntry($bug_id, 'bug_group', '',
- $new_setting->{group}->name,
- Bugzilla->user->id, $timestamp);
- }
- push(@{$changes->{'_group_controls'}->{'now_mandatory'}},
- {name => $new_setting->{group}->name,
- bug_count => scalar @$bug_ids});
- }
- }
- # If the group can no longer be used to restrict bugs, remove them.
- elsif ($new_setting->{membercontrol} == CONTROLMAPNA) {
- my $bug_ids =
- $dbh->selectcol_arrayref('SELECT bugs.bug_id
+ undef, $gid, $self->id
+ );
+
+ if (scalar @$bug_ids) {
+ 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);
+
+ # Add this change to the bug history.
+ LogActivityEntry($bug_id, 'bug_group', '', $new_setting->{group}->name,
+ Bugzilla->user->id, $timestamp);
+ }
+ push(
+ @{$changes->{'_group_controls'}->{'now_mandatory'}},
+ {name => $new_setting->{group}->name, bug_count => scalar @$bug_ids}
+ );
+ }
+ }
+
+ # If the group can no longer be used to restrict bugs, remove them.
+ elsif ($new_setting->{membercontrol} == CONTROLMAPNA) {
+ my $bug_ids = $dbh->selectcol_arrayref(
+ 'SELECT bugs.bug_id
FROM bugs
INNER JOIN bug_group_map
ON bug_group_map.bug_id = bugs.bug_id
WHERE product_id = ? AND group_id = ?',
- undef, $self->id, $gid);
-
- if (scalar @$bug_ids) {
- $dbh->do('DELETE FROM bug_group_map WHERE group_id = ? AND ' .
- $dbh->sql_in('bug_id', $bug_ids), undef, $gid);
-
- # Add this change to the bug history.
- foreach my $bug_id (@$bug_ids) {
- LogActivityEntry($bug_id, 'bug_group',
- $old_setting->{group}->name, '',
- Bugzilla->user->id, $timestamp);
- }
- push(@{$changes->{'_group_controls'}->{'now_na'}},
- {name => $old_setting->{group}->name,
- bug_count => scalar @$bug_ids});
- }
- }
+ undef, $self->id, $gid
+ );
+
+ if (scalar @$bug_ids) {
+ $dbh->do(
+ 'DELETE FROM bug_group_map WHERE group_id = ? AND '
+ . $dbh->sql_in('bug_id', $bug_ids),
+ undef, $gid
+ );
+
+ # Add this change to the bug history.
+ foreach my $bug_id (@$bug_ids) {
+ LogActivityEntry($bug_id, 'bug_group', $old_setting->{group}->name,
+ '', Bugzilla->user->id, $timestamp);
+ }
+ push(
+ @{$changes->{'_group_controls'}->{'now_na'}},
+ {name => $old_setting->{group}->name, bug_count => scalar @$bug_ids}
+ );
}
-
- delete $self->{groups_available};
- delete $self->{groups_mandatory};
+ }
}
- $dbh->bz_commit_transaction();
- # Changes have been committed.
- delete $self->{check_group_controls};
- Bugzilla->user->clear_product_cache();
- Bugzilla->memcached->clear_config();
- return $changes;
+ delete $self->{groups_available};
+ delete $self->{groups_mandatory};
+ }
+ $dbh->bz_commit_transaction();
+
+ # Changes have been committed.
+ delete $self->{check_group_controls};
+ Bugzilla->user->clear_product_cache();
+ Bugzilla->memcached->clear_config();
+
+ return $changes;
}
sub remove_from_db {
- my ($self, $params) = @_;
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
-
- $dbh->bz_start_transaction();
-
- $self->_check_if_controller();
-
- if ($self->bug_count) {
- if (Bugzilla->params->{'allowbugdeletion'}) {
- require Bugzilla::Bug;
- foreach my $bug_id (@{$self->bug_ids}) {
- # Note that we allow the user to delete bugs they can't see,
- # which is okay, because they're deleting the whole Product.
- my $bug = new Bugzilla::Bug($bug_id);
- $bug->remove_from_db();
- }
- }
- else {
- ThrowUserError('product_has_bugs', { nb => $self->bug_count });
- }
+ my ($self, $params) = @_;
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+
+ $self->_check_if_controller();
+
+ if ($self->bug_count) {
+ if (Bugzilla->params->{'allowbugdeletion'}) {
+ require Bugzilla::Bug;
+ foreach my $bug_id (@{$self->bug_ids}) {
+
+ # Note that we allow the user to delete bugs they can't see,
+ # which is okay, because they're deleting the whole Product.
+ my $bug = new Bugzilla::Bug($bug_id);
+ $bug->remove_from_db();
+ }
}
+ else {
+ ThrowUserError('product_has_bugs', {nb => $self->bug_count});
+ }
+ }
- if ($params->{delete_series}) {
- my $series_ids =
- $dbh->selectcol_arrayref('SELECT series_id
+ if ($params->{delete_series}) {
+ my $series_ids = $dbh->selectcol_arrayref(
+ 'SELECT series_id
FROM series
INNER JOIN series_categories
ON series_categories.id = series.category
- WHERE series_categories.name = ?',
- undef, $self->name);
+ WHERE series_categories.name = ?', undef,
+ $self->name
+ );
- if (scalar @$series_ids) {
- $dbh->do('DELETE FROM series WHERE ' . $dbh->sql_in('series_id', $series_ids));
- }
+ if (scalar @$series_ids) {
+ $dbh->do('DELETE FROM series WHERE ' . $dbh->sql_in('series_id', $series_ids));
+ }
- # If no subcategory uses this product name, completely purge it.
- my $in_use =
- $dbh->selectrow_array('SELECT 1
+ # If no subcategory uses this product name, completely purge it.
+ my $in_use = $dbh->selectrow_array(
+ 'SELECT 1
FROM series
INNER JOIN series_categories
ON series_categories.id = series.subcategory
- WHERE series_categories.name = ? ' .
- $dbh->sql_limit(1),
- undef, $self->name);
- if (!$in_use) {
- $dbh->do('DELETE FROM series_categories WHERE name = ?', undef, $self->name);
- }
+ WHERE series_categories.name = ? '
+ . $dbh->sql_limit(1), undef, $self->name
+ );
+ if (!$in_use) {
+ $dbh->do('DELETE FROM series_categories WHERE name = ?', undef, $self->name);
}
+ }
- $self->SUPER::remove_from_db();
+ $self->SUPER::remove_from_db();
- $dbh->bz_commit_transaction();
- Bugzilla->memcached->clear_config();
+ $dbh->bz_commit_transaction();
+ Bugzilla->memcached->clear_config();
- # We have to delete these internal variables, else we get
- # the old lists of products and classifications again.
- delete $user->{selectable_products};
- delete $user->{selectable_classifications};
+ # We have to delete these internal variables, else we get
+ # the old lists of products and classifications again.
+ delete $user->{selectable_products};
+ delete $user->{selectable_classifications};
}
@@ -332,91 +357,94 @@ sub remove_from_db {
###############################
sub _check_classification {
- my ($invocant, $classification_name) = @_;
-
- my $classification_id = 1;
- if (Bugzilla->params->{'useclassification'}) {
- my $classification = Bugzilla::Classification->check($classification_name);
- $classification_id = $classification->id;
- }
- return $classification_id;
+ my ($invocant, $classification_name) = @_;
+
+ my $classification_id = 1;
+ if (Bugzilla->params->{'useclassification'}) {
+ my $classification = Bugzilla::Classification->check($classification_name);
+ $classification_id = $classification->id;
+ }
+ return $classification_id;
}
sub _check_name {
- my ($invocant, $name) = @_;
+ my ($invocant, $name) = @_;
- $name = trim($name);
- $name || ThrowUserError('product_blank_name');
+ $name = trim($name);
+ $name || ThrowUserError('product_blank_name');
- if (length($name) > MAX_PRODUCT_SIZE) {
- ThrowUserError('product_name_too_long', {'name' => $name});
- }
+ if (length($name) > MAX_PRODUCT_SIZE) {
+ ThrowUserError('product_name_too_long', {'name' => $name});
+ }
- my $product = new Bugzilla::Product({name => $name});
- if ($product && (!ref $invocant || $product->id != $invocant->id)) {
- # Check for exact case sensitive match:
- if ($product->name eq $name) {
- ThrowUserError('product_name_already_in_use', {'product' => $product->name});
- }
- else {
- ThrowUserError('product_name_diff_in_case', {'product' => $name,
- 'existing_product' => $product->name});
- }
+ my $product = new Bugzilla::Product({name => $name});
+ if ($product && (!ref $invocant || $product->id != $invocant->id)) {
+
+ # Check for exact case sensitive match:
+ if ($product->name eq $name) {
+ ThrowUserError('product_name_already_in_use', {'product' => $product->name});
}
- return $name;
+ else {
+ ThrowUserError('product_name_diff_in_case',
+ {'product' => $name, 'existing_product' => $product->name});
+ }
+ }
+ return $name;
}
sub _check_description {
- my ($invocant, $description) = @_;
+ my ($invocant, $description) = @_;
- $description = trim($description);
- $description || ThrowUserError('product_must_have_description');
- return $description;
+ $description = trim($description);
+ $description || ThrowUserError('product_must_have_description');
+ return $description;
}
sub _check_version {
- my ($invocant, $version) = @_;
+ my ($invocant, $version) = @_;
+
+ $version = trim($version);
+ $version || ThrowUserError('product_must_have_version');
- $version = trim($version);
- $version || ThrowUserError('product_must_have_version');
- # We will check the version length when Bugzilla::Version->create will do it.
- return $version;
+ # We will check the version length when Bugzilla::Version->create will do it.
+ return $version;
}
sub _check_default_milestone {
- my ($invocant, $milestone) = @_;
+ my ($invocant, $milestone) = @_;
- # Do nothing if target milestones are not in use.
- unless (Bugzilla->params->{'usetargetmilestone'}) {
- return (ref $invocant) ? $invocant->default_milestone : '---';
- }
+ # Do nothing if target milestones are not in use.
+ unless (Bugzilla->params->{'usetargetmilestone'}) {
+ return (ref $invocant) ? $invocant->default_milestone : '---';
+ }
- $milestone = trim($milestone);
+ $milestone = trim($milestone);
- if (ref $invocant) {
- # The default milestone must be one of the existing milestones.
- my $mil_obj = new Bugzilla::Milestone({name => $milestone, product => $invocant});
+ if (ref $invocant) {
- $mil_obj || ThrowUserError('product_must_define_defaultmilestone',
- {product => $invocant->name,
- milestone => $milestone});
- }
- else {
- $milestone ||= '---';
- }
- return $milestone;
+ # The default milestone must be one of the existing milestones.
+ my $mil_obj
+ = new Bugzilla::Milestone({name => $milestone, product => $invocant});
+
+ $mil_obj || ThrowUserError('product_must_define_defaultmilestone',
+ {product => $invocant->name, milestone => $milestone});
+ }
+ else {
+ $milestone ||= '---';
+ }
+ return $milestone;
}
sub _check_milestone_url {
- my ($invocant, $url) = @_;
+ my ($invocant, $url) = @_;
- # Do nothing if target milestones are not in use.
- unless (Bugzilla->params->{'usetargetmilestone'}) {
- return (ref $invocant) ? $invocant->milestone_url : '';
- }
+ # Do nothing if target milestones are not in use.
+ unless (Bugzilla->params->{'usetargetmilestone'}) {
+ return (ref $invocant) ? $invocant->milestone_url : '';
+ }
- $url = trim($url || '');
- return $url;
+ $url = trim($url || '');
+ return $url;
}
#####################################
@@ -431,393 +459,427 @@ use constant is_default => 0;
###############################
sub _create_bug_group {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- my $group_name = $self->name;
- while (new Bugzilla::Group({name => $group_name})) {
- $group_name .= '_';
- }
- my $group_description = get_text('bug_group_description', {product => $self});
+ my $group_name = $self->name;
+ while (new Bugzilla::Group({name => $group_name})) {
+ $group_name .= '_';
+ }
+ my $group_description = get_text('bug_group_description', {product => $self});
- my $group = Bugzilla::Group->create({name => $group_name,
- description => $group_description,
- isbuggroup => 1});
+ my $group = Bugzilla::Group->create(
+ {name => $group_name, description => $group_description, isbuggroup => 1});
- # Associate the new group and new product.
- $dbh->do('INSERT INTO group_control_map
+ # Associate the new group and new product.
+ $dbh->do(
+ 'INSERT INTO group_control_map
(group_id, product_id, membercontrol, othercontrol)
- VALUES (?, ?, ?, ?)',
- undef, ($group->id, $self->id, CONTROLMAPDEFAULT, CONTROLMAPNA));
+ VALUES (?, ?, ?, ?)', undef,
+ ($group->id, $self->id, CONTROLMAPDEFAULT, CONTROLMAPNA)
+ );
}
sub _create_series {
- my $self = shift;
-
- my @series;
- # We do every status, every resolution, and an "opened" one as well.
- foreach my $bug_status (@{get_legal_field_values('bug_status')}) {
- push(@series, [$bug_status, "bug_status=" . url_quote($bug_status)]);
- }
-
- foreach my $resolution (@{get_legal_field_values('resolution')}) {
- next if !$resolution;
- push(@series, [$resolution, "resolution=" . url_quote($resolution)]);
- }
-
- my @openedstatuses = BUG_STATE_OPEN;
- my $query = join("&", map { "bug_status=" . url_quote($_) } @openedstatuses);
- push(@series, [get_text('series_all_open'), $query]);
-
- foreach my $sdata (@series) {
- my $series = new Bugzilla::Series(undef, $self->name,
- get_text('series_subcategory'),
- $sdata->[0], Bugzilla->user->id, 1,
- $sdata->[1] . "&product=" . url_quote($self->name), 1);
- $series->writeToDatabase();
- }
+ my $self = shift;
+
+ my @series;
+
+ # We do every status, every resolution, and an "opened" one as well.
+ foreach my $bug_status (@{get_legal_field_values('bug_status')}) {
+ push(@series, [$bug_status, "bug_status=" . url_quote($bug_status)]);
+ }
+
+ foreach my $resolution (@{get_legal_field_values('resolution')}) {
+ next if !$resolution;
+ push(@series, [$resolution, "resolution=" . url_quote($resolution)]);
+ }
+
+ my @openedstatuses = BUG_STATE_OPEN;
+ my $query = join("&", map { "bug_status=" . url_quote($_) } @openedstatuses);
+ push(@series, [get_text('series_all_open'), $query]);
+
+ foreach my $sdata (@series) {
+ my $series
+ = new Bugzilla::Series(undef, $self->name, get_text('series_subcategory'),
+ $sdata->[0], Bugzilla->user->id, 1,
+ $sdata->[1] . "&product=" . url_quote($self->name), 1);
+ $series->writeToDatabase();
+ }
}
-sub set_name { $_[0]->set('name', $_[1]); }
-sub set_description { $_[0]->set('description', $_[1]); }
-sub set_default_milestone { $_[0]->set('defaultmilestone', $_[1]); }
-sub set_is_active { $_[0]->set('isactive', $_[1]); }
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_default_milestone { $_[0]->set('defaultmilestone', $_[1]); }
+sub set_is_active { $_[0]->set('isactive', $_[1]); }
sub set_allows_unconfirmed { $_[0]->set('allows_unconfirmed', $_[1]); }
sub set_group_controls {
- my ($self, $group, $settings) = @_;
-
- $group->is_active_bug_group
- || ThrowUserError('product_illegal_group', {group => $group});
-
- scalar(keys %$settings)
- || ThrowCodeError('product_empty_group_controls', {group => $group});
-
- # We store current settings for this group.
- my $gs = $self->group_controls->{$group->id};
- # If there is no entry for this group yet, create a default hash.
- unless (defined $gs) {
- $gs = { entry => 0,
- membercontrol => CONTROLMAPNA,
- othercontrol => CONTROLMAPNA,
- canedit => 0,
- editcomponents => 0,
- editbugs => 0,
- canconfirm => 0,
- group => $group };
- }
-
- # Both settings must be defined, or none of them can be updated.
- if (defined $settings->{membercontrol} && defined $settings->{othercontrol}) {
- # Legality of control combination is a function of
- # membercontrol\othercontrol
- # NA SH DE MA
- # NA + - - -
- # SH + + + +
- # DE + - + +
- # MA - - - +
- foreach my $field ('membercontrol', 'othercontrol') {
- my ($is_legal) = grep { $settings->{$field} == $_ }
- (CONTROLMAPNA, CONTROLMAPSHOWN, CONTROLMAPDEFAULT, CONTROLMAPMANDATORY);
- defined $is_legal || ThrowCodeError('product_illegal_group_control',
- { field => $field, value => $settings->{$field} });
- }
- unless ($settings->{membercontrol} == $settings->{othercontrol}
- || $settings->{membercontrol} == CONTROLMAPSHOWN
- || ($settings->{membercontrol} == CONTROLMAPDEFAULT
- && $settings->{othercontrol} != CONTROLMAPSHOWN))
- {
- ThrowUserError('illegal_group_control_combination', {groupname => $group->name});
- }
- $gs->{membercontrol} = $settings->{membercontrol};
- $gs->{othercontrol} = $settings->{othercontrol};
+ my ($self, $group, $settings) = @_;
+
+ $group->is_active_bug_group
+ || ThrowUserError('product_illegal_group', {group => $group});
+
+ scalar(keys %$settings)
+ || ThrowCodeError('product_empty_group_controls', {group => $group});
+
+ # We store current settings for this group.
+ my $gs = $self->group_controls->{$group->id};
+
+ # If there is no entry for this group yet, create a default hash.
+ unless (defined $gs) {
+ $gs = {
+ entry => 0,
+ membercontrol => CONTROLMAPNA,
+ othercontrol => CONTROLMAPNA,
+ canedit => 0,
+ editcomponents => 0,
+ editbugs => 0,
+ canconfirm => 0,
+ group => $group
+ };
+ }
+
+ # Both settings must be defined, or none of them can be updated.
+ if (defined $settings->{membercontrol} && defined $settings->{othercontrol}) {
+
+ # Legality of control combination is a function of
+ # membercontrol\othercontrol
+ # NA SH DE MA
+ # NA + - - -
+ # SH + + + +
+ # DE + - + +
+ # MA - - - +
+ foreach my $field ('membercontrol', 'othercontrol') {
+ my ($is_legal)
+ = grep { $settings->{$field} == $_ }
+ (CONTROLMAPNA, CONTROLMAPSHOWN, CONTROLMAPDEFAULT, CONTROLMAPMANDATORY);
+ defined $is_legal || ThrowCodeError('product_illegal_group_control',
+ {field => $field, value => $settings->{$field}});
}
-
- foreach my $field ('entry', 'canedit', 'editcomponents', 'editbugs', 'canconfirm') {
- next unless defined $settings->{$field};
- $gs->{$field} = $settings->{$field} ? 1 : 0;
+ unless (
+ $settings->{membercontrol} == $settings->{othercontrol}
+ || $settings->{membercontrol} == CONTROLMAPSHOWN
+ || ( $settings->{membercontrol} == CONTROLMAPDEFAULT
+ && $settings->{othercontrol} != CONTROLMAPSHOWN)
+ )
+ {
+ ThrowUserError('illegal_group_control_combination',
+ {groupname => $group->name});
}
- $self->{group_controls}->{$group->id} = $gs;
- $self->{check_group_controls} = 1;
+ $gs->{membercontrol} = $settings->{membercontrol};
+ $gs->{othercontrol} = $settings->{othercontrol};
+ }
+
+ foreach
+ my $field ('entry', 'canedit', 'editcomponents', 'editbugs', 'canconfirm')
+ {
+ next unless defined $settings->{$field};
+ $gs->{$field} = $settings->{$field} ? 1 : 0;
+ }
+ $self->{group_controls}->{$group->id} = $gs;
+ $self->{check_group_controls} = 1;
}
sub components {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!defined $self->{components}) {
- my $ids = $dbh->selectcol_arrayref(q{
+ if (!defined $self->{components}) {
+ my $ids = $dbh->selectcol_arrayref(
+ q{
SELECT id FROM components
WHERE product_id = ?
- ORDER BY name}, undef, $self->id);
+ ORDER BY name}, undef, $self->id
+ );
- require Bugzilla::Component;
- $self->{components} = Bugzilla::Component->new_from_list($ids);
- }
- return $self->{components};
+ require Bugzilla::Component;
+ $self->{components} = Bugzilla::Component->new_from_list($ids);
+ }
+ return $self->{components};
}
sub group_controls {
- my ($self, $full_data) = @_;
- my $dbh = Bugzilla->dbh;
-
- # By default, we don't return groups which are not listed in
- # group_control_map. If $full_data is true, then we also
- # return groups whose settings could be set for the product.
- my $where_or_and = 'WHERE';
- my $and_or_where = 'AND';
- if ($full_data) {
- $where_or_and = 'AND';
- $and_or_where = 'WHERE';
- }
-
- # If $full_data is true, we collect all the data in all cases,
- # even if the cache is already populated.
- # $full_data is never used except in the very special case where
- # all configurable bug groups are displayed to administrators,
- # so we don't care about collecting all the data again in this case.
- if (!defined $self->{group_controls} || $full_data) {
- # Include name to the list, to allow us sorting data more easily.
- my $query = qq{SELECT id, name, entry, membercontrol, othercontrol,
+ my ($self, $full_data) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # By default, we don't return groups which are not listed in
+ # group_control_map. If $full_data is true, then we also
+ # return groups whose settings could be set for the product.
+ my $where_or_and = 'WHERE';
+ my $and_or_where = 'AND';
+ if ($full_data) {
+ $where_or_and = 'AND';
+ $and_or_where = 'WHERE';
+ }
+
+ # If $full_data is true, we collect all the data in all cases,
+ # even if the cache is already populated.
+ # $full_data is never used except in the very special case where
+ # all configurable bug groups are displayed to administrators,
+ # so we don't care about collecting all the data again in this case.
+ if (!defined $self->{group_controls} || $full_data) {
+
+ # Include name to the list, to allow us sorting data more easily.
+ my $query = qq{SELECT id, name, entry, membercontrol, othercontrol,
canedit, editcomponents, editbugs, canconfirm
FROM groups
LEFT JOIN group_control_map
ON id = group_id
$where_or_and product_id = ?
$and_or_where isbuggroup = 1};
- $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;
- }
-
- # We never cache bug counts, for the same reason as above.
- if ($full_data) {
- my $counts =
- $dbh->selectall_arrayref('SELECT group_id, COUNT(bugs.bug_id) AS bug_count
+ $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;
+ }
+
+ # We never cache bug counts, for the same reason as above.
+ if ($full_data) {
+ my $counts = $dbh->selectall_arrayref(
+ 'SELECT group_id, COUNT(bugs.bug_id) AS bug_count
FROM bug_group_map
INNER JOIN bugs
ON bugs.bug_id = bug_group_map.bug_id
- WHERE bugs.product_id = ? ' .
- $dbh->sql_group_by('group_id'),
- {'Slice' => {}}, $self->id);
- foreach my $data (@$counts) {
- $self->{group_controls}->{$data->{group_id}}->{bug_count} = $data->{bug_count};
- }
+ WHERE bugs.product_id = ? '
+ . $dbh->sql_group_by('group_id'), {'Slice' => {}}, $self->id
+ );
+ foreach my $data (@$counts) {
+ $self->{group_controls}->{$data->{group_id}}->{bug_count} = $data->{bug_count};
}
- return $self->{group_controls};
+ }
+ return $self->{group_controls};
}
sub groups_available {
- my ($self) = @_;
- return $self->{groups_available} if defined $self->{groups_available};
- my $dbh = Bugzilla->dbh;
- my $shown = CONTROLMAPSHOWN;
- my $default = CONTROLMAPDEFAULT;
- my %member_groups = @{ $dbh->selectcol_arrayref(
- "SELECT group_id, membercontrol
+ my ($self) = @_;
+ return $self->{groups_available} if defined $self->{groups_available};
+ my $dbh = Bugzilla->dbh;
+ my $shown = CONTROLMAPSHOWN;
+ my $default = CONTROLMAPDEFAULT;
+ my %member_groups = @{
+ $dbh->selectcol_arrayref(
+ "SELECT group_id, membercontrol
FROM group_control_map
INNER JOIN groups ON group_control_map.group_id = groups.id
WHERE isbuggroup = 1 AND isactive = 1 AND product_id = ?
AND (membercontrol = $shown OR membercontrol = $default)
- AND " . Bugzilla->user->groups_in_sql(),
- {Columns=>[1,2]}, $self->id) };
- # We don't need to check the group membership here, because we only
- # add these groups to the list below if the group isn't already listed
- # for membercontrol.
- my %other_groups = @{ $dbh->selectcol_arrayref(
- "SELECT group_id, othercontrol
+ AND " . Bugzilla->user->groups_in_sql(), {Columns => [1, 2]},
+ $self->id
+ )
+ };
+
+ # We don't need to check the group membership here, because we only
+ # add these groups to the list below if the group isn't already listed
+ # for membercontrol.
+ my %other_groups = @{
+ $dbh->selectcol_arrayref(
+ "SELECT group_id, othercontrol
FROM group_control_map
INNER JOIN groups ON group_control_map.group_id = groups.id
WHERE isbuggroup = 1 AND isactive = 1 AND product_id = ?
- AND (othercontrol = $shown OR othercontrol = $default)",
- {Columns=>[1,2]}, $self->id) };
-
- # If the user is a member, then we use the membercontrol value.
- # Otherwise, we use the othercontrol value.
- my %all_groups = %member_groups;
- foreach my $id (keys %other_groups) {
- if (!defined $all_groups{$id}) {
- $all_groups{$id} = $other_groups{$id};
- }
+ AND (othercontrol = $shown OR othercontrol = $default)",
+ {Columns => [1, 2]}, $self->id
+ )
+ };
+
+ # If the user is a member, then we use the membercontrol value.
+ # Otherwise, we use the othercontrol value.
+ my %all_groups = %member_groups;
+ foreach my $id (keys %other_groups) {
+ if (!defined $all_groups{$id}) {
+ $all_groups{$id} = $other_groups{$id};
}
+ }
- my $available = Bugzilla::Group->new_from_list([keys %all_groups]);
- foreach my $group (@$available) {
- $group->{is_default} = 1 if $all_groups{$group->id} == $default;
- }
+ my $available = Bugzilla::Group->new_from_list([keys %all_groups]);
+ foreach my $group (@$available) {
+ $group->{is_default} = 1 if $all_groups{$group->id} == $default;
+ }
- $self->{groups_available} = $available;
- return $self->{groups_available};
+ $self->{groups_available} = $available;
+ return $self->{groups_available};
}
sub groups_mandatory {
- my ($self) = @_;
- return $self->{groups_mandatory} if $self->{groups_mandatory};
- my $groups = Bugzilla->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
+ my ($self) = @_;
+ return $self->{groups_mandatory} if $self->{groups_mandatory};
+ my $groups = Bugzilla->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
INNER JOIN groups ON group_control_map.group_id = groups.id
WHERE product_id = ? AND isactive = 1
AND (membercontrol = $mandatory
OR (othercontrol = $mandatory
- AND group_id NOT IN ($groups)))",
- undef, $self->id);
- $self->{groups_mandatory} = Bugzilla::Group->new_from_list($ids);
- return $self->{groups_mandatory};
+ AND group_id NOT IN ($groups)))", undef, $self->id
+ );
+ $self->{groups_mandatory} = Bugzilla::Group->new_from_list($ids);
+ return $self->{groups_mandatory};
}
# We don't just check groups_valid, because we want to know specifically
# if this group can be validly set by the currently-logged-in user.
sub group_is_settable {
- my ($self, $group) = @_;
+ my ($self, $group) = @_;
- return 0 unless ($group->is_active && $group->is_bug_group);
+ return 0 unless ($group->is_active && $group->is_bug_group);
- my $is_mandatory = grep { $group->id == $_->id }
- @{ $self->groups_mandatory };
- my $is_available = grep { $group->id == $_->id }
- @{ $self->groups_available };
- return ($is_mandatory or $is_available) ? 1 : 0;
+ my $is_mandatory = grep { $group->id == $_->id } @{$self->groups_mandatory};
+ my $is_available = grep { $group->id == $_->id } @{$self->groups_available};
+ return ($is_mandatory or $is_available) ? 1 : 0;
}
sub group_is_valid {
- my ($self, $group) = @_;
- return grep($_->id == $group->id, @{ $self->groups_valid }) ? 1 : 0;
+ my ($self, $group) = @_;
+ return grep($_->id == $group->id, @{$self->groups_valid}) ? 1 : 0;
}
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
+ 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};
+ 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;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!defined $self->{versions}) {
- my $ids = $dbh->selectcol_arrayref(q{
+ if (!defined $self->{versions}) {
+ my $ids = $dbh->selectcol_arrayref(
+ q{
SELECT id FROM versions
- WHERE product_id = ?}, undef, $self->id);
+ WHERE product_id = ?}, undef, $self->id
+ );
- $self->{versions} = Bugzilla::Version->new_from_list($ids);
- }
- return $self->{versions};
+ $self->{versions} = Bugzilla::Version->new_from_list($ids);
+ }
+ return $self->{versions};
}
sub milestones {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!defined $self->{milestones}) {
- my $ids = $dbh->selectcol_arrayref(q{
+ 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};
+ 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;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!defined $self->{'bug_count'}) {
- $self->{'bug_count'} = $dbh->selectrow_array(qq{
+ if (!defined $self->{'bug_count'}) {
+ $self->{'bug_count'} = $dbh->selectrow_array(
+ qq{
SELECT COUNT(bug_id) FROM bugs
- WHERE product_id = ?}, undef, $self->id);
+ WHERE product_id = ?}, undef, $self->id
+ );
- }
- return $self->{'bug_count'};
+ }
+ 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'};
+ 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) = @_;
+ my ($self, $user) = @_;
- return Bugzilla->dbh->selectrow_array(
- 'SELECT CASE WHEN group_id IS NULL THEN 1 ELSE 0 END
+ 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);
+ WHERE products.id = ? ' . Bugzilla->dbh->sql_limit(1), undef, $self->id
+ );
}
sub flag_types {
- my $self = shift;
-
- return $self->{'flag_types'} if defined $self->{'flag_types'};
-
- # We cache flag types to avoid useless calls to get_clusions().
- my $cache = Bugzilla->request_cache->{flag_types_per_product} ||= {};
- $self->{flag_types} = {};
- my $prod_id = $self->id;
- my $flagtypes = Bugzilla::FlagType::match({ product_id => $prod_id });
-
- foreach my $type ('bug', 'attachment') {
- my @flags = grep { $_->target_type eq $type } @$flagtypes;
- $self->{flag_types}->{$type} = \@flags;
-
- # Also populate component flag types, while we are here.
- foreach my $comp (@{$self->components}) {
- $comp->{flag_types} ||= {};
- my $comp_id = $comp->id;
-
- foreach my $flag (@flags) {
- my $flag_id = $flag->id;
- $cache->{$flag_id} ||= $flag;
- my $i = $cache->{$flag_id}->inclusions_as_hash;
- my $e = $cache->{$flag_id}->exclusions_as_hash;
- my $included = $i->{0}->{0} || $i->{0}->{$comp_id}
- || $i->{$prod_id}->{0} || $i->{$prod_id}->{$comp_id};
- my $excluded = $e->{0}->{0} || $e->{0}->{$comp_id}
- || $e->{$prod_id}->{0} || $e->{$prod_id}->{$comp_id};
- push(@{$comp->{flag_types}->{$type}}, $flag) if ($included && !$excluded);
- }
- }
+ my $self = shift;
+
+ return $self->{'flag_types'} if defined $self->{'flag_types'};
+
+ # We cache flag types to avoid useless calls to get_clusions().
+ my $cache = Bugzilla->request_cache->{flag_types_per_product} ||= {};
+ $self->{flag_types} = {};
+ my $prod_id = $self->id;
+ my $flagtypes = Bugzilla::FlagType::match({product_id => $prod_id});
+
+ foreach my $type ('bug', 'attachment') {
+ my @flags = grep { $_->target_type eq $type } @$flagtypes;
+ $self->{flag_types}->{$type} = \@flags;
+
+ # Also populate component flag types, while we are here.
+ foreach my $comp (@{$self->components}) {
+ $comp->{flag_types} ||= {};
+ my $comp_id = $comp->id;
+
+ foreach my $flag (@flags) {
+ my $flag_id = $flag->id;
+ $cache->{$flag_id} ||= $flag;
+ my $i = $cache->{$flag_id}->inclusions_as_hash;
+ my $e = $cache->{$flag_id}->exclusions_as_hash;
+ my $included
+ = $i->{0}->{0}
+ || $i->{0}->{$comp_id}
+ || $i->{$prod_id}->{0}
+ || $i->{$prod_id}->{$comp_id};
+ my $excluded
+ = $e->{0}->{0}
+ || $e->{0}->{$comp_id}
+ || $e->{$prod_id}->{0}
+ || $e->{$prod_id}->{$comp_id};
+ push(@{$comp->{flag_types}->{$type}}, $flag) if ($included && !$excluded);
+ }
}
- return $self->{'flag_types'};
+ }
+ return $self->{'flag_types'};
}
sub classification {
- my $self = shift;
- $self->{'classification'} ||=
- new Bugzilla::Classification({ id => $self->classification_id, cache => 1 });
- return $self->{'classification'};
+ my $self = shift;
+ $self->{'classification'} ||= new Bugzilla::Classification(
+ {id => $self->classification_id, cache => 1});
+ return $self->{'classification'};
}
###############################
@@ -825,29 +887,29 @@ sub classification {
###############################
sub allows_unconfirmed { return $_[0]->{'allows_unconfirmed'}; }
-sub description { return $_[0]->{'description'}; }
-sub is_active { return $_[0]->{'isactive'}; }
-sub default_milestone { return $_[0]->{'defaultmilestone'}; }
-sub classification_id { return $_[0]->{'classification_id'}; }
+sub description { return $_[0]->{'description'}; }
+sub is_active { return $_[0]->{'isactive'}; }
+sub default_milestone { return $_[0]->{'defaultmilestone'}; }
+sub classification_id { return $_[0]->{'classification_id'}; }
###############################
#### Subroutines ######
###############################
sub check {
- my ($class, $params) = @_;
- $params = { name => $params } if !ref $params;
- if (!$params->{allow_inaccessible}) {
- $params->{_error} = 'product_access_denied';
- }
- my $product = $class->SUPER::check($params);
-
- if (!$params->{allow_inaccessible}
- && !Bugzilla->user->can_access_product($product))
- {
- ThrowUserError('product_access_denied', $params);
- }
- return $product;
+ my ($class, $params) = @_;
+ $params = {name => $params} if !ref $params;
+ if (!$params->{allow_inaccessible}) {
+ $params->{_error} = 'product_access_denied';
+ }
+ my $product = $class->SUPER::check($params);
+
+ if ( !$params->{allow_inaccessible}
+ && !Bugzilla->user->can_access_product($product))
+ {
+ ThrowUserError('product_access_denied', $params);
+ }
+ return $product;
}
1;
diff --git a/Bugzilla/RNG.pm b/Bugzilla/RNG.pm
index 96e442fa0..b92cbd720 100644
--- a/Bugzilla/RNG.pm
+++ b/Bugzilla/RNG.pm
@@ -27,7 +27,7 @@ our @EXPORT_OK = qw(rand srand irand);
use constant DIVIDE_BY => 2**32;
# How many bytes of seed to read.
-use constant SEED_SIZE => 16; # 128 bits.
+use constant SEED_SIZE => 16; # 128 bits.
#################
# Windows Stuff #
@@ -42,10 +42,11 @@ use constant PROV_RSA_FULL => 1;
# Flags for CryptGenRandom:
# Don't ever display a UI to the user, just fail if one would be needed.
use constant CRYPT_SILENT => 64;
+
# Don't require existing public/private keypairs.
use constant CRYPT_VERIFYCONTEXT => 0xF0000000;
-# For some reason, BOOLEAN doesn't work properly as a return type with
+# For some reason, BOOLEAN doesn't work properly as a return type with
# Win32::API.
use constant RTLGENRANDOM_PROTO => <<END;
INT SystemFunction036(
@@ -59,40 +60,42 @@ END
#################
sub rand (;$) {
- my ($limit) = @_;
- my $int = irand();
- return _to_float($int, $limit);
+ my ($limit) = @_;
+ my $int = irand();
+ return _to_float($int, $limit);
}
sub irand (;$) {
- my ($limit) = @_;
- Bugzilla::RNG::srand() if !defined $RNG;
- my $int = $RNG->irand();
- if (defined $limit) {
- # We can't just use the mod operator because it will bias
- # our output. Search for "modulo bias" on the Internet for
- # details. This is slower than mod(), but does not have a bias,
- # as demonstrated by Math::Random::Secure's uniform.t test.
- return int(_to_float($int, $limit));
- }
- return $int;
+ my ($limit) = @_;
+ Bugzilla::RNG::srand() if !defined $RNG;
+ my $int = $RNG->irand();
+ if (defined $limit) {
+
+ # We can't just use the mod operator because it will bias
+ # our output. Search for "modulo bias" on the Internet for
+ # details. This is slower than mod(), but does not have a bias,
+ # as demonstrated by Math::Random::Secure's uniform.t test.
+ return int(_to_float($int, $limit));
+ }
+ return $int;
}
sub srand (;$) {
- my ($value) = @_;
- # Remove any RNG that might already have been made.
- $RNG = undef;
- my %args;
- if (defined $value) {
- $args{seed} = $value;
- }
- $RNG = _create_rng(\%args);
+ my ($value) = @_;
+
+ # Remove any RNG that might already have been made.
+ $RNG = undef;
+ my %args;
+ if (defined $value) {
+ $args{seed} = $value;
+ }
+ $RNG = _create_rng(\%args);
}
sub _to_float {
- my ($integer, $limit) = @_;
- $limit ||= 1;
- return ($integer / DIVIDE_BY) * $limit;
+ my ($integer, $limit) = @_;
+ $limit ||= 1;
+ return ($integer / DIVIDE_BY) * $limit;
}
##########################
@@ -100,123 +103,123 @@ sub _to_float {
##########################
sub _create_rng {
- my ($params) = @_;
+ my ($params) = @_;
- if (!defined $params->{seed}) {
- $params->{seed} = _get_seed();
- }
+ if (!defined $params->{seed}) {
+ $params->{seed} = _get_seed();
+ }
- _check_seed($params->{seed});
+ _check_seed($params->{seed});
- my @seed_ints = unpack('L*', $params->{seed});
+ my @seed_ints = unpack('L*', $params->{seed});
- my $rng = Math::Random::ISAAC->new(@seed_ints);
+ my $rng = Math::Random::ISAAC->new(@seed_ints);
- # It's faster to skip the frontend interface of Math::Random::ISAAC
- # and just use the backend directly. However, in case the internal
- # code of Math::Random::ISAAC changes at some point, we do make sure
- # that the {backend} element actually exists first.
- return $rng->{backend} ? $rng->{backend} : $rng;
+ # It's faster to skip the frontend interface of Math::Random::ISAAC
+ # and just use the backend directly. However, in case the internal
+ # code of Math::Random::ISAAC changes at some point, we do make sure
+ # that the {backend} element actually exists first.
+ return $rng->{backend} ? $rng->{backend} : $rng;
}
sub _check_seed {
- my ($seed) = @_;
- if (length($seed) < 8) {
- warn "Your seed is less than 8 bytes (64 bits). It could be"
- . " easy to crack";
- }
- # If it looks like we were seeded with a 32-bit integer, warn the
- # user that they are making a dangerous, easily-crackable mistake.
- elsif (length($seed) <= 10 and $seed =~ /^\d+$/) {
- warn "RNG seeded with a 32-bit integer, this is easy to crack";
- }
+ my ($seed) = @_;
+ if (length($seed) < 8) {
+ warn "Your seed is less than 8 bytes (64 bits). It could be" . " easy to crack";
+ }
+
+ # If it looks like we were seeded with a 32-bit integer, warn the
+ # user that they are making a dangerous, easily-crackable mistake.
+ elsif (length($seed) <= 10 and $seed =~ /^\d+$/) {
+ warn "RNG seeded with a 32-bit integer, this is easy to crack";
+ }
}
sub _get_seed {
- return _windows_seed() if ON_WINDOWS;
+ return _windows_seed() if ON_WINDOWS;
- if (-r '/dev/urandom') {
- return _read_seed_from('/dev/urandom');
- }
+ if (-r '/dev/urandom') {
+ return _read_seed_from('/dev/urandom');
+ }
- return _read_seed_from('/dev/random');
+ return _read_seed_from('/dev/random');
}
sub _read_seed_from {
- my ($from) = @_;
-
- open(my $fh, '<', $from) or die "$from: $!";
- my $buffer;
- read($fh, $buffer, SEED_SIZE);
- if (length($buffer) < SEED_SIZE) {
- die "Could not read enough seed bytes from $from, got only "
- . length($buffer);
- }
- close $fh;
- return $buffer;
+ my ($from) = @_;
+
+ open(my $fh, '<', $from) or die "$from: $!";
+ my $buffer;
+ read($fh, $buffer, SEED_SIZE);
+ if (length($buffer) < SEED_SIZE) {
+ die "Could not read enough seed bytes from $from, got only " . length($buffer);
+ }
+ close $fh;
+ return $buffer;
}
sub _windows_seed {
- my ($major, $minor) = (Win32::GetOSVersion())[1,2];
- if ($major < 5) {
- die "Bugzilla does not support versions of Windows before"
- . " Windows 2000";
- }
- # This means Windows 2000.
- if ($major == 5 and $minor == 0) {
- return _win2k_seed();
- }
-
- my $rtlgenrand = Win32::API->new('advapi32', RTLGENRANDOM_PROTO);
- if (!defined $rtlgenrand) {
- die "Could not import RtlGenRand: $^E";
- }
- my $buffer = chr(0) x SEED_SIZE;
- my $result = $rtlgenrand->Call($buffer, SEED_SIZE);
- if (!$result) {
- die "RtlGenRand failed: $^E";
- }
- return $buffer;
+ my ($major, $minor) = (Win32::GetOSVersion())[1, 2];
+ if ($major < 5) {
+ die "Bugzilla does not support versions of Windows before" . " Windows 2000";
+ }
+
+ # This means Windows 2000.
+ if ($major == 5 and $minor == 0) {
+ return _win2k_seed();
+ }
+
+ my $rtlgenrand = Win32::API->new('advapi32', RTLGENRANDOM_PROTO);
+ if (!defined $rtlgenrand) {
+ die "Could not import RtlGenRand: $^E";
+ }
+ my $buffer = chr(0) x SEED_SIZE;
+ my $result = $rtlgenrand->Call($buffer, SEED_SIZE);
+ if (!$result) {
+ die "RtlGenRand failed: $^E";
+ }
+ return $buffer;
}
sub _win2k_seed {
- my $crypt_acquire = Win32::API->new(
- "advapi32", 'CryptAcquireContext', 'PPPNN', 'I');
- if (!defined $crypt_acquire) {
- die "Could not import CryptAcquireContext: $^E";
- }
-
- my $crypt_release = Win32::API->new(
- "advapi32", 'CryptReleaseContext', 'NN', 'I');
- if (!defined $crypt_release) {
- die "Could not import CryptReleaseContext: $^E";
- }
-
- my $crypt_gen_random = Win32::API->new(
- "advapi32", 'CryptGenRandom', 'NNP', 'I');
- if (!defined $crypt_gen_random) {
- die "Could not import CryptGenRandom: $^E";
- }
-
- my $context = chr(0) x Win32::API::Type->sizeof('PULONG');
- my $acquire_result = $crypt_acquire->Call(
- $context, 0, 0, PROV_RSA_FULL, CRYPT_SILENT | CRYPT_VERIFYCONTEXT);
- if (!defined $acquire_result) {
- die "CryptAcquireContext failed: $^E";
- }
-
- my $pack_type = Win32::API::Type::packing('PULONG');
- $context = unpack($pack_type, $context);
-
- my $buffer = chr(0) x SEED_SIZE;
- my $rand_result = $crypt_gen_random->Call($context, SEED_SIZE, $buffer);
- my $rand_error = $^E;
- # We don't check this if it fails, we don't care.
- $crypt_release->Call($context, 0);
- if (!defined $rand_result) {
- die "CryptGenRandom failed: $rand_error";
- }
- return $buffer;
+ my $crypt_acquire
+ = Win32::API->new("advapi32", 'CryptAcquireContext', 'PPPNN', 'I');
+ if (!defined $crypt_acquire) {
+ die "Could not import CryptAcquireContext: $^E";
+ }
+
+ my $crypt_release
+ = Win32::API->new("advapi32", 'CryptReleaseContext', 'NN', 'I');
+ if (!defined $crypt_release) {
+ die "Could not import CryptReleaseContext: $^E";
+ }
+
+ my $crypt_gen_random
+ = Win32::API->new("advapi32", 'CryptGenRandom', 'NNP', 'I');
+ if (!defined $crypt_gen_random) {
+ die "Could not import CryptGenRandom: $^E";
+ }
+
+ my $context = chr(0) x Win32::API::Type->sizeof('PULONG');
+ my $acquire_result = $crypt_acquire->Call($context, 0, 0, PROV_RSA_FULL,
+ CRYPT_SILENT | CRYPT_VERIFYCONTEXT);
+ if (!defined $acquire_result) {
+ die "CryptAcquireContext failed: $^E";
+ }
+
+ my $pack_type = Win32::API::Type::packing('PULONG');
+ $context = unpack($pack_type, $context);
+
+ my $buffer = chr(0) x SEED_SIZE;
+ my $rand_result = $crypt_gen_random->Call($context, SEED_SIZE, $buffer);
+ my $rand_error = $^E;
+
+ # We don't check this if it fails, we don't care.
+ $crypt_release->Call($context, 0);
+ if (!defined $rand_result) {
+ die "CryptGenRandom failed: $rand_error";
+ }
+ return $buffer;
}
1;
diff --git a/Bugzilla/Report.pm b/Bugzilla/Report.pm
index 10af2ea9e..84ea3b38b 100644
--- a/Bugzilla/Report.pm
+++ b/Bugzilla/Report.pm
@@ -26,43 +26,40 @@ use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
- id
- user_id
- name
- query
+ id
+ user_id
+ name
+ query
);
use constant UPDATE_COLUMNS => qw(
- name
- query
+ name
+ query
);
-use constant VALIDATORS => {
- name => \&_check_name,
- query => \&_check_query,
-};
+use constant VALIDATORS => {name => \&_check_name, query => \&_check_query,};
##############
# Validators #
##############
sub _check_name {
- my ($invocant, $name) = @_;
- $name = clean_text($name);
- $name || ThrowUserError("report_name_missing");
- $name !~ /[<>&]/ || ThrowUserError("illegal_query_name");
- if (length($name) > MAX_LEN_QUERY_NAME) {
- ThrowUserError("query_name_too_long");
- }
- return $name;
+ my ($invocant, $name) = @_;
+ $name = clean_text($name);
+ $name || ThrowUserError("report_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;
- return $cgi->query_string;
+ my ($invocant, $query) = @_;
+ $query || ThrowUserError("buglist_parameters_required");
+ my $cgi = new Bugzilla::CGI($query);
+ $cgi->clean_search_url;
+ return $cgi->query_string;
}
#############
@@ -71,7 +68,7 @@ sub _check_query {
sub query { return $_[0]->{'query'}; }
-sub set_name { $_[0]->set('name', $_[1]); }
+sub set_name { $_[0]->set('name', $_[1]); }
sub set_query { $_[0]->set('query', $_[1]); }
###########
@@ -79,25 +76,26 @@ sub set_query { $_[0]->set('query', $_[1]); }
###########
sub create {
- my $class = shift;
- my $param = shift;
+ my $class = shift;
+ my $param = shift;
- Bugzilla->login(LOGIN_REQUIRED);
- $param->{'user_id'} = Bugzilla->user->id;
+ Bugzilla->login(LOGIN_REQUIRED);
+ $param->{'user_id'} = Bugzilla->user->id;
- unshift @_, $param;
- my $self = $class->SUPER::create(@_);
+ unshift @_, $param;
+ my $self = $class->SUPER::create(@_);
}
sub check {
- my $class = shift;
- my $report = $class->SUPER::check(@_);
- my $user = Bugzilla->user;
- if ( grep($_->id eq $report->id, @{$user->reports})) {
- return $report;
- } else {
- ThrowUserError('report_access_denied');
- }
+ my $class = shift;
+ my $report = $class->SUPER::check(@_);
+ my $user = Bugzilla->user;
+ if (grep($_->id eq $report->id, @{$user->reports})) {
+ return $report;
+ }
+ else {
+ ThrowUserError('report_access_denied');
+ }
}
1;
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index 0694dd98c..a6e85f35b 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -13,8 +13,8 @@ use warnings;
use parent qw(Exporter);
@Bugzilla::Search::EXPORT = qw(
- IsValidQueryType
- split_order_term
+ IsValidQueryType
+ split_order_term
);
use Bugzilla::Error;
@@ -135,311 +135,275 @@ use constant NUMBER_REGEX => qr/
# If you specify a search type in the boolean charts, this describes
# which operator maps to which internal function here.
use constant OPERATORS => {
- equals => \&_simple_operator,
- notequals => \&_simple_operator,
- casesubstring => \&_casesubstring,
- substring => \&_substring,
- substr => \&_substring,
- notsubstring => \&_notsubstring,
- regexp => \&_regexp,
- notregexp => \&_notregexp,
- lessthan => \&_simple_operator,
- lessthaneq => \&_simple_operator,
- matches => sub { ThrowUserError("search_content_without_matches"); },
- notmatches => sub { ThrowUserError("search_content_without_matches"); },
- greaterthan => \&_simple_operator,
- greaterthaneq => \&_simple_operator,
- anyexact => \&_anyexact,
- anywordssubstr => \&_anywordsubstr,
- allwordssubstr => \&_allwordssubstr,
- nowordssubstr => \&_nowordssubstr,
- anywords => \&_anywords,
- allwords => \&_allwords,
- nowords => \&_nowords,
- changedbefore => \&_changedbefore_changedafter,
- changedafter => \&_changedbefore_changedafter,
- changedfrom => \&_changedfrom_changedto,
- changedto => \&_changedfrom_changedto,
- changedby => \&_changedby,
- isempty => \&_isempty,
- isnotempty => \&_isnotempty,
+ equals => \&_simple_operator,
+ notequals => \&_simple_operator,
+ casesubstring => \&_casesubstring,
+ substring => \&_substring,
+ substr => \&_substring,
+ notsubstring => \&_notsubstring,
+ regexp => \&_regexp,
+ notregexp => \&_notregexp,
+ lessthan => \&_simple_operator,
+ lessthaneq => \&_simple_operator,
+ matches => sub { ThrowUserError("search_content_without_matches"); },
+ notmatches => sub { ThrowUserError("search_content_without_matches"); },
+ greaterthan => \&_simple_operator,
+ greaterthaneq => \&_simple_operator,
+ anyexact => \&_anyexact,
+ anywordssubstr => \&_anywordsubstr,
+ allwordssubstr => \&_allwordssubstr,
+ nowordssubstr => \&_nowordssubstr,
+ anywords => \&_anywords,
+ allwords => \&_allwords,
+ nowords => \&_nowords,
+ changedbefore => \&_changedbefore_changedafter,
+ changedafter => \&_changedbefore_changedafter,
+ changedfrom => \&_changedfrom_changedto,
+ changedto => \&_changedfrom_changedto,
+ changedby => \&_changedby,
+ isempty => \&_isempty,
+ isnotempty => \&_isnotempty,
};
# Some operators are really just standard SQL operators, and are
# all implemented by the _simple_operator function, which uses this
# constant.
use constant SIMPLE_OPERATORS => {
- equals => '=',
- notequals => '!=',
- greaterthan => '>',
- greaterthaneq => '>=',
- lessthan => '<',
- lessthaneq => "<=",
+ equals => '=',
+ notequals => '!=',
+ greaterthan => '>',
+ greaterthaneq => '>=',
+ lessthan => '<',
+ lessthaneq => "<=",
};
# Most operators just reverse by removing or adding "not" from/to them.
# However, some operators reverse in a different way, so those are listed
# here.
use constant OPERATOR_REVERSE => {
- nowords => 'anywords',
- nowordssubstr => 'anywordssubstr',
- anywords => 'nowords',
- anywordssubstr => 'nowordssubstr',
- lessthan => 'greaterthaneq',
- lessthaneq => 'greaterthan',
- greaterthan => 'lessthaneq',
- greaterthaneq => 'lessthan',
- isempty => 'isnotempty',
- isnotempty => 'isempty',
- # The following don't currently have reversals:
- # casesubstring, anyexact, allwords, allwordssubstr
+ nowords => 'anywords',
+ nowordssubstr => 'anywordssubstr',
+ anywords => 'nowords',
+ anywordssubstr => 'nowordssubstr',
+ lessthan => 'greaterthaneq',
+ lessthaneq => 'greaterthan',
+ greaterthan => 'lessthaneq',
+ greaterthaneq => 'lessthan',
+ isempty => 'isnotempty',
+ isnotempty => 'isempty',
+
+ # The following don't currently have reversals:
+ # casesubstring, anyexact, allwords, allwordssubstr
};
# For these operators, even if a field is numeric (is_numeric returns true),
# we won't treat the input like a number.
use constant NON_NUMERIC_OPERATORS => qw(
- changedafter
- changedbefore
- changedfrom
- changedto
- regexp
- notregexp
+ changedafter
+ changedbefore
+ changedfrom
+ changedto
+ regexp
+ notregexp
);
# These operators ignore the entered value
use constant NO_VALUE_OPERATORS => qw(
- isempty
- isnotempty
+ isempty
+ isnotempty
);
use constant MULTI_SELECT_OVERRIDE => {
- notequals => \&_multiselect_negative,
- notregexp => \&_multiselect_negative,
- notsubstring => \&_multiselect_negative,
- nowords => \&_multiselect_negative,
- nowordssubstr => \&_multiselect_negative,
-
- allwords => \&_multiselect_multiple,
- allwordssubstr => \&_multiselect_multiple,
- anyexact => \&_multiselect_multiple,
- anywords => \&_multiselect_multiple,
- anywordssubstr => \&_multiselect_multiple,
-
- _non_changed => \&_multiselect_nonchanged,
+ notequals => \&_multiselect_negative,
+ notregexp => \&_multiselect_negative,
+ notsubstring => \&_multiselect_negative,
+ nowords => \&_multiselect_negative,
+ nowordssubstr => \&_multiselect_negative,
+
+ allwords => \&_multiselect_multiple,
+ allwordssubstr => \&_multiselect_multiple,
+ anyexact => \&_multiselect_multiple,
+ anywords => \&_multiselect_multiple,
+ anywordssubstr => \&_multiselect_multiple,
+
+ _non_changed => \&_multiselect_nonchanged,
};
use constant OPERATOR_FIELD_OVERRIDE => {
- # User fields
- 'attachments.submitter' => {
- _non_changed => \&_user_nonchanged,
- },
- assigned_to => {
- _non_changed => \&_user_nonchanged,
- },
- assigned_to_realname => {
- _non_changed => \&_user_nonchanged,
- },
- cc => {
- _non_changed => \&_user_nonchanged,
- },
- commenter => {
- _non_changed => \&_user_nonchanged,
- },
- reporter => {
- _non_changed => \&_user_nonchanged,
- },
- reporter_realname => {
- _non_changed => \&_user_nonchanged,
- },
- 'requestees.login_name' => {
- _non_changed => \&_user_nonchanged,
- },
- 'setters.login_name' => {
- _non_changed => \&_user_nonchanged,
- },
- qa_contact => {
- _non_changed => \&_user_nonchanged,
- },
- qa_contact_realname => {
- _non_changed => \&_user_nonchanged,
- },
- # General Bug Fields
- alias => { _non_changed => \&_alias_nonchanged },
- 'attach_data.thedata' => MULTI_SELECT_OVERRIDE,
- # We check all attachment fields against this.
- attachments => MULTI_SELECT_OVERRIDE,
- blocked => MULTI_SELECT_OVERRIDE,
- bug_file_loc => { _non_changed => \&_nullable },
- bug_group => MULTI_SELECT_OVERRIDE,
- classification => {
- _non_changed => \&_classification_nonchanged,
- },
- component => {
- _non_changed => \&_component_nonchanged,
- },
- content => {
- matches => \&_content_matches,
- notmatches => \&_content_matches,
- _default => sub { ThrowUserError("search_content_without_matches"); },
- },
- days_elapsed => {
- _default => \&_days_elapsed,
- },
- dependson => MULTI_SELECT_OVERRIDE,
- keywords => MULTI_SELECT_OVERRIDE,
- 'flagtypes.name' => {
- _non_changed => \&_flagtypes_nonchanged,
- },
- longdesc => {
- changedby => \&_long_desc_changedby,
- changedbefore => \&_long_desc_changedbefore_after,
- changedafter => \&_long_desc_changedbefore_after,
- _non_changed => \&_long_desc_nonchanged,
- },
- 'longdescs.count' => {
- changedby => \&_long_desc_changedby,
- changedbefore => \&_long_desc_changedbefore_after,
- changedafter => \&_long_desc_changedbefore_after,
- changedfrom => \&_invalid_combination,
- changedto => \&_invalid_combination,
- _default => \&_long_descs_count,
- },
- 'longdescs.isprivate' => MULTI_SELECT_OVERRIDE,
- owner_idle_time => {
- greaterthan => \&_owner_idle_time_greater_less,
- greaterthaneq => \&_owner_idle_time_greater_less,
- lessthan => \&_owner_idle_time_greater_less,
- lessthaneq => \&_owner_idle_time_greater_less,
- _default => \&_invalid_combination,
- },
- product => {
- _non_changed => \&_product_nonchanged,
- },
- tag => MULTI_SELECT_OVERRIDE,
- comment_tag => MULTI_SELECT_OVERRIDE,
-
- # Timetracking Fields
- deadline => { _non_changed => \&_deadline },
- percentage_complete => {
- _non_changed => \&_percentage_complete,
- },
- work_time => {
- changedby => \&_work_time_changedby,
- changedbefore => \&_work_time_changedbefore_after,
- changedafter => \&_work_time_changedbefore_after,
- _default => \&_work_time,
- },
- last_visit_ts => {
- _non_changed => \&_last_visit_ts,
- _default => \&_last_visit_ts_invalid_operator,
- },
-
- # Custom Fields
- FIELD_TYPE_FREETEXT, { _non_changed => \&_nullable },
- FIELD_TYPE_BUG_ID, { _non_changed => \&_nullable_int },
- FIELD_TYPE_DATETIME, { _non_changed => \&_nullable_datetime },
- FIELD_TYPE_DATE, { _non_changed => \&_nullable_date },
- FIELD_TYPE_TEXTAREA, { _non_changed => \&_nullable },
- FIELD_TYPE_MULTI_SELECT, MULTI_SELECT_OVERRIDE,
- FIELD_TYPE_BUG_URLS, MULTI_SELECT_OVERRIDE,
+ # User fields
+ 'attachments.submitter' => {_non_changed => \&_user_nonchanged,},
+ assigned_to => {_non_changed => \&_user_nonchanged,},
+ assigned_to_realname => {_non_changed => \&_user_nonchanged,},
+ cc => {_non_changed => \&_user_nonchanged,},
+ commenter => {_non_changed => \&_user_nonchanged,},
+ reporter => {_non_changed => \&_user_nonchanged,},
+ reporter_realname => {_non_changed => \&_user_nonchanged,},
+ 'requestees.login_name' => {_non_changed => \&_user_nonchanged,},
+ 'setters.login_name' => {_non_changed => \&_user_nonchanged,},
+ qa_contact => {_non_changed => \&_user_nonchanged,},
+ qa_contact_realname => {_non_changed => \&_user_nonchanged,},
+
+ # General Bug Fields
+ alias => {_non_changed => \&_alias_nonchanged},
+ 'attach_data.thedata' => MULTI_SELECT_OVERRIDE,
+
+ # We check all attachment fields against this.
+ attachments => MULTI_SELECT_OVERRIDE,
+ blocked => MULTI_SELECT_OVERRIDE,
+ bug_file_loc => {_non_changed => \&_nullable},
+ bug_group => MULTI_SELECT_OVERRIDE,
+ classification => {_non_changed => \&_classification_nonchanged,},
+ component => {_non_changed => \&_component_nonchanged,},
+ content => {
+ matches => \&_content_matches,
+ notmatches => \&_content_matches,
+ _default => sub { ThrowUserError("search_content_without_matches"); },
+ },
+ days_elapsed => {_default => \&_days_elapsed,},
+ dependson => MULTI_SELECT_OVERRIDE,
+ keywords => MULTI_SELECT_OVERRIDE,
+ 'flagtypes.name' => {_non_changed => \&_flagtypes_nonchanged,},
+ longdesc => {
+ changedby => \&_long_desc_changedby,
+ changedbefore => \&_long_desc_changedbefore_after,
+ changedafter => \&_long_desc_changedbefore_after,
+ _non_changed => \&_long_desc_nonchanged,
+ },
+ 'longdescs.count' => {
+ changedby => \&_long_desc_changedby,
+ changedbefore => \&_long_desc_changedbefore_after,
+ changedafter => \&_long_desc_changedbefore_after,
+ changedfrom => \&_invalid_combination,
+ changedto => \&_invalid_combination,
+ _default => \&_long_descs_count,
+ },
+ 'longdescs.isprivate' => MULTI_SELECT_OVERRIDE,
+ owner_idle_time => {
+ greaterthan => \&_owner_idle_time_greater_less,
+ greaterthaneq => \&_owner_idle_time_greater_less,
+ lessthan => \&_owner_idle_time_greater_less,
+ lessthaneq => \&_owner_idle_time_greater_less,
+ _default => \&_invalid_combination,
+ },
+ product => {_non_changed => \&_product_nonchanged,},
+ tag => MULTI_SELECT_OVERRIDE,
+ comment_tag => MULTI_SELECT_OVERRIDE,
+
+ # Timetracking Fields
+ deadline => {_non_changed => \&_deadline},
+ percentage_complete => {_non_changed => \&_percentage_complete,},
+ work_time => {
+ changedby => \&_work_time_changedby,
+ changedbefore => \&_work_time_changedbefore_after,
+ changedafter => \&_work_time_changedbefore_after,
+ _default => \&_work_time,
+ },
+ last_visit_ts => {
+ _non_changed => \&_last_visit_ts,
+ _default => \&_last_visit_ts_invalid_operator,
+ },
+
+ # Custom Fields
+ FIELD_TYPE_FREETEXT,
+ {_non_changed => \&_nullable},
+ FIELD_TYPE_BUG_ID,
+ {_non_changed => \&_nullable_int},
+ FIELD_TYPE_DATETIME,
+ {_non_changed => \&_nullable_datetime},
+ FIELD_TYPE_DATE,
+ {_non_changed => \&_nullable_date},
+ FIELD_TYPE_TEXTAREA,
+ {_non_changed => \&_nullable},
+ FIELD_TYPE_MULTI_SELECT,
+ MULTI_SELECT_OVERRIDE,
+ FIELD_TYPE_BUG_URLS,
+ MULTI_SELECT_OVERRIDE,
};
# These are fields where special action is taken depending on the
# *value* passed in to the chart, sometimes.
# This is a sub because custom fields are dynamic
sub SPECIAL_PARSING {
- my $map = {
- # Pronoun Fields (Ones that can accept %user%, etc.)
- assigned_to => \&_contact_pronoun,
- cc => \&_contact_pronoun,
- commenter => \&_contact_pronoun,
- qa_contact => \&_contact_pronoun,
- reporter => \&_contact_pronoun,
- 'setters.login_name' => \&_contact_pronoun,
- 'requestees.login_name' => \&_contact_pronoun,
-
- # Date Fields that accept the 1d, 1w, 1m, 1y, etc. format.
- creation_ts => \&_datetime_translate,
- deadline => \&_date_translate,
- delta_ts => \&_datetime_translate,
-
- # last_visit field that accept both a 1d, 1w, 1m, 1y format and the
- # %last_changed% pronoun.
- last_visit_ts => \&_last_visit_datetime,
- };
- foreach my $field (Bugzilla->active_custom_fields) {
- if ($field->type == FIELD_TYPE_DATETIME) {
- $map->{$field->name} = \&_datetime_translate;
- } elsif ($field->type == FIELD_TYPE_DATE) {
- $map->{$field->name} = \&_date_translate;
- }
+ my $map = {
+
+ # Pronoun Fields (Ones that can accept %user%, etc.)
+ assigned_to => \&_contact_pronoun,
+ cc => \&_contact_pronoun,
+ commenter => \&_contact_pronoun,
+ qa_contact => \&_contact_pronoun,
+ reporter => \&_contact_pronoun,
+ 'setters.login_name' => \&_contact_pronoun,
+ 'requestees.login_name' => \&_contact_pronoun,
+
+ # Date Fields that accept the 1d, 1w, 1m, 1y, etc. format.
+ creation_ts => \&_datetime_translate,
+ deadline => \&_date_translate,
+ delta_ts => \&_datetime_translate,
+
+ # last_visit field that accept both a 1d, 1w, 1m, 1y format and the
+ # %last_changed% pronoun.
+ last_visit_ts => \&_last_visit_datetime,
+ };
+ foreach my $field (Bugzilla->active_custom_fields) {
+ if ($field->type == FIELD_TYPE_DATETIME) {
+ $map->{$field->name} = \&_datetime_translate;
}
- return $map;
-};
+ elsif ($field->type == FIELD_TYPE_DATE) {
+ $map->{$field->name} = \&_date_translate;
+ }
+ }
+ return $map;
+}
# Information about fields that represent "users", used by _user_nonchanged.
# There are other user fields than the ones listed here, but those use
# defaults in _user_nonchanged.
use constant USER_FIELDS => {
- 'attachments.submitter' => {
- field => 'submitter_id',
- join => { table => 'attachments' },
- isprivate => 1,
- },
- cc => {
- field => 'who',
- join => { table => 'cc' },
- },
- commenter => {
- field => 'who',
- join => { table => 'longdescs', join => 'INNER' },
- isprivate => 1,
- },
- qa_contact => {
- nullable => 1,
- },
- 'requestees.login_name' => {
- nullable => 1,
- field => 'requestee_id',
- join => { table => 'flags' },
- },
- 'setters.login_name' => {
- field => 'setter_id',
- join => { table => 'flags' },
- },
+ 'attachments.submitter' =>
+ {field => 'submitter_id', join => {table => 'attachments'}, isprivate => 1,},
+ cc => {field => 'who', join => {table => 'cc'},},
+ commenter => {
+ field => 'who',
+ join => {table => 'longdescs', join => 'INNER'},
+ isprivate => 1,
+ },
+ qa_contact => {nullable => 1,},
+ 'requestees.login_name' =>
+ {nullable => 1, field => 'requestee_id', join => {table => 'flags'},},
+ 'setters.login_name' => {field => 'setter_id', join => {table => 'flags'},},
};
# Backwards compatibility for times that we changed the names of fields
# or URL parameters.
use constant FIELD_MAP => {
- 'attachments.thedata' => 'attach_data.thedata',
- bugidtype => 'bug_id_type',
- changedin => 'days_elapsed',
- long_desc => 'longdesc',
- tags => 'tag',
+ 'attachments.thedata' => 'attach_data.thedata',
+ bugidtype => 'bug_id_type',
+ changedin => 'days_elapsed',
+ long_desc => 'longdesc',
+ tags => 'tag',
};
# Some fields are not sorted on themselves, but on other fields.
# We need to have a list of these fields and what they map to.
use constant SPECIAL_ORDER => {
- 'target_milestone' => {
- order => ['map_target_milestone.sortkey','map_target_milestone.value'],
- join => {
- table => 'milestones',
- from => 'target_milestone',
- to => 'value',
- extra => ['bugs.product_id = map_target_milestone.product_id'],
- join => 'INNER',
- }
- },
+ 'target_milestone' => {
+ order => ['map_target_milestone.sortkey', 'map_target_milestone.value'],
+ join => {
+ table => 'milestones',
+ from => 'target_milestone',
+ to => 'value',
+ extra => ['bugs.product_id = map_target_milestone.product_id'],
+ join => 'INNER',
+ }
+ },
};
# Certain columns require other columns to come before them
# in _select_columns, and should be put there if they're not there.
use constant COLUMN_DEPENDS => {
- classification => ['product'],
- percentage_complete => ['actual_time', 'remaining_time'],
+ classification => ['product'],
+ percentage_complete => ['actual_time', 'remaining_time'],
};
# This describes tables that must be joined when you want to display
@@ -447,109 +411,81 @@ use constant COLUMN_DEPENDS => {
# DB::Schema to figure out what needs to be joined, but for some
# fields it needs a little help.
sub COLUMN_JOINS {
- my $invocant = shift;
- my $user = blessed($invocant) ? $invocant->_user : Bugzilla->user;
-
- my $joins = {
- actual_time => {
- table => '(SELECT bug_id, SUM(work_time) AS total'
- . ' FROM longdescs GROUP BY bug_id)',
- join => 'INNER',
- },
- alias => {
- table => 'bugs_aliases',
- as => 'map_alias',
- },
- assigned_to => {
- from => 'assigned_to',
- to => 'userid',
- table => 'profiles',
- join => 'INNER',
- },
- reporter => {
- from => 'reporter',
- to => 'userid',
- table => 'profiles',
- join => 'INNER',
- },
- qa_contact => {
- from => 'qa_contact',
- to => 'userid',
- table => 'profiles',
- },
- component => {
- from => 'component_id',
- to => 'id',
- table => 'components',
- join => 'INNER',
- },
- product => {
- from => 'product_id',
- to => 'id',
- table => 'products',
- join => 'INNER',
- },
- classification => {
- table => 'classifications',
- from => 'map_product.classification_id',
- to => 'id',
- join => 'INNER',
- },
- 'flagtypes.name' => {
- as => 'map_flags',
- table => 'flags',
- extra => ['map_flags.attach_id IS NULL'],
- then_to => {
- as => 'map_flagtypes',
- table => 'flagtypes',
- from => 'map_flags.type_id',
- to => 'id',
- },
- },
- keywords => {
- table => 'keywords',
- then_to => {
- as => 'map_keyworddefs',
- table => 'keyworddefs',
- from => 'map_keywords.keywordid',
- to => 'id',
- },
- },
- blocked => {
- table => 'dependencies',
- to => 'dependson',
- },
- dependson => {
- table => 'dependencies',
- to => 'blocked',
- },
- 'longdescs.count' => {
- table => 'longdescs',
- join => 'INNER',
- },
- tag => {
- as => 'map_bug_tag',
- table => 'bug_tag',
- then_to => {
- as => 'map_tag',
- table => 'tag',
- extra => ['map_tag.user_id = ' . $user->id],
- from => 'map_bug_tag.tag_id',
- to => 'id',
- },
- },
- last_visit_ts => {
- as => 'bug_user_last_visit',
- table => 'bug_user_last_visit',
- extra => ['bug_user_last_visit.user_id = ' . $user->id],
- from => 'bug_id',
- to => 'bug_id',
- },
- };
- return $joins;
-};
+ my $invocant = shift;
+ my $user = blessed($invocant) ? $invocant->_user : Bugzilla->user;
+
+ my $joins = {
+ actual_time => {
+ table => '(SELECT bug_id, SUM(work_time) AS total'
+ . ' FROM longdescs GROUP BY bug_id)',
+ join => 'INNER',
+ },
+ alias => {table => 'bugs_aliases', as => 'map_alias',},
+ assigned_to => {
+ from => 'assigned_to',
+ to => 'userid',
+ table => 'profiles',
+ join => 'INNER',
+ },
+ reporter =>
+ {from => 'reporter', to => 'userid', table => 'profiles', join => 'INNER',},
+ qa_contact => {from => 'qa_contact', to => 'userid', table => 'profiles',},
+ component =>
+ {from => 'component_id', to => 'id', table => 'components', join => 'INNER',},
+ product =>
+ {from => 'product_id', to => 'id', table => 'products', join => 'INNER',},
+ classification => {
+ table => 'classifications',
+ from => 'map_product.classification_id',
+ to => 'id',
+ join => 'INNER',
+ },
+ 'flagtypes.name' => {
+ as => 'map_flags',
+ table => 'flags',
+ extra => ['map_flags.attach_id IS NULL'],
+ then_to => {
+ as => 'map_flagtypes',
+ table => 'flagtypes',
+ from => 'map_flags.type_id',
+ to => 'id',
+ },
+ },
+ keywords => {
+ table => 'keywords',
+ then_to => {
+ as => 'map_keyworddefs',
+ table => 'keyworddefs',
+ from => 'map_keywords.keywordid',
+ to => 'id',
+ },
+ },
+ blocked => {table => 'dependencies', to => 'dependson',},
+ dependson => {table => 'dependencies', to => 'blocked',},
+ 'longdescs.count' => {table => 'longdescs', join => 'INNER',},
+ tag => {
+ as => 'map_bug_tag',
+ table => 'bug_tag',
+ then_to => {
+ as => 'map_tag',
+ table => 'tag',
+ extra => ['map_tag.user_id = ' . $user->id],
+ from => 'map_bug_tag.tag_id',
+ to => 'id',
+ },
+ },
+ last_visit_ts => {
+ as => 'bug_user_last_visit',
+ table => 'bug_user_last_visit',
+ extra => ['bug_user_last_visit.user_id = ' . $user->id],
+ from => 'bug_id',
+ to => 'bug_id',
+ },
+ };
+ return $joins;
+}
-# This constant defines the columns that can be selected in a query
+# This constant defines the columns that can be selected in a query
# and/or displayed in a bug list. Column records include the following
# fields:
#
@@ -559,7 +495,7 @@ sub COLUMN_JOINS {
# that returns the value of the column);
#
# 3. title: The title of the column as displayed to users.
-#
+#
# Note: There are a few hacks in the code that deviate from these definitions.
# In particular, the redundant short_desc column is removed when the
# client requests "all" columns.
@@ -570,150 +506,149 @@ sub COLUMN_JOINS {
# and we don't want it to happen at compile time, so we have it as a
# subroutine.
sub COLUMNS {
- my $invocant = shift;
- my $user = blessed($invocant) ? $invocant->_user : Bugzilla->user;
- my $dbh = Bugzilla->dbh;
- my $cache = Bugzilla->request_cache;
+ my $invocant = shift;
+ my $user = blessed($invocant) ? $invocant->_user : Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+ my $cache = Bugzilla->request_cache;
- if (defined $cache->{search_columns}->{$user->id}) {
- return $cache->{search_columns}->{$user->id};
- }
-
- # These are columns that don't exist in fielddefs, but are valid buglist
- # columns. (Also see near the bottom of this function for the definition
- # of short_short_desc.)
- my %columns = (
- relevance => { title => 'Relevance' },
- );
-
- # Next we define columns that have special SQL instead of just something
- # like "bugs.bug_id".
- my $total_time = "(map_actual_time.total + bugs.remaining_time)";
- my %special_sql = (
- alias => $dbh->sql_group_concat('DISTINCT map_alias.alias'),
- deadline => $dbh->sql_date_format('bugs.deadline', '%Y-%m-%d'),
- actual_time => 'map_actual_time.total',
-
- # "FLOOR" is in there to turn this into an integer, making searches
- # totally predictable. Otherwise you get floating-point numbers that
- # are rather hard to search reliably if you're asking for exact
- # numbers.
- percentage_complete =>
- "(CASE WHEN $total_time = 0"
- . " THEN 0"
- . " ELSE FLOOR(100 * (map_actual_time.total / $total_time))"
- . " END)",
-
- 'flagtypes.name' => $dbh->sql_group_concat('DISTINCT '
- . $dbh->sql_string_concat('map_flagtypes.name', 'map_flags.status'),
- undef, undef, 'map_flagtypes.sortkey, map_flagtypes.name'),
-
- 'keywords' => $dbh->sql_group_concat('DISTINCT map_keyworddefs.name'),
-
- blocked => $dbh->sql_group_concat('DISTINCT map_blocked.blocked'),
- dependson => $dbh->sql_group_concat('DISTINCT map_dependson.dependson'),
-
- 'longdescs.count' => 'COUNT(DISTINCT map_longdescs_count.comment_id)',
-
- tag => $dbh->sql_group_concat('DISTINCT map_tag.name'),
- last_visit_ts => 'bug_user_last_visit.last_visit_ts',
- );
-
- # Backward-compatibility for old field names. Goes new_name => old_name.
- # These are here and not in _translate_old_column because the rest of the
- # code actually still uses the old names, while the fielddefs table uses
- # the new names (which is not the case for the fields handled by
- # _translate_old_column).
- my %old_names = (
- creation_ts => 'opendate',
- delta_ts => 'changeddate',
- work_time => 'actual_time',
- );
-
- # Fields that are email addresses
- my @email_fields = qw(assigned_to reporter qa_contact);
- # Other fields that are stored in the bugs table as an id, but
- # should be displayed using their name.
- my @id_fields = qw(product component classification);
-
- foreach my $col (@email_fields) {
- my $sql = "map_${col}.login_name";
- if (!$user->id) {
- $sql = $dbh->sql_string_until($sql, $dbh->quote('@'));
- }
- $special_sql{$col} = $sql;
- $special_sql{"${col}_realname"} = "map_${col}.realname";
- }
-
- foreach my $col (@id_fields) {
- $special_sql{$col} = "map_${col}.name";
+ if (defined $cache->{search_columns}->{$user->id}) {
+ return $cache->{search_columns}->{$user->id};
+ }
+
+ # These are columns that don't exist in fielddefs, but are valid buglist
+ # columns. (Also see near the bottom of this function for the definition
+ # of short_short_desc.)
+ my %columns = (relevance => {title => 'Relevance'},);
+
+ # Next we define columns that have special SQL instead of just something
+ # like "bugs.bug_id".
+ my $total_time = "(map_actual_time.total + bugs.remaining_time)";
+ my %special_sql = (
+ alias => $dbh->sql_group_concat('DISTINCT map_alias.alias'),
+ deadline => $dbh->sql_date_format('bugs.deadline', '%Y-%m-%d'),
+ actual_time => 'map_actual_time.total',
+
+ # "FLOOR" is in there to turn this into an integer, making searches
+ # totally predictable. Otherwise you get floating-point numbers that
+ # are rather hard to search reliably if you're asking for exact
+ # numbers.
+ percentage_complete => "(CASE WHEN $total_time = 0"
+ . " THEN 0"
+ . " ELSE FLOOR(100 * (map_actual_time.total / $total_time))" . " END)",
+
+ 'flagtypes.name' => $dbh->sql_group_concat(
+ 'DISTINCT ' . $dbh->sql_string_concat('map_flagtypes.name', 'map_flags.status'),
+ undef,
+ undef,
+ 'map_flagtypes.sortkey, map_flagtypes.name'
+ ),
+
+ 'keywords' => $dbh->sql_group_concat('DISTINCT map_keyworddefs.name'),
+
+ blocked => $dbh->sql_group_concat('DISTINCT map_blocked.blocked'),
+ dependson => $dbh->sql_group_concat('DISTINCT map_dependson.dependson'),
+
+ 'longdescs.count' => 'COUNT(DISTINCT map_longdescs_count.comment_id)',
+
+ tag => $dbh->sql_group_concat('DISTINCT map_tag.name'),
+ last_visit_ts => 'bug_user_last_visit.last_visit_ts',
+ );
+
+ # Backward-compatibility for old field names. Goes new_name => old_name.
+ # These are here and not in _translate_old_column because the rest of the
+ # code actually still uses the old names, while the fielddefs table uses
+ # the new names (which is not the case for the fields handled by
+ # _translate_old_column).
+ my %old_names = (
+ creation_ts => 'opendate',
+ delta_ts => 'changeddate',
+ work_time => 'actual_time',
+ );
+
+ # Fields that are email addresses
+ my @email_fields = qw(assigned_to reporter qa_contact);
+
+ # Other fields that are stored in the bugs table as an id, but
+ # should be displayed using their name.
+ my @id_fields = qw(product component classification);
+
+ foreach my $col (@email_fields) {
+ my $sql = "map_${col}.login_name";
+ if (!$user->id) {
+ $sql = $dbh->sql_string_until($sql, $dbh->quote('@'));
+ }
+ $special_sql{$col} = $sql;
+ $special_sql{"${col}_realname"} = "map_${col}.realname";
+ }
+
+ foreach my $col (@id_fields) {
+ $special_sql{$col} = "map_${col}.name";
+ }
+
+ # Do the actual column-getting from fielddefs, now.
+ my @fields = @{Bugzilla->fields({obsolete => 0, buglist => 1})};
+ foreach my $field (@fields) {
+ my $id = $field->name;
+ $id = $old_names{$id} if exists $old_names{$id};
+ my $sql;
+ if (exists $special_sql{$id}) {
+ $sql = $special_sql{$id};
+ }
+ elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ $sql = $dbh->sql_group_concat('DISTINCT map_' . $field->name . '.value');
}
-
- # Do the actual column-getting from fielddefs, now.
- my @fields = @{ Bugzilla->fields({ obsolete => 0, buglist => 1 }) };
- foreach my $field (@fields) {
- my $id = $field->name;
- $id = $old_names{$id} if exists $old_names{$id};
- my $sql;
- if (exists $special_sql{$id}) {
- $sql = $special_sql{$id};
- }
- elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
- $sql = $dbh->sql_group_concat(
- 'DISTINCT map_' . $field->name . '.value');
- }
- else {
- $sql = 'bugs.' . $field->name;
- }
- $columns{$id} = { name => $sql, title => $field->description };
+ else {
+ $sql = 'bugs.' . $field->name;
}
+ $columns{$id} = {name => $sql, title => $field->description};
+ }
- # The short_short_desc column is identical to short_desc
- $columns{'short_short_desc'} = $columns{'short_desc'};
+ # The short_short_desc column is identical to short_desc
+ $columns{'short_short_desc'} = $columns{'short_desc'};
- Bugzilla::Hook::process('buglist_columns', { columns => \%columns });
+ Bugzilla::Hook::process('buglist_columns', {columns => \%columns});
- $cache->{search_columns}->{$user->id} = \%columns;
- return $cache->{search_columns}->{$user->id};
+ $cache->{search_columns}->{$user->id} = \%columns;
+ return $cache->{search_columns}->{$user->id};
}
sub REPORT_COLUMNS {
- my $invocant = shift;
- my $user = blessed($invocant) ? $invocant->_user : Bugzilla->user;
-
- my $columns = dclone(blessed($invocant) ? $invocant->COLUMNS : COLUMNS);
- # There's no reason to support reporting on unique fields.
- # Also, some other fields don't make very good reporting axises,
- # or simply don't work with the current reporting system.
- my @no_report_columns =
- qw(bug_id alias short_short_desc opendate changeddate
- flagtypes.name relevance);
-
- # If you're not a time-tracker, you can't use time-tracking
- # columns.
- if (!$user->is_timetracker) {
- push(@no_report_columns, TIMETRACKING_FIELDS);
- }
+ my $invocant = shift;
+ my $user = blessed($invocant) ? $invocant->_user : Bugzilla->user;
- foreach my $name (@no_report_columns) {
- delete $columns->{$name};
- }
- return $columns;
+ my $columns = dclone(blessed($invocant) ? $invocant->COLUMNS : COLUMNS);
+
+ # There's no reason to support reporting on unique fields.
+ # Also, some other fields don't make very good reporting axises,
+ # or simply don't work with the current reporting system.
+ my @no_report_columns = qw(bug_id alias short_short_desc opendate changeddate
+ flagtypes.name relevance);
+
+ # If you're not a time-tracker, you can't use time-tracking
+ # columns.
+ if (!$user->is_timetracker) {
+ push(@no_report_columns, TIMETRACKING_FIELDS);
+ }
+
+ foreach my $name (@no_report_columns) {
+ delete $columns->{$name};
+ }
+ return $columns;
}
# These are fields that never go into the GROUP BY on any DB. bug_id
# is here because it *always* goes into the GROUP BY as the first item,
# so it should be skipped when determining extra GROUP BY columns.
use constant GROUP_BY_SKIP => qw(
- alias
- blocked
- bug_id
- dependson
- flagtypes.name
- keywords
- longdescs.count
- percentage_complete
- tag
+ alias
+ blocked
+ bug_id
+ dependson
+ flagtypes.name
+ keywords
+ longdescs.count
+ percentage_complete
+ tag
);
###############
@@ -722,27 +657,27 @@ use constant GROUP_BY_SKIP => qw(
# Note that the params argument may be modified by Bugzilla::Search
sub new {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
-
- my $self = { @_ };
- bless($self, $class);
- $self->{'user'} ||= Bugzilla->user;
-
- # There are certain behaviors of the CGI "Vars" hash that we don't want.
- # In particular, if you put a single-value arrayref into it, later you
- # get back out a string, which breaks anyexact charts (because they
- # need arrays even for individual items, or we will re-trigger bug 67036).
- #
- # We can't just untie the hash--that would give us a hash with no values.
- # We have to manually copy the hash into a new one, and we have to always
- # do it, because there's no way to know if we were passed a tied hash
- # or not.
- my $params_in = $self->_params;
- my %params = map { $_ => $params_in->{$_} } keys %$params_in;
- $self->{params} = \%params;
-
- return $self;
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+
+ my $self = {@_};
+ bless($self, $class);
+ $self->{'user'} ||= Bugzilla->user;
+
+ # There are certain behaviors of the CGI "Vars" hash that we don't want.
+ # In particular, if you put a single-value arrayref into it, later you
+ # get back out a string, which breaks anyexact charts (because they
+ # need arrays even for individual items, or we will re-trigger bug 67036).
+ #
+ # We can't just untie the hash--that would give us a hash with no values.
+ # We have to manually copy the hash into a new one, and we have to always
+ # do it, because there's no way to know if we were passed a tied hash
+ # or not.
+ my $params_in = $self->_params;
+ my %params = map { $_ => $params_in->{$_} } keys %$params_in;
+ $self->{params} = \%params;
+
+ return $self;
}
@@ -751,148 +686,156 @@ sub new {
####################
sub data {
- my $self = shift;
- return $self->{data} if $self->{data};
- my $dbh = Bugzilla->dbh;
-
- # If all fields belong to the 'bugs' table, there is no need to split
- # the original query into two pieces. Else we override the 'fields'
- # argument to first get bug IDs based on the search criteria defined
- # by the caller, and the desired fields are collected in the 2nd query.
- my @orig_fields = $self->_input_columns;
- my $all_in_bugs_table = 1;
- foreach my $field (@orig_fields) {
- next if ($self->COLUMNS->{$field}->{name} // $field) =~ /^bugs\.\w+$/;
- $self->{fields} = ['bug_id'];
- $all_in_bugs_table = 0;
- last;
- }
-
- my $start_time = [gettimeofday()];
- my $sql = $self->_sql;
- # Do we just want bug IDs to pass to the 2nd query or all the data immediately?
- my $func = $all_in_bugs_table ? 'selectall_arrayref' : 'selectcol_arrayref';
- my $bug_ids = $dbh->$func($sql);
- my @extra_data = ({sql => $sql, time => tv_interval($start_time)});
- # Restore the original 'fields' argument, just in case.
- $self->{fields} = \@orig_fields unless $all_in_bugs_table;
-
- # If there are no bugs found, or all fields are in the 'bugs' table,
- # there is no need for another query.
- if (!scalar @$bug_ids || $all_in_bugs_table) {
- $self->{data} = $bug_ids;
- return wantarray ? ($self->{data}, \@extra_data) : $self->{data};
- }
-
- # Make sure the bug_id will be returned. If not, append it to the list.
- my $pos = firstidx { $_ eq 'bug_id' } @orig_fields;
- if ($pos < 0) {
- push(@orig_fields, 'bug_id');
- $pos = $#orig_fields;
- }
-
- # Now create a query with the buglist above as the single criteria
- # and the fields that the caller wants. No need to redo security checks;
- # the list has already been validated above.
- my $search = $self->new('fields' => \@orig_fields,
- 'params' => {bug_id => $bug_ids, bug_id_type => 'anyexact'},
- 'sharer' => $self->_sharer_id,
- 'user' => $self->_user,
- 'allow_unlimited' => 1,
- '_no_security_check' => 1);
-
- $start_time = [gettimeofday()];
- $sql = $search->_sql;
- my $unsorted_data = $dbh->selectall_arrayref($sql);
- push(@extra_data, {sql => $sql, time => tv_interval($start_time)});
- # Let's sort the data. We didn't do it in the query itself because
- # we already know in which order to sort bugs thanks to the first query,
- # and this avoids additional table joins in the SQL query.
- my %data = map { $_->[$pos] => $_ } @$unsorted_data;
- $self->{data} = [map { $data{$_} } @$bug_ids];
+ my $self = shift;
+ return $self->{data} if $self->{data};
+ my $dbh = Bugzilla->dbh;
+
+ # If all fields belong to the 'bugs' table, there is no need to split
+ # the original query into two pieces. Else we override the 'fields'
+ # argument to first get bug IDs based on the search criteria defined
+ # by the caller, and the desired fields are collected in the 2nd query.
+ my @orig_fields = $self->_input_columns;
+ my $all_in_bugs_table = 1;
+ foreach my $field (@orig_fields) {
+ next if ($self->COLUMNS->{$field}->{name} // $field) =~ /^bugs\.\w+$/;
+ $self->{fields} = ['bug_id'];
+ $all_in_bugs_table = 0;
+ last;
+ }
+
+ my $start_time = [gettimeofday()];
+ my $sql = $self->_sql;
+
+ # Do we just want bug IDs to pass to the 2nd query or all the data immediately?
+ my $func = $all_in_bugs_table ? 'selectall_arrayref' : 'selectcol_arrayref';
+ my $bug_ids = $dbh->$func($sql);
+ my @extra_data = ({sql => $sql, time => tv_interval($start_time)});
+
+ # Restore the original 'fields' argument, just in case.
+ $self->{fields} = \@orig_fields unless $all_in_bugs_table;
+
+ # If there are no bugs found, or all fields are in the 'bugs' table,
+ # there is no need for another query.
+ if (!scalar @$bug_ids || $all_in_bugs_table) {
+ $self->{data} = $bug_ids;
return wantarray ? ($self->{data}, \@extra_data) : $self->{data};
+ }
+
+ # Make sure the bug_id will be returned. If not, append it to the list.
+ my $pos = firstidx { $_ eq 'bug_id' } @orig_fields;
+ if ($pos < 0) {
+ push(@orig_fields, 'bug_id');
+ $pos = $#orig_fields;
+ }
+
+ # Now create a query with the buglist above as the single criteria
+ # and the fields that the caller wants. No need to redo security checks;
+ # the list has already been validated above.
+ my $search = $self->new(
+ 'fields' => \@orig_fields,
+ 'params' => {bug_id => $bug_ids, bug_id_type => 'anyexact'},
+ 'sharer' => $self->_sharer_id,
+ 'user' => $self->_user,
+ 'allow_unlimited' => 1,
+ '_no_security_check' => 1
+ );
+
+ $start_time = [gettimeofday()];
+ $sql = $search->_sql;
+ my $unsorted_data = $dbh->selectall_arrayref($sql);
+ push(@extra_data, {sql => $sql, time => tv_interval($start_time)});
+
+ # Let's sort the data. We didn't do it in the query itself because
+ # we already know in which order to sort bugs thanks to the first query,
+ # and this avoids additional table joins in the SQL query.
+ my %data = map { $_->[$pos] => $_ } @$unsorted_data;
+ $self->{data} = [map { $data{$_} } @$bug_ids];
+ return wantarray ? ($self->{data}, \@extra_data) : $self->{data};
}
sub _sql {
- my ($self) = @_;
- return $self->{sql} if $self->{sql};
- my $dbh = Bugzilla->dbh;
-
- my ($joins, $clause) = $self->_charts_to_conditions();
-
- if (!$clause->as_string
- && !Bugzilla->params->{'search_allow_no_criteria'}
- && !$self->{allow_unlimited})
- {
- ThrowUserError('buglist_parameters_required');
- }
-
- my $select = join(', ', $self->_sql_select);
- my $from = $self->_sql_from($joins);
- my $where = $self->_sql_where($clause);
- my $group_by = $dbh->sql_group_by($self->_sql_group_by);
- my $order_by = $self->_sql_order_by
- ? "\nORDER BY " . join(', ', $self->_sql_order_by) : '';
- my $limit = $self->_sql_limit;
- $limit = "\n$limit" if $limit;
-
- my $query = <<END;
+ my ($self) = @_;
+ return $self->{sql} if $self->{sql};
+ my $dbh = Bugzilla->dbh;
+
+ my ($joins, $clause) = $self->_charts_to_conditions();
+
+ if ( !$clause->as_string
+ && !Bugzilla->params->{'search_allow_no_criteria'}
+ && !$self->{allow_unlimited})
+ {
+ ThrowUserError('buglist_parameters_required');
+ }
+
+ my $select = join(', ', $self->_sql_select);
+ my $from = $self->_sql_from($joins);
+ my $where = $self->_sql_where($clause);
+ my $group_by = $dbh->sql_group_by($self->_sql_group_by);
+ my $order_by
+ = $self->_sql_order_by
+ ? "\nORDER BY " . join(', ', $self->_sql_order_by)
+ : '';
+ my $limit = $self->_sql_limit;
+ $limit = "\n$limit" if $limit;
+
+ my $query = <<END;
SELECT $select
FROM $from
WHERE $where
$group_by$order_by$limit
END
- $self->{sql} = $query;
- return $self->{sql};
+ $self->{sql} = $query;
+ return $self->{sql};
}
sub search_description {
- my ($self, $params) = @_;
- my $desc = $self->{'search_description'} ||= [];
- if ($params) {
- push(@$desc, $params);
- }
- # Make sure that the description has actually been generated if
- # people are asking for the whole thing.
- else {
- $self->_sql;
- }
- return $self->{'search_description'};
+ my ($self, $params) = @_;
+ my $desc = $self->{'search_description'} ||= [];
+ if ($params) {
+ push(@$desc, $params);
+ }
+
+ # Make sure that the description has actually been generated if
+ # people are asking for the whole thing.
+ else {
+ $self->_sql;
+ }
+ return $self->{'search_description'};
}
sub boolean_charts_to_custom_search {
- my ($self, $cgi_buffer) = @_;
- my $boolean_charts = $self->_boolean_charts;
- my @as_params = $boolean_charts ? $boolean_charts->as_params : ();
-
- # We need to start our new ids after the last custom search "f" id.
- # We can just pick the last id in the array because they are sorted
- # numerically.
- my $last_id = ($self->_field_ids)[-1];
- my $count = defined($last_id) ? $last_id + 1 : 0;
- foreach my $param_set (@as_params) {
- foreach my $name (keys %$param_set) {
- my $value = $param_set->{$name};
- next if !defined $value;
- $cgi_buffer->param($name . $count, $value);
- }
- $count++;
+ my ($self, $cgi_buffer) = @_;
+ my $boolean_charts = $self->_boolean_charts;
+ my @as_params = $boolean_charts ? $boolean_charts->as_params : ();
+
+ # We need to start our new ids after the last custom search "f" id.
+ # We can just pick the last id in the array because they are sorted
+ # numerically.
+ my $last_id = ($self->_field_ids)[-1];
+ my $count = defined($last_id) ? $last_id + 1 : 0;
+ foreach my $param_set (@as_params) {
+ foreach my $name (keys %$param_set) {
+ my $value = $param_set->{$name};
+ next if !defined $value;
+ $cgi_buffer->param($name . $count, $value);
}
+ $count++;
+ }
}
sub invalid_order_columns {
- my ($self) = @_;
- my @invalid_columns;
- foreach my $order ($self->_input_order) {
- next if defined $self->_validate_order_column($order);
- push(@invalid_columns, $order);
- }
- return \@invalid_columns;
+ my ($self) = @_;
+ my @invalid_columns;
+ foreach my $order ($self->_input_order) {
+ next if defined $self->_validate_order_column($order);
+ push(@invalid_columns, $order);
+ }
+ return \@invalid_columns;
}
sub order {
- my ($self) = @_;
- return $self->_valid_order;
+ my ($self) = @_;
+ return $self->_valid_order;
}
######################
@@ -901,49 +844,48 @@ sub order {
# Fields that are legal for boolean charts of any kind.
sub _chart_fields {
- my ($self) = @_;
+ my ($self) = @_;
- if (!$self->{chart_fields}) {
- my $chart_fields = Bugzilla->fields({ by_name => 1 });
+ if (!$self->{chart_fields}) {
+ my $chart_fields = Bugzilla->fields({by_name => 1});
- if (!$self->_user->is_timetracker) {
- foreach my $tt_field (TIMETRACKING_FIELDS) {
- delete $chart_fields->{$tt_field};
- }
- }
- $self->{chart_fields} = $chart_fields;
+ if (!$self->_user->is_timetracker) {
+ foreach my $tt_field (TIMETRACKING_FIELDS) {
+ delete $chart_fields->{$tt_field};
+ }
}
- return $self->{chart_fields};
+ $self->{chart_fields} = $chart_fields;
+ }
+ return $self->{chart_fields};
}
# There are various places in Search.pm that we need to know the list of
# valid multi-select fields--or really, fields that are stored like
# multi-selects, which includes BUG_URLS fields.
sub _multi_select_fields {
- my ($self) = @_;
- $self->{multi_select_fields} ||= Bugzilla->fields({
- by_name => 1,
- type => [FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_BUG_URLS]});
- return $self->{multi_select_fields};
+ my ($self) = @_;
+ $self->{multi_select_fields} ||= Bugzilla->fields(
+ {by_name => 1, type => [FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_BUG_URLS]});
+ return $self->{multi_select_fields};
}
# $self->{params} contains values that could be undef, could be a string,
# or could be an arrayref. Sometimes we want that value as an array,
# always.
sub _param_array {
- my ($self, $name) = @_;
- my $value = $self->_params->{$name};
- if (!defined $value) {
- return ();
- }
- if (ref($value) eq 'ARRAY') {
- return @$value;
- }
- return ($value);
-}
-
-sub _params { $_[0]->{params} }
-sub _user { return $_[0]->{user} }
+ my ($self, $name) = @_;
+ my $value = $self->_params->{$name};
+ if (!defined $value) {
+ return ();
+ }
+ if (ref($value) eq 'ARRAY') {
+ return @$value;
+ }
+ return ($value);
+}
+
+sub _params { $_[0]->{params} }
+sub _user { return $_[0]->{user} }
sub _sharer_id { $_[0]->{sharer} }
##############################
@@ -952,81 +894,84 @@ sub _sharer_id { $_[0]->{sharer} }
# These are the fields the user has chosen to display on the buglist,
# exactly as they were passed to new().
-sub _input_columns { @{ $_[0]->{'fields'} || [] } }
+sub _input_columns { @{$_[0]->{'fields'} || []} }
# These are columns that are also going to be in the SELECT for one reason
# or another, but weren't actually requested by the caller.
sub _extra_columns {
- my ($self) = @_;
- # Everything that's going to be in the ORDER BY must also be
- # in the SELECT.
- push(@{ $self->{extra_columns} }, $self->_valid_order_columns);
- return @{ $self->{extra_columns} };
+ my ($self) = @_;
+
+ # Everything that's going to be in the ORDER BY must also be
+ # in the SELECT.
+ push(@{$self->{extra_columns}}, $self->_valid_order_columns);
+ return @{$self->{extra_columns}};
}
# For search functions to modify extra_columns. It doesn't matter if
# people push the same column onto this array multiple times, because
# _select_columns will call "uniq" on its final result.
sub _add_extra_column {
- my ($self, $column) = @_;
- push(@{ $self->{extra_columns} }, $column);
+ my ($self, $column) = @_;
+ push(@{$self->{extra_columns}}, $column);
}
# These are the columns that we're going to be actually SELECTing.
sub _display_columns {
- my ($self) = @_;
- return @{ $self->{display_columns} } if $self->{display_columns};
-
- # Do not alter the list from _input_columns at all, even if there are
- # duplicated columns. Those are passed by the caller, and the caller
- # expects to get them back in the exact same order.
- my @columns = $self->_input_columns;
-
- # Only add columns which are not already listed.
- my %list = map { $_ => 1 } @columns;
- foreach my $column ($self->_extra_columns) {
- push(@columns, $column) unless $list{$column}++;
- }
- $self->{display_columns} = \@columns;
- return @{ $self->{display_columns} };
+ my ($self) = @_;
+ return @{$self->{display_columns}} if $self->{display_columns};
+
+ # Do not alter the list from _input_columns at all, even if there are
+ # duplicated columns. Those are passed by the caller, and the caller
+ # expects to get them back in the exact same order.
+ my @columns = $self->_input_columns;
+
+ # Only add columns which are not already listed.
+ my %list = map { $_ => 1 } @columns;
+ foreach my $column ($self->_extra_columns) {
+ push(@columns, $column) unless $list{$column}++;
+ }
+ $self->{display_columns} = \@columns;
+ return @{$self->{display_columns}};
}
# These are the columns that are involved in the query.
sub _select_columns {
- my ($self) = @_;
- return @{ $self->{select_columns} } if $self->{select_columns};
-
- my @select_columns;
- foreach my $column ($self->_display_columns) {
- if (my $add_first = COLUMN_DEPENDS->{$column}) {
- push(@select_columns, @$add_first);
- }
- push(@select_columns, $column);
+ my ($self) = @_;
+ return @{$self->{select_columns}} if $self->{select_columns};
+
+ my @select_columns;
+ foreach my $column ($self->_display_columns) {
+ if (my $add_first = COLUMN_DEPENDS->{$column}) {
+ push(@select_columns, @$add_first);
}
- # Remove duplicated columns.
- $self->{select_columns} = [uniq @select_columns];
- return @{ $self->{select_columns} };
+ push(@select_columns, $column);
+ }
+
+ # Remove duplicated columns.
+ $self->{select_columns} = [uniq @select_columns];
+ return @{$self->{select_columns}};
}
# This takes _display_columns and translates it into the actual SQL that
# will go into the SELECT clause.
sub _sql_select {
- my ($self) = @_;
- my @sql_fields;
- foreach my $column ($self->_display_columns) {
- my $sql = $self->COLUMNS->{$column}->{name} // '';
- if ($sql) {
- my $alias = $column;
- # Aliases cannot contain dots in them. We convert them to underscores.
- $alias =~ tr/./_/;
- $sql .= " AS $alias";
- }
- else {
- $sql = $column;
- }
- push(@sql_fields, $sql);
+ my ($self) = @_;
+ my @sql_fields;
+ foreach my $column ($self->_display_columns) {
+ my $sql = $self->COLUMNS->{$column}->{name} // '';
+ if ($sql) {
+ my $alias = $column;
+
+ # Aliases cannot contain dots in them. We convert them to underscores.
+ $alias =~ tr/./_/;
+ $sql .= " AS $alias";
+ }
+ else {
+ $sql = $column;
}
- return @sql_fields;
+ push(@sql_fields, $sql);
+ }
+ return @sql_fields;
}
################################
@@ -1035,85 +980,83 @@ sub _sql_select {
# The "order" that was requested by the consumer, exactly as it was
# requested.
-sub _input_order { @{ $_[0]->{'order'} || [] } }
+sub _input_order { @{$_[0]->{'order'} || []} }
+
# Requested order with invalid values removed and old names translated
sub _valid_order {
- my ($self) = @_;
- return map { ($self->_validate_order_column($_)) } $self->_input_order;
+ my ($self) = @_;
+ return map { ($self->_validate_order_column($_)) } $self->_input_order;
}
+
# The valid order with just the column names, and no ASC or DESC.
sub _valid_order_columns {
- my ($self) = @_;
- return map { (split_order_term($_))[0] } $self->_valid_order;
+ my ($self) = @_;
+ return map { (split_order_term($_))[0] } $self->_valid_order;
}
sub _validate_order_column {
- my ($self, $order_item) = @_;
+ my ($self, $order_item) = @_;
- # Translate old column names
- my ($field, $direction) = split_order_term($order_item);
- $field = $self->_translate_old_column($field);
+ # Translate old column names
+ my ($field, $direction) = split_order_term($order_item);
+ $field = $self->_translate_old_column($field);
- # Only accept valid columns
- return if (!exists $self->COLUMNS->{$field});
+ # Only accept valid columns
+ return if (!exists $self->COLUMNS->{$field});
- # Relevance column can be used only with one or more fulltext searches
- return if ($field eq 'relevance' && !$self->COLUMNS->{$field}->{name});
+ # Relevance column can be used only with one or more fulltext searches
+ return if ($field eq 'relevance' && !$self->COLUMNS->{$field}->{name});
- $direction = " $direction" if $direction;
- return "$field$direction";
+ $direction = " $direction" if $direction;
+ return "$field$direction";
}
# A hashref that describes all the special stuff that has to be done
# for various fields if they go into the ORDER BY clause.
sub _special_order {
- my ($self) = @_;
- return $self->{special_order} if $self->{special_order};
-
- my %special_order = %{ SPECIAL_ORDER() };
- my $select_fields = Bugzilla->fields({ type => FIELD_TYPE_SINGLE_SELECT });
- foreach my $field (@$select_fields) {
- next if $field->is_abnormal;
- my $name = $field->name;
- $special_order{$name} = {
- order => ["map_$name.sortkey", "map_$name.value"],
- join => {
- table => $name,
- from => "bugs.$name",
- to => "value",
- join => 'INNER',
- }
- };
- }
- $self->{special_order} = \%special_order;
- return $self->{special_order};
+ my ($self) = @_;
+ return $self->{special_order} if $self->{special_order};
+
+ my %special_order = %{SPECIAL_ORDER()};
+ my $select_fields = Bugzilla->fields({type => FIELD_TYPE_SINGLE_SELECT});
+ foreach my $field (@$select_fields) {
+ next if $field->is_abnormal;
+ my $name = $field->name;
+ $special_order{$name} = {
+ order => ["map_$name.sortkey", "map_$name.value"],
+ join => {table => $name, from => "bugs.$name", to => "value", join => 'INNER',}
+ };
+ }
+ $self->{special_order} = \%special_order;
+ return $self->{special_order};
}
sub _sql_order_by {
- my ($self) = @_;
- if (!$self->{sql_order_by}) {
- my @order_by = map { $self->_translate_order_by_column($_) }
- $self->_valid_order;
- $self->{sql_order_by} = \@order_by;
- }
- return @{ $self->{sql_order_by} };
+ my ($self) = @_;
+ if (!$self->{sql_order_by}) {
+ my @order_by
+ = map { $self->_translate_order_by_column($_) } $self->_valid_order;
+ $self->{sql_order_by} = \@order_by;
+ }
+ return @{$self->{sql_order_by}};
}
sub _translate_order_by_column {
- my ($self, $order_by_item) = @_;
-
- my ($field, $direction) = split_order_term($order_by_item);
-
- $direction = '' if lc($direction) eq 'asc';
- my $special_order = $self->_special_order->{$field}->{order};
- # Standard fields have underscores in their SELECT alias instead
- # of a period (because aliases can't have periods).
- $field =~ s/\./_/g;
- my @items = $special_order ? @$special_order : $field;
- if (lc($direction) eq 'desc') {
- @items = map { "$_ DESC" } @items;
- }
- return @items;
+ my ($self, $order_by_item) = @_;
+
+ my ($field, $direction) = split_order_term($order_by_item);
+
+ $direction = '' if lc($direction) eq 'asc';
+ my $special_order = $self->_special_order->{$field}->{order};
+
+ # Standard fields have underscores in their SELECT alias instead
+ # of a period (because aliases can't have periods).
+ $field =~ s/\./_/g;
+ my @items = $special_order ? @$special_order : $field;
+ if (lc($direction) eq 'desc') {
+ @items = map {"$_ DESC"} @items;
+ }
+ return @items;
}
#############################
@@ -1121,32 +1064,30 @@ sub _translate_order_by_column {
#############################
sub _sql_limit {
- my ($self) = @_;
- my $limit = $self->_params->{limit};
- my $offset = $self->_params->{offset};
-
- my $max_results = Bugzilla->params->{'max_search_results'};
- if (!$self->{allow_unlimited} && (!$limit || $limit > $max_results)) {
- $limit = $max_results;
- }
-
- if (defined($offset) && !$limit) {
- $limit = INT_MAX;
- }
- if (defined $limit) {
- detaint_natural($limit)
- || ThrowCodeError('param_must_be_numeric',
- { function => 'Bugzilla::Search::new',
- param => 'limit' });
- if (defined $offset) {
- detaint_natural($offset)
- || ThrowCodeError('param_must_be_numeric',
- { function => 'Bugzilla::Search::new',
- param => 'offset' });
- }
- return Bugzilla->dbh->sql_limit($limit, $offset);
- }
- return '';
+ my ($self) = @_;
+ my $limit = $self->_params->{limit};
+ my $offset = $self->_params->{offset};
+
+ my $max_results = Bugzilla->params->{'max_search_results'};
+ if (!$self->{allow_unlimited} && (!$limit || $limit > $max_results)) {
+ $limit = $max_results;
+ }
+
+ if (defined($offset) && !$limit) {
+ $limit = INT_MAX;
+ }
+ if (defined $limit) {
+ detaint_natural($limit)
+ || ThrowCodeError('param_must_be_numeric',
+ {function => 'Bugzilla::Search::new', param => 'limit'});
+ if (defined $offset) {
+ detaint_natural($offset)
+ || ThrowCodeError('param_must_be_numeric',
+ {function => 'Bugzilla::Search::new', param => 'offset'});
+ }
+ return Bugzilla->dbh->sql_limit($limit, $offset);
+ }
+ return '';
}
############################
@@ -1154,176 +1095,176 @@ sub _sql_limit {
############################
sub _column_join {
- my ($self, $field) = @_;
- # The _realname fields require the same join as the username fields.
- $field =~ s/_realname$//;
- my $column_joins = $self->_get_column_joins();
- my $join_info = $column_joins->{$field};
- if ($join_info) {
- # Don't allow callers to modify the constant.
- $join_info = dclone($join_info);
- }
- else {
- if ($self->_multi_select_fields->{$field}) {
- $join_info = { table => "bug_$field" };
- }
- }
- if ($join_info and !$join_info->{as}) {
- $join_info = dclone($join_info);
- $join_info->{as} = "map_$field";
+ my ($self, $field) = @_;
+
+ # The _realname fields require the same join as the username fields.
+ $field =~ s/_realname$//;
+ my $column_joins = $self->_get_column_joins();
+ my $join_info = $column_joins->{$field};
+ if ($join_info) {
+
+ # Don't allow callers to modify the constant.
+ $join_info = dclone($join_info);
+ }
+ else {
+ if ($self->_multi_select_fields->{$field}) {
+ $join_info = {table => "bug_$field"};
}
- return $join_info ? $join_info : ();
+ }
+ if ($join_info and !$join_info->{as}) {
+ $join_info = dclone($join_info);
+ $join_info->{as} = "map_$field";
+ }
+ return $join_info ? $join_info : ();
}
# Sometimes we join the same table more than once. In this case, we
# want to AND all the various critiera that were used in both joins.
sub _combine_joins {
- my ($self, $joins) = @_;
- my @result;
- while(my $join = shift @$joins) {
- my $name = $join->{as};
- my ($others_like_me, $the_rest) = part { $_->{as} eq $name ? 0 : 1 }
- @$joins;
- if ($others_like_me) {
- my $from = $join->{from};
- my $to = $join->{to};
- # Sanity check to make sure that we have the same from and to
- # for all the same-named joins.
- if ($from) {
- all { $_->{from} eq $from } @$others_like_me
- or die "Not all same-named joins have identical 'from': "
- . Dumper($join, $others_like_me);
- }
- if ($to) {
- all { $_->{to} eq $to } @$others_like_me
- or die "Not all same-named joins have identical 'to': "
- . Dumper($join, $others_like_me);
- }
-
- # We don't need to call uniq here--translate_join will do that
- # for us.
- my @conditions = map { @{ $_->{extra} || [] } }
- ($join, @$others_like_me);
- $join->{extra} = \@conditions;
- $joins = $the_rest;
- }
- push(@result, $join);
- }
-
- return @result;
+ my ($self, $joins) = @_;
+ my @result;
+ while (my $join = shift @$joins) {
+ my $name = $join->{as};
+ my ($others_like_me, $the_rest) = part { $_->{as} eq $name ? 0 : 1 }
+ @$joins;
+ if ($others_like_me) {
+ my $from = $join->{from};
+ my $to = $join->{to};
+
+ # Sanity check to make sure that we have the same from and to
+ # for all the same-named joins.
+ if ($from) {
+ all { $_->{from} eq $from } @$others_like_me
+ or die "Not all same-named joins have identical 'from': "
+ . Dumper($join, $others_like_me);
+ }
+ if ($to) {
+ all { $_->{to} eq $to } @$others_like_me
+ or die "Not all same-named joins have identical 'to': "
+ . Dumper($join, $others_like_me);
+ }
+
+ # We don't need to call uniq here--translate_join will do that
+ # for us.
+ my @conditions = map { @{$_->{extra} || []} } ($join, @$others_like_me);
+ $join->{extra} = \@conditions;
+ $joins = $the_rest;
+ }
+ push(@result, $join);
+ }
+
+ return @result;
}
# Takes all the "then_to" items and just puts them as the next item in
# the array. Right now this only does one level of "then_to", but we
# could re-write this to handle then_to recursively if we need more levels.
sub _extract_then_to {
- my ($self, $joins) = @_;
- my @result;
- foreach my $join (@$joins) {
- push(@result, $join);
- if (my $then_to = $join->{then_to}) {
- push(@result, $then_to);
- }
+ my ($self, $joins) = @_;
+ my @result;
+ foreach my $join (@$joins) {
+ push(@result, $join);
+ if (my $then_to = $join->{then_to}) {
+ push(@result, $then_to);
}
- return @result;
+ }
+ return @result;
}
# JOIN statements for the SELECT and ORDER BY columns. This should not be
# called until the moment it is needed, because _select_columns might be
# modified by the charts.
sub _select_order_joins {
- my ($self) = @_;
- my @joins;
- foreach my $field ($self->_select_columns) {
- my @column_join = $self->_column_join($field);
- push(@joins, @column_join);
- }
- foreach my $field ($self->_valid_order_columns) {
- my $join_info = $self->_special_order->{$field}->{join};
- if ($join_info) {
- # Don't let callers modify SPECIAL_ORDER.
- $join_info = dclone($join_info);
- if (!$join_info->{as}) {
- $join_info->{as} = "map_$field";
- }
- push(@joins, $join_info);
- }
+ my ($self) = @_;
+ my @joins;
+ foreach my $field ($self->_select_columns) {
+ my @column_join = $self->_column_join($field);
+ push(@joins, @column_join);
+ }
+ foreach my $field ($self->_valid_order_columns) {
+ my $join_info = $self->_special_order->{$field}->{join};
+ if ($join_info) {
+
+ # Don't let callers modify SPECIAL_ORDER.
+ $join_info = dclone($join_info);
+ if (!$join_info->{as}) {
+ $join_info->{as} = "map_$field";
+ }
+ push(@joins, $join_info);
}
- return @joins;
+ }
+ return @joins;
}
# These are the joins that are *always* in the FROM clause.
sub _standard_joins {
- my ($self) = @_;
- my $user = $self->_user;
- my @joins;
- return () if $self->{_no_security_check};
-
- my $security_join = {
- table => 'bug_group_map',
- as => 'security_map',
- };
- push(@joins, $security_join);
+ my ($self) = @_;
+ my $user = $self->_user;
+ my @joins;
+ return () if $self->{_no_security_check};
- if ($user->id) {
- # See also _standard_joins for the other half of the below statement
- if (!Bugzilla->params->{'or_groups'}) {
- $security_join->{extra} =
- ["NOT (" . $user->groups_in_sql('security_map.group_id') . ")"];
- }
-
- my $security_cc_join = {
- table => 'cc',
- as => 'security_cc',
- extra => ['security_cc.who = ' . $user->id],
- };
- push(@joins, $security_cc_join);
+ my $security_join = {table => 'bug_group_map', as => 'security_map',};
+ push(@joins, $security_join);
+
+ if ($user->id) {
+
+ # See also _standard_joins for the other half of the below statement
+ if (!Bugzilla->params->{'or_groups'}) {
+ $security_join->{extra}
+ = ["NOT (" . $user->groups_in_sql('security_map.group_id') . ")"];
}
-
- return @joins;
+
+ my $security_cc_join = {
+ table => 'cc',
+ as => 'security_cc',
+ extra => ['security_cc.who = ' . $user->id],
+ };
+ push(@joins, $security_cc_join);
+ }
+
+ return @joins;
}
sub _sql_from {
- my ($self, $joins_input) = @_;
- my @joins = ($self->_standard_joins, $self->_select_order_joins,
- @$joins_input);
- @joins = $self->_extract_then_to(\@joins);
- @joins = $self->_combine_joins(\@joins);
- my @join_sql = map { $self->_translate_join($_) } @joins;
- return "bugs\n" . join("\n", @join_sql);
+ my ($self, $joins_input) = @_;
+ my @joins = ($self->_standard_joins, $self->_select_order_joins, @$joins_input);
+ @joins = $self->_extract_then_to(\@joins);
+ @joins = $self->_combine_joins(\@joins);
+ my @join_sql = map { $self->_translate_join($_) } @joins;
+ return "bugs\n" . join("\n", @join_sql);
}
# This takes a join data structure and turns it into actual JOIN SQL.
sub _translate_join {
- my ($self, $join_info) = @_;
-
- die "join with no table: " . Dumper($join_info) if !$join_info->{table};
- die "join with no 'as': " . Dumper($join_info) if !$join_info->{as};
-
- my $from_table = $join_info->{bugs_table} || "bugs";
- my $from = $join_info->{from} || "bug_id";
- if ($from =~ /^(\w+)\.(\w+)$/) {
- ($from_table, $from) = ($1, $2);
- }
- my $table = $join_info->{table};
- my $name = $join_info->{as};
- my $to = $join_info->{to} || "bug_id";
- my $join = $join_info->{join} || 'LEFT';
- my @extra = @{ $join_info->{extra} || [] };
- $name =~ s/\./_/g;
-
- # If a term contains ORs, we need to put parens around the condition.
- # This is a pretty weak test, but it's actually OK to put parens
- # around too many things.
- @extra = map { $_ =~ /\bOR\b/i ? "($_)" : $_ } @extra;
- my $extra_condition = join(' AND ', uniq @extra);
- if ($extra_condition) {
- $extra_condition = " AND $extra_condition";
- }
-
- my @join_sql = "$join JOIN $table AS $name"
- . " ON $from_table.$from = $name.$to$extra_condition";
- return @join_sql;
+ my ($self, $join_info) = @_;
+
+ die "join with no table: " . Dumper($join_info) if !$join_info->{table};
+ die "join with no 'as': " . Dumper($join_info) if !$join_info->{as};
+
+ my $from_table = $join_info->{bugs_table} || "bugs";
+ my $from = $join_info->{from} || "bug_id";
+ if ($from =~ /^(\w+)\.(\w+)$/) {
+ ($from_table, $from) = ($1, $2);
+ }
+ my $table = $join_info->{table};
+ my $name = $join_info->{as};
+ my $to = $join_info->{to} || "bug_id";
+ my $join = $join_info->{join} || 'LEFT';
+ my @extra = @{$join_info->{extra} || []};
+ $name =~ s/\./_/g;
+
+ # If a term contains ORs, we need to put parens around the condition.
+ # This is a pretty weak test, but it's actually OK to put parens
+ # around too many things.
+ @extra = map { $_ =~ /\bOR\b/i ? "($_)" : $_ } @extra;
+ my $extra_condition = join(' AND ', uniq @extra);
+ if ($extra_condition) {
+ $extra_condition = " AND $extra_condition";
+ }
+
+ my @join_sql = "$join JOIN $table AS $name"
+ . " ON $from_table.$from = $name.$to$extra_condition";
+ return @join_sql;
}
#############################
@@ -1336,54 +1277,60 @@ sub _translate_join {
# The terms that are always in the WHERE clause. These implement bug
# group security.
sub _standard_where {
- my ($self) = @_;
- return ('1=1') if $self->{_no_security_check};
- # If replication lags badly between the shadow db and the main DB,
- # it's possible for bugs to show up in searches before their group
- # controls are properly set. To prevent this, when initially creating
- # bugs we set their creation_ts to NULL, and don't give them a creation_ts
- # until their group controls are set. So if a bug has a NULL creation_ts,
- # it shouldn't show up in searches at all.
- my @where = ('bugs.creation_ts IS NOT NULL');
-
- my $user = $self->_user;
- my $security_term = '';
- # See also _standard_joins for the other half of the below statement
- if (Bugzilla->params->{'or_groups'}) {
- $security_term .= " (security_map.group_id IS NULL OR security_map.group_id IN (" . $user->groups_as_string . "))";
- }
- else {
- $security_term = 'security_map.group_id IS NULL';
- }
-
- if ($user->id) {
- my $userid = $user->id;
- # This indentation makes the resulting SQL more readable.
- $security_term .= <<END;
+ my ($self) = @_;
+ return ('1=1') if $self->{_no_security_check};
+
+ # If replication lags badly between the shadow db and the main DB,
+ # it's possible for bugs to show up in searches before their group
+ # controls are properly set. To prevent this, when initially creating
+ # bugs we set their creation_ts to NULL, and don't give them a creation_ts
+ # until their group controls are set. So if a bug has a NULL creation_ts,
+ # it shouldn't show up in searches at all.
+ my @where = ('bugs.creation_ts IS NOT NULL');
+
+ my $user = $self->_user;
+ my $security_term = '';
+
+ # See also _standard_joins for the other half of the below statement
+ if (Bugzilla->params->{'or_groups'}) {
+ $security_term
+ .= " (security_map.group_id IS NULL OR security_map.group_id IN ("
+ . $user->groups_as_string . "))";
+ }
+ else {
+ $security_term = 'security_map.group_id IS NULL';
+ }
+
+ if ($user->id) {
+ my $userid = $user->id;
+
+ # This indentation makes the resulting SQL more readable.
+ $security_term .= <<END;
OR (bugs.reporter_accessible = 1 AND bugs.reporter = $userid)
OR (bugs.cclist_accessible = 1 AND security_cc.who IS NOT NULL)
OR bugs.assigned_to = $userid
END
- if (Bugzilla->params->{'useqacontact'}) {
- $security_term.= " OR bugs.qa_contact = $userid";
- }
- $security_term = "($security_term)";
+ if (Bugzilla->params->{'useqacontact'}) {
+ $security_term .= " OR bugs.qa_contact = $userid";
}
+ $security_term = "($security_term)";
+ }
- push(@where, $security_term);
+ push(@where, $security_term);
- return @where;
+ return @where;
}
sub _sql_where {
- my ($self, $main_clause) = @_;
- # The newline and this particular spacing makes the resulting
- # SQL a bit more readable for debugging.
- my $where = join("\n AND ", $self->_standard_where);
- my $clause_sql = $main_clause->as_string;
- $where .= "\n AND " . $clause_sql if $clause_sql;
- return $where;
+ my ($self, $main_clause) = @_;
+
+ # The newline and this particular spacing makes the resulting
+ # SQL a bit more readable for debugging.
+ my $where = join("\n AND ", $self->_standard_where);
+ my $clause_sql = $main_clause->as_string;
+ $where .= "\n AND " . $clause_sql if $clause_sql;
+ return $where;
}
################################
@@ -1393,40 +1340,40 @@ sub _sql_where {
# And these are the fields that we have to do GROUP BY for in DBs
# that are more strict about putting everything into GROUP BY.
sub _sql_group_by {
- my ($self) = @_;
-
- # Strict DBs require every element from the SELECT to be in the GROUP BY,
- # unless that element is being used in an aggregate function.
- my @extra_group_by;
- foreach my $column ($self->_select_columns) {
- next if $self->_skip_group_by->{$column};
- my $sql = $self->COLUMNS->{$column}->{name} // $column;
- push(@extra_group_by, $sql);
- }
+ my ($self) = @_;
- # And all items from ORDER BY must be in the GROUP BY. The above loop
- # doesn't catch items that were put into the ORDER BY from SPECIAL_ORDER.
- foreach my $column ($self->_valid_order_columns) {
- my $special_order = $self->_special_order->{$column}->{order};
- next if !$special_order;
- push(@extra_group_by, @$special_order);
- }
-
- @extra_group_by = uniq @extra_group_by;
-
- # bug_id is the only field we actually group by.
- return ('bugs.bug_id', join(',', @extra_group_by));
+ # Strict DBs require every element from the SELECT to be in the GROUP BY,
+ # unless that element is being used in an aggregate function.
+ my @extra_group_by;
+ foreach my $column ($self->_select_columns) {
+ next if $self->_skip_group_by->{$column};
+ my $sql = $self->COLUMNS->{$column}->{name} // $column;
+ push(@extra_group_by, $sql);
+ }
+
+ # And all items from ORDER BY must be in the GROUP BY. The above loop
+ # doesn't catch items that were put into the ORDER BY from SPECIAL_ORDER.
+ foreach my $column ($self->_valid_order_columns) {
+ my $special_order = $self->_special_order->{$column}->{order};
+ next if !$special_order;
+ push(@extra_group_by, @$special_order);
+ }
+
+ @extra_group_by = uniq @extra_group_by;
+
+ # bug_id is the only field we actually group by.
+ return ('bugs.bug_id', join(',', @extra_group_by));
}
# A helper for _sql_group_by.
sub _skip_group_by {
- my ($self) = @_;
- return $self->{skip_group_by} if $self->{skip_group_by};
- my @skip_list = GROUP_BY_SKIP;
- push(@skip_list, keys %{ $self->_multi_select_fields });
- my %skip_hash = map { $_ => 1 } @skip_list;
- $self->{skip_group_by} = \%skip_hash;
- return $self->{skip_group_by};
+ my ($self) = @_;
+ return $self->{skip_group_by} if $self->{skip_group_by};
+ my @skip_list = GROUP_BY_SKIP;
+ push(@skip_list, keys %{$self->_multi_select_fields});
+ my %skip_hash = map { $_ => 1 } @skip_list;
+ $self->{skip_group_by} = \%skip_hash;
+ return $self->{skip_group_by};
}
##############################################
@@ -1435,244 +1382,255 @@ sub _skip_group_by {
# Backwards compatibility for old field names.
sub _convert_old_params {
- my ($self) = @_;
- my $params = $self->_params;
-
- # bugidtype has different values in modern Search.pm.
- if (defined $params->{'bugidtype'}) {
- my $value = $params->{'bugidtype'};
- $params->{'bugidtype'} = $value eq 'exclude' ? 'nowords' : 'anyexact';
- }
-
- foreach my $old_name (keys %{ FIELD_MAP() }) {
- if (defined $params->{$old_name}) {
- my $new_name = FIELD_MAP->{$old_name};
- $params->{$new_name} = delete $params->{$old_name};
- }
+ my ($self) = @_;
+ my $params = $self->_params;
+
+ # bugidtype has different values in modern Search.pm.
+ if (defined $params->{'bugidtype'}) {
+ my $value = $params->{'bugidtype'};
+ $params->{'bugidtype'} = $value eq 'exclude' ? 'nowords' : 'anyexact';
+ }
+
+ foreach my $old_name (keys %{FIELD_MAP()}) {
+ if (defined $params->{$old_name}) {
+ my $new_name = FIELD_MAP->{$old_name};
+ $params->{$new_name} = delete $params->{$old_name};
}
+ }
}
# This parses all the standard search parameters except for the boolean
# charts.
sub _special_charts {
- my ($self) = @_;
- $self->_convert_old_params();
- $self->_special_parse_bug_status();
- $self->_special_parse_resolution();
- my $clause = new Bugzilla::Search::Clause();
- $clause->add( $self->_parse_basic_fields() );
- $clause->add( $self->_special_parse_email() );
- $clause->add( $self->_special_parse_chfield() );
- $clause->add( $self->_special_parse_deadline() );
- return $clause;
+ my ($self) = @_;
+ $self->_convert_old_params();
+ $self->_special_parse_bug_status();
+ $self->_special_parse_resolution();
+ my $clause = new Bugzilla::Search::Clause();
+ $clause->add($self->_parse_basic_fields());
+ $clause->add($self->_special_parse_email());
+ $clause->add($self->_special_parse_chfield());
+ $clause->add($self->_special_parse_deadline());
+ return $clause;
}
sub _parse_basic_fields {
- my ($self) = @_;
- my $params = $self->_params;
- my $chart_fields = $self->_chart_fields;
-
- my $clause = new Bugzilla::Search::Clause();
- foreach my $field_name (keys %$chart_fields) {
- # CGI params shouldn't have periods in them, so we only accept
- # period-separated fields with underscores where the periods go.
- my $param_name = $field_name;
- $param_name =~ s/\./_/g;
- my @values = $self->_param_array($param_name);
- next if !@values;
- my $default_op = $param_name eq 'content' ? 'matches' : 'anyexact';
- my $operator = $params->{"${param_name}_type"} || $default_op;
- # Fields that are displayed as multi-selects are passed as arrays,
- # so that they can properly search values that contain commas.
- # However, other fields are sent as strings, so that they are properly
- # split on commas if required.
- my $field = $chart_fields->{$field_name};
- my $pass_value;
- if ($field->is_select or $field->name eq 'version'
- or $field->name eq 'target_milestone')
- {
- $pass_value = \@values;
- }
- else {
- $pass_value = join(',', @values);
- }
- $clause->add($field_name, $operator, $pass_value);
+ my ($self) = @_;
+ my $params = $self->_params;
+ my $chart_fields = $self->_chart_fields;
+
+ my $clause = new Bugzilla::Search::Clause();
+ foreach my $field_name (keys %$chart_fields) {
+
+ # CGI params shouldn't have periods in them, so we only accept
+ # period-separated fields with underscores where the periods go.
+ my $param_name = $field_name;
+ $param_name =~ s/\./_/g;
+ my @values = $self->_param_array($param_name);
+ next if !@values;
+ my $default_op = $param_name eq 'content' ? 'matches' : 'anyexact';
+ my $operator = $params->{"${param_name}_type"} || $default_op;
+
+ # Fields that are displayed as multi-selects are passed as arrays,
+ # so that they can properly search values that contain commas.
+ # However, other fields are sent as strings, so that they are properly
+ # split on commas if required.
+ my $field = $chart_fields->{$field_name};
+ my $pass_value;
+ if ( $field->is_select
+ or $field->name eq 'version'
+ or $field->name eq 'target_milestone')
+ {
+ $pass_value = \@values;
+ }
+ else {
+ $pass_value = join(',', @values);
}
- return @{$clause->children} ? $clause : undef;
+ $clause->add($field_name, $operator, $pass_value);
+ }
+ return @{$clause->children} ? $clause : undef;
}
sub _special_parse_bug_status {
- my ($self) = @_;
- my $params = $self->_params;
- return if !defined $params->{'bug_status'};
- # We want to allow the bug_status_type parameter to work normally,
- # meaning that this special code should only be activated if we are
- # doing the normal "anyexact" search on bug_status.
- return if (defined $params->{'bug_status_type'}
- and $params->{'bug_status_type'} ne 'anyexact');
-
- my @bug_status = $self->_param_array('bug_status');
- # Also include inactive bug statuses, as you can query them.
- my $legal_statuses = $self->_chart_fields->{'bug_status'}->legal_values;
-
- # If the status contains __open__ or __closed__, translate those
- # into their equivalent lists of open and closed statuses.
- if (grep { $_ eq '__open__' } @bug_status) {
- my @open = grep { $_->is_open } @$legal_statuses;
- @open = map { $_->name } @open;
- push(@bug_status, @open);
- }
- if (grep { $_ eq '__closed__' } @bug_status) {
- my @closed = grep { not $_->is_open } @$legal_statuses;
- @closed = map { $_->name } @closed;
- push(@bug_status, @closed);
- }
-
- @bug_status = uniq @bug_status;
- my $all = grep { $_ eq "__all__" } @bug_status;
- # This will also handle removing __open__ and __closed__ for us
- # (__all__ too, which is why we check for it above, first).
- @bug_status = _valid_values(\@bug_status, $legal_statuses);
-
- # If the user has selected every status, change to selecting none.
- # This is functionally equivalent, but quite a lot faster.
- if ($all or scalar(@bug_status) == scalar(@$legal_statuses)) {
- delete $params->{'bug_status'};
- }
- else {
- $params->{'bug_status'} = \@bug_status;
- }
+ my ($self) = @_;
+ my $params = $self->_params;
+ return if !defined $params->{'bug_status'};
+
+ # We want to allow the bug_status_type parameter to work normally,
+ # meaning that this special code should only be activated if we are
+ # doing the normal "anyexact" search on bug_status.
+ return
+ if (defined $params->{'bug_status_type'}
+ and $params->{'bug_status_type'} ne 'anyexact');
+
+ my @bug_status = $self->_param_array('bug_status');
+
+ # Also include inactive bug statuses, as you can query them.
+ my $legal_statuses = $self->_chart_fields->{'bug_status'}->legal_values;
+
+ # If the status contains __open__ or __closed__, translate those
+ # into their equivalent lists of open and closed statuses.
+ if (grep { $_ eq '__open__' } @bug_status) {
+ my @open = grep { $_->is_open } @$legal_statuses;
+ @open = map { $_->name } @open;
+ push(@bug_status, @open);
+ }
+ if (grep { $_ eq '__closed__' } @bug_status) {
+ my @closed = grep { not $_->is_open } @$legal_statuses;
+ @closed = map { $_->name } @closed;
+ push(@bug_status, @closed);
+ }
+
+ @bug_status = uniq @bug_status;
+ my $all = grep { $_ eq "__all__" } @bug_status;
+
+ # This will also handle removing __open__ and __closed__ for us
+ # (__all__ too, which is why we check for it above, first).
+ @bug_status = _valid_values(\@bug_status, $legal_statuses);
+
+ # If the user has selected every status, change to selecting none.
+ # This is functionally equivalent, but quite a lot faster.
+ if ($all or scalar(@bug_status) == scalar(@$legal_statuses)) {
+ delete $params->{'bug_status'};
+ }
+ else {
+ $params->{'bug_status'} = \@bug_status;
+ }
}
sub _special_parse_chfield {
- my ($self) = @_;
- my $params = $self->_params;
-
- my $date_from = trim(lc($params->{'chfieldfrom'} || ''));
- my $date_to = trim(lc($params->{'chfieldto'} || ''));
- $date_from = '' if $date_from eq 'now';
- $date_to = '' if $date_to eq 'now';
- my @fields = $self->_param_array('chfield');
- my $value_to = $params->{'chfieldvalue'};
- $value_to = '' if !defined $value_to;
-
- @fields = map { $_ eq '[Bug creation]' ? 'creation_ts' : $_ } @fields;
-
- return undef unless ($date_from ne '' || $date_to ne '' || $value_to ne '');
-
- my $clause = new Bugzilla::Search::Clause();
-
- # It is always safe and useful to push delta_ts into the charts
- # if there is a "from" date specified. It doesn't conflict with
- # searching [Bug creation], because a bug's delta_ts is set to
- # its creation_ts when it is created. So this just gives the
- # database an additional index to possibly choose, on a table that
- # is smaller than bugs_activity.
- if ($date_from ne '') {
- $clause->add('delta_ts', 'greaterthaneq', $date_from);
- }
- # It's not normally safe to do it for "to" dates, though--"chfieldto" means
- # "a field that changed before this date", and delta_ts could be either
- # later or earlier than that, if we're searching for the time that a field
- # changed. However, chfieldto all by itself, without any chfieldvalue or
- # chfield, means "just search delta_ts", and so we still want that to
- # work.
- if ($date_to ne '' and !@fields and $value_to eq '') {
- $clause->add('delta_ts', 'lessthaneq', $date_to);
- }
-
- # chfieldto is supposed to be a relative date or a date of the form
- # YYYY-MM-DD, i.e. without the time appended to it. We append the
- # time ourselves so that the end date is correctly taken into account.
- $date_to .= ' 23:59:59' if $date_to =~ /^\d{4}-\d{1,2}-\d{1,2}$/;
-
- my $join_clause = new Bugzilla::Search::Clause('OR');
-
- foreach my $field (@fields) {
- my $sub_clause = new Bugzilla::Search::ClauseGroup();
- $sub_clause->add(condition($field, 'changedto', $value_to)) if $value_to ne '';
- $sub_clause->add(condition($field, 'changedafter', $date_from)) if $date_from ne '';
- $sub_clause->add(condition($field, 'changedbefore', $date_to)) if $date_to ne '';
- $join_clause->add($sub_clause);
- }
- $clause->add($join_clause);
-
- return @{$clause->children} ? $clause : undef;
+ my ($self) = @_;
+ my $params = $self->_params;
+
+ my $date_from = trim(lc($params->{'chfieldfrom'} || ''));
+ my $date_to = trim(lc($params->{'chfieldto'} || ''));
+ $date_from = '' if $date_from eq 'now';
+ $date_to = '' if $date_to eq 'now';
+ my @fields = $self->_param_array('chfield');
+ my $value_to = $params->{'chfieldvalue'};
+ $value_to = '' if !defined $value_to;
+
+ @fields = map { $_ eq '[Bug creation]' ? 'creation_ts' : $_ } @fields;
+
+ return undef unless ($date_from ne '' || $date_to ne '' || $value_to ne '');
+
+ my $clause = new Bugzilla::Search::Clause();
+
+ # It is always safe and useful to push delta_ts into the charts
+ # if there is a "from" date specified. It doesn't conflict with
+ # searching [Bug creation], because a bug's delta_ts is set to
+ # its creation_ts when it is created. So this just gives the
+ # database an additional index to possibly choose, on a table that
+ # is smaller than bugs_activity.
+ if ($date_from ne '') {
+ $clause->add('delta_ts', 'greaterthaneq', $date_from);
+ }
+
+ # It's not normally safe to do it for "to" dates, though--"chfieldto" means
+ # "a field that changed before this date", and delta_ts could be either
+ # later or earlier than that, if we're searching for the time that a field
+ # changed. However, chfieldto all by itself, without any chfieldvalue or
+ # chfield, means "just search delta_ts", and so we still want that to
+ # work.
+ if ($date_to ne '' and !@fields and $value_to eq '') {
+ $clause->add('delta_ts', 'lessthaneq', $date_to);
+ }
+
+ # chfieldto is supposed to be a relative date or a date of the form
+ # YYYY-MM-DD, i.e. without the time appended to it. We append the
+ # time ourselves so that the end date is correctly taken into account.
+ $date_to .= ' 23:59:59' if $date_to =~ /^\d{4}-\d{1,2}-\d{1,2}$/;
+
+ my $join_clause = new Bugzilla::Search::Clause('OR');
+
+ foreach my $field (@fields) {
+ my $sub_clause = new Bugzilla::Search::ClauseGroup();
+ $sub_clause->add(condition($field, 'changedto', $value_to)) if $value_to ne '';
+ $sub_clause->add(condition($field, 'changedafter', $date_from))
+ if $date_from ne '';
+ $sub_clause->add(condition($field, 'changedbefore', $date_to))
+ if $date_to ne '';
+ $join_clause->add($sub_clause);
+ }
+ $clause->add($join_clause);
+
+ return @{$clause->children} ? $clause : undef;
}
sub _special_parse_deadline {
- my ($self) = @_;
- my $params = $self->_params;
+ my ($self) = @_;
+ my $params = $self->_params;
- my $clause = new Bugzilla::Search::Clause();
- if (my $from = $params->{'deadlinefrom'}) {
- $clause->add('deadline', 'greaterthaneq', $from);
- }
- if (my $to = $params->{'deadlineto'}) {
- $clause->add('deadline', 'lessthaneq', $to);
- }
+ my $clause = new Bugzilla::Search::Clause();
+ if (my $from = $params->{'deadlinefrom'}) {
+ $clause->add('deadline', 'greaterthaneq', $from);
+ }
+ if (my $to = $params->{'deadlineto'}) {
+ $clause->add('deadline', 'lessthaneq', $to);
+ }
- return @{$clause->children} ? $clause : undef;
+ return @{$clause->children} ? $clause : undef;
}
sub _special_parse_email {
- my ($self) = @_;
- my $params = $self->_params;
-
- my @email_params = grep { $_ =~ /^email\d+$/ } keys %$params;
-
- my $clause = new Bugzilla::Search::Clause();
- foreach my $param (@email_params) {
- $param =~ /(\d+)$/;
- my $id = $1;
- my $email = trim($params->{"email$id"});
- next if !$email;
- my $type = $params->{"emailtype$id"} || 'anyexact';
- # for backward compatibility
- $type = "equals" if $type eq "exact";
-
- my $or_clause = new Bugzilla::Search::Clause('OR');
- foreach my $field (qw(assigned_to reporter cc qa_contact)) {
- if ($params->{"email$field$id"}) {
- $or_clause->add($field, $type, $email);
- }
- }
- if ($params->{"emaillongdesc$id"}) {
- $or_clause->add("commenter", $type, $email);
- }
-
- $clause->add($or_clause);
+ my ($self) = @_;
+ my $params = $self->_params;
+
+ my @email_params = grep { $_ =~ /^email\d+$/ } keys %$params;
+
+ my $clause = new Bugzilla::Search::Clause();
+ foreach my $param (@email_params) {
+ $param =~ /(\d+)$/;
+ my $id = $1;
+ my $email = trim($params->{"email$id"});
+ next if !$email;
+ my $type = $params->{"emailtype$id"} || 'anyexact';
+
+ # for backward compatibility
+ $type = "equals" if $type eq "exact";
+
+ my $or_clause = new Bugzilla::Search::Clause('OR');
+ foreach my $field (qw(assigned_to reporter cc qa_contact)) {
+ if ($params->{"email$field$id"}) {
+ $or_clause->add($field, $type, $email);
+ }
+ }
+ if ($params->{"emaillongdesc$id"}) {
+ $or_clause->add("commenter", $type, $email);
}
- return @{$clause->children} ? $clause : undef;
+ $clause->add($or_clause);
+ }
+
+ return @{$clause->children} ? $clause : undef;
}
sub _special_parse_resolution {
- my ($self) = @_;
- my $params = $self->_params;
- return if !defined $params->{'resolution'};
-
- my @resolution = $self->_param_array('resolution');
- my $legal_resolutions = $self->_chart_fields->{resolution}->legal_values;
- @resolution = _valid_values(\@resolution, $legal_resolutions, '---');
- if (scalar(@resolution) == scalar(@$legal_resolutions)) {
- delete $params->{'resolution'};
- }
+ my ($self) = @_;
+ my $params = $self->_params;
+ return if !defined $params->{'resolution'};
+
+ my @resolution = $self->_param_array('resolution');
+ my $legal_resolutions = $self->_chart_fields->{resolution}->legal_values;
+ @resolution = _valid_values(\@resolution, $legal_resolutions, '---');
+ if (scalar(@resolution) == scalar(@$legal_resolutions)) {
+ delete $params->{'resolution'};
+ }
}
sub _valid_values {
- my ($input, $valid, $extra_value) = @_;
- my @result;
- foreach my $item (@$input) {
- $item = trim($item);
- if (defined $extra_value and $item eq $extra_value) {
- push(@result, $item);
- }
- elsif (grep { $_->name eq $item } @$valid) {
- push(@result, $item);
- }
+ my ($input, $valid, $extra_value) = @_;
+ my @result;
+ foreach my $item (@$input) {
+ $item = trim($item);
+ if (defined $extra_value and $item eq $extra_value) {
+ push(@result, $item);
}
- return @result;
+ elsif (grep { $_->name eq $item } @$valid) {
+ push(@result, $item);
+ }
+ }
+ return @result;
}
######################################
@@ -1680,239 +1638,247 @@ sub _valid_values {
######################################
sub _charts_to_conditions {
- my ($self) = @_;
-
- my $clause = $self->_charts;
- my @joins;
- $clause->walk_conditions(sub {
- my ($clause, $condition) = @_;
- return if !$condition->translated;
- push(@joins, @{ $condition->translated->{joins} });
- });
- return (\@joins, $clause);
+ my ($self) = @_;
+
+ my $clause = $self->_charts;
+ my @joins;
+ $clause->walk_conditions(sub {
+ my ($clause, $condition) = @_;
+ return if !$condition->translated;
+ push(@joins, @{$condition->translated->{joins}});
+ });
+ return (\@joins, $clause);
}
sub _charts {
- my ($self) = @_;
-
- my $clause = $self->_params_to_data_structure();
- my $chart_id = 0;
- $clause->walk_conditions(sub { $self->_handle_chart($chart_id++, @_) });
- return $clause;
+ my ($self) = @_;
+
+ my $clause = $self->_params_to_data_structure();
+ my $chart_id = 0;
+ $clause->walk_conditions(sub { $self->_handle_chart($chart_id++, @_) });
+ return $clause;
}
sub _params_to_data_structure {
- my ($self) = @_;
-
- # First we get the "special" charts, representing all the normal
- # fields on the search page. This may modify _params, so it needs to
- # happen first.
- my $clause = $self->_special_charts;
-
- # Then we process the old Boolean Charts input format.
- $clause->add( $self->_boolean_charts );
-
- # And then process the modern "custom search" format.
- $clause->add( $self->_custom_search );
-
- return $clause;
+ my ($self) = @_;
+
+ # First we get the "special" charts, representing all the normal
+ # fields on the search page. This may modify _params, so it needs to
+ # happen first.
+ my $clause = $self->_special_charts;
+
+ # Then we process the old Boolean Charts input format.
+ $clause->add($self->_boolean_charts);
+
+ # And then process the modern "custom search" format.
+ $clause->add($self->_custom_search);
+
+ return $clause;
}
sub _boolean_charts {
- my ($self) = @_;
-
- my $params = $self->_params;
- my @param_list = keys %$params;
-
- my @all_field_params = grep { /^field-?\d+/ } @param_list;
- my @chart_ids = map { /^field(-?\d+)/; $1 } @all_field_params;
- @chart_ids = sort { $a <=> $b } uniq @chart_ids;
-
- my $clause = new Bugzilla::Search::Clause();
- foreach my $chart_id (@chart_ids) {
- my @all_and = grep { /^field$chart_id-\d+/ } @param_list;
- my @and_ids = map { /^field$chart_id-(\d+)/; $1 } @all_and;
- @and_ids = sort { $a <=> $b } uniq @and_ids;
-
- my $and_clause = new Bugzilla::Search::Clause();
- foreach my $and_id (@and_ids) {
- my @all_or = grep { /^field$chart_id-$and_id-\d+/ } @param_list;
- my @or_ids = map { /^field$chart_id-$and_id-(\d+)/; $1 } @all_or;
- @or_ids = sort { $a <=> $b } uniq @or_ids;
-
- my $or_clause = new Bugzilla::Search::Clause('OR');
- foreach my $or_id (@or_ids) {
- my $identifier = "$chart_id-$and_id-$or_id";
- my $field = $params->{"field$identifier"};
- my $operator = $params->{"type$identifier"};
- my $value = $params->{"value$identifier"};
- # no-value operators ignore the value, however a value needs to be set
- $value = ' ' if $operator && grep { $_ eq $operator } NO_VALUE_OPERATORS;
- $or_clause->add($field, $operator, $value);
- }
- $and_clause->add($or_clause);
- $and_clause->negate(1) if $params->{"negate$chart_id"};
- }
- $clause->add($and_clause);
+ my ($self) = @_;
+
+ my $params = $self->_params;
+ my @param_list = keys %$params;
+
+ my @all_field_params = grep {/^field-?\d+/} @param_list;
+ my @chart_ids = map { /^field(-?\d+)/; $1 } @all_field_params;
+ @chart_ids = sort { $a <=> $b } uniq @chart_ids;
+
+ my $clause = new Bugzilla::Search::Clause();
+ foreach my $chart_id (@chart_ids) {
+ my @all_and = grep {/^field$chart_id-\d+/} @param_list;
+ my @and_ids = map { /^field$chart_id-(\d+)/; $1 } @all_and;
+ @and_ids = sort { $a <=> $b } uniq @and_ids;
+
+ my $and_clause = new Bugzilla::Search::Clause();
+ foreach my $and_id (@and_ids) {
+ my @all_or = grep {/^field$chart_id-$and_id-\d+/} @param_list;
+ my @or_ids = map { /^field$chart_id-$and_id-(\d+)/; $1 } @all_or;
+ @or_ids = sort { $a <=> $b } uniq @or_ids;
+
+ my $or_clause = new Bugzilla::Search::Clause('OR');
+ foreach my $or_id (@or_ids) {
+ my $identifier = "$chart_id-$and_id-$or_id";
+ my $field = $params->{"field$identifier"};
+ my $operator = $params->{"type$identifier"};
+ my $value = $params->{"value$identifier"};
+
+ # no-value operators ignore the value, however a value needs to be set
+ $value = ' ' if $operator && grep { $_ eq $operator } NO_VALUE_OPERATORS;
+ $or_clause->add($field, $operator, $value);
+ }
+ $and_clause->add($or_clause);
+ $and_clause->negate(1) if $params->{"negate$chart_id"};
}
+ $clause->add($and_clause);
+ }
- return @{$clause->children} ? $clause : undef;
+ return @{$clause->children} ? $clause : undef;
}
sub _custom_search {
- my ($self) = @_;
- my $params = $self->_params;
-
- my @field_ids = $self->_field_ids;
- return unless scalar @field_ids;
-
- my $joiner = $params->{j_top} || '';
- my $current_clause = $joiner eq 'AND_G'
+ my ($self) = @_;
+ my $params = $self->_params;
+
+ my @field_ids = $self->_field_ids;
+ return unless scalar @field_ids;
+
+ my $joiner = $params->{j_top} || '';
+ my $current_clause
+ = $joiner eq 'AND_G'
+ ? new Bugzilla::Search::ClauseGroup()
+ : new Bugzilla::Search::Clause($joiner);
+
+ my @clause_stack;
+ foreach my $id (@field_ids) {
+ my $field = $params->{"f$id"};
+ if ($field eq 'OP') {
+ my $joiner = $params->{"j$id"} || '';
+ my $new_clause
+ = $joiner eq 'AND_G'
? new Bugzilla::Search::ClauseGroup()
: new Bugzilla::Search::Clause($joiner);
-
- my @clause_stack;
- foreach my $id (@field_ids) {
- my $field = $params->{"f$id"};
- if ($field eq 'OP') {
- my $joiner = $params->{"j$id"} || '';
- my $new_clause = $joiner eq 'AND_G'
- ? new Bugzilla::Search::ClauseGroup()
- : new Bugzilla::Search::Clause($joiner);
- $new_clause->negate($params->{"n$id"});
- $current_clause->add($new_clause);
- push(@clause_stack, $current_clause);
- $current_clause = $new_clause;
- next;
- }
- if ($field eq 'CP') {
- $current_clause = pop @clause_stack;
- ThrowCodeError('search_cp_without_op', { id => $id })
- if !$current_clause;
- next;
- }
-
- my $operator = $params->{"o$id"};
- my $value = $params->{"v$id"};
- # no-value operators ignore the value, however a value needs to be set
- $value = ' ' if $operator && grep { $_ eq $operator } NO_VALUE_OPERATORS;
- my $condition = condition($field, $operator, $value);
- $condition->negate($params->{"n$id"});
- $current_clause->add($condition);
+ $new_clause->negate($params->{"n$id"});
+ $current_clause->add($new_clause);
+ push(@clause_stack, $current_clause);
+ $current_clause = $new_clause;
+ next;
}
-
- # We allow people to specify more OPs than CPs, so at the end of the
- # loop our top clause may be still in the stack instead of being
- # $current_clause.
- return $clause_stack[0] || $current_clause;
+ if ($field eq 'CP') {
+ $current_clause = pop @clause_stack;
+ ThrowCodeError('search_cp_without_op', {id => $id}) if !$current_clause;
+ next;
+ }
+
+ my $operator = $params->{"o$id"};
+ my $value = $params->{"v$id"};
+
+ # no-value operators ignore the value, however a value needs to be set
+ $value = ' ' if $operator && grep { $_ eq $operator } NO_VALUE_OPERATORS;
+ my $condition = condition($field, $operator, $value);
+ $condition->negate($params->{"n$id"});
+ $current_clause->add($condition);
+ }
+
+ # We allow people to specify more OPs than CPs, so at the end of the
+ # loop our top clause may be still in the stack instead of being
+ # $current_clause.
+ return $clause_stack[0] || $current_clause;
}
sub _field_ids {
- my ($self) = @_;
- my $params = $self->_params;
- my @param_list = keys %$params;
-
- my @field_params = grep { /^f\d+$/ } @param_list;
- my @field_ids = map { /(\d+)/; $1 } @field_params;
- @field_ids = sort { $a <=> $b } @field_ids;
- return @field_ids;
+ my ($self) = @_;
+ my $params = $self->_params;
+ my @param_list = keys %$params;
+
+ my @field_params = grep {/^f\d+$/} @param_list;
+ my @field_ids = map { /(\d+)/; $1 } @field_params;
+ @field_ids = sort { $a <=> $b } @field_ids;
+ return @field_ids;
}
sub _handle_chart {
- my ($self, $chart_id, $clause, $condition) = @_;
- my $dbh = Bugzilla->dbh;
- my $params = $self->_params;
- my ($field, $operator, $value) = $condition->fov;
- return if (!defined $field or !defined $operator or !defined $value);
- $field = FIELD_MAP->{$field} || $field;
-
- my ($string_value, $orig_value);
- state $is_mysql = $dbh->isa('Bugzilla::DB::Mysql') ? 1 : 0;
-
- if (ref $value eq 'ARRAY') {
- # Trim input and ignore blank values.
- @$value = map { trim($_) } @$value;
- @$value = grep { defined $_ and $_ ne '' } @$value;
- return if !@$value;
- $orig_value = join(',', @$value);
- if ($field eq 'longdesc' && $is_mysql) {
- @$value = map { _convert_unicode_characters($_) } @$value;
- }
- $string_value = join(',', @$value);
- }
- else {
- return if $value eq '';
- $orig_value = $value;
- if ($field eq 'longdesc' && $is_mysql) {
- $value = _convert_unicode_characters($value);
- }
- $string_value = $value;
- }
-
- $self->_chart_fields->{$field}
- or ThrowCodeError("invalid_field_name", { field => $field });
- trick_taint($field);
-
- # This is the field as you'd reference it in a SQL statement.
- my $full_field = $field =~ /\./ ? $field : "bugs.$field";
-
- # "value" and "quoted" are for search functions that always operate
- # on a scalar string and never care if they were passed multiple
- # parameters. If the user does pass multiple parameters, they will
- # become a space-separated string for those search functions.
- #
- # all_values is for search functions that do operate
- # on multiple values, like anyexact.
-
- my %search_args = (
- chart_id => $chart_id,
- sequence => $chart_id,
- field => $field,
- full_field => $full_field,
- operator => $operator,
- value => $string_value,
- all_values => $value,
- joins => [],
- bugs_table => 'bugs',
- table_suffix => '',
- condition => $condition,
- );
- $clause->update_search_args(\%search_args);
-
- $search_args{quoted} = $self->_quote_unless_numeric(\%search_args);
- # This should add a "term" selement to %search_args.
- $self->do_search_function(\%search_args);
-
- # If term is left empty, then this means the criteria
- # has no effect and can be ignored.
- return unless $search_args{term};
-
- # All the things here that don't get pulled out of
- # %search_args are their original values before
- # do_search_function modified them.
- $self->search_description({
- field => $field, type => $operator,
- value => $orig_value, term => $search_args{term},
- });
-
- foreach my $join (@{ $search_args{joins} }) {
- $join->{bugs_table} = $search_args{bugs_table};
- $join->{table_suffix} = $search_args{table_suffix};
- }
-
- $condition->translated(\%search_args);
+ my ($self, $chart_id, $clause, $condition) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $params = $self->_params;
+ my ($field, $operator, $value) = $condition->fov;
+ return if (!defined $field or !defined $operator or !defined $value);
+ $field = FIELD_MAP->{$field} || $field;
+
+ my ($string_value, $orig_value);
+ state $is_mysql = $dbh->isa('Bugzilla::DB::Mysql') ? 1 : 0;
+
+ if (ref $value eq 'ARRAY') {
+
+ # Trim input and ignore blank values.
+ @$value = map { trim($_) } @$value;
+ @$value = grep { defined $_ and $_ ne '' } @$value;
+ return if !@$value;
+ $orig_value = join(',', @$value);
+ if ($field eq 'longdesc' && $is_mysql) {
+ @$value = map { _convert_unicode_characters($_) } @$value;
+ }
+ $string_value = join(',', @$value);
+ }
+ else {
+ return if $value eq '';
+ $orig_value = $value;
+ if ($field eq 'longdesc' && $is_mysql) {
+ $value = _convert_unicode_characters($value);
+ }
+ $string_value = $value;
+ }
+
+ $self->_chart_fields->{$field}
+ or ThrowCodeError("invalid_field_name", {field => $field});
+ trick_taint($field);
+
+ # This is the field as you'd reference it in a SQL statement.
+ my $full_field = $field =~ /\./ ? $field : "bugs.$field";
+
+ # "value" and "quoted" are for search functions that always operate
+ # on a scalar string and never care if they were passed multiple
+ # parameters. If the user does pass multiple parameters, they will
+ # become a space-separated string for those search functions.
+ #
+ # all_values is for search functions that do operate
+ # on multiple values, like anyexact.
+
+ my %search_args = (
+ chart_id => $chart_id,
+ sequence => $chart_id,
+ field => $field,
+ full_field => $full_field,
+ operator => $operator,
+ value => $string_value,
+ all_values => $value,
+ joins => [],
+ bugs_table => 'bugs',
+ table_suffix => '',
+ condition => $condition,
+ );
+ $clause->update_search_args(\%search_args);
+
+ $search_args{quoted} = $self->_quote_unless_numeric(\%search_args);
+
+ # This should add a "term" selement to %search_args.
+ $self->do_search_function(\%search_args);
+
+ # If term is left empty, then this means the criteria
+ # has no effect and can be ignored.
+ return unless $search_args{term};
+
+ # All the things here that don't get pulled out of
+ # %search_args are their original values before
+ # do_search_function modified them.
+ $self->search_description({
+ field => $field,
+ type => $operator,
+ value => $orig_value,
+ term => $search_args{term},
+ });
+
+ foreach my $join (@{$search_args{joins}}) {
+ $join->{bugs_table} = $search_args{bugs_table};
+ $join->{table_suffix} = $search_args{table_suffix};
+ }
+
+ $condition->translated(\%search_args);
}
# XXX - This is a hack for MySQL which doesn't understand Unicode characters
# above U+FFFF, see Bugzilla::Comment::_check_thetext(). This hack can go away
# once we require MySQL 5.5.3 and use utf8mb4.
sub _convert_unicode_characters {
- my $string = shift;
+ my $string = shift;
- # Perl 5.13.8 and older complain about non-characters.
- no warnings 'utf8';
- $string =~ s/([\x{10000}-\x{10FFFF}])/"\x{FDD0}[" . uc(sprintf('U+%04x', ord($1))) . "]\x{FDD1}"/eg;
- return $string;
+ # Perl 5.13.8 and older complain about non-characters.
+ no warnings 'utf8';
+ $string
+ =~ s/([\x{10000}-\x{10FFFF}])/"\x{FDD0}[" . uc(sprintf('U+%04x', ord($1))) . "]\x{FDD1}"/eg;
+ return $string;
}
##################################
@@ -1922,121 +1888,126 @@ sub _convert_unicode_characters {
# This takes information about the current boolean chart and translates
# it into SQL, using the constants at the top of this file.
sub do_search_function {
- my ($self, $args) = @_;
- my ($field, $operator) = @$args{qw(field operator)};
-
- if (my $parse_func = SPECIAL_PARSING->{$field}) {
- $self->$parse_func($args);
- # Some parsing functions set $term, though most do not.
- # For the ones that set $term, we don't need to do any further
- # parsing.
- return if $args->{term};
- }
-
- my $operator_field_override = $self->_get_operator_field_override();
- my $override = $operator_field_override->{$field};
- # Attachment fields get special handling, if they don't have a specific
- # individual override.
- if (!$override and $field =~ /^attachments\./) {
- $override = $operator_field_override->{attachments};
- }
- # If there's still no override, check for an override on the field's type.
- if (!$override) {
- my $field_obj = $self->_chart_fields->{$field};
- $override = $operator_field_override->{$field_obj->type};
- }
-
- if ($override) {
- my $search_func = $self->_pick_override_function($override, $operator);
- $self->$search_func($args) if $search_func;
- }
+ my ($self, $args) = @_;
+ my ($field, $operator) = @$args{qw(field operator)};
- # Some search functions set $term, and some don't. For the ones that
- # don't (or for fields that don't have overrides) we now call the
- # direct operator function from OPERATORS.
- if (!defined $args->{term}) {
- $self->_do_operator_function($args);
- }
-
- if (!defined $args->{term}) {
- # This field and this type don't work together. Generally,
- # this should never be reached, because it should be handled
- # explicitly by OPERATOR_FIELD_OVERRIDE.
- ThrowUserError("search_field_operator_invalid",
- { field => $field, operator => $operator });
- }
+ if (my $parse_func = SPECIAL_PARSING->{$field}) {
+ $self->$parse_func($args);
+
+ # Some parsing functions set $term, though most do not.
+ # For the ones that set $term, we don't need to do any further
+ # parsing.
+ return if $args->{term};
+ }
+
+ my $operator_field_override = $self->_get_operator_field_override();
+ my $override = $operator_field_override->{$field};
+
+ # Attachment fields get special handling, if they don't have a specific
+ # individual override.
+ if (!$override and $field =~ /^attachments\./) {
+ $override = $operator_field_override->{attachments};
+ }
+
+ # If there's still no override, check for an override on the field's type.
+ if (!$override) {
+ my $field_obj = $self->_chart_fields->{$field};
+ $override = $operator_field_override->{$field_obj->type};
+ }
+
+ if ($override) {
+ my $search_func = $self->_pick_override_function($override, $operator);
+ $self->$search_func($args) if $search_func;
+ }
+
+ # Some search functions set $term, and some don't. For the ones that
+ # don't (or for fields that don't have overrides) we now call the
+ # direct operator function from OPERATORS.
+ if (!defined $args->{term}) {
+ $self->_do_operator_function($args);
+ }
+
+ if (!defined $args->{term}) {
+
+ # This field and this type don't work together. Generally,
+ # this should never be reached, because it should be handled
+ # explicitly by OPERATOR_FIELD_OVERRIDE.
+ ThrowUserError("search_field_operator_invalid",
+ {field => $field, operator => $operator});
+ }
}
# A helper for various search functions that need to run operator
# functions directly.
sub _do_operator_function {
- my ($self, $func_args) = @_;
- my $operator = $func_args->{operator};
- my $operator_func = OPERATORS->{$operator}
- || ThrowCodeError("search_field_operator_unsupported",
- { operator => $operator });
- $self->$operator_func($func_args);
+ my ($self, $func_args) = @_;
+ my $operator = $func_args->{operator};
+ my $operator_func
+ = OPERATORS->{$operator}
+ || ThrowCodeError("search_field_operator_unsupported",
+ {operator => $operator});
+ $self->$operator_func($func_args);
}
sub _reverse_operator {
- my ($self, $operator) = @_;
- my $reverse = OPERATOR_REVERSE->{$operator};
- return $reverse if $reverse;
- if ($operator =~ s/^not//) {
- return $operator;
- }
- return "not$operator";
+ my ($self, $operator) = @_;
+ my $reverse = OPERATOR_REVERSE->{$operator};
+ return $reverse if $reverse;
+ if ($operator =~ s/^not//) {
+ return $operator;
+ }
+ return "not$operator";
}
sub _pick_override_function {
- my ($self, $override, $operator) = @_;
- my $search_func = $override->{$operator};
-
- if (!$search_func) {
- # If we don't find an override for one specific operator,
- # then there are some special override types:
- # _non_changed: For any operator that doesn't have the word
- # "changed" in it
- # _default: Overrides all operators that aren't explicitly specified.
- if ($override->{_non_changed} and $operator !~ /changed/) {
- $search_func = $override->{_non_changed};
- }
- elsif ($override->{_default}) {
- $search_func = $override->{_default};
- }
+ my ($self, $override, $operator) = @_;
+ my $search_func = $override->{$operator};
+
+ if (!$search_func) {
+
+ # If we don't find an override for one specific operator,
+ # then there are some special override types:
+ # _non_changed: For any operator that doesn't have the word
+ # "changed" in it
+ # _default: Overrides all operators that aren't explicitly specified.
+ if ($override->{_non_changed} and $operator !~ /changed/) {
+ $search_func = $override->{_non_changed};
}
+ elsif ($override->{_default}) {
+ $search_func = $override->{_default};
+ }
+ }
- return $search_func;
+ return $search_func;
}
sub _get_operator_field_override {
- my $self = shift;
- my $cache = Bugzilla->request_cache;
+ my $self = shift;
+ my $cache = Bugzilla->request_cache;
- return $cache->{operator_field_override}
- if defined $cache->{operator_field_override};
+ return $cache->{operator_field_override}
+ if defined $cache->{operator_field_override};
- my %operator_field_override = %{ OPERATOR_FIELD_OVERRIDE() };
- Bugzilla::Hook::process('search_operator_field_override',
- { search => $self,
- operators => \%operator_field_override });
+ my %operator_field_override = %{OPERATOR_FIELD_OVERRIDE()};
+ Bugzilla::Hook::process('search_operator_field_override',
+ {search => $self, operators => \%operator_field_override});
- $cache->{operator_field_override} = \%operator_field_override;
- return $cache->{operator_field_override};
+ $cache->{operator_field_override} = \%operator_field_override;
+ return $cache->{operator_field_override};
}
sub _get_column_joins {
- my $self = shift;
- my $cache = Bugzilla->request_cache;
+ my $self = shift;
+ my $cache = Bugzilla->request_cache;
- return $cache->{column_joins} if defined $cache->{column_joins};
+ return $cache->{column_joins} if defined $cache->{column_joins};
- my %column_joins = %{ $self->COLUMN_JOINS() };
- Bugzilla::Hook::process('buglist_column_joins',
- { column_joins => \%column_joins });
+ my %column_joins = %{$self->COLUMN_JOINS()};
+ Bugzilla::Hook::process('buglist_column_joins',
+ {column_joins => \%column_joins});
- $cache->{column_joins} = \%column_joins;
- return $cache->{column_joins};
+ $cache->{column_joins} = \%column_joins;
+ return $cache->{column_joins};
}
###########################
@@ -2048,47 +2019,49 @@ sub _get_column_joins {
# is just a performance optimization, but on SQLite it actually changes
# the behavior of some searches.
sub _quote_unless_numeric {
- my ($self, $args, $value) = @_;
- if (!defined $value) {
- $value = $args->{value};
- }
- my ($field, $operator) = @$args{qw(field operator)};
-
- my $numeric_operator = !grep { $_ eq $operator } NON_NUMERIC_OPERATORS;
- my $numeric_field = $self->_chart_fields->{$field}->is_numeric;
- my $numeric_value = ($value =~ NUMBER_REGEX) ? 1 : 0;
- my $is_numeric = $numeric_operator && $numeric_field && $numeric_value;
-
- # These operators are really numeric operators with numeric fields.
- $numeric_operator = grep { $_ eq $operator } keys %{ SIMPLE_OPERATORS() };
-
- if ($is_numeric) {
- my $quoted = $value;
- trick_taint($quoted);
- return $quoted;
- }
- elsif ($numeric_field && !$numeric_value && $numeric_operator) {
- ThrowUserError('number_not_numeric', { field => $field, num => $value });
- }
- return Bugzilla->dbh->quote($value);
+ my ($self, $args, $value) = @_;
+ if (!defined $value) {
+ $value = $args->{value};
+ }
+ my ($field, $operator) = @$args{qw(field operator)};
+
+ my $numeric_operator = !grep { $_ eq $operator } NON_NUMERIC_OPERATORS;
+ my $numeric_field = $self->_chart_fields->{$field}->is_numeric;
+ my $numeric_value = ($value =~ NUMBER_REGEX) ? 1 : 0;
+ my $is_numeric = $numeric_operator && $numeric_field && $numeric_value;
+
+ # These operators are really numeric operators with numeric fields.
+ $numeric_operator = grep { $_ eq $operator } keys %{SIMPLE_OPERATORS()};
+
+ if ($is_numeric) {
+ my $quoted = $value;
+ trick_taint($quoted);
+ return $quoted;
+ }
+ elsif ($numeric_field && !$numeric_value && $numeric_operator) {
+ ThrowUserError('number_not_numeric', {field => $field, num => $value});
+ }
+ return Bugzilla->dbh->quote($value);
}
sub build_subselect {
- my ($outer, $inner, $table, $cond, $negate) = @_;
- if ($table =~ /\battach_data\b/) {
- # It takes a long time to scan the whole attach_data table
- # unconditionally, so we return the subselect and let the DB optimizer
- # restrict the search based on other search criteria.
- my $not = $negate ? "NOT" : "";
- return "$outer $not IN (SELECT DISTINCT $inner FROM $table WHERE $cond)";
- }
- # Execute subselects immediately to avoid dependent subqueries, which are
- # large performance hits on MySql
- my $q = "SELECT DISTINCT $inner FROM $table WHERE $cond";
- my $dbh = Bugzilla->dbh;
- my $list = $dbh->selectcol_arrayref($q);
- return $negate ? "1=1" : "1=2" unless @$list;
- return $dbh->sql_in($outer, $list, $negate);
+ my ($outer, $inner, $table, $cond, $negate) = @_;
+ if ($table =~ /\battach_data\b/) {
+
+ # It takes a long time to scan the whole attach_data table
+ # unconditionally, so we return the subselect and let the DB optimizer
+ # restrict the search based on other search criteria.
+ my $not = $negate ? "NOT" : "";
+ return "$outer $not IN (SELECT DISTINCT $inner FROM $table WHERE $cond)";
+ }
+
+ # Execute subselects immediately to avoid dependent subqueries, which are
+ # large performance hits on MySql
+ my $q = "SELECT DISTINCT $inner FROM $table WHERE $cond";
+ my $dbh = Bugzilla->dbh;
+ my $list = $dbh->selectcol_arrayref($q);
+ return $negate ? "1=1" : "1=2" unless @$list;
+ return $dbh->sql_in($outer, $list, $negate);
}
# Used by anyexact to get the list of input values. This allows us to
@@ -2096,68 +2069,69 @@ sub build_subselect {
# still accept string values for the boolean charts (and split them on
# commas).
sub _all_values {
- my ($self, $args, $split_on) = @_;
- $split_on ||= qr/[\s,]+/;
- my $dbh = Bugzilla->dbh;
- my $all_values = $args->{all_values};
-
- my @array;
- if (ref $all_values eq 'ARRAY') {
- @array = @$all_values;
- }
- else {
- @array = split($split_on, $all_values);
- @array = map { trim($_) } @array;
- @array = grep { defined $_ and $_ ne '' } @array;
- }
-
- if ($args->{field} eq 'resolution') {
- @array = map { $_ eq '---' ? '' : $_ } @array;
- }
-
- return @array;
+ my ($self, $args, $split_on) = @_;
+ $split_on ||= qr/[\s,]+/;
+ my $dbh = Bugzilla->dbh;
+ my $all_values = $args->{all_values};
+
+ my @array;
+ if (ref $all_values eq 'ARRAY') {
+ @array = @$all_values;
+ }
+ else {
+ @array = split($split_on, $all_values);
+ @array = map { trim($_) } @array;
+ @array = grep { defined $_ and $_ ne '' } @array;
+ }
+
+ if ($args->{field} eq 'resolution') {
+ @array = map { $_ eq '---' ? '' : $_ } @array;
+ }
+
+ return @array;
}
# Support for "any/all/nowordssubstr" comparison type ("words as substrings")
sub _substring_terms {
- my ($self, $args) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($self, $args) = @_;
+ my $dbh = Bugzilla->dbh;
- # We don't have to (or want to) use _all_values, because we'd just
- # split each term on spaces and commas anyway.
- my @words = split(/[\s,]+/, $args->{value});
- @words = grep { defined $_ and $_ ne '' } @words;
- my @terms = map { $dbh->sql_ilike($_, $args->{full_field}) } @words;
- return @terms;
+ # We don't have to (or want to) use _all_values, because we'd just
+ # split each term on spaces and commas anyway.
+ my @words = split(/[\s,]+/, $args->{value});
+ @words = grep { defined $_ and $_ ne '' } @words;
+ my @terms = map { $dbh->sql_ilike($_, $args->{full_field}) } @words;
+ return @terms;
}
sub _word_terms {
- my ($self, $args) = @_;
- my $dbh = Bugzilla->dbh;
-
- my @values = split(/[\s,]+/, $args->{value});
- @values = grep { defined $_ and $_ ne '' } @values;
- my @substring_terms = $self->_substring_terms($args);
-
- my @terms;
- my $start = $dbh->WORD_START;
- my $end = $dbh->WORD_END;
- foreach my $word (@values) {
- my $regex = $start . quotemeta($word) . $end;
- my $quoted = $dbh->quote($regex);
- # We don't have to check the regexp, because we escaped it, so we're
- # sure it's valid.
- my $regex_term = $dbh->sql_regexp($args->{full_field}, $quoted,
- 'no check');
- # Regular expressions are slow--substring searches are faster.
- # If we're searching for a word, we're also certain that the
- # substring will appear in the value. So we limit first by
- # substring and then by a regex that will match just words.
- my $substring_term = shift @substring_terms;
- push(@terms, "$substring_term AND $regex_term");
- }
-
- return @terms;
+ my ($self, $args) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my @values = split(/[\s,]+/, $args->{value});
+ @values = grep { defined $_ and $_ ne '' } @values;
+ my @substring_terms = $self->_substring_terms($args);
+
+ my @terms;
+ my $start = $dbh->WORD_START;
+ my $end = $dbh->WORD_END;
+ foreach my $word (@values) {
+ my $regex = $start . quotemeta($word) . $end;
+ my $quoted = $dbh->quote($regex);
+
+ # We don't have to check the regexp, because we escaped it, so we're
+ # sure it's valid.
+ my $regex_term = $dbh->sql_regexp($args->{full_field}, $quoted, 'no check');
+
+ # Regular expressions are slow--substring searches are faster.
+ # If we're searching for a word, we're also certain that the
+ # substring will appear in the value. So we limit first by
+ # substring and then by a regex that will match just words.
+ my $substring_term = shift @substring_terms;
+ push(@terms, "$substring_term AND $regex_term");
+ }
+
+ return @terms;
}
#####################################
@@ -2165,109 +2139,118 @@ sub _word_terms {
#####################################
sub _timestamp_translate {
- my ($self, $ignore_time, $args) = @_;
- my $value = $args->{value};
- my $dbh = Bugzilla->dbh;
-
- # Force parsing of all dates & times, so that we filter weird values out
- # from users.
- #return if $value !~ /^(?:[\+\-]?\d+[hdwmy]s?|now)$/i;
-
- $value = SqlifyDate($value);
- # By default, the time is appended to the date, which we don't always want.
- if ($ignore_time) {
- ($value) = split(/\s/, $value);
- }
- $args->{value} = $value;
- $args->{quoted} = $dbh->quote($value);
+ my ($self, $ignore_time, $args) = @_;
+ my $value = $args->{value};
+ my $dbh = Bugzilla->dbh;
+
+ # Force parsing of all dates & times, so that we filter weird values out
+ # from users.
+ #return if $value !~ /^(?:[\+\-]?\d+[hdwmy]s?|now)$/i;
+
+ $value = SqlifyDate($value);
+
+ # By default, the time is appended to the date, which we don't always want.
+ if ($ignore_time) {
+ ($value) = split(/\s/, $value);
+ }
+ $args->{value} = $value;
+ $args->{quoted} = $dbh->quote($value);
}
sub _datetime_translate {
- return shift->_timestamp_translate(0, @_);
+ return shift->_timestamp_translate(0, @_);
}
sub _last_visit_datetime {
- my ($self, $args) = @_;
- my $value = $args->{value};
-
- $self->_datetime_translate($args);
- if ($value eq $args->{value}) {
- # Failed to translate a datetime. let's try the pronoun expando.
- if ($value eq '%last_changed%') {
- $self->_add_extra_column('changeddate');
- $args->{value} = $args->{quoted} = 'bugs.delta_ts';
- }
+ my ($self, $args) = @_;
+ my $value = $args->{value};
+
+ $self->_datetime_translate($args);
+ if ($value eq $args->{value}) {
+
+ # Failed to translate a datetime. let's try the pronoun expando.
+ if ($value eq '%last_changed%') {
+ $self->_add_extra_column('changeddate');
+ $args->{value} = $args->{quoted} = 'bugs.delta_ts';
}
+ }
}
sub _date_translate {
- return shift->_timestamp_translate(1, @_);
+ return shift->_timestamp_translate(1, @_);
}
sub SqlifyDate {
- my ($str) = @_;
- my $fmt = "%Y-%m-%d %H:%M:%S";
- $str = "" if (!defined $str || lc($str) eq 'now');
- if ($str eq "") {
- my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime(time());
- return sprintf("%4d-%02d-%02d 00:00:00", $year+1900, $month+1, $mday);
- }
-
- if ($str =~ /^(-|\+)?(\d+)([hdwmy])(s?)$/i) { # relative date
- my ($sign, $amount, $unit, $startof, $date) = ($1, $2, lc $3, lc $4, time);
- my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime($date);
- if ($sign && $sign eq '+') { $amount = -$amount; }
- $startof = 1 if $amount == 0;
- if ($unit eq 'w') { # convert weeks to days
- $amount = 7*$amount;
- $amount += $wday if $startof;
- $unit = 'd';
- }
- if ($unit eq 'd') {
- if ($startof) {
- $fmt = "%Y-%m-%d 00:00:00";
- $date -= $sec + 60*$min + 3600*$hour;
- }
- $date -= 24*3600*$amount;
- return time2str($fmt, $date);
- }
- elsif ($unit eq 'y') {
- if ($startof) {
- return sprintf("%4d-01-01 00:00:00", $year+1900-$amount);
- }
- else {
- return sprintf("%4d-%02d-%02d %02d:%02d:%02d",
- $year+1900-$amount, $month+1, $mday, $hour, $min, $sec);
- }
- }
- elsif ($unit eq 'm') {
- $month -= $amount;
- $year += floor($month/12);
- $month %= 12;
- if ($startof) {
- return sprintf("%4d-%02d-01 00:00:00", $year+1900, $month+1);
- }
- else {
- return sprintf("%4d-%02d-%02d %02d:%02d:%02d",
- $year+1900, $month+1, $mday, $hour, $min, $sec);
- }
- }
- elsif ($unit eq 'h') {
- # Special case for 'beginning of an hour'
- if ($startof) {
- $fmt = "%Y-%m-%d %H:00:00";
- }
- $date -= 3600*$amount;
- return time2str($fmt, $date);
- }
- return undef; # should not happen due to regexp at top
- }
- my $date = str2time($str);
- if (!defined($date)) {
- ThrowUserError("illegal_date", { date => $str });
- }
- return time2str($fmt, $date);
+ my ($str) = @_;
+ my $fmt = "%Y-%m-%d %H:%M:%S";
+ $str = "" if (!defined $str || lc($str) eq 'now');
+ if ($str eq "") {
+ my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime(time());
+ return sprintf("%4d-%02d-%02d 00:00:00", $year + 1900, $month + 1, $mday);
+ }
+
+ if ($str =~ /^(-|\+)?(\d+)([hdwmy])(s?)$/i) { # relative date
+ my ($sign, $amount, $unit, $startof, $date) = ($1, $2, lc $3, lc $4, time);
+ my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime($date);
+ if ($sign && $sign eq '+') { $amount = -$amount; }
+ $startof = 1 if $amount == 0;
+ if ($unit eq 'w') { # convert weeks to days
+ $amount = 7 * $amount;
+ $amount += $wday if $startof;
+ $unit = 'd';
+ }
+ if ($unit eq 'd') {
+ if ($startof) {
+ $fmt = "%Y-%m-%d 00:00:00";
+ $date -= $sec + 60 * $min + 3600 * $hour;
+ }
+ $date -= 24 * 3600 * $amount;
+ return time2str($fmt, $date);
+ }
+ elsif ($unit eq 'y') {
+ if ($startof) {
+ return sprintf("%4d-01-01 00:00:00", $year + 1900 - $amount);
+ }
+ else {
+ return sprintf(
+ "%4d-%02d-%02d %02d:%02d:%02d",
+ $year + 1900 - $amount,
+ $month + 1, $mday, $hour, $min, $sec
+ );
+ }
+ }
+ elsif ($unit eq 'm') {
+ $month -= $amount;
+ $year += floor($month / 12);
+ $month %= 12;
+ if ($startof) {
+ return sprintf("%4d-%02d-01 00:00:00", $year + 1900, $month + 1);
+ }
+ else {
+ return sprintf(
+ "%4d-%02d-%02d %02d:%02d:%02d",
+ $year + 1900,
+ $month + 1, $mday, $hour, $min, $sec
+ );
+ }
+ }
+ elsif ($unit eq 'h') {
+
+ # Special case for 'beginning of an hour'
+ if ($startof) {
+ $fmt = "%Y-%m-%d %H:00:00";
+ }
+ $date -= 3600 * $amount;
+ return time2str($fmt, $date);
+ }
+ return undef; # should not happen due to regexp at top
+ }
+ my $date = str2time($str);
+ if (!defined($date)) {
+ ThrowUserError("illegal_date", {date => $str});
+ }
+ return time2str($fmt, $date);
}
######################################
@@ -2275,104 +2258,109 @@ sub SqlifyDate {
######################################
sub pronoun {
- my ($noun, $user) = (@_);
- if ($noun eq "%user%") {
- if ($user->id) {
- return $user->id;
- } else {
- ThrowUserError('login_required_for_pronoun');
- }
- }
- if ($noun eq "%reporter%") {
- return "bugs.reporter";
- }
- if ($noun eq "%assignee%") {
- return "bugs.assigned_to";
+ my ($noun, $user) = (@_);
+ if ($noun eq "%user%") {
+ if ($user->id) {
+ return $user->id;
}
- if ($noun eq "%qacontact%") {
- return "COALESCE(bugs.qa_contact,0)";
+ else {
+ ThrowUserError('login_required_for_pronoun');
}
+ }
+ if ($noun eq "%reporter%") {
+ return "bugs.reporter";
+ }
+ if ($noun eq "%assignee%") {
+ return "bugs.assigned_to";
+ }
+ if ($noun eq "%qacontact%") {
+ return "COALESCE(bugs.qa_contact,0)";
+ }
- ThrowUserError('illegal_pronoun', { pronoun => $noun });
+ ThrowUserError('illegal_pronoun', {pronoun => $noun});
}
sub _contact_pronoun {
- my ($self, $args) = @_;
- my $value = $args->{value};
- my $user = $self->_user;
+ my ($self, $args) = @_;
+ my $value = $args->{value};
+ my $user = $self->_user;
- if ($value =~ /^\%group\.[^%]+%$/) {
- $self->_contact_exact_group($args);
- }
- elsif ($value =~ /^(%\w+%)$/) {
- $args->{value} = pronoun($1, $user);
- $args->{quoted} = $args->{value};
- $args->{value_is_id} = 1;
- }
+ if ($value =~ /^\%group\.[^%]+%$/) {
+ $self->_contact_exact_group($args);
+ }
+ elsif ($value =~ /^(%\w+%)$/) {
+ $args->{value} = pronoun($1, $user);
+ $args->{quoted} = $args->{value};
+ $args->{value_is_id} = 1;
+ }
}
sub _contact_exact_group {
- my ($self, $args) = @_;
- my ($value, $operator, $field, $chart_id, $joins, $sequence) =
- @$args{qw(value operator field chart_id joins sequence)};
- my $dbh = Bugzilla->dbh;
- my $user = $self->_user;
-
- # We already know $value will match this regexp, else we wouldn't be here.
- $value =~ /\%group\.([^%]+)%/;
- my $group_name = $1;
- my $group = Bugzilla::Group->check({ name => $group_name, _error => 'invalid_group_name' });
- # Pass $group_name instead of $group->name to the error message
- # to not leak the existence of the group.
- $user->in_group($group)
- || ThrowUserError('invalid_group_name', { name => $group_name });
- # Now that we know the user belongs to this group, it's safe
- # to disclose more information.
- $group->check_members_are_visible();
-
- my $group_ids = Bugzilla::Group->flatten_group_membership($group->id);
-
- if ($field eq 'cc' && $chart_id eq '') {
- # This is for the email1, email2, email3 fields from query.cgi.
- $chart_id = "CC$$sequence";
- $args->{sequence}++;
- }
-
- my $from = $field;
- # These fields need an additional table.
- if ($field =~ /^(commenter|cc)$/) {
- my $join_table = $field;
- $join_table = 'longdescs' if $field eq 'commenter';
- my $join_table_alias = "${field}_$chart_id";
- push(@$joins, { table => $join_table, as => $join_table_alias });
- $from = "$join_table_alias.who";
- }
-
- my $table = "user_group_map_$chart_id";
- my $join = {
- table => 'user_group_map',
- as => $table,
- from => $from,
- to => 'user_id',
- extra => [$dbh->sql_in("$table.group_id", $group_ids),
- "$table.isbless = 0"],
- };
- push(@$joins, $join);
- if ($operator =~ /^not/) {
- $args->{term} = "$table.group_id IS NULL";
- }
- else {
- $args->{term} = "$table.group_id IS NOT NULL";
- }
+ my ($self, $args) = @_;
+ my ($value, $operator, $field, $chart_id, $joins, $sequence)
+ = @$args{qw(value operator field chart_id joins sequence)};
+ my $dbh = Bugzilla->dbh;
+ my $user = $self->_user;
+
+ # We already know $value will match this regexp, else we wouldn't be here.
+ $value =~ /\%group\.([^%]+)%/;
+ my $group_name = $1;
+ my $group = Bugzilla::Group->check(
+ {name => $group_name, _error => 'invalid_group_name'});
+
+ # Pass $group_name instead of $group->name to the error message
+ # to not leak the existence of the group.
+ $user->in_group($group)
+ || ThrowUserError('invalid_group_name', {name => $group_name});
+
+ # Now that we know the user belongs to this group, it's safe
+ # to disclose more information.
+ $group->check_members_are_visible();
+
+ my $group_ids = Bugzilla::Group->flatten_group_membership($group->id);
+
+ if ($field eq 'cc' && $chart_id eq '') {
+
+ # This is for the email1, email2, email3 fields from query.cgi.
+ $chart_id = "CC$$sequence";
+ $args->{sequence}++;
+ }
+
+ my $from = $field;
+
+ # These fields need an additional table.
+ if ($field =~ /^(commenter|cc)$/) {
+ my $join_table = $field;
+ $join_table = 'longdescs' if $field eq 'commenter';
+ my $join_table_alias = "${field}_$chart_id";
+ push(@$joins, {table => $join_table, as => $join_table_alias});
+ $from = "$join_table_alias.who";
+ }
+
+ my $table = "user_group_map_$chart_id";
+ my $join = {
+ table => 'user_group_map',
+ as => $table,
+ from => $from,
+ to => 'user_id',
+ extra => [$dbh->sql_in("$table.group_id", $group_ids), "$table.isbless = 0"],
+ };
+ push(@$joins, $join);
+ if ($operator =~ /^not/) {
+ $args->{term} = "$table.group_id IS NULL";
+ }
+ else {
+ $args->{term} = "$table.group_id IS NOT NULL";
+ }
}
sub _get_user_id {
- my ($self, $value) = @_;
+ my ($self, $value) = @_;
- if ($value =~ /^%\w+%$/) {
- return pronoun($value, $self->_user);
- }
- return login_to_id($value, THROW_ERROR);
+ if ($value =~ /^%\w+%$/) {
+ return pronoun($value, $self->_user);
+ }
+ return login_to_id($value, THROW_ERROR);
}
#####################################################################
@@ -2380,546 +2368,556 @@ sub _get_user_id {
#####################################################################
sub _invalid_combination {
- my ($self, $args) = @_;
- my ($field, $operator) = @$args{qw(field operator)};
- ThrowUserError('search_field_operator_invalid',
- { field => $field, operator => $operator });
+ my ($self, $args) = @_;
+ my ($field, $operator) = @$args{qw(field operator)};
+ ThrowUserError('search_field_operator_invalid',
+ {field => $field, operator => $operator});
}
# For all the "user" fields--assigned_to, reporter, qa_contact,
# cc, commenter, requestee, etc.
sub _user_nonchanged {
- my ($self, $args) = @_;
- my ($field, $operator, $chart_id, $sequence, $joins) =
- @$args{qw(field operator chart_id sequence joins)};
-
- my $is_in_other_table;
- if (my $join = USER_FIELDS->{$field}->{join}) {
- $is_in_other_table = 1;
- my $as = "${field}_$chart_id";
- # Needed for setters.login_name and requestees.login_name.
- # Otherwise when we try to join "profiles" below, we'd get
- # something like "setters.login_name.login_name" in the "from".
- $as =~ s/\./_/g;
- # This helps implement the email1, email2, etc. parameters.
- if ($chart_id =~ /default/) {
- $as .= "_$sequence";
- }
- my $isprivate = USER_FIELDS->{$field}->{isprivate};
- my $extra = ($isprivate and !$self->_user->is_insider)
- ? ["$as.isprivate = 0"] : [];
- # We want to copy $join so as not to modify USER_FIELDS.
- push(@$joins, { %$join, as => $as, extra => $extra });
- my $search_field = USER_FIELDS->{$field}->{field};
- $args->{full_field} = "$as.$search_field";
- }
+ my ($self, $args) = @_;
+ my ($field, $operator, $chart_id, $sequence, $joins)
+ = @$args{qw(field operator chart_id sequence joins)};
+
+ my $is_in_other_table;
+ if (my $join = USER_FIELDS->{$field}->{join}) {
+ $is_in_other_table = 1;
+ my $as = "${field}_$chart_id";
+
+ # Needed for setters.login_name and requestees.login_name.
+ # Otherwise when we try to join "profiles" below, we'd get
+ # something like "setters.login_name.login_name" in the "from".
+ $as =~ s/\./_/g;
+
+ # This helps implement the email1, email2, etc. parameters.
+ if ($chart_id =~ /default/) {
+ $as .= "_$sequence";
+ }
+ my $isprivate = USER_FIELDS->{$field}->{isprivate};
+ my $extra
+ = ($isprivate and !$self->_user->is_insider) ? ["$as.isprivate = 0"] : [];
+
+ # We want to copy $join so as not to modify USER_FIELDS.
+ push(@$joins, {%$join, as => $as, extra => $extra});
+ my $search_field = USER_FIELDS->{$field}->{field};
+ $args->{full_field} = "$as.$search_field";
+ }
+
+ my $is_nullable = USER_FIELDS->{$field}->{nullable};
+ my $null_alternate = "''";
+
+ # When using a pronoun, we use the userid, and we don't have to
+ # join the profiles table.
+ if ($args->{value_is_id}) {
+ $null_alternate = 0;
+ }
+ elsif (substr($field, -9) eq '_realname') {
+ my $as = "name_${field}_$chart_id";
+
+ # For fields with periods in their name.
+ $as =~ s/\./_/;
+ my $join = {
+ table => 'profiles',
+ as => $as,
+ from => substr($args->{full_field}, 0, -9),
+ to => 'userid',
+ join => (!$is_in_other_table and !$is_nullable) ? 'INNER' : undef,
+ };
+ push(@$joins, $join);
+ $args->{full_field} = "$as.realname";
+ }
+ else {
+ my $as = "name_${field}_$chart_id";
- my $is_nullable = USER_FIELDS->{$field}->{nullable};
- my $null_alternate = "''";
- # When using a pronoun, we use the userid, and we don't have to
- # join the profiles table.
- if ($args->{value_is_id}) {
- $null_alternate = 0;
+ # For fields with periods in their name.
+ $as =~ s/\./_/;
+ my $join = {
+ table => 'profiles',
+ as => $as,
+ from => $args->{full_field},
+ to => 'userid',
+ join => (!$is_in_other_table and !$is_nullable) ? 'INNER' : undef,
+ };
+ push(@$joins, $join);
+ $args->{full_field} = "$as.login_name";
+ }
+
+ # We COALESCE fields that can be NULL, to make "not"-style operators
+ # continue to work properly. For example, "qa_contact is not equal to bob"
+ # should also show bugs where the qa_contact is NULL. With COALESCE,
+ # it does.
+ if ($is_nullable) {
+ $args->{full_field} = "COALESCE($args->{full_field}, $null_alternate)";
+ }
+
+ # For fields whose values are stored in other tables, negation (NOT)
+ # only works properly if we put the condition into the JOIN instead
+ # of the WHERE.
+ if ($is_in_other_table) {
+
+ # Using the last join works properly whether we're searching based
+ # on userid or login_name.
+ my $last_join = $joins->[-1];
+
+ # For negative operators, the system we're using here
+ # only works properly if we reverse the operator and check IS NULL
+ # in the WHERE.
+ my $is_negative = $operator =~ /^(?:no|isempty)/ ? 1 : 0;
+ if ($is_negative) {
+ $args->{operator} = $self->_reverse_operator($operator);
}
- elsif (substr($field, -9) eq '_realname') {
- my $as = "name_${field}_$chart_id";
- # For fields with periods in their name.
- $as =~ s/\./_/;
- my $join = {
- table => 'profiles',
- as => $as,
- from => substr($args->{full_field}, 0, -9),
- to => 'userid',
- join => (!$is_in_other_table and !$is_nullable) ? 'INNER' : undef,
- };
- push(@$joins, $join);
- $args->{full_field} = "$as.realname";
+ $self->_do_operator_function($args);
+ push(@{$last_join->{extra}}, $args->{term});
+
+ # For login_name searches, we only want a single join.
+ # So we create a subselect table out of our two joins. This makes
+ # negation (NOT) work properly for values that are in other
+ # tables.
+ if ($last_join->{table} eq 'profiles') {
+ pop @$joins;
+ $last_join->{join} = 'INNER';
+ my ($join_sql) = $self->_translate_join($last_join);
+ my $first_join = $joins->[-1];
+ my $as = $first_join->{as};
+ my $table = $first_join->{table};
+ my $columns = "bug_id";
+ $columns .= ",isprivate" if @{$first_join->{extra}};
+ my $new_table = "SELECT DISTINCT $columns FROM $table AS $as $join_sql";
+ $first_join->{table} = "($new_table)";
+
+ # We always want to LEFT JOIN the generated table.
+ delete $first_join->{join};
+
+ # To support OR charts, we need multiple tables.
+ my $new_as = $first_join->{as} . "_$sequence";
+ $_ =~ s/\Q$as\E/$new_as/ foreach @{$first_join->{extra}};
+ $first_join->{as} = $new_as;
+ $last_join = $first_join;
+ }
+
+ # If we're joining the first table (we're using a pronoun and
+ # searching by user id) then we need to check $other_table->{field}.
+ my $check_field = $last_join->{as} . '.bug_id';
+ if ($is_negative) {
+ $args->{term} = "$check_field IS NULL";
}
else {
- my $as = "name_${field}_$chart_id";
- # For fields with periods in their name.
- $as =~ s/\./_/;
- my $join = {
- table => 'profiles',
- as => $as,
- from => $args->{full_field},
- to => 'userid',
- join => (!$is_in_other_table and !$is_nullable) ? 'INNER' : undef,
- };
- push(@$joins, $join);
- $args->{full_field} = "$as.login_name";
- }
-
- # We COALESCE fields that can be NULL, to make "not"-style operators
- # continue to work properly. For example, "qa_contact is not equal to bob"
- # should also show bugs where the qa_contact is NULL. With COALESCE,
- # it does.
- if ($is_nullable) {
- $args->{full_field} = "COALESCE($args->{full_field}, $null_alternate)";
- }
-
- # For fields whose values are stored in other tables, negation (NOT)
- # only works properly if we put the condition into the JOIN instead
- # of the WHERE.
- if ($is_in_other_table) {
- # Using the last join works properly whether we're searching based
- # on userid or login_name.
- my $last_join = $joins->[-1];
-
- # For negative operators, the system we're using here
- # only works properly if we reverse the operator and check IS NULL
- # in the WHERE.
- my $is_negative = $operator =~ /^(?:no|isempty)/ ? 1 : 0;
- if ($is_negative) {
- $args->{operator} = $self->_reverse_operator($operator);
- }
- $self->_do_operator_function($args);
- push(@{ $last_join->{extra} }, $args->{term});
-
- # For login_name searches, we only want a single join.
- # So we create a subselect table out of our two joins. This makes
- # negation (NOT) work properly for values that are in other
- # tables.
- if ($last_join->{table} eq 'profiles') {
- pop @$joins;
- $last_join->{join} = 'INNER';
- my ($join_sql) = $self->_translate_join($last_join);
- my $first_join = $joins->[-1];
- my $as = $first_join->{as};
- my $table = $first_join->{table};
- my $columns = "bug_id";
- $columns .= ",isprivate" if @{ $first_join->{extra} };
- my $new_table = "SELECT DISTINCT $columns FROM $table AS $as $join_sql";
- $first_join->{table} = "($new_table)";
- # We always want to LEFT JOIN the generated table.
- delete $first_join->{join};
- # To support OR charts, we need multiple tables.
- my $new_as = $first_join->{as} . "_$sequence";
- $_ =~ s/\Q$as\E/$new_as/ foreach @{ $first_join->{extra} };
- $first_join->{as} = $new_as;
- $last_join = $first_join;
- }
-
- # If we're joining the first table (we're using a pronoun and
- # searching by user id) then we need to check $other_table->{field}.
- my $check_field = $last_join->{as} . '.bug_id';
- if ($is_negative) {
- $args->{term} = "$check_field IS NULL";
- }
- else {
- $args->{term} = "$check_field IS NOT NULL";
- }
+ $args->{term} = "$check_field IS NOT NULL";
}
+ }
}
# XXX This duplicates having Commenter as a search field.
sub _long_desc_changedby {
- my ($self, $args) = @_;
- my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
-
- my $table = "longdescs_$chart_id";
- push(@$joins, { table => 'longdescs', as => $table });
- my $user_id = $self->_get_user_id($value);
- $args->{term} = "$table.who = $user_id";
-
- # If the user is not part of the insiders group, they cannot see
- # private comments
- if (!$self->_user->is_insider) {
- $args->{term} .= " AND $table.isprivate = 0";
- }
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
+
+ my $table = "longdescs_$chart_id";
+ push(@$joins, {table => 'longdescs', as => $table});
+ my $user_id = $self->_get_user_id($value);
+ $args->{term} = "$table.who = $user_id";
+
+ # If the user is not part of the insiders group, they cannot see
+ # private comments
+ if (!$self->_user->is_insider) {
+ $args->{term} .= " AND $table.isprivate = 0";
+ }
}
sub _long_desc_changedbefore_after {
- my ($self, $args) = @_;
- my ($chart_id, $operator, $value, $joins) =
- @$args{qw(chart_id operator value joins)};
- my $dbh = Bugzilla->dbh;
-
- my $sql_operator = ($operator =~ /before/) ? '<=' : '>=';
- my $table = "longdescs_$chart_id";
- my $sql_date = $dbh->quote(SqlifyDate($value));
- my $join = {
- table => 'longdescs',
- as => $table,
- extra => ["$table.bug_when $sql_operator $sql_date"],
- };
- push(@$joins, $join);
- $args->{term} = "$table.bug_when IS NOT NULL";
-
- # If the user is not part of the insiders group, they cannot see
- # private comments
- if (!$self->_user->is_insider) {
- $args->{term} .= " AND $table.isprivate = 0";
- }
+ my ($self, $args) = @_;
+ my ($chart_id, $operator, $value, $joins)
+ = @$args{qw(chart_id operator value joins)};
+ my $dbh = Bugzilla->dbh;
+
+ my $sql_operator = ($operator =~ /before/) ? '<=' : '>=';
+ my $table = "longdescs_$chart_id";
+ my $sql_date = $dbh->quote(SqlifyDate($value));
+ my $join = {
+ table => 'longdescs',
+ as => $table,
+ extra => ["$table.bug_when $sql_operator $sql_date"],
+ };
+ push(@$joins, $join);
+ $args->{term} = "$table.bug_when IS NOT NULL";
+
+ # If the user is not part of the insiders group, they cannot see
+ # private comments
+ if (!$self->_user->is_insider) {
+ $args->{term} .= " AND $table.isprivate = 0";
+ }
}
sub _long_desc_nonchanged {
- my ($self, $args) = @_;
- my ($chart_id, $operator, $value, $joins, $bugs_table) =
- @$args{qw(chart_id operator value joins bugs_table)};
-
- if ($operator =~ /^is(not)?empty$/) {
- $args->{term} = $self->_multiselect_isempty($args, $operator eq 'isnotempty');
- return;
- }
- my $dbh = Bugzilla->dbh;
-
- my $table = "longdescs_$chart_id";
- my $join_args = {
- chart_id => $chart_id,
- sequence => $chart_id,
- field => 'longdesc',
- full_field => "$table.thetext",
- operator => $operator,
- value => $value,
- all_values => $value,
- quoted => $dbh->quote($value),
- joins => [],
- bugs_table => $bugs_table,
- };
- $self->_do_operator_function($join_args);
-
- # If the user is not part of the insiders group, they cannot see
- # private comments
- if (!$self->_user->is_insider) {
- $join_args->{term} .= " AND $table.isprivate = 0";
- }
-
- my $join = {
- table => 'longdescs',
- as => $table,
- extra => [ $join_args->{term} ],
- };
- push(@$joins, $join);
-
- $args->{term} = "$table.comment_id IS NOT NULL";
+ my ($self, $args) = @_;
+ my ($chart_id, $operator, $value, $joins, $bugs_table)
+ = @$args{qw(chart_id operator value joins bugs_table)};
+
+ if ($operator =~ /^is(not)?empty$/) {
+ $args->{term} = $self->_multiselect_isempty($args, $operator eq 'isnotempty');
+ return;
+ }
+ my $dbh = Bugzilla->dbh;
+
+ my $table = "longdescs_$chart_id";
+ my $join_args = {
+ chart_id => $chart_id,
+ sequence => $chart_id,
+ field => 'longdesc',
+ full_field => "$table.thetext",
+ operator => $operator,
+ value => $value,
+ all_values => $value,
+ quoted => $dbh->quote($value),
+ joins => [],
+ bugs_table => $bugs_table,
+ };
+ $self->_do_operator_function($join_args);
+
+ # If the user is not part of the insiders group, they cannot see
+ # private comments
+ if (!$self->_user->is_insider) {
+ $join_args->{term} .= " AND $table.isprivate = 0";
+ }
+
+ my $join = {table => 'longdescs', as => $table, extra => [$join_args->{term}],};
+ push(@$joins, $join);
+
+ $args->{term} = "$table.comment_id IS NOT NULL";
}
sub _content_matches {
- my ($self, $args) = @_;
- my ($chart_id, $joins, $fields, $operator, $value) =
- @$args{qw(chart_id joins fields operator value)};
- my $dbh = Bugzilla->dbh;
-
- # "content" is an alias for columns containing text for which we
- # can search a full-text index and retrieve results by relevance,
- # currently just bug comments (and summaries to some degree).
- # There's only one way to search a full-text index, so we only
- # accept the "matches" operator, which is specific to full-text
- # index searches.
-
- # Add the fulltext table to the query so we can search on it.
- my $table = "bugs_fulltext_$chart_id";
- my $comments_col = "comments";
- $comments_col = "comments_noprivate" unless $self->_user->is_insider;
- push(@$joins, { table => 'bugs_fulltext', as => $table });
-
- # Create search terms to add to the SELECT and WHERE clauses.
- my ($term1, $rterm1) =
- $dbh->sql_fulltext_search("$table.$comments_col", $value);
- my ($term2, $rterm2) =
- $dbh->sql_fulltext_search("$table.short_desc", $value);
- $rterm1 = $term1 if !$rterm1;
- $rterm2 = $term2 if !$rterm2;
-
- # The term to use in the WHERE clause.
- my $term = "$term1 OR $term2";
- if ($operator =~ /not/i) {
- $term = "NOT($term)";
- }
- $args->{term} = $term;
-
- # In order to sort by relevance (in case the user requests it),
- # we SELECT the relevance value so we can add it to the ORDER BY
- # clause. Every time a new fulltext chart isadded, this adds more
- # terms to the relevance sql.
- #
- # We build the relevance SQL by modifying the COLUMNS list directly,
- # which is kind of a hack but works.
- my $current = $self->COLUMNS->{'relevance'}->{name};
- $current = $current ? "$current + " : '';
- # For NOT searches, we just add 0 to the relevance.
- my $select_term = $operator =~ /not/ ? 0 : "($current$rterm1 + $rterm2)";
- $self->COLUMNS->{'relevance'}->{name} = $select_term;
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $fields, $operator, $value)
+ = @$args{qw(chart_id joins fields operator value)};
+ my $dbh = Bugzilla->dbh;
+
+ # "content" is an alias for columns containing text for which we
+ # can search a full-text index and retrieve results by relevance,
+ # currently just bug comments (and summaries to some degree).
+ # There's only one way to search a full-text index, so we only
+ # accept the "matches" operator, which is specific to full-text
+ # index searches.
+
+ # Add the fulltext table to the query so we can search on it.
+ my $table = "bugs_fulltext_$chart_id";
+ my $comments_col = "comments";
+ $comments_col = "comments_noprivate" unless $self->_user->is_insider;
+ push(@$joins, {table => 'bugs_fulltext', as => $table});
+
+ # Create search terms to add to the SELECT and WHERE clauses.
+ my ($term1, $rterm1)
+ = $dbh->sql_fulltext_search("$table.$comments_col", $value);
+ my ($term2, $rterm2) = $dbh->sql_fulltext_search("$table.short_desc", $value);
+ $rterm1 = $term1 if !$rterm1;
+ $rterm2 = $term2 if !$rterm2;
+
+ # The term to use in the WHERE clause.
+ my $term = "$term1 OR $term2";
+ if ($operator =~ /not/i) {
+ $term = "NOT($term)";
+ }
+ $args->{term} = $term;
+
+ # In order to sort by relevance (in case the user requests it),
+ # we SELECT the relevance value so we can add it to the ORDER BY
+ # clause. Every time a new fulltext chart isadded, this adds more
+ # terms to the relevance sql.
+ #
+ # We build the relevance SQL by modifying the COLUMNS list directly,
+ # which is kind of a hack but works.
+ my $current = $self->COLUMNS->{'relevance'}->{name};
+ $current = $current ? "$current + " : '';
+
+ # For NOT searches, we just add 0 to the relevance.
+ my $select_term = $operator =~ /not/ ? 0 : "($current$rterm1 + $rterm2)";
+ $self->COLUMNS->{'relevance'}->{name} = $select_term;
}
sub _long_descs_count {
- my ($self, $args) = @_;
- my ($chart_id, $joins) = @$args{qw(chart_id joins)};
- my $table = "longdescs_count_$chart_id";
- my $extra = $self->_user->is_insider ? "" : "WHERE isprivate = 0";
- my $join = {
- table => "(SELECT bug_id, COUNT(*) AS num"
- . " FROM longdescs $extra GROUP BY bug_id)",
- as => $table,
- };
- push(@$joins, $join);
- $args->{full_field} = "${table}.num";
+ my ($self, $args) = @_;
+ my ($chart_id, $joins) = @$args{qw(chart_id joins)};
+ my $table = "longdescs_count_$chart_id";
+ my $extra = $self->_user->is_insider ? "" : "WHERE isprivate = 0";
+ my $join = {
+ table => "(SELECT bug_id, COUNT(*) AS num"
+ . " FROM longdescs $extra GROUP BY bug_id)",
+ as => $table,
+ };
+ push(@$joins, $join);
+ $args->{full_field} = "${table}.num";
}
sub _work_time_changedby {
- my ($self, $args) = @_;
- my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
-
- my $table = "longdescs_$chart_id";
- push(@$joins, { table => 'longdescs', as => $table });
- my $user_id = $self->_get_user_id($value);
- $args->{term} = "$table.who = $user_id AND $table.work_time != 0";
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
+
+ my $table = "longdescs_$chart_id";
+ push(@$joins, {table => 'longdescs', as => $table});
+ my $user_id = $self->_get_user_id($value);
+ $args->{term} = "$table.who = $user_id AND $table.work_time != 0";
}
sub _work_time_changedbefore_after {
- my ($self, $args) = @_;
- my ($chart_id, $operator, $value, $joins) =
- @$args{qw(chart_id operator value joins)};
- my $dbh = Bugzilla->dbh;
-
- my $table = "longdescs_$chart_id";
- my $sql_operator = ($operator =~ /before/) ? '<=' : '>=';
- my $sql_date = $dbh->quote(SqlifyDate($value));
- my $join = {
- table => 'longdescs',
- as => $table,
- extra => ["$table.work_time != 0",
- "$table.bug_when $sql_operator $sql_date"],
- };
- push(@$joins, $join);
-
- $args->{term} = "$table.bug_when IS NOT NULL";
+ my ($self, $args) = @_;
+ my ($chart_id, $operator, $value, $joins)
+ = @$args{qw(chart_id operator value joins)};
+ my $dbh = Bugzilla->dbh;
+
+ my $table = "longdescs_$chart_id";
+ my $sql_operator = ($operator =~ /before/) ? '<=' : '>=';
+ my $sql_date = $dbh->quote(SqlifyDate($value));
+ my $join = {
+ table => 'longdescs',
+ as => $table,
+ extra => ["$table.work_time != 0", "$table.bug_when $sql_operator $sql_date"],
+ };
+ push(@$joins, $join);
+
+ $args->{term} = "$table.bug_when IS NOT NULL";
}
sub _work_time {
- my ($self, $args) = @_;
- $self->_add_extra_column('actual_time');
- $args->{full_field} = $self->COLUMNS->{actual_time}->{name};
+ my ($self, $args) = @_;
+ $self->_add_extra_column('actual_time');
+ $args->{full_field} = $self->COLUMNS->{actual_time}->{name};
}
sub _percentage_complete {
- my ($self, $args) = @_;
-
- $args->{full_field} = $self->COLUMNS->{percentage_complete}->{name};
+ my ($self, $args) = @_;
- # We need actual_time in _select_columns, otherwise we can't use
- # it in the expression for searching percentage_complete.
- $self->_add_extra_column('actual_time');
+ $args->{full_field} = $self->COLUMNS->{percentage_complete}->{name};
+
+ # We need actual_time in _select_columns, otherwise we can't use
+ # it in the expression for searching percentage_complete.
+ $self->_add_extra_column('actual_time');
}
sub _last_visit_ts {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- $args->{full_field} = $self->COLUMNS->{last_visit_ts}->{name};
- $self->_add_extra_column('last_visit_ts');
+ $args->{full_field} = $self->COLUMNS->{last_visit_ts}->{name};
+ $self->_add_extra_column('last_visit_ts');
}
sub _last_visit_ts_invalid_operator {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- ThrowUserError('search_field_operator_invalid',
- { field => $args->{field},
- operator => $args->{operator} });
+ ThrowUserError('search_field_operator_invalid',
+ {field => $args->{field}, operator => $args->{operator}});
}
sub _days_elapsed {
- my ($self, $args) = @_;
- my $dbh = Bugzilla->dbh;
-
- $args->{full_field} = "(" . $dbh->sql_to_days('NOW()') . " - " .
- $dbh->sql_to_days('bugs.delta_ts') . ")";
+ my ($self, $args) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $args->{full_field}
+ = "("
+ . $dbh->sql_to_days('NOW()') . " - "
+ . $dbh->sql_to_days('bugs.delta_ts') . ")";
}
sub _component_nonchanged {
- my ($self, $args) = @_;
-
- $args->{full_field} = "components.name";
- $self->_do_operator_function($args);
- my $term = $args->{term};
- $args->{term} = build_subselect("bugs.component_id",
- "components.id", "components", $args->{term});
+ my ($self, $args) = @_;
+
+ $args->{full_field} = "components.name";
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $args->{term}
+ = build_subselect("bugs.component_id", "components.id", "components",
+ $args->{term});
}
sub _product_nonchanged {
- my ($self, $args) = @_;
-
- # Generate the restriction condition
- $args->{full_field} = "products.name";
- $self->_do_operator_function($args);
- my $term = $args->{term};
- $args->{term} = build_subselect("bugs.product_id",
- "products.id", "products", $term);
+ my ($self, $args) = @_;
+
+ # Generate the restriction condition
+ $args->{full_field} = "products.name";
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $args->{term}
+ = build_subselect("bugs.product_id", "products.id", "products", $term);
}
sub _alias_nonchanged {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- $args->{full_field} = "bugs_aliases.alias";
- $self->_do_operator_function($args);
- $args->{term} = build_subselect("bugs.bug_id",
- "bugs_aliases.bug_id", "bugs_aliases", $args->{term});
+ $args->{full_field} = "bugs_aliases.alias";
+ $self->_do_operator_function($args);
+ $args->{term}
+ = build_subselect("bugs.bug_id", "bugs_aliases.bug_id", "bugs_aliases",
+ $args->{term});
}
sub _classification_nonchanged {
- my ($self, $args) = @_;
- my $joins = $args->{joins};
-
- # This joins the right tables for us.
- $self->_add_extra_column('product');
-
- # Generate the restriction condition
- $args->{full_field} = "classifications.name";
- $self->_do_operator_function($args);
- my $term = $args->{term};
- $args->{term} = build_subselect("map_product.classification_id",
- "classifications.id", "classifications", $term);
+ my ($self, $args) = @_;
+ my $joins = $args->{joins};
+
+ # This joins the right tables for us.
+ $self->_add_extra_column('product');
+
+ # Generate the restriction condition
+ $args->{full_field} = "classifications.name";
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $args->{term} = build_subselect("map_product.classification_id",
+ "classifications.id", "classifications", $term);
}
sub _nullable {
- my ($self, $args) = @_;
- my $field = $args->{full_field};
- $args->{full_field} = "COALESCE($field, '')";
+ my ($self, $args) = @_;
+ my $field = $args->{full_field};
+ $args->{full_field} = "COALESCE($field, '')";
}
sub _nullable_int {
- my ($self, $args) = @_;
- my $field = $args->{full_field};
- $args->{full_field} = "COALESCE($field, 0)";
+ my ($self, $args) = @_;
+ my $field = $args->{full_field};
+ $args->{full_field} = "COALESCE($field, 0)";
}
sub _nullable_datetime {
- my ($self, $args) = @_;
- my $field = $args->{full_field};
- my $empty = Bugzilla->dbh->quote(EMPTY_DATETIME);
- $args->{full_field} = "COALESCE($field, $empty)";
+ my ($self, $args) = @_;
+ my $field = $args->{full_field};
+ my $empty = Bugzilla->dbh->quote(EMPTY_DATETIME);
+ $args->{full_field} = "COALESCE($field, $empty)";
}
sub _nullable_date {
- my ($self, $args) = @_;
- my $field = $args->{full_field};
- my $empty = Bugzilla->dbh->quote(EMPTY_DATE);
- $args->{full_field} = "COALESCE($field, $empty)";
+ my ($self, $args) = @_;
+ my $field = $args->{full_field};
+ my $empty = Bugzilla->dbh->quote(EMPTY_DATE);
+ $args->{full_field} = "COALESCE($field, $empty)";
}
sub _deadline {
- my ($self, $args) = @_;
- my $field = $args->{full_field};
- # This makes "equals" searches work on all DBs (even on MySQL, which
- # has a bug: http://bugs.mysql.com/bug.php?id=60324).
- $args->{full_field} = Bugzilla->dbh->sql_date_format($field, '%Y-%m-%d');
- $self->_nullable_datetime($args);
+ my ($self, $args) = @_;
+ my $field = $args->{full_field};
+
+ # This makes "equals" searches work on all DBs (even on MySQL, which
+ # has a bug: http://bugs.mysql.com/bug.php?id=60324).
+ $args->{full_field} = Bugzilla->dbh->sql_date_format($field, '%Y-%m-%d');
+ $self->_nullable_datetime($args);
}
sub _owner_idle_time_greater_less {
- my ($self, $args) = @_;
- my ($chart_id, $joins, $value, $operator) =
- @$args{qw(chart_id joins value operator)};
- my $dbh = Bugzilla->dbh;
-
- my $table = "idle_$chart_id";
- my $quoted = $dbh->quote(SqlifyDate($value));
-
- my $ld_table = "comment_$table";
- my $act_table = "activity_$table";
- my $comments_join = {
- table => 'longdescs',
- as => $ld_table,
- from => 'assigned_to',
- to => 'who',
- extra => ["$ld_table.bug_when > $quoted"],
- };
- my $activity_join = {
- table => 'bugs_activity',
- as => $act_table,
- from => 'assigned_to',
- to => 'who',
- extra => ["$act_table.bug_when > $quoted"]
- };
-
- push(@$joins, $comments_join, $activity_join);
-
- if ($operator =~ /greater/) {
- $args->{term} =
- "$ld_table.who IS NULL AND $act_table.who IS NULL";
- } else {
- $args->{term} =
- "($ld_table.who IS NOT NULL OR $act_table.who IS NOT NULL)";
- }
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $value, $operator)
+ = @$args{qw(chart_id joins value operator)};
+ my $dbh = Bugzilla->dbh;
+
+ my $table = "idle_$chart_id";
+ my $quoted = $dbh->quote(SqlifyDate($value));
+
+ my $ld_table = "comment_$table";
+ my $act_table = "activity_$table";
+ my $comments_join = {
+ table => 'longdescs',
+ as => $ld_table,
+ from => 'assigned_to',
+ to => 'who',
+ extra => ["$ld_table.bug_when > $quoted"],
+ };
+ my $activity_join = {
+ table => 'bugs_activity',
+ as => $act_table,
+ from => 'assigned_to',
+ to => 'who',
+ extra => ["$act_table.bug_when > $quoted"]
+ };
+
+ push(@$joins, $comments_join, $activity_join);
+
+ if ($operator =~ /greater/) {
+ $args->{term} = "$ld_table.who IS NULL AND $act_table.who IS NULL";
+ }
+ else {
+ $args->{term} = "($ld_table.who IS NOT NULL OR $act_table.who IS NOT NULL)";
+ }
}
sub _multiselect_negative {
- my ($self, $args) = @_;
- my ($field, $operator) = @$args{qw(field operator)};
+ my ($self, $args) = @_;
+ my ($field, $operator) = @$args{qw(field operator)};
- $args->{operator} = $self->_reverse_operator($operator);
- $args->{term} = $self->_multiselect_term($args, 1);
+ $args->{operator} = $self->_reverse_operator($operator);
+ $args->{term} = $self->_multiselect_term($args, 1);
}
sub _multiselect_multiple {
- my ($self, $args) = @_;
- my ($chart_id, $field, $operator, $value)
- = @$args{qw(chart_id field operator value)};
- my $dbh = Bugzilla->dbh;
-
- # We want things like "cf_multi_select=two+words" to still be
- # considered a search for two separate words, unless we're using
- # anyexact. (_all_values would consider that to be one "word" with a
- # space in it, because it's not in the Boolean Charts).
- my @words = $operator eq 'anyexact' ? $self->_all_values($args)
- : split(/[\s,]+/, $value);
-
- my @terms;
- foreach my $word (@words) {
- next if $word eq '';
- $args->{value} = $word;
- $args->{quoted} = $dbh->quote($word);
- push(@terms, $self->_multiselect_term($args));
- }
-
- # The spacing in the joins helps make the resulting SQL more readable.
- if ($operator =~ /^any/) {
- $args->{term} = join("\n OR ", @terms);
- }
- else {
- $args->{term} = join("\n AND ", @terms);
- }
+ my ($self, $args) = @_;
+ my ($chart_id, $field, $operator, $value)
+ = @$args{qw(chart_id field operator value)};
+ my $dbh = Bugzilla->dbh;
+
+ # We want things like "cf_multi_select=two+words" to still be
+ # considered a search for two separate words, unless we're using
+ # anyexact. (_all_values would consider that to be one "word" with a
+ # space in it, because it's not in the Boolean Charts).
+ my @words
+ = $operator eq 'anyexact'
+ ? $self->_all_values($args)
+ : split(/[\s,]+/, $value);
+
+ my @terms;
+ foreach my $word (@words) {
+ next if $word eq '';
+ $args->{value} = $word;
+ $args->{quoted} = $dbh->quote($word);
+ push(@terms, $self->_multiselect_term($args));
+ }
+
+ # The spacing in the joins helps make the resulting SQL more readable.
+ if ($operator =~ /^any/) {
+ $args->{term} = join("\n OR ", @terms);
+ }
+ else {
+ $args->{term} = join("\n AND ", @terms);
+ }
}
sub _flagtypes_nonchanged {
- my ($self, $args) = @_;
- my ($chart_id, $operator, $value, $joins, $bugs_table, $condition) =
- @$args{qw(chart_id operator value joins bugs_table condition)};
-
- if ($operator =~ /^is(not)?empty$/) {
- $args->{term} = $self->_multiselect_isempty($args, $operator eq 'isnotempty');
- return;
- }
-
- my $dbh = Bugzilla->dbh;
-
- # For 'not' operators, we need to negate the whole term.
- # If you search for "Flags" (does not contain) "approval+" we actually want
- # to return *bugs* that don't contain an approval+ flag. Without rewriting
- # the negation we'll search for *flags* which don't contain approval+.
- if ($operator =~ s/^not//) {
- $args->{operator} = $operator;
- $condition->operator($operator);
- $condition->negate(1);
- }
-
- my $subselect_args = {
- chart_id => $chart_id,
- sequence => $chart_id,
- field => 'flagtypes.name',
- full_field => $dbh->sql_string_concat("flagtypes_$chart_id.name", "flags_$chart_id.status"),
- operator => $operator,
- value => $value,
- all_values => $value,
- quoted => $dbh->quote($value),
- joins => [],
- bugs_table => "bugs_$chart_id",
- };
- $self->_do_operator_function($subselect_args);
- my $subselect_term = $subselect_args->{term};
-
- # don't call build_subselect as this must run as a true sub-select
- $args->{term} = "EXISTS (
+ my ($self, $args) = @_;
+ my ($chart_id, $operator, $value, $joins, $bugs_table, $condition)
+ = @$args{qw(chart_id operator value joins bugs_table condition)};
+
+ if ($operator =~ /^is(not)?empty$/) {
+ $args->{term} = $self->_multiselect_isempty($args, $operator eq 'isnotempty');
+ return;
+ }
+
+ my $dbh = Bugzilla->dbh;
+
+ # For 'not' operators, we need to negate the whole term.
+ # If you search for "Flags" (does not contain) "approval+" we actually want
+ # to return *bugs* that don't contain an approval+ flag. Without rewriting
+ # the negation we'll search for *flags* which don't contain approval+.
+ if ($operator =~ s/^not//) {
+ $args->{operator} = $operator;
+ $condition->operator($operator);
+ $condition->negate(1);
+ }
+
+ my $subselect_args = {
+ chart_id => $chart_id,
+ sequence => $chart_id,
+ field => 'flagtypes.name',
+ full_field =>
+ $dbh->sql_string_concat("flagtypes_$chart_id.name", "flags_$chart_id.status"),
+ operator => $operator,
+ value => $value,
+ all_values => $value,
+ quoted => $dbh->quote($value),
+ joins => [],
+ bugs_table => "bugs_$chart_id",
+ };
+ $self->_do_operator_function($subselect_args);
+ my $subselect_term = $subselect_args->{term};
+
+ # don't call build_subselect as this must run as a true sub-select
+ $args->{term} = "EXISTS (
SELECT 1
FROM $bugs_table bugs_$chart_id
LEFT JOIN attachments AS attachments_$chart_id
@@ -2936,209 +2934,224 @@ sub _flagtypes_nonchanged {
}
sub _multiselect_nonchanged {
- my ($self, $args) = @_;
- my ($chart_id, $joins, $field, $operator) =
- @$args{qw(chart_id joins field operator)};
- $args->{term} = $self->_multiselect_term($args)
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator)
+ = @$args{qw(chart_id joins field operator)};
+ $args->{term} = $self->_multiselect_term($args);
}
sub _multiselect_table {
- my ($self, $args) = @_;
- my ($field, $chart_id) = @$args{qw(field chart_id)};
- my $dbh = Bugzilla->dbh;
-
- if ($field eq 'keywords') {
- $args->{full_field} = 'keyworddefs.name';
- return "keywords INNER JOIN keyworddefs".
- " ON keywords.keywordid = keyworddefs.id";
- }
- elsif ($field eq 'tag') {
- $args->{full_field} = 'tag.name';
- return "bug_tag INNER JOIN tag ON bug_tag.tag_id = tag.id AND user_id = "
- . ($self->_sharer_id || $self->_user->id);
- }
- elsif ($field eq 'bug_group') {
- $args->{full_field} = 'groups.name';
- return "bug_group_map INNER JOIN groups
+ my ($self, $args) = @_;
+ my ($field, $chart_id) = @$args{qw(field chart_id)};
+ my $dbh = Bugzilla->dbh;
+
+ if ($field eq 'keywords') {
+ $args->{full_field} = 'keyworddefs.name';
+ return "keywords INNER JOIN keyworddefs"
+ . " ON keywords.keywordid = keyworddefs.id";
+ }
+ elsif ($field eq 'tag') {
+ $args->{full_field} = 'tag.name';
+ return "bug_tag INNER JOIN tag ON bug_tag.tag_id = tag.id AND user_id = "
+ . ($self->_sharer_id || $self->_user->id);
+ }
+ elsif ($field eq 'bug_group') {
+ $args->{full_field} = 'groups.name';
+ return "bug_group_map INNER JOIN groups
ON bug_group_map.group_id = groups.id";
- }
- elsif ($field eq 'blocked' or $field eq 'dependson') {
- my $select = $field eq 'blocked' ? 'dependson' : 'blocked';
- $args->{_select_field} = $select;
- $args->{full_field} = $field;
- return "dependencies";
- }
- elsif ($field eq 'longdesc') {
- $args->{_extra_where} = " AND isprivate = 0"
- if !$self->_user->is_insider;
- $args->{full_field} = 'thetext';
- return "longdescs";
- }
- elsif ($field eq 'longdescs.isprivate') {
- ThrowUserError('auth_failure', { action => 'search',
- object => 'bug_fields',
- field => 'longdescs.isprivate' })
- if !$self->_user->is_insider;
- $args->{full_field} = 'isprivate';
- return "longdescs";
- }
- elsif ($field =~ /^attachments/) {
- $args->{_extra_where} = " AND isprivate = 0"
- if !$self->_user->is_insider;
- $field =~ /^attachments\.(.+)$/;
- $args->{full_field} = $1;
- return "attachments";
- }
- elsif ($field eq 'attach_data.thedata') {
- $args->{_extra_where} = " AND attachments.isprivate = 0"
- if !$self->_user->is_insider;
- return "attachments INNER JOIN attach_data "
- . " ON attachments.attach_id = attach_data.id"
- }
- elsif ($field eq 'comment_tag') {
- $args->{_extra_where} = " AND longdescs.isprivate = 0"
- if !$self->_user->is_insider;
- $args->{full_field} = 'longdescs_tags.tag';
- return "longdescs INNER JOIN longdescs_tags".
- " ON longdescs.comment_id = longdescs_tags.comment_id";
- }
- my $table = "bug_$field";
- $args->{full_field} = "bug_$field.value";
- return $table;
+ }
+ elsif ($field eq 'blocked' or $field eq 'dependson') {
+ my $select = $field eq 'blocked' ? 'dependson' : 'blocked';
+ $args->{_select_field} = $select;
+ $args->{full_field} = $field;
+ return "dependencies";
+ }
+ elsif ($field eq 'longdesc') {
+ $args->{_extra_where} = " AND isprivate = 0" if !$self->_user->is_insider;
+ $args->{full_field} = 'thetext';
+ return "longdescs";
+ }
+ elsif ($field eq 'longdescs.isprivate') {
+ ThrowUserError('auth_failure',
+ {action => 'search', object => 'bug_fields', field => 'longdescs.isprivate'})
+ if !$self->_user->is_insider;
+ $args->{full_field} = 'isprivate';
+ return "longdescs";
+ }
+ elsif ($field =~ /^attachments/) {
+ $args->{_extra_where} = " AND isprivate = 0" if !$self->_user->is_insider;
+ $field =~ /^attachments\.(.+)$/;
+ $args->{full_field} = $1;
+ return "attachments";
+ }
+ elsif ($field eq 'attach_data.thedata') {
+ $args->{_extra_where} = " AND attachments.isprivate = 0"
+ if !$self->_user->is_insider;
+ return "attachments INNER JOIN attach_data "
+ . " ON attachments.attach_id = attach_data.id";
+ }
+ elsif ($field eq 'comment_tag') {
+ $args->{_extra_where} = " AND longdescs.isprivate = 0"
+ if !$self->_user->is_insider;
+ $args->{full_field} = 'longdescs_tags.tag';
+ return "longdescs INNER JOIN longdescs_tags"
+ . " ON longdescs.comment_id = longdescs_tags.comment_id";
+ }
+ my $table = "bug_$field";
+ $args->{full_field} = "bug_$field.value";
+ return $table;
}
sub _multiselect_term {
- my ($self, $args, $not) = @_;
- my ($operator) = $args->{operator};
- my $value = $args->{value} || '';
- # 'empty' operators require special handling
- return $self->_multiselect_isempty($args, $not)
- if ($operator =~ /^is(not)?empty$/ || $value eq '---');
- my $table = $self->_multiselect_table($args);
- $self->_do_operator_function($args);
- my $term = $args->{term};
- $term .= $args->{_extra_where} || '';
- my $select = $args->{_select_field} || 'bug_id';
- return build_subselect("$args->{bugs_table}.bug_id", $select, $table, $term, $not);
+ my ($self, $args, $not) = @_;
+ my ($operator) = $args->{operator};
+ my $value = $args->{value} || '';
+
+ # 'empty' operators require special handling
+ return $self->_multiselect_isempty($args, $not)
+ if ($operator =~ /^is(not)?empty$/ || $value eq '---');
+ my $table = $self->_multiselect_table($args);
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $term .= $args->{_extra_where} || '';
+ my $select = $args->{_select_field} || 'bug_id';
+ return build_subselect("$args->{bugs_table}.bug_id", $select, $table, $term,
+ $not);
}
# We can't use the normal operator_functions to build isempty queries which
# join to different tables.
sub _multiselect_isempty {
- my ($self, $args, $not) = @_;
- my ($field, $operator, $joins, $chart_id) = @$args{qw(field operator joins chart_id)};
- my $dbh = Bugzilla->dbh;
- $operator = $self->_reverse_operator($operator) if $not;
- $not = $operator eq 'isnotempty' ? 'NOT' : '';
-
- if ($field eq 'keywords') {
- push @$joins, {
- table => 'keywords',
- as => "keywords_$chart_id",
- from => 'bug_id',
- to => 'bug_id',
- };
- return "keywords_$chart_id.bug_id IS $not NULL";
- }
- elsif ($field eq 'bug_group') {
- push @$joins, {
- table => 'bug_group_map',
- as => "bug_group_map_$chart_id",
- from => 'bug_id',
- to => 'bug_id',
- };
- return "bug_group_map_$chart_id.bug_id IS $not NULL";
- }
- elsif ($field eq 'flagtypes.name') {
- push @$joins, {
- table => 'flags',
- as => "flags_$chart_id",
- from => 'bug_id',
- to => 'bug_id',
- };
- return "flags_$chart_id.bug_id IS $not NULL";
- }
- elsif ($field eq 'blocked' or $field eq 'dependson') {
- my $to = $field eq 'blocked' ? 'dependson' : 'blocked';
- push @$joins, {
- table => 'dependencies',
- as => "dependencies_$chart_id",
- from => 'bug_id',
- to => $to,
- };
- return "dependencies_$chart_id.$to IS $not NULL";
- }
- elsif ($field eq 'longdesc') {
- my @extra = ( "longdescs_$chart_id.type != " . CMT_HAS_DUPE );
- push @extra, "longdescs_$chart_id.isprivate = 0"
- unless $self->_user->is_insider;
- push @$joins, {
- table => 'longdescs',
- as => "longdescs_$chart_id",
- from => 'bug_id',
- to => 'bug_id',
- extra => \@extra,
- };
- return $not
- ? "longdescs_$chart_id.thetext != ''"
- : "longdescs_$chart_id.thetext = ''";
- }
- elsif ($field eq 'longdescs.isprivate') {
- ThrowUserError('search_field_operator_invalid', { field => $field,
- operator => $operator });
- }
- elsif ($field =~ /^attachments\.(.+)/) {
- my $sub_field = $1;
- if ($sub_field eq 'description' || $sub_field eq 'filename' || $sub_field eq 'mimetype') {
- # can't be null/empty
- return $not ? '1=1' : '1=2';
- } else {
- # all other fields which get here are boolean
- ThrowUserError('search_field_operator_invalid', { field => $field,
- operator => $operator });
- }
- }
- elsif ($field eq 'attach_data.thedata') {
- push @$joins, {
- table => 'attachments',
- as => "attachments_$chart_id",
- from => 'bug_id',
- to => 'bug_id',
- extra => [ $self->_user->is_insider ? '' : "attachments_$chart_id.isprivate = 0" ],
- };
- push @$joins, {
- table => 'attach_data',
- as => "attach_data_$chart_id",
- from => "attachments_$chart_id.attach_id",
- to => 'id',
- };
- return "attach_data_$chart_id.thedata IS $not NULL";
- }
- elsif ($field eq 'tag') {
- push @$joins, {
- table => 'bug_tag',
- as => "bug_tag_$chart_id",
- from => 'bug_id',
- to => 'bug_id',
- };
- push @$joins, {
- table => 'tag',
- as => "tag_$chart_id",
- from => "bug_tag_$chart_id.tag_id",
- to => 'id',
- extra => [ "tag_$chart_id.user_id = " . ($self->_sharer_id || $self->_user->id) ],
- };
- return "tag_$chart_id.id IS $not NULL";
- }
- elsif ($self->_multi_select_fields->{$field}) {
- push @$joins, {
- table => "bug_$field",
- as => "bug_${field}_$chart_id",
- from => 'bug_id',
- to => 'bug_id',
- };
- return "bug_${field}_$chart_id.bug_id IS $not NULL";
+ my ($self, $args, $not) = @_;
+ my ($field, $operator, $joins, $chart_id)
+ = @$args{qw(field operator joins chart_id)};
+ my $dbh = Bugzilla->dbh;
+ $operator = $self->_reverse_operator($operator) if $not;
+ $not = $operator eq 'isnotempty' ? 'NOT' : '';
+
+ if ($field eq 'keywords') {
+ push @$joins,
+ {
+ table => 'keywords',
+ as => "keywords_$chart_id",
+ from => 'bug_id',
+ to => 'bug_id',
+ };
+ return "keywords_$chart_id.bug_id IS $not NULL";
+ }
+ elsif ($field eq 'bug_group') {
+ push @$joins,
+ {
+ table => 'bug_group_map',
+ as => "bug_group_map_$chart_id",
+ from => 'bug_id',
+ to => 'bug_id',
+ };
+ return "bug_group_map_$chart_id.bug_id IS $not NULL";
+ }
+ elsif ($field eq 'flagtypes.name') {
+ push @$joins,
+ {
+ table => 'flags',
+ as => "flags_$chart_id",
+ from => 'bug_id',
+ to => 'bug_id',
+ };
+ return "flags_$chart_id.bug_id IS $not NULL";
+ }
+ elsif ($field eq 'blocked' or $field eq 'dependson') {
+ my $to = $field eq 'blocked' ? 'dependson' : 'blocked';
+ push @$joins,
+ {
+ table => 'dependencies',
+ as => "dependencies_$chart_id",
+ from => 'bug_id',
+ to => $to,
+ };
+ return "dependencies_$chart_id.$to IS $not NULL";
+ }
+ elsif ($field eq 'longdesc') {
+ my @extra = ("longdescs_$chart_id.type != " . CMT_HAS_DUPE);
+ push @extra, "longdescs_$chart_id.isprivate = 0"
+ unless $self->_user->is_insider;
+ push @$joins,
+ {
+ table => 'longdescs',
+ as => "longdescs_$chart_id",
+ from => 'bug_id',
+ to => 'bug_id',
+ extra => \@extra,
+ };
+ return $not
+ ? "longdescs_$chart_id.thetext != ''"
+ : "longdescs_$chart_id.thetext = ''";
+ }
+ elsif ($field eq 'longdescs.isprivate') {
+ ThrowUserError('search_field_operator_invalid',
+ {field => $field, operator => $operator});
+ }
+ elsif ($field =~ /^attachments\.(.+)/) {
+ my $sub_field = $1;
+ if ( $sub_field eq 'description'
+ || $sub_field eq 'filename'
+ || $sub_field eq 'mimetype')
+ {
+ # can't be null/empty
+ return $not ? '1=1' : '1=2';
}
+ else {
+ # all other fields which get here are boolean
+ ThrowUserError('search_field_operator_invalid',
+ {field => $field, operator => $operator});
+ }
+ }
+ elsif ($field eq 'attach_data.thedata') {
+ push @$joins,
+ {
+ table => 'attachments',
+ as => "attachments_$chart_id",
+ from => 'bug_id',
+ to => 'bug_id',
+ extra =>
+ [$self->_user->is_insider ? '' : "attachments_$chart_id.isprivate = 0"],
+ };
+ push @$joins,
+ {
+ table => 'attach_data',
+ as => "attach_data_$chart_id",
+ from => "attachments_$chart_id.attach_id",
+ to => 'id',
+ };
+ return "attach_data_$chart_id.thedata IS $not NULL";
+ }
+ elsif ($field eq 'tag') {
+ push @$joins,
+ {
+ table => 'bug_tag',
+ as => "bug_tag_$chart_id",
+ from => 'bug_id',
+ to => 'bug_id',
+ };
+ push @$joins,
+ {
+ table => 'tag',
+ as => "tag_$chart_id",
+ from => "bug_tag_$chart_id.tag_id",
+ to => 'id',
+ extra => ["tag_$chart_id.user_id = " . ($self->_sharer_id || $self->_user->id)],
+ };
+ return "tag_$chart_id.id IS $not NULL";
+ }
+ elsif ($self->_multi_select_fields->{$field}) {
+ push @$joins,
+ {
+ table => "bug_$field",
+ as => "bug_${field}_$chart_id",
+ from => 'bug_id',
+ to => 'bug_id',
+ };
+ return "bug_${field}_$chart_id.bug_id IS $not NULL";
+ }
}
###############################
@@ -3146,234 +3159,236 @@ sub _multiselect_isempty {
###############################
sub _simple_operator {
- my ($self, $args) = @_;
- my ($full_field, $quoted, $operator) =
- @$args{qw(full_field quoted operator)};
- my $sql_operator = SIMPLE_OPERATORS->{$operator};
- $args->{term} = "$full_field $sql_operator $quoted";
+ my ($self, $args) = @_;
+ my ($full_field, $quoted, $operator) = @$args{qw(full_field quoted operator)};
+ my $sql_operator = SIMPLE_OPERATORS->{$operator};
+ $args->{term} = "$full_field $sql_operator $quoted";
}
sub _casesubstring {
- my ($self, $args) = @_;
- my ($full_field, $value) = @$args{qw(full_field value)};
- my $dbh = Bugzilla->dbh;
+ my ($self, $args) = @_;
+ my ($full_field, $value) = @$args{qw(full_field value)};
+ my $dbh = Bugzilla->dbh;
- $args->{term} = $dbh->sql_like($value, $full_field);
+ $args->{term} = $dbh->sql_like($value, $full_field);
}
sub _substring {
- my ($self, $args) = @_;
- my ($full_field, $value) = @$args{qw(full_field value)};
- my $dbh = Bugzilla->dbh;
+ my ($self, $args) = @_;
+ my ($full_field, $value) = @$args{qw(full_field value)};
+ my $dbh = Bugzilla->dbh;
- $args->{term} = $dbh->sql_ilike($value, $full_field);
+ $args->{term} = $dbh->sql_ilike($value, $full_field);
}
sub _notsubstring {
- my ($self, $args) = @_;
- my ($full_field, $value) = @$args{qw(full_field value)};
- my $dbh = Bugzilla->dbh;
+ my ($self, $args) = @_;
+ my ($full_field, $value) = @$args{qw(full_field value)};
+ my $dbh = Bugzilla->dbh;
- $args->{term} = $dbh->sql_not_ilike($value, $full_field);
+ $args->{term} = $dbh->sql_not_ilike($value, $full_field);
}
sub _regexp {
- my ($self, $args) = @_;
- my ($full_field, $quoted) = @$args{qw(full_field quoted)};
- my $dbh = Bugzilla->dbh;
-
- $args->{term} = $dbh->sql_regexp($full_field, $quoted);
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
+ my $dbh = Bugzilla->dbh;
+
+ $args->{term} = $dbh->sql_regexp($full_field, $quoted);
}
sub _notregexp {
- my ($self, $args) = @_;
- my ($full_field, $quoted) = @$args{qw(full_field quoted)};
- my $dbh = Bugzilla->dbh;
-
- $args->{term} = $dbh->sql_not_regexp($full_field, $quoted);
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
+ my $dbh = Bugzilla->dbh;
+
+ $args->{term} = $dbh->sql_not_regexp($full_field, $quoted);
}
sub _anyexact {
- my ($self, $args) = @_;
- my ($field, $full_field) = @$args{qw(field full_field)};
- my $dbh = Bugzilla->dbh;
-
- my @list = $self->_all_values($args, ',');
- @list = map { $self->_quote_unless_numeric($args, $_) } @list;
-
- if (@list) {
- $args->{term} = $dbh->sql_in($full_field, \@list);
- }
- else {
- $args->{term} = '';
- }
+ my ($self, $args) = @_;
+ my ($field, $full_field) = @$args{qw(field full_field)};
+ my $dbh = Bugzilla->dbh;
+
+ my @list = $self->_all_values($args, ',');
+ @list = map { $self->_quote_unless_numeric($args, $_) } @list;
+
+ if (@list) {
+ $args->{term} = $dbh->sql_in($full_field, \@list);
+ }
+ else {
+ $args->{term} = '';
+ }
}
sub _anywordsubstr {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my @terms = $self->_substring_terms($args);
- $args->{term} = @terms ? '(' . join("\n\tOR ", @terms) . ')' : '';
+ my @terms = $self->_substring_terms($args);
+ $args->{term} = @terms ? '(' . join("\n\tOR ", @terms) . ')' : '';
}
sub _allwordssubstr {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my @terms = $self->_substring_terms($args);
- $args->{term} = @terms ? '(' . join("\n\tAND ", @terms) . ')' : '';
+ my @terms = $self->_substring_terms($args);
+ $args->{term} = @terms ? '(' . join("\n\tAND ", @terms) . ')' : '';
}
sub _nowordssubstr {
- my ($self, $args) = @_;
- $self->_anywordsubstr($args);
- my $term = $args->{term};
- $args->{term} = "NOT($term)";
+ my ($self, $args) = @_;
+ $self->_anywordsubstr($args);
+ my $term = $args->{term};
+ $args->{term} = "NOT($term)";
}
sub _anywords {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
+
+ my @terms = $self->_word_terms($args);
- my @terms = $self->_word_terms($args);
- # Because _word_terms uses AND, we need to parenthesize its terms
- # if there are more than one.
- @terms = map("($_)", @terms) if scalar(@terms) > 1;
- $args->{term} = @terms ? '(' . join("\n\tOR ", @terms) . ')' : '';
+ # Because _word_terms uses AND, we need to parenthesize its terms
+ # if there are more than one.
+ @terms = map("($_)", @terms) if scalar(@terms) > 1;
+ $args->{term} = @terms ? '(' . join("\n\tOR ", @terms) . ')' : '';
}
sub _allwords {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my @terms = $self->_word_terms($args);
- $args->{term} = @terms ? '(' . join("\n\tAND ", @terms) . ')' : '';
+ my @terms = $self->_word_terms($args);
+ $args->{term} = @terms ? '(' . join("\n\tAND ", @terms) . ')' : '';
}
sub _nowords {
- my ($self, $args) = @_;
- $self->_anywords($args);
- my $term = $args->{term};
- $args->{term} = "NOT($term)";
+ my ($self, $args) = @_;
+ $self->_anywords($args);
+ my $term = $args->{term};
+ $args->{term} = "NOT($term)";
}
sub _changedbefore_changedafter {
- my ($self, $args) = @_;
- my ($chart_id, $joins, $field, $operator, $value) =
- @$args{qw(chart_id joins field operator value)};
- my $dbh = Bugzilla->dbh;
-
- my $field_object = $self->_chart_fields->{$field}
- || ThrowCodeError("invalid_field_name", { field => $field });
-
- # Asking when creation_ts changed is just asking when the bug was created.
- if ($field_object->name eq 'creation_ts') {
- $args->{operator} =
- $operator eq 'changedbefore' ? 'lessthaneq' : 'greaterthaneq';
- return $self->_do_operator_function($args);
- }
-
- my $sql_operator = ($operator =~ /before/) ? '<=' : '>=';
- my $field_id = $field_object->id;
- # Charts on changed* fields need to be field-specific. Otherwise,
- # OR chart rows make no sense if they contain multiple fields.
- my $table = "act_${field_id}_$chart_id";
-
- my $sql_date = $dbh->quote(SqlifyDate($value));
- my $join = {
- table => 'bugs_activity',
- as => $table,
- extra => ["$table.fieldid = $field_id",
- "$table.bug_when $sql_operator $sql_date"],
- };
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator, $value)
+ = @$args{qw(chart_id joins field operator value)};
+ my $dbh = Bugzilla->dbh;
- $args->{term} = "$table.bug_when IS NOT NULL";
- $self->_changed_security_check($args, $join);
- push(@$joins, $join);
+ my $field_object = $self->_chart_fields->{$field}
+ || ThrowCodeError("invalid_field_name", {field => $field});
+
+ # Asking when creation_ts changed is just asking when the bug was created.
+ if ($field_object->name eq 'creation_ts') {
+ $args->{operator}
+ = $operator eq 'changedbefore' ? 'lessthaneq' : 'greaterthaneq';
+ return $self->_do_operator_function($args);
+ }
+
+ my $sql_operator = ($operator =~ /before/) ? '<=' : '>=';
+ my $field_id = $field_object->id;
+
+ # Charts on changed* fields need to be field-specific. Otherwise,
+ # OR chart rows make no sense if they contain multiple fields.
+ my $table = "act_${field_id}_$chart_id";
+
+ my $sql_date = $dbh->quote(SqlifyDate($value));
+ my $join = {
+ table => 'bugs_activity',
+ as => $table,
+ extra =>
+ ["$table.fieldid = $field_id", "$table.bug_when $sql_operator $sql_date"],
+ };
+
+ $args->{term} = "$table.bug_when IS NOT NULL";
+ $self->_changed_security_check($args, $join);
+ push(@$joins, $join);
}
sub _changedfrom_changedto {
- my ($self, $args) = @_;
- my ($chart_id, $joins, $field, $operator, $quoted) =
- @$args{qw(chart_id joins field operator quoted)};
-
- my $column = ($operator =~ /from/) ? 'removed' : 'added';
- my $field_object = $self->_chart_fields->{$field}
- || ThrowCodeError("invalid_field_name", { field => $field });
- my $field_id = $field_object->id;
- my $table = "act_${field_id}_$chart_id";
- my $join = {
- table => 'bugs_activity',
- as => $table,
- extra => ["$table.fieldid = $field_id",
- "$table.$column = $quoted"],
- };
-
- $args->{term} = "$table.bug_when IS NOT NULL";
- $self->_changed_security_check($args, $join);
- push(@$joins, $join);
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator, $quoted)
+ = @$args{qw(chart_id joins field operator quoted)};
+
+ my $column = ($operator =~ /from/) ? 'removed' : 'added';
+ my $field_object = $self->_chart_fields->{$field}
+ || ThrowCodeError("invalid_field_name", {field => $field});
+ my $field_id = $field_object->id;
+ my $table = "act_${field_id}_$chart_id";
+ my $join = {
+ table => 'bugs_activity',
+ as => $table,
+ extra => ["$table.fieldid = $field_id", "$table.$column = $quoted"],
+ };
+
+ $args->{term} = "$table.bug_when IS NOT NULL";
+ $self->_changed_security_check($args, $join);
+ push(@$joins, $join);
}
sub _changedby {
- my ($self, $args) = @_;
- my ($chart_id, $joins, $field, $operator, $value) =
- @$args{qw(chart_id joins field operator value)};
-
- my $field_object = $self->_chart_fields->{$field}
- || ThrowCodeError("invalid_field_name", { field => $field });
- my $field_id = $field_object->id;
- my $table = "act_${field_id}_$chart_id";
- my $user_id = $self->_get_user_id($value);
- my $join = {
- table => 'bugs_activity',
- as => $table,
- extra => ["$table.fieldid = $field_id",
- "$table.who = $user_id"],
- };
-
- $args->{term} = "$table.bug_when IS NOT NULL";
- $self->_changed_security_check($args, $join);
- push(@$joins, $join);
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator, $value)
+ = @$args{qw(chart_id joins field operator value)};
+
+ my $field_object = $self->_chart_fields->{$field}
+ || ThrowCodeError("invalid_field_name", {field => $field});
+ my $field_id = $field_object->id;
+ my $table = "act_${field_id}_$chart_id";
+ my $user_id = $self->_get_user_id($value);
+ my $join = {
+ table => 'bugs_activity',
+ as => $table,
+ extra => ["$table.fieldid = $field_id", "$table.who = $user_id"],
+ };
+
+ $args->{term} = "$table.bug_when IS NOT NULL";
+ $self->_changed_security_check($args, $join);
+ push(@$joins, $join);
}
sub _changed_security_check {
- my ($self, $args, $join) = @_;
- my ($chart_id, $field) = @$args{qw(chart_id field)};
-
- my $field_object = $self->_chart_fields->{$field}
- || ThrowCodeError("invalid_field_name", { field => $field });
- my $field_id = $field_object->id;
-
- # If the user is not part of the insiders group, they cannot see
- # changes to attachments (including attachment flags) that are private
- if ($field =~ /^(?:flagtypes\.name$|attach)/ and !$self->_user->is_insider) {
- $join->{then_to} = {
- as => "attach_${field_id}_$chart_id",
- table => 'attachments',
- from => "act_${field_id}_$chart_id.attach_id",
- to => 'attach_id',
- };
-
- $args->{term} .= " AND COALESCE(attach_${field_id}_$chart_id.isprivate, 0) = 0";
- }
+ my ($self, $args, $join) = @_;
+ my ($chart_id, $field) = @$args{qw(chart_id field)};
+
+ my $field_object = $self->_chart_fields->{$field}
+ || ThrowCodeError("invalid_field_name", {field => $field});
+ my $field_id = $field_object->id;
+
+ # If the user is not part of the insiders group, they cannot see
+ # changes to attachments (including attachment flags) that are private
+ if ($field =~ /^(?:flagtypes\.name$|attach)/ and !$self->_user->is_insider) {
+ $join->{then_to} = {
+ as => "attach_${field_id}_$chart_id",
+ table => 'attachments',
+ from => "act_${field_id}_$chart_id.attach_id",
+ to => 'attach_id',
+ };
+
+ $args->{term} .= " AND COALESCE(attach_${field_id}_$chart_id.isprivate, 0) = 0";
+ }
}
sub _isempty {
- my ($self, $args) = @_;
- my $full_field = $args->{full_field};
- $args->{term} = "$full_field IS NULL OR $full_field = " . $self->_empty_value($args->{field});
+ my ($self, $args) = @_;
+ my $full_field = $args->{full_field};
+ $args->{term} = "$full_field IS NULL OR $full_field = "
+ . $self->_empty_value($args->{field});
}
sub _isnotempty {
- my ($self, $args) = @_;
- my $full_field = $args->{full_field};
- $args->{term} = "$full_field IS NOT NULL AND $full_field != " . $self->_empty_value($args->{field});
+ my ($self, $args) = @_;
+ my $full_field = $args->{full_field};
+ $args->{term} = "$full_field IS NOT NULL AND $full_field != "
+ . $self->_empty_value($args->{field});
}
sub _empty_value {
- my ($self, $field) = @_;
- my $field_obj = $self->_chart_fields->{$field};
- return "0" if $field_obj->type == FIELD_TYPE_BUG_ID;
- return Bugzilla->dbh->quote(EMPTY_DATETIME) if $field_obj->type == FIELD_TYPE_DATETIME;
- return Bugzilla->dbh->quote(EMPTY_DATE) if $field_obj->type == FIELD_TYPE_DATE;
- return "''";
+ my ($self, $field) = @_;
+ my $field_obj = $self->_chart_fields->{$field};
+ return "0" if $field_obj->type == FIELD_TYPE_BUG_ID;
+ return Bugzilla->dbh->quote(EMPTY_DATETIME)
+ if $field_obj->type == FIELD_TYPE_DATETIME;
+ return Bugzilla->dbh->quote(EMPTY_DATE) if $field_obj->type == FIELD_TYPE_DATE;
+ return "''";
}
######################
@@ -3381,46 +3396,47 @@ sub _empty_value {
######################
# Validate that the query type is one we can deal with
-sub IsValidQueryType
-{
- my ($queryType) = @_;
- if (grep { $_ eq $queryType } qw(specific advanced)) {
- return 1;
- }
- return 0;
+sub IsValidQueryType {
+ my ($queryType) = @_;
+ if (grep { $_ eq $queryType } qw(specific advanced)) {
+ return 1;
+ }
+ return 0;
}
# Splits out "asc|desc" from a sort order item.
sub split_order_term {
- my $fragment = shift;
- $fragment =~ /^(.+?)(?:\s+(ASC|DESC))?$/i;
- my ($column_name, $direction) = (lc($1), uc($2 || ''));
- return wantarray ? ($column_name, $direction) : $column_name;
+ my $fragment = shift;
+ $fragment =~ /^(.+?)(?:\s+(ASC|DESC))?$/i;
+ my ($column_name, $direction) = (lc($1), uc($2 || ''));
+ return wantarray ? ($column_name, $direction) : $column_name;
}
# Used to translate old SQL fragments from buglist.cgi's "order" argument
# into our modern field IDs.
sub _translate_old_column {
- my ($self, $column) = @_;
- # All old SQL fragments have a period in them somewhere.
- return $column if $column !~ /\./;
+ my ($self, $column) = @_;
- if ($column =~ /\bAS\s+(\w+)$/i) {
- return $1;
- }
- # product, component, classification, assigned_to, qa_contact, reporter
- elsif ($column =~ /map_(\w+?)s?\.(login_)?name/i) {
- return $1;
- }
-
- # If it doesn't match the regexps above, check to see if the old
- # SQL fragment matches the SQL of an existing column
- foreach my $key (%{ $self->COLUMNS }) {
- next unless exists $self->COLUMNS->{$key}->{name};
- return $key if $self->COLUMNS->{$key}->{name} eq $column;
- }
+ # All old SQL fragments have a period in them somewhere.
+ return $column if $column !~ /\./;
+
+ if ($column =~ /\bAS\s+(\w+)$/i) {
+ return $1;
+ }
+
+ # product, component, classification, assigned_to, qa_contact, reporter
+ elsif ($column =~ /map_(\w+?)s?\.(login_)?name/i) {
+ return $1;
+ }
+
+ # If it doesn't match the regexps above, check to see if the old
+ # SQL fragment matches the SQL of an existing column
+ foreach my $key (%{$self->COLUMNS}) {
+ next unless exists $self->COLUMNS->{$key}->{name};
+ return $key if $self->COLUMNS->{$key}->{name} eq $column;
+ }
- return $column;
+ return $column;
}
1;
diff --git a/Bugzilla/Search/Clause.pm b/Bugzilla/Search/Clause.pm
index 1d7872c78..940f88ff3 100644
--- a/Bugzilla/Search/Clause.pm
+++ b/Bugzilla/Search/Clause.pm
@@ -16,121 +16,123 @@ use Bugzilla::Search::Condition qw(condition);
use Bugzilla::Util qw(trick_taint);
sub new {
- my ($class, $joiner) = @_;
- if ($joiner and $joiner ne 'OR' and $joiner ne 'AND') {
- ThrowCodeError('search_invalid_joiner', { joiner => $joiner });
- }
- # This will go into SQL directly so needs to be untainted.
- trick_taint($joiner) if $joiner;
- bless { joiner => $joiner || 'AND' }, $class;
+ my ($class, $joiner) = @_;
+ if ($joiner and $joiner ne 'OR' and $joiner ne 'AND') {
+ ThrowCodeError('search_invalid_joiner', {joiner => $joiner});
+ }
+
+ # This will go into SQL directly so needs to be untainted.
+ trick_taint($joiner) if $joiner;
+ bless {joiner => $joiner || 'AND'}, $class;
}
sub children {
- my ($self) = @_;
- $self->{children} ||= [];
- return $self->{children};
+ my ($self) = @_;
+ $self->{children} ||= [];
+ return $self->{children};
}
sub update_search_args {
- my ($self, $search_args) = @_;
- # abstract
+ my ($self, $search_args) = @_;
+
+ # abstract
}
sub joiner { return $_[0]->{joiner} }
sub has_translated_conditions {
- my ($self) = @_;
- my $children = $self->children;
- return 1 if grep { $_->isa('Bugzilla::Search::Condition')
- && $_->translated } @$children;
- foreach my $child (@$children) {
- next if $child->isa('Bugzilla::Search::Condition');
- return 1 if $child->has_translated_conditions;
- }
- return 0;
+ my ($self) = @_;
+ my $children = $self->children;
+ return 1
+ if grep { $_->isa('Bugzilla::Search::Condition') && $_->translated }
+ @$children;
+ foreach my $child (@$children) {
+ next if $child->isa('Bugzilla::Search::Condition');
+ return 1 if $child->has_translated_conditions;
+ }
+ return 0;
}
sub add {
- my $self = shift;
- my $children = $self->children;
- if (@_ == 3) {
- push(@$children, condition(@_));
- return;
- }
-
- my ($child) = @_;
- return if !defined $child;
- $child->isa(__PACKAGE__) || $child->isa('Bugzilla::Search::Condition')
- || die 'child not the right type: ' . $child;
- push(@{ $self->children }, $child);
+ my $self = shift;
+ my $children = $self->children;
+ if (@_ == 3) {
+ push(@$children, condition(@_));
+ return;
+ }
+
+ my ($child) = @_;
+ return if !defined $child;
+ $child->isa(__PACKAGE__)
+ || $child->isa('Bugzilla::Search::Condition')
+ || die 'child not the right type: ' . $child;
+ push(@{$self->children}, $child);
}
sub negate {
- my ($self, $value) = @_;
- if (@_ == 2) {
- $self->{negate} = $value ? 1 : 0;
- }
- return $self->{negate};
+ my ($self, $value) = @_;
+ if (@_ == 2) {
+ $self->{negate} = $value ? 1 : 0;
+ }
+ return $self->{negate};
}
sub walk_conditions {
- my ($self, $callback) = @_;
- foreach my $child (@{ $self->children }) {
- if ($child->isa('Bugzilla::Search::Condition')) {
- $callback->($self, $child);
- }
- else {
- $child->walk_conditions($callback);
- }
+ my ($self, $callback) = @_;
+ foreach my $child (@{$self->children}) {
+ if ($child->isa('Bugzilla::Search::Condition')) {
+ $callback->($self, $child);
+ }
+ else {
+ $child->walk_conditions($callback);
}
+ }
}
sub as_string {
- my ($self) = @_;
- if (!$self->{sql}) {
- my @strings;
- foreach my $child (@{ $self->children }) {
- next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions;
- next if $child->isa('Bugzilla::Search::Condition')
- && !$child->translated;
-
- my $string = $child->as_string;
- next unless $string;
- if ($self->joiner eq 'AND') {
- $string = "( $string )" if $string =~ /OR/;
- }
- else {
- $string = "( $string )" if $string =~ /AND/;
- }
- push(@strings, $string);
- }
-
- my $sql = join(' ' . $self->joiner . ' ', @strings);
- $sql = "NOT( $sql )" if $sql && $self->negate;
- $self->{sql} = $sql;
+ my ($self) = @_;
+ if (!$self->{sql}) {
+ my @strings;
+ foreach my $child (@{$self->children}) {
+ next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions;
+ next if $child->isa('Bugzilla::Search::Condition') && !$child->translated;
+
+ my $string = $child->as_string;
+ next unless $string;
+ if ($self->joiner eq 'AND') {
+ $string = "( $string )" if $string =~ /OR/;
+ }
+ else {
+ $string = "( $string )" if $string =~ /AND/;
+ }
+ push(@strings, $string);
}
- return $self->{sql};
+
+ my $sql = join(' ' . $self->joiner . ' ', @strings);
+ $sql = "NOT( $sql )" if $sql && $self->negate;
+ $self->{sql} = $sql;
+ }
+ return $self->{sql};
}
# Search.pm converts URL parameters to Clause objects. This helps do the
# reverse.
sub as_params {
- my ($self) = @_;
- my @params;
- foreach my $child (@{ $self->children }) {
- if ($child->isa(__PACKAGE__)) {
- my %open_paren = (f => 'OP', n => scalar $child->negate,
- j => $child->joiner);
- push(@params, \%open_paren);
- push(@params, $child->as_params);
- my %close_paren = (f => 'CP');
- push(@params, \%close_paren);
- }
- else {
- push(@params, $child->as_params);
- }
+ my ($self) = @_;
+ my @params;
+ foreach my $child (@{$self->children}) {
+ if ($child->isa(__PACKAGE__)) {
+ my %open_paren = (f => 'OP', n => scalar $child->negate, j => $child->joiner);
+ push(@params, \%open_paren);
+ push(@params, $child->as_params);
+ my %close_paren = (f => 'CP');
+ push(@params, \%close_paren);
+ }
+ else {
+ push(@params, $child->as_params);
}
- return @params;
+ }
+ return @params;
}
1;
diff --git a/Bugzilla/Search/ClauseGroup.pm b/Bugzilla/Search/ClauseGroup.pm
index 590c737fa..5c7791734 100644
--- a/Bugzilla/Search/ClauseGroup.pm
+++ b/Bugzilla/Search/ClauseGroup.pm
@@ -19,83 +19,88 @@ use Bugzilla::Util qw(trick_taint);
use List::MoreUtils qw(uniq);
use constant UNSUPPORTED_FIELDS => qw(
- attach_data.thedata
- classification
- commenter
- component
- longdescs.count
- product
- owner_idle_time
+ attach_data.thedata
+ classification
+ commenter
+ component
+ longdescs.count
+ product
+ owner_idle_time
);
sub new {
- my ($class) = @_;
- my $self = bless({ joiner => 'AND' }, $class);
- # Add a join back to the bugs table which will be used to group conditions
- # for this clause
- my $condition = Bugzilla::Search::Condition->new({});
- $condition->translated({
- joins => [{
- table => 'bugs',
- as => 'bugs_g0',
- from => 'bug_id',
- to => 'bug_id',
- extra => [],
- }],
- term => '1 = 1',
- });
- $self->SUPER::add($condition);
- $self->{group_condition} = $condition;
- return $self;
+ my ($class) = @_;
+ my $self = bless({joiner => 'AND'}, $class);
+
+ # Add a join back to the bugs table which will be used to group conditions
+ # for this clause
+ my $condition = Bugzilla::Search::Condition->new({});
+ $condition->translated({
+ joins => [{
+ table => 'bugs',
+ as => 'bugs_g0',
+ from => 'bug_id',
+ to => 'bug_id',
+ extra => [],
+ }],
+ term => '1 = 1',
+ });
+ $self->SUPER::add($condition);
+ $self->{group_condition} = $condition;
+ return $self;
}
sub add {
- my ($self, @args) = @_;
- my $field = scalar(@args) == 3 ? $args[0] : $args[0]->{field};
-
- # We don't support nesting of conditions under this clause
- if (scalar(@args) == 1 && !$args[0]->isa('Bugzilla::Search::Condition')) {
- ThrowUserError('search_grouped_invalid_nesting');
- }
-
- # Ensure all conditions use the same field
- if (!$self->{_field}) {
- $self->{_field} = $field;
- } elsif ($field ne $self->{_field}) {
- ThrowUserError('search_grouped_field_mismatch');
- }
-
- # Unsupported fields
- if (grep { $_ eq $field } UNSUPPORTED_FIELDS ) {
- # XXX - Hack till bug 916882 is fixed.
- my $operator = scalar(@args) == 3 ? $args[1] : $args[0]->{operator};
- ThrowUserError('search_grouped_field_invalid', { field => $field })
- unless (($field eq 'product' || $field eq 'component') && $operator =~ /^changed/);
- }
-
- $self->SUPER::add(@args);
+ my ($self, @args) = @_;
+ my $field = scalar(@args) == 3 ? $args[0] : $args[0]->{field};
+
+ # We don't support nesting of conditions under this clause
+ if (scalar(@args) == 1 && !$args[0]->isa('Bugzilla::Search::Condition')) {
+ ThrowUserError('search_grouped_invalid_nesting');
+ }
+
+ # Ensure all conditions use the same field
+ if (!$self->{_field}) {
+ $self->{_field} = $field;
+ }
+ elsif ($field ne $self->{_field}) {
+ ThrowUserError('search_grouped_field_mismatch');
+ }
+
+ # Unsupported fields
+ if (grep { $_ eq $field } UNSUPPORTED_FIELDS) {
+
+ # XXX - Hack till bug 916882 is fixed.
+ my $operator = scalar(@args) == 3 ? $args[1] : $args[0]->{operator};
+ ThrowUserError('search_grouped_field_invalid', {field => $field})
+ unless (($field eq 'product' || $field eq 'component')
+ && $operator =~ /^changed/);
+ }
+
+ $self->SUPER::add(@args);
}
sub update_search_args {
- my ($self, $search_args) = @_;
+ my ($self, $search_args) = @_;
- # No need to change things if there's only one child condition
- return unless scalar(@{ $self->children }) > 1;
+ # No need to change things if there's only one child condition
+ return unless scalar(@{$self->children}) > 1;
- # we want all the terms to use the same join table
- if (!exists $self->{_first_chart_id}) {
- $self->{_first_chart_id} = $search_args->{chart_id};
- } else {
- $search_args->{chart_id} = $self->{_first_chart_id};
- }
+ # we want all the terms to use the same join table
+ if (!exists $self->{_first_chart_id}) {
+ $self->{_first_chart_id} = $search_args->{chart_id};
+ }
+ else {
+ $search_args->{chart_id} = $self->{_first_chart_id};
+ }
- my $suffix = '_g' . $self->{_first_chart_id};
- $self->{group_condition}->{translated}->{joins}->[0]->{as} = "bugs$suffix";
+ my $suffix = '_g' . $self->{_first_chart_id};
+ $self->{group_condition}->{translated}->{joins}->[0]->{as} = "bugs$suffix";
- $search_args->{full_field} =~ s/^bugs\./bugs$suffix\./;
+ $search_args->{full_field} =~ s/^bugs\./bugs$suffix\./;
- $search_args->{table_suffix} = $suffix;
- $search_args->{bugs_table} = "bugs$suffix";
+ $search_args->{table_suffix} = $suffix;
+ $search_args->{bugs_table} = "bugs$suffix";
}
1;
diff --git a/Bugzilla/Search/Condition.pm b/Bugzilla/Search/Condition.pm
index 306a63eed..9ddb6a898 100644
--- a/Bugzilla/Search/Condition.pm
+++ b/Bugzilla/Search/Condition.pm
@@ -15,55 +15,59 @@ use parent qw(Exporter);
our @EXPORT_OK = qw(condition);
sub new {
- my ($class, $params) = @_;
- my %self = %$params;
- bless \%self, $class;
- return \%self;
+ my ($class, $params) = @_;
+ my %self = %$params;
+ bless \%self, $class;
+ return \%self;
}
-sub field { return $_[0]->{field} }
-sub value { return $_[0]->{value} }
+sub field { return $_[0]->{field} }
+sub value { return $_[0]->{value} }
sub operator {
- my ($self, $value) = @_;
- if (@_ == 2) {
- $self->{operator} = $value;
- }
- return $self->{operator};
+ my ($self, $value) = @_;
+ if (@_ == 2) {
+ $self->{operator} = $value;
+ }
+ return $self->{operator};
}
sub fov {
- my ($self) = @_;
- return ($self->field, $self->operator, $self->value);
+ my ($self) = @_;
+ return ($self->field, $self->operator, $self->value);
}
sub translated {
- my ($self, $params) = @_;
- if (@_ == 2) {
- $self->{translated} = $params;
- }
- return $self->{translated};
+ my ($self, $params) = @_;
+ if (@_ == 2) {
+ $self->{translated} = $params;
+ }
+ return $self->{translated};
}
sub as_string {
- my ($self) = @_;
- my $term = $self->translated->{term};
- $term = "NOT( $term )" if $term && $self->negate;
- return $term;
+ my ($self) = @_;
+ my $term = $self->translated->{term};
+ $term = "NOT( $term )" if $term && $self->negate;
+ return $term;
}
sub as_params {
- my ($self) = @_;
- return { f => $self->field, o => $self->operator, v => $self->value,
- n => scalar $self->negate };
+ my ($self) = @_;
+ return {
+ f => $self->field,
+ o => $self->operator,
+ v => $self->value,
+ n => scalar $self->negate
+ };
}
sub negate {
- my ($self, $value) = @_;
- if (@_ == 2) {
- $self->{negate} = $value ? 1 : 0;
- }
- return $self->{negate};
+ my ($self, $value) = @_;
+ if (@_ == 2) {
+ $self->{negate} = $value ? 1 : 0;
+ }
+ return $self->{negate};
}
###########################
@@ -71,9 +75,9 @@ sub negate {
###########################
sub condition {
- my ($field, $operator, $value) = @_;
- return __PACKAGE__->new({ field => $field, operator => $operator,
- value => $value });
+ my ($field, $operator, $value) = @_;
+ return __PACKAGE__->new(
+ {field => $field, operator => $operator, value => $value});
}
1;
diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm
index 4f57b4ebc..e4093fcb5 100644
--- a/Bugzilla/Search/Quicksearch.pm
+++ b/Bugzilla/Search/Quicksearch.pm
@@ -27,246 +27,249 @@ use parent qw(Exporter);
# Custom mappings for some fields.
use constant MAPPINGS => {
- # Status, Resolution, Platform, OS, Priority, Severity
- "status" => "bug_status",
- "platform" => "rep_platform",
- "os" => "op_sys",
- "severity" => "bug_severity",
-
- # People: AssignedTo, Reporter, QA Contact, CC, etc.
- "assignee" => "assigned_to",
- "owner" => "assigned_to",
-
- # Product, Version, Component, Target Milestone
- "milestone" => "target_milestone",
-
- # Summary, Description, URL, Status whiteboard, Keywords
- "summary" => "short_desc",
- "description" => "longdesc",
- "comment" => "longdesc",
- "url" => "bug_file_loc",
- "whiteboard" => "status_whiteboard",
- "sw" => "status_whiteboard",
- "kw" => "keywords",
- "group" => "bug_group",
-
- # Flags
- "flag" => "flagtypes.name",
- "requestee" => "requestees.login_name",
- "setter" => "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"
+
+ # Status, Resolution, Platform, OS, Priority, Severity
+ "status" => "bug_status",
+ "platform" => "rep_platform",
+ "os" => "op_sys",
+ "severity" => "bug_severity",
+
+ # People: AssignedTo, Reporter, QA Contact, CC, etc.
+ "assignee" => "assigned_to",
+ "owner" => "assigned_to",
+
+ # Product, Version, Component, Target Milestone
+ "milestone" => "target_milestone",
+
+ # Summary, Description, URL, Status whiteboard, Keywords
+ "summary" => "short_desc",
+ "description" => "longdesc",
+ "comment" => "longdesc",
+ "url" => "bug_file_loc",
+ "whiteboard" => "status_whiteboard",
+ "sw" => "status_whiteboard",
+ "kw" => "keywords",
+ "group" => "bug_group",
+
+ # Flags
+ "flag" => "flagtypes.name",
+ "requestee" => "requestees.login_name",
+ "setter" => "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"
};
sub FIELD_MAP {
- my $cache = Bugzilla->request_cache;
- return $cache->{quicksearch_fields} if $cache->{quicksearch_fields};
-
- # Get all the fields whose names don't contain periods. (Fields that
- # contain periods are always handled in MAPPINGS.)
- my @db_fields = grep { $_->name !~ /\./ }
- @{ Bugzilla->fields({ obsolete => 0 }) };
- my %full_map = (%{ MAPPINGS() }, map { $_->name => $_->name } @db_fields);
-
- # Eliminate the fields that start with bug_ or rep_, because those are
- # handled by the MAPPINGS instead, and we don't want too many names
- # for them. (Also, otherwise "rep" doesn't match "reporter".)
- #
- # Remove "status_whiteboard" because we have "whiteboard" for it in
- # the mappings, and otherwise "stat" can't match "status".
- #
- # Also, don't allow searching the _accessible stuff via quicksearch
- # (both because it's unnecessary and because otherwise
- # "reporter_accessible" and "reporter" both match "rep".
- delete @full_map{qw(rep_platform bug_status bug_file_loc bug_group
- bug_severity bug_status
- status_whiteboard
- cclist_accessible reporter_accessible)};
-
- Bugzilla::Hook::process('quicksearch_map', {'map' => \%full_map} );
-
- $cache->{quicksearch_fields} = \%full_map;
-
- return $cache->{quicksearch_fields};
+ my $cache = Bugzilla->request_cache;
+ return $cache->{quicksearch_fields} if $cache->{quicksearch_fields};
+
+ # Get all the fields whose names don't contain periods. (Fields that
+ # contain periods are always handled in MAPPINGS.)
+ my @db_fields = grep { $_->name !~ /\./ } @{Bugzilla->fields({obsolete => 0})};
+ my %full_map = (%{MAPPINGS()}, map { $_->name => $_->name } @db_fields);
+
+ # Eliminate the fields that start with bug_ or rep_, because those are
+ # handled by the MAPPINGS instead, and we don't want too many names
+ # for them. (Also, otherwise "rep" doesn't match "reporter".)
+ #
+ # Remove "status_whiteboard" because we have "whiteboard" for it in
+ # the mappings, and otherwise "stat" can't match "status".
+ #
+ # Also, don't allow searching the _accessible stuff via quicksearch
+ # (both because it's unnecessary and because otherwise
+ # "reporter_accessible" and "reporter" both match "rep".
+ delete @full_map{qw(rep_platform bug_status bug_file_loc bug_group
+ bug_severity bug_status
+ status_whiteboard
+ cclist_accessible reporter_accessible)};
+
+ Bugzilla::Hook::process('quicksearch_map', {'map' => \%full_map});
+
+ $cache->{quicksearch_fields} = \%full_map;
+
+ return $cache->{quicksearch_fields};
}
# Certain fields, when specified like "field:value" get an operator other
# than "substring"
-use constant FIELD_OPERATOR => {
- content => 'matches',
- owner_idle_time => 'greaterthan',
-};
+use constant FIELD_OPERATOR =>
+ {content => 'matches', owner_idle_time => 'greaterthan',};
# Mappings for operators symbols to support operators other than "substring"
use constant OPERATOR_SYMBOLS => {
- ':' => 'substring',
- '=' => 'equals',
- '!=' => 'notequals',
- '>=' => 'greaterthaneq',
- '<=' => 'lessthaneq',
- '>' => 'greaterthan',
- '<' => 'lessthan',
+ ':' => 'substring',
+ '=' => 'equals',
+ '!=' => 'notequals',
+ '>=' => 'greaterthaneq',
+ '<=' => 'lessthaneq',
+ '>' => 'greaterthan',
+ '<' => 'lessthan',
};
# We might want to put this into localconfig or somewhere
use constant PRODUCT_EXCEPTIONS => (
- 'row', # [Browser]
- # ^^^
- 'new', # [MailNews]
- # ^^^
+ 'row', # [Browser]
+ # ^^^
+ 'new', # [MailNews]
+ # ^^^
);
use constant COMPONENT_EXCEPTIONS => (
- 'hang' # [Bugzilla: Component/Keyword Changes]
- # ^^^^
+ 'hang' # [Bugzilla: Component/Keyword Changes]
+ # ^^^^
);
# Quicksearch-wide globals for boolean charts.
our ($chart, $and, $or, $fulltext, $bug_status_set);
sub quicksearch {
- my ($searchstring) = (@_);
- my $cgi = Bugzilla->cgi;
-
- $chart = 0;
- $and = 0;
- $or = 0;
+ my ($searchstring) = (@_);
+ my $cgi = Bugzilla->cgi;
+
+ $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_numbers_only($searchstring);
+ }
+ else {
+ _handle_alias($searchstring);
+
+ # Retain backslashes and quotes, to know which strings are quoted,
+ # and which ones are not.
+ my @words = _parse_line('\s+', 1, $searchstring);
+
+ # If parse_line() returns no data, this means strings are badly quoted.
+ # Rather than trying to guess what the user wanted to do, we throw an error.
+ scalar(@words)
+ || ThrowUserError('quicksearch_unbalanced_quotes', {string => $searchstring});
+
+ # A query cannot start with AND or OR, nor can it end with AND, OR or NOT.
+ ThrowUserError('quicksearch_invalid_query')
+ if ($words[0] =~ /^(?:AND|OR)$/ || $words[$#words] =~ /^(?:AND|OR|NOT)$/);
+
+ my (@qswords, @or_group);
+ while (scalar @words) {
+ my $word = shift @words;
+
+ # AND is the default word separator, similar to a whitespace,
+ # but |a AND OR b| is not a valid combination.
+ if ($word eq 'AND') {
+ ThrowUserError('quicksearch_invalid_query', {operators => ['AND', 'OR']})
+ if $words[0] eq 'OR';
+ }
+
+ # |a OR AND b| is not a valid combination.
+ # |a OR OR b| is equivalent to |a OR b| and so is harmless.
+ elsif ($word eq 'OR') {
+ ThrowUserError('quicksearch_invalid_query', {operators => ['OR', 'AND']})
+ if $words[0] eq 'AND';
+ }
+
+ # NOT negates the following word.
+ # |NOT AND| and |NOT OR| are not valid combinations.
+ # |NOT NOT| is fine but has no effect as they cancel themselves.
+ elsif ($word eq 'NOT') {
+ $word = shift @words;
+ next if $word eq 'NOT';
+ if ($word eq 'AND' || $word eq 'OR') {
+ ThrowUserError('quicksearch_invalid_query', {operators => ['NOT', $word]});
+ }
+ unshift(@words, "-$word");
+ }
+ else {
+ # OR groups words together, as OR has higher precedence than AND.
+ push(@or_group, $word);
+
+ # If the next word is not OR, then we are not in a OR group,
+ # or we are leaving it.
+ if (!defined $words[0] || $words[0] ne 'OR') {
+ push(@qswords, join('|', @or_group));
+ @or_group = ();
+ }
+ }
+ }
- # Remove leading and trailing commas and whitespace.
- $searchstring =~ s/(^[\s,]+|[\s,]+$)//g;
- ThrowUserError('buglist_parameters_required') unless ($searchstring);
+ _handle_status_and_resolution($qswords[0]);
+ shift(@qswords) if $bug_status_set;
- if ($searchstring =~ m/^#?[0-9,\s]*$/) {
- _bug_numbers_only($searchstring);
- }
- else {
- _handle_alias($searchstring);
-
- # Retain backslashes and quotes, to know which strings are quoted,
- # and which ones are not.
- my @words = _parse_line('\s+', 1, $searchstring);
- # If parse_line() returns no data, this means strings are badly quoted.
- # Rather than trying to guess what the user wanted to do, we throw an error.
- scalar(@words)
- || ThrowUserError('quicksearch_unbalanced_quotes', {string => $searchstring});
-
- # A query cannot start with AND or OR, nor can it end with AND, OR or NOT.
- ThrowUserError('quicksearch_invalid_query')
- if ($words[0] =~ /^(?:AND|OR)$/ || $words[$#words] =~ /^(?:AND|OR|NOT)$/);
-
- my (@qswords, @or_group);
- while (scalar @words) {
- my $word = shift @words;
- # AND is the default word separator, similar to a whitespace,
- # but |a AND OR b| is not a valid combination.
- if ($word eq 'AND') {
- ThrowUserError('quicksearch_invalid_query', {operators => ['AND', 'OR']})
- if $words[0] eq 'OR';
- }
- # |a OR AND b| is not a valid combination.
- # |a OR OR b| is equivalent to |a OR b| and so is harmless.
- elsif ($word eq 'OR') {
- ThrowUserError('quicksearch_invalid_query', {operators => ['OR', 'AND']})
- if $words[0] eq 'AND';
- }
- # NOT negates the following word.
- # |NOT AND| and |NOT OR| are not valid combinations.
- # |NOT NOT| is fine but has no effect as they cancel themselves.
- elsif ($word eq 'NOT') {
- $word = shift @words;
- next if $word eq 'NOT';
- if ($word eq 'AND' || $word eq 'OR') {
- ThrowUserError('quicksearch_invalid_query', {operators => ['NOT', $word]});
- }
- unshift(@words, "-$word");
- }
- else {
- # OR groups words together, as OR has higher precedence than AND.
- push(@or_group, $word);
- # If the next word is not OR, then we are not in a OR group,
- # or we are leaving it.
- if (!defined $words[0] || $words[0] ne 'OR') {
- push(@qswords, join('|', @or_group));
- @or_group = ();
- }
- }
- }
+ my (@unknownFields, %ambiguous_fields);
+ $fulltext = Bugzilla->user->setting('quicksearch_fulltext') eq 'on' ? 1 : 0;
- _handle_status_and_resolution($qswords[0]);
- shift(@qswords) if $bug_status_set;
-
- my (@unknownFields, %ambiguous_fields);
- $fulltext = Bugzilla->user->setting('quicksearch_fulltext') eq 'on' ? 1 : 0;
-
- # Loop over all main-level QuickSearch words.
- foreach my $qsword (@qswords) {
- my @or_operand = _parse_line('\|', 1, $qsword);
- foreach my $term (@or_operand) {
- next unless defined $term;
- my $negate = substr($term, 0, 1) eq '-';
- if ($negate) {
- $term = substr($term, 1);
- }
-
- next if _handle_special_first_chars($term, $negate);
- next if _handle_field_names($term, $negate, \@unknownFields,
- \%ambiguous_fields);
-
- # Having ruled out the special cases, we may now split
- # by comma, which is another legal boolean OR indicator.
- # Remove quotes from quoted words, if any.
- @words = _parse_line(',', 0, $term);
- foreach my $word (@words) {
- if (!_special_field_syntax($word, $negate)) {
- _default_quicksearch_word($word, $negate);
- }
- _handle_urls($word, $negate);
- }
- }
- $chart++;
- $and = 0;
- $or = 0;
+ # Loop over all main-level QuickSearch words.
+ foreach my $qsword (@qswords) {
+ my @or_operand = _parse_line('\|', 1, $qsword);
+ foreach my $term (@or_operand) {
+ next unless defined $term;
+ my $negate = substr($term, 0, 1) eq '-';
+ if ($negate) {
+ $term = substr($term, 1);
}
- # If there is no mention of a bug status, we restrict the query
- # to open bugs by default.
- unless ($bug_status_set) {
- $cgi->param('bug_status', BUG_STATE_OPEN);
+ next if _handle_special_first_chars($term, $negate);
+ next
+ if _handle_field_names($term, $negate, \@unknownFields, \%ambiguous_fields);
+
+ # Having ruled out the special cases, we may now split
+ # by comma, which is another legal boolean OR indicator.
+ # Remove quotes from quoted words, if any.
+ @words = _parse_line(',', 0, $term);
+ foreach my $word (@words) {
+ if (!_special_field_syntax($word, $negate)) {
+ _default_quicksearch_word($word, $negate);
+ }
+ _handle_urls($word, $negate);
}
+ }
+ $chart++;
+ $and = 0;
+ $or = 0;
+ }
- # Inform user about any unknown fields
- if (scalar(@unknownFields) || scalar(keys %ambiguous_fields)) {
- ThrowUserError("quicksearch_unknown_field",
- { unknown => \@unknownFields,
- ambiguous => \%ambiguous_fields });
- }
+ # If there is no mention of a bug status, we restrict the query
+ # to open bugs by default.
+ unless ($bug_status_set) {
+ $cgi->param('bug_status', BUG_STATE_OPEN);
+ }
- # Make sure we have some query terms left
- scalar($cgi->param())>0 || ThrowUserError("buglist_parameters_required");
+ # Inform user about any unknown fields
+ if (scalar(@unknownFields) || scalar(keys %ambiguous_fields)) {
+ ThrowUserError("quicksearch_unknown_field",
+ {unknown => \@unknownFields, ambiguous => \%ambiguous_fields});
}
- # 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);
+ # Make sure we have some query terms left
+ scalar($cgi->param()) > 0 || ThrowUserError("buglist_parameters_required");
+ }
- if ($cgi->param('load')) {
- my $urlbase = correct_urlbase();
- # Param 'load' asks us to display the query in the advanced search form.
- print $cgi->redirect(-uri => "${urlbase}query.cgi?format=advanced&amp;"
- . $modified_query_string);
- }
+ # 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);
- # 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;
+ if ($cgi->param('load')) {
+ my $urlbase = correct_urlbase();
+
+ # Param 'load' asks us to display the query in the advanced search form.
+ print $cgi->redirect(
+ -uri => "${urlbase}query.cgi?format=advanced&amp;" . $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;
}
##########################
@@ -274,337 +277,355 @@ sub quicksearch {
##########################
sub _parse_line {
- my ($delim, $keep, $line) = @_;
- return () unless defined $line;
-
- # parse_line always treats ' as a quote character, making it impossible
- # to sanely search for contractions. As this behavour isn't
- # configurable, we replace ' with a placeholder to hide it from the
- # parser.
-
- # only treat ' at the start or end of words as quotes
- # it's easier to do this in reverse with regexes
- $line =~ s/(^|\s|:)'/$1\001/g;
- $line =~ s/'($|\s)/\001$1/g;
- $line =~ s/\\?'/\000/g;
- $line =~ tr/\001/'/;
-
- my @words = parse_line($delim, $keep, $line);
- foreach my $word (@words) {
- $word =~ tr/\000/'/ if defined $word;
- }
- return @words;
+ my ($delim, $keep, $line) = @_;
+ return () unless defined $line;
+
+ # parse_line always treats ' as a quote character, making it impossible
+ # to sanely search for contractions. As this behavour isn't
+ # configurable, we replace ' with a placeholder to hide it from the
+ # parser.
+
+ # only treat ' at the start or end of words as quotes
+ # it's easier to do this in reverse with regexes
+ $line =~ s/(^|\s|:)'/$1\001/g;
+ $line =~ s/'($|\s)/\001$1/g;
+ $line =~ s/\\?'/\000/g;
+ $line =~ tr/\001/'/;
+
+ my @words = parse_line($delim, $keep, $line);
+ foreach my $word (@words) {
+ $word =~ tr/\000/'/ if defined $word;
+ }
+ return @words;
}
sub _bug_numbers_only {
- my $searchstring = shift;
- my $cgi = Bugzilla->cgi;
- # Allow separation by comma or whitespace.
- $searchstring =~ s/[,\s]+/,/g;
- # Trim the leading # if used.
- $searchstring =~ s/^#//;
-
- if ($searchstring !~ /,/ && !i_am_webservice()) {
- # Single bug number; shortcut to show_bug.cgi.
- print $cgi->redirect(
- -uri => correct_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('bug_id_type', 'anyexact');
- }
+ my $searchstring = shift;
+ my $cgi = Bugzilla->cgi;
+
+ # Allow separation by comma or whitespace.
+ $searchstring =~ s/[,\s]+/,/g;
+
+ # Trim the leading # if used.
+ $searchstring =~ s/^#//;
+
+ if ($searchstring !~ /,/ && !i_am_webservice()) {
+
+ # Single bug number; shortcut to show_bug.cgi.
+ print $cgi->redirect(
+ -uri => correct_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('bug_id_type', 'anyexact');
+ }
}
sub _handle_alias {
- my $searchstring = shift;
- if ($searchstring =~ /^([^,\s]+)$/) {
- my $alias = $1;
- # We use this direct SQL because we want quicksearch to be VERY fast.
- my $bug_id = Bugzilla->dbh->selectrow_array(
- q{SELECT bug_id FROM bugs_aliases WHERE alias = ?}, undef, $alias);
- # If the user cannot see the bug or if we are using a webservice,
- # do not resolve its alias.
- if ($bug_id && Bugzilla->user->can_see_bug($bug_id) && !i_am_webservice()) {
- $alias = url_quote($alias);
- print Bugzilla->cgi->redirect(
- -uri => correct_urlbase() . "show_bug.cgi?id=$alias");
- exit;
- }
+ my $searchstring = shift;
+ if ($searchstring =~ /^([^,\s]+)$/) {
+ my $alias = $1;
+
+ # We use this direct SQL because we want quicksearch to be VERY fast.
+ my $bug_id
+ = Bugzilla->dbh->selectrow_array(
+ q{SELECT bug_id FROM bugs_aliases WHERE alias = ?},
+ undef, $alias);
+
+ # If the user cannot see the bug or if we are using a webservice,
+ # do not resolve its alias.
+ if ($bug_id && Bugzilla->user->can_see_bug($bug_id) && !i_am_webservice()) {
+ $alias = url_quote($alias);
+ print Bugzilla->cgi->redirect(
+ -uri => correct_urlbase() . "show_bug.cgi?id=$alias");
+ exit;
}
+ }
}
sub _handle_status_and_resolution {
- my $word = shift;
- my $legal_statuses = get_legal_field_values('bug_status');
- my (%states, %resolutions);
- $bug_status_set = 1;
-
- if ($word eq 'OPEN') {
- $states{$_} = 1 foreach BUG_STATE_OPEN;
- }
- # If we want all bugs, then there is nothing to do.
- elsif ($word ne 'ALL'
- && !matchPrefixes(\%states, \%resolutions, $word, $legal_statuses))
- {
- $bug_status_set = 0;
- }
-
- # If we have wanted resolutions, allow closed states
- if (keys(%resolutions)) {
- foreach my $status (@$legal_statuses) {
- $states{$status} = 1 unless is_open_state($status);
- }
+ my $word = shift;
+ my $legal_statuses = get_legal_field_values('bug_status');
+ my (%states, %resolutions);
+ $bug_status_set = 1;
+
+ if ($word eq 'OPEN') {
+ $states{$_} = 1 foreach BUG_STATE_OPEN;
+ }
+
+ # If we want all bugs, then there is nothing to do.
+ elsif ($word ne 'ALL'
+ && !matchPrefixes(\%states, \%resolutions, $word, $legal_statuses))
+ {
+ $bug_status_set = 0;
+ }
+
+ # If we have wanted resolutions, allow closed states
+ if (keys(%resolutions)) {
+ foreach my $status (@$legal_statuses) {
+ $states{$status} = 1 unless is_open_state($status);
}
+ }
- Bugzilla->cgi->param('bug_status', keys(%states));
- Bugzilla->cgi->param('resolution', keys(%resolutions));
+ Bugzilla->cgi->param('bug_status', keys(%states));
+ Bugzilla->cgi->param('resolution', keys(%resolutions));
}
sub _handle_special_first_chars {
- my ($qsword, $negate) = @_;
- return 0 if !defined $qsword || length($qsword) <= 1;
-
- my $firstChar = substr($qsword, 0, 1);
- my $baseWord = substr($qsword, 1);
- my @subWords = split(/,/, $baseWord);
-
- if ($firstChar eq '#') {
- addChart('short_desc', 'substring', $baseWord, $negate);
- addChart('content', 'matches', _matches_phrase($baseWord), $negate) if $fulltext;
- return 1;
- }
- if ($firstChar eq ':') {
- foreach (@subWords) {
- addChart('product', 'substring', $_, $negate);
- addChart('component', 'substring', $_, $negate);
- }
- return 1;
- }
- if ($firstChar eq '@') {
- addChart('assigned_to', 'substring', $_, $negate) foreach (@subWords);
- return 1;
- }
- if ($firstChar eq '[') {
- addChart('short_desc', 'substring', $baseWord, $negate);
- addChart('status_whiteboard', 'substring', $baseWord, $negate);
- return 1;
- }
- if ($firstChar eq '!') {
- addChart('keywords', 'anywords', $baseWord, $negate);
- return 1;
+ my ($qsword, $negate) = @_;
+ return 0 if !defined $qsword || length($qsword) <= 1;
+
+ my $firstChar = substr($qsword, 0, 1);
+ my $baseWord = substr($qsword, 1);
+ my @subWords = split(/,/, $baseWord);
+
+ if ($firstChar eq '#') {
+ addChart('short_desc', 'substring', $baseWord, $negate);
+ addChart('content', 'matches', _matches_phrase($baseWord), $negate)
+ if $fulltext;
+ return 1;
+ }
+ if ($firstChar eq ':') {
+ foreach (@subWords) {
+ addChart('product', 'substring', $_, $negate);
+ addChart('component', 'substring', $_, $negate);
}
- return 0;
+ return 1;
+ }
+ if ($firstChar eq '@') {
+ addChart('assigned_to', 'substring', $_, $negate) foreach (@subWords);
+ return 1;
+ }
+ if ($firstChar eq '[') {
+ addChart('short_desc', 'substring', $baseWord, $negate);
+ addChart('status_whiteboard', 'substring', $baseWord, $negate);
+ return 1;
+ }
+ if ($firstChar eq '!') {
+ addChart('keywords', 'anywords', $baseWord, $negate);
+ return 1;
+ }
+ return 0;
}
sub _handle_field_names {
- my ($or_operand, $negate, $unknownFields, $ambiguous_fields) = @_;
-
- # Generic field1,field2,field3:value1,value2 notation.
- # We have to correctly ignore commas and colons in quotes.
- # Longer operators must be tested first as we don't want single character
- # operators such as <, > and = to be tested before <=, >= and !=.
- my @operators = sort { length($b) <=> length($a) } keys %{ OPERATOR_SYMBOLS() };
-
- foreach my $symbol (@operators) {
- my @field_values = _parse_line($symbol, 1, $or_operand);
- next unless scalar @field_values == 2;
- my @fields = _parse_line(',', 1, $field_values[0]);
- my @values = _parse_line(',', 1, $field_values[1]);
- foreach my $field (@fields) {
- my $translated = _translate_field_name($field);
- # Skip and record any unknown fields
- if (!defined $translated) {
- push(@$unknownFields, $field);
- }
- # If we got back an array, that means the substring is
- # ambiguous and could match more than field name
- elsif (ref $translated) {
- $ambiguous_fields->{$field} = $translated;
- }
- else {
- if ($translated eq 'bug_status' || $translated eq 'resolution') {
- $bug_status_set = 1;
- }
- foreach my $value (@values) {
- my $operator = FIELD_OPERATOR->{$translated}
- || OPERATOR_SYMBOLS->{$symbol}
- || 'substring';
- # If the string was quoted to protect some special
- # characters such as commas and colons, we need
- # to remove quotes.
- if ($value =~ /^(["'])(.+)\1$/) {
- $value = $2;
- $value =~ s/\\(["'])/$1/g;
- }
- # If a requestee is set, we need to handle it separately.
- if ($translated eq 'flagtypes.name' && $value =~ /^([^\?]+\?)([^\?]+)$/) {
- _handle_flags($1, $2, $negate);
- next;
- }
- addChart($translated, $operator, $value, $negate);
- }
- }
+ my ($or_operand, $negate, $unknownFields, $ambiguous_fields) = @_;
+
+ # Generic field1,field2,field3:value1,value2 notation.
+ # We have to correctly ignore commas and colons in quotes.
+ # Longer operators must be tested first as we don't want single character
+ # operators such as <, > and = to be tested before <=, >= and !=.
+ my @operators = sort { length($b) <=> length($a) } keys %{OPERATOR_SYMBOLS()};
+
+ foreach my $symbol (@operators) {
+ my @field_values = _parse_line($symbol, 1, $or_operand);
+ next unless scalar @field_values == 2;
+ my @fields = _parse_line(',', 1, $field_values[0]);
+ my @values = _parse_line(',', 1, $field_values[1]);
+ foreach my $field (@fields) {
+ my $translated = _translate_field_name($field);
+
+ # Skip and record any unknown fields
+ if (!defined $translated) {
+ push(@$unknownFields, $field);
+ }
+
+ # If we got back an array, that means the substring is
+ # ambiguous and could match more than field name
+ elsif (ref $translated) {
+ $ambiguous_fields->{$field} = $translated;
+ }
+ else {
+ if ($translated eq 'bug_status' || $translated eq 'resolution') {
+ $bug_status_set = 1;
}
- return 1;
+ foreach my $value (@values) {
+ my $operator
+ = FIELD_OPERATOR->{$translated} || OPERATOR_SYMBOLS->{$symbol} || 'substring';
+
+ # If the string was quoted to protect some special
+ # characters such as commas and colons, we need
+ # to remove quotes.
+ if ($value =~ /^(["'])(.+)\1$/) {
+ $value = $2;
+ $value =~ s/\\(["'])/$1/g;
+ }
+
+ # If a requestee is set, we need to handle it separately.
+ if ($translated eq 'flagtypes.name' && $value =~ /^([^\?]+\?)([^\?]+)$/) {
+ _handle_flags($1, $2, $negate);
+ next;
+ }
+ addChart($translated, $operator, $value, $negate);
+ }
+ }
}
+ return 1;
+ }
- # Do not look inside quoted strings.
- return 0 if ($or_operand =~ /^(["']).*\1$/);
+ # Do not look inside quoted strings.
+ return 0 if ($or_operand =~ /^(["']).*\1$/);
- # Flag and requestee shortcut.
- if ($or_operand =~ /^([^\?]+\?)([^\?]*)$/) {
- _handle_flags($1, $2, $negate);
- return 1;
- }
+ # Flag and requestee shortcut.
+ if ($or_operand =~ /^([^\?]+\?)([^\?]*)$/) {
+ _handle_flags($1, $2, $negate);
+ return 1;
+ }
- return 0;
+ return 0;
}
sub _handle_flags {
- my ($flag, $requestee, $negate) = @_;
-
- addChart('flagtypes.name', 'substring', $flag, $negate);
- if ($requestee) {
- # FIXME - Every time a requestee is involved and you use OR somewhere
- # in your quick search, the logic will be wrong because boolean charts
- # are unable to run queries of the form (a AND b) OR c. In our case:
- # (flag name is foo AND requestee is bar) OR (any other criteria).
- # But this has never been possible, so this is not a regression. If one
- # needs to run such queries, they must use the Custom Search section of
- # the Advanced Search page.
- $chart++;
- $and = $or = 0;
- addChart('requestees.login_name', 'substring', $requestee, $negate);
- }
+ my ($flag, $requestee, $negate) = @_;
+
+ addChart('flagtypes.name', 'substring', $flag, $negate);
+ if ($requestee) {
+
+ # FIXME - Every time a requestee is involved and you use OR somewhere
+ # in your quick search, the logic will be wrong because boolean charts
+ # are unable to run queries of the form (a AND b) OR c. In our case:
+ # (flag name is foo AND requestee is bar) OR (any other criteria).
+ # But this has never been possible, so this is not a regression. If one
+ # needs to run such queries, they must use the Custom Search section of
+ # the Advanced Search page.
+ $chart++;
+ $and = $or = 0;
+ addChart('requestees.login_name', 'substring', $requestee, $negate);
+ }
}
sub _translate_field_name {
- my $field = shift;
- $field = lc($field);
- my $field_map = FIELD_MAP;
-
- # If the field exactly matches a mapping, just return right now.
- return $field_map->{$field} if exists $field_map->{$field};
-
- # Check if we match, as a starting substring, exactly one field.
- my @field_names = keys %$field_map;
- my @matches = grep { $_ =~ /^\Q$field\E/ } @field_names;
- # Eliminate duplicates that are actually the same field
- # (otherwise "assi" matches both "assignee" and "assigned_to", and
- # the lines below fail when they shouldn't.)
- my %match_unique = map { $field_map->{$_} => $_ } @matches;
- @matches = values %match_unique;
-
- if (scalar(@matches) == 1) {
- return $field_map->{$matches[0]};
- }
- elsif (scalar(@matches) > 1) {
- return \@matches;
+ my $field = shift;
+ $field = lc($field);
+ my $field_map = FIELD_MAP;
+
+ # If the field exactly matches a mapping, just return right now.
+ return $field_map->{$field} if exists $field_map->{$field};
+
+ # Check if we match, as a starting substring, exactly one field.
+ my @field_names = keys %$field_map;
+ my @matches = grep { $_ =~ /^\Q$field\E/ } @field_names;
+
+ # Eliminate duplicates that are actually the same field
+ # (otherwise "assi" matches both "assignee" and "assigned_to", and
+ # the lines below fail when they shouldn't.)
+ my %match_unique = map { $field_map->{$_} => $_ } @matches;
+ @matches = values %match_unique;
+
+ if (scalar(@matches) == 1) {
+ return $field_map->{$matches[0]};
+ }
+ elsif (scalar(@matches) > 1) {
+ return \@matches;
+ }
+
+ # Check if we match exactly one custom field, ignoring the cf_ on the
+ # custom fields (to allow people to type things like "build" for
+ # "cf_build").
+ my %cfless;
+ foreach my $name (@field_names) {
+ my $no_cf = $name;
+ if ($no_cf =~ s/^cf_//) {
+ if ($field eq $no_cf) {
+ return $field_map->{$name};
+ }
+ $cfless{$no_cf} = $name;
}
+ }
- # Check if we match exactly one custom field, ignoring the cf_ on the
- # custom fields (to allow people to type things like "build" for
- # "cf_build").
- my %cfless;
- foreach my $name (@field_names) {
- my $no_cf = $name;
- if ($no_cf =~ s/^cf_//) {
- if ($field eq $no_cf) {
- return $field_map->{$name};
- }
- $cfless{$no_cf} = $name;
- }
- }
+ # See if we match exactly one substring of any of the cf_-less fields.
+ my @cfless_matches = grep { $_ =~ /^\Q$field\E/ } (keys %cfless);
- # See if we match exactly one substring of any of the cf_-less fields.
- my @cfless_matches = grep { $_ =~ /^\Q$field\E/ } (keys %cfless);
+ if (scalar(@cfless_matches) == 1) {
+ my $match = $cfless_matches[0];
+ my $actual_field = $cfless{$match};
+ return $field_map->{$actual_field};
+ }
+ elsif (scalar(@matches) > 1) {
+ return \@matches;
+ }
- if (scalar(@cfless_matches) == 1) {
- my $match = $cfless_matches[0];
- my $actual_field = $cfless{$match};
- return $field_map->{$actual_field};
- }
- elsif (scalar(@matches) > 1) {
- return \@matches;
- }
-
- return undef;
+ return undef;
}
sub _special_field_syntax {
- my ($word, $negate) = @_;
-
- # P1-5 Syntax
- if ($word =~ m/^P(\d+)(?:-(\d+))?$/i) {
- my ($p_start, $p_end) = ($1, $2);
- my $legal_priorities = get_legal_field_values('priority');
-
- # If Pn exists explicitly, use it.
- my $start = firstidx { $_ eq "P$p_start" } @$legal_priorities;
- my $end;
- $end = firstidx { $_ eq "P$p_end" } @$legal_priorities if defined $p_end;
-
- # If Pn doesn't exist explicitly, then we mean the nth priority.
- if ($start == -1) {
- $start = max(0, $p_start - 1);
- }
- my $prios = $legal_priorities->[$start];
-
- if (defined $end) {
- # If Pn doesn't exist explicitly, then we mean the nth priority.
- if ($end == -1) {
- $end = min(scalar(@$legal_priorities), $p_end) - 1;
- $end = max(0, $end); # Just in case the user typed P0.
- }
- ($start, $end) = ($end, $start) if $end < $start;
- $prios = join(',', @$legal_priorities[$start..$end])
- }
+ my ($word, $negate) = @_;
+
+ # P1-5 Syntax
+ if ($word =~ m/^P(\d+)(?:-(\d+))?$/i) {
+ my ($p_start, $p_end) = ($1, $2);
+ my $legal_priorities = get_legal_field_values('priority');
+
+ # If Pn exists explicitly, use it.
+ my $start = firstidx { $_ eq "P$p_start" } @$legal_priorities;
+ my $end;
+ $end = firstidx { $_ eq "P$p_end" } @$legal_priorities if defined $p_end;
+
+ # If Pn doesn't exist explicitly, then we mean the nth priority.
+ if ($start == -1) {
+ $start = max(0, $p_start - 1);
+ }
+ my $prios = $legal_priorities->[$start];
- addChart('priority', 'anyexact', $prios, $negate);
- return 1;
+ if (defined $end) {
+
+ # If Pn doesn't exist explicitly, then we mean the nth priority.
+ if ($end == -1) {
+ $end = min(scalar(@$legal_priorities), $p_end) - 1;
+ $end = max(0, $end); # Just in case the user typed P0.
+ }
+ ($start, $end) = ($end, $start) if $end < $start;
+ $prios = join(',', @$legal_priorities[$start .. $end]);
}
- return 0;
+
+ addChart('priority', 'anyexact', $prios, $negate);
+ return 1;
+ }
+ return 0;
}
sub _default_quicksearch_word {
- my ($word, $negate) = @_;
-
+ my ($word, $negate) = @_;
+
# if (!grep { lc($word) eq $_ } PRODUCT_EXCEPTIONS and length($word) > 2) {
# addChart('product', 'substring', $word, $negate);
# }
-
+
# if (!grep { lc($word) eq $_ } COMPONENT_EXCEPTIONS and length($word) > 2) {
# addChart('component', 'substring', $word, $negate);
# }
-
+
# my @legal_keywords = map($_->name, Bugzilla::Keyword->get_all);
# if (grep { lc($word) eq lc($_) } @legal_keywords) {
# addChart('keywords', 'substring', $word, $negate);
# }
-
- addChart('alias', 'substring', $word, $negate);
- addChart('short_desc', 'substring', $word, $negate);
+
+ addChart('alias', 'substring', $word, $negate);
+ addChart('short_desc', 'substring', $word, $negate);
+
# addChart('status_whiteboard', 'substring', $word, $negate);
# addChart('content', 'matches', _matches_phrase($word), $negate) if $fulltext;
}
sub _handle_urls {
- my ($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);
- }
+ my ($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);
+ }
}
###########################################################################
@@ -613,70 +634,70 @@ sub _handle_urls {
# Quote and escape a phrase appropriately for a "content matches" search.
sub _matches_phrase {
- my ($phrase) = @_;
- $phrase =~ s/"/\\"/g;
- return "\"$phrase\"";
+ my ($phrase) = @_;
+ $phrase =~ s/"/\\"/g;
+ return "\"$phrase\"";
}
# Expand found prefixes to states or resolutions
sub matchPrefixes {
- my ($hr_states, $hr_resolutions, $word, $ar_check_states) = @_;
- return unless $word =~ /^[A-Z_]+(,[A-Z_]+)*$/;
-
- my @ar_prefixes = split(/,/, $word);
- my $ar_check_resolutions = get_legal_field_values('resolution');
- 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;
- }
- }
+ my ($hr_states, $hr_resolutions, $word, $ar_check_states) = @_;
+ return unless $word =~ /^[A-Z_]+(,[A-Z_]+)*$/;
+
+ my @ar_prefixes = split(/,/, $word);
+ my $ar_check_resolutions = get_legal_field_values('resolution');
+ my $foundMatch = 0;
+
+ foreach my $prefix (@ar_prefixes) {
+ foreach (@$ar_check_states) {
+ if (/^$prefix/) {
+ $$hr_states{$_} = 1;
+ $foundMatch = 1;
+ }
}
- return $foundMatch;
+ foreach (@$ar_check_resolutions) {
+ if (/^$prefix/) {
+ $$hr_resolutions{$_} = 1;
+ $foundMatch = 1;
+ }
+ }
+ }
+ return $foundMatch;
}
# Negate comparison type
sub negateComparisonType {
- my $comparisonType = shift;
+ my $comparisonType = shift;
- if ($comparisonType eq 'anywords') {
- return 'nowords';
- }
- return "not$comparisonType";
+ if ($comparisonType eq 'anywords') {
+ return 'nowords';
+ }
+ return "not$comparisonType";
}
# 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++;
- }
+ 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 ($expr, $field, $type, $value) = @_;
- my $cgi = Bugzilla->cgi;
- $cgi->param("field$expr", $field);
- $cgi->param("type$expr", $type);
- $cgi->param("value$expr", $value);
+ my $cgi = Bugzilla->cgi;
+ $cgi->param("field$expr", $field);
+ $cgi->param("type$expr", $type);
+ $cgi->param("value$expr", $value);
}
1;
diff --git a/Bugzilla/Search/Recent.pm b/Bugzilla/Search/Recent.pm
index e774c7fe0..5c12db156 100644
--- a/Bugzilla/Search/Recent.pm
+++ b/Bugzilla/Search/Recent.pm
@@ -21,24 +21,25 @@ use Bugzilla::Util;
# Constants #
#############
-use constant DB_TABLE => 'profile_search';
+use constant DB_TABLE => 'profile_search';
use constant LIST_ORDER => 'id DESC';
+
# Do not track buglists viewed by users.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
- id
- user_id
- bug_list
- list_order
+ id
+ user_id
+ bug_list
+ list_order
);
use constant VALIDATORS => {
- user_id => \&_check_user_id,
- bug_list => \&_check_bug_list,
- list_order => \&_check_list_order,
+ user_id => \&_check_user_id,
+ bug_list => \&_check_bug_list,
+ list_order => \&_check_list_order,
};
use constant UPDATE_COLUMNS => qw(bug_list list_order);
@@ -51,29 +52,30 @@ use constant USE_MEMCACHED => 0;
###################
sub create {
- my $class = shift;
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- my $search = $class->SUPER::create(@_);
- my $user_id = $search->user_id;
-
- # Enforce there only being SAVE_NUM_SEARCHES per user.
- my @ids = @{ $dbh->selectcol_arrayref(
- "SELECT id FROM profile_search WHERE user_id = ? ORDER BY id",
- undef, $user_id) };
- if (scalar(@ids) > SAVE_NUM_SEARCHES) {
- splice(@ids, - SAVE_NUM_SEARCHES);
- $dbh->do(
- "DELETE FROM profile_search WHERE id IN (" . join(',', @ids) . ")");
- }
- $dbh->bz_commit_transaction();
- return $search;
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ my $search = $class->SUPER::create(@_);
+ my $user_id = $search->user_id;
+
+ # Enforce there only being SAVE_NUM_SEARCHES per user.
+ my @ids = @{
+ $dbh->selectcol_arrayref(
+ "SELECT id FROM profile_search WHERE user_id = ? ORDER BY id", undef,
+ $user_id
+ )
+ };
+ if (scalar(@ids) > SAVE_NUM_SEARCHES) {
+ splice(@ids, - SAVE_NUM_SEARCHES);
+ $dbh->do("DELETE FROM profile_search WHERE id IN (" . join(',', @ids) . ")");
+ }
+ $dbh->bz_commit_transaction();
+ return $search;
}
sub create_placeholder {
- my $class = shift;
- return $class->create({ user_id => Bugzilla->user->id,
- bug_list => '' });
+ my $class = shift;
+ return $class->create({user_id => Bugzilla->user->id, bug_list => ''});
}
###############
@@ -81,41 +83,43 @@ sub create_placeholder {
###############
sub check {
- my $class = shift;
- my $search = $class->SUPER::check(@_);
- my $user = Bugzilla->user;
- if ($search->user_id != $user->id) {
- ThrowUserError('object_does_not_exist', { id => $search->id });
- }
- return $search;
+ my $class = shift;
+ my $search = $class->SUPER::check(@_);
+ my $user = Bugzilla->user;
+ if ($search->user_id != $user->id) {
+ ThrowUserError('object_does_not_exist', {id => $search->id});
+ }
+ return $search;
}
sub check_quietly {
- my $class = shift;
- my $error_mode = Bugzilla->error_mode;
- Bugzilla->error_mode(ERROR_MODE_DIE);
- my $search = eval { $class->check(@_) };
- Bugzilla->error_mode($error_mode);
- return $search;
+ my $class = shift;
+ my $error_mode = Bugzilla->error_mode;
+ Bugzilla->error_mode(ERROR_MODE_DIE);
+ my $search = eval { $class->check(@_) };
+ Bugzilla->error_mode($error_mode);
+ return $search;
}
sub new_from_cookie {
- my ($invocant, $bug_ids) = @_;
- my $class = ref($invocant) || $invocant;
+ my ($invocant, $bug_ids) = @_;
+ my $class = ref($invocant) || $invocant;
- my $search = { id => 'cookie',
- user_id => Bugzilla->user->id,
- bug_list => join(',', @$bug_ids) };
+ my $search = {
+ id => 'cookie',
+ user_id => Bugzilla->user->id,
+ bug_list => join(',', @$bug_ids)
+ };
- bless $search, $class;
- return $search;
+ bless $search, $class;
+ return $search;
}
####################
# Simple Accessors #
####################
-sub bug_list { return [split(',', $_[0]->{'bug_list'})]; }
+sub bug_list { return [split(',', $_[0]->{'bug_list'})]; }
sub list_order { return $_[0]->{'list_order'}; }
sub user_id { return $_[0]->{'user_id'}; }
@@ -131,17 +135,17 @@ sub set_list_order { $_[0]->set('list_order', $_[1]); }
##############
sub _check_user_id {
- my ($invocant, $id) = @_;
- require Bugzilla::User;
- return Bugzilla::User->check({ id => $id })->id;
+ my ($invocant, $id) = @_;
+ require Bugzilla::User;
+ return Bugzilla::User->check({id => $id})->id;
}
sub _check_bug_list {
- my ($invocant, $list) = @_;
+ my ($invocant, $list) = @_;
- my @bug_ids = ref($list) ? @$list : split(',', $list || '');
- detaint_natural($_) foreach @bug_ids;
- return join(',', @bug_ids);
+ my @bug_ids = ref($list) ? @$list : split(',', $list || '');
+ detaint_natural($_) foreach @bug_ids;
+ return join(',', @bug_ids);
}
sub _check_list_order { defined $_[1] ? trim($_[1]) : '' }
diff --git a/Bugzilla/Search/Saved.pm b/Bugzilla/Search/Saved.pm
index 50a9cdd67..c988e8997 100644
--- a/Bugzilla/Search/Saved.pm
+++ b/Bugzilla/Search/Saved.pm
@@ -28,22 +28,23 @@ use Scalar::Util qw(blessed);
#############
use constant DB_TABLE => 'namedqueries';
+
# Do not track buglists saved by users.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
- id
- userid
- name
- query
+ id
+ userid
+ name
+ query
);
use constant VALIDATORS => {
- name => \&_check_name,
- query => \&_check_query,
- link_in_footer => \&_check_link_in_footer,
+ name => \&_check_name,
+ query => \&_check_query,
+ link_in_footer => \&_check_link_in_footer,
};
use constant UPDATE_COLUMNS => qw(name query);
@@ -53,56 +54,53 @@ use constant UPDATE_COLUMNS => qw(name query);
###############
sub new {
- my $class = shift;
- my $param = shift;
- my $dbh = Bugzilla->dbh;
-
- my $user;
- if (ref $param) {
- $user = $param->{user} || Bugzilla->user;
- my $name = $param->{name};
- if (!defined $name) {
- ThrowCodeError('bad_arg',
- {argument => 'name',
- function => "${class}::new"});
- }
- my $condition = 'userid = ? AND name = ?';
- my $user_id = blessed $user ? $user->id : $user;
- detaint_natural($user_id)
- || ThrowCodeError('param_must_be_numeric',
- {function => $class . '::_init', param => 'user'});
- my @values = ($user_id, $name);
- $param = { condition => $condition, values => \@values };
- }
-
- unshift @_, $param;
- my $self = $class->SUPER::new(@_);
- if ($self) {
- $self->{user} = $user if blessed $user;
-
- # Some DBs (read: Oracle) incorrectly mark the query string as UTF-8
- # when it's coming out of the database, even though it has no UTF-8
- # characters in it, which prevents Bugzilla::CGI from later reading
- # it correctly.
- utf8::downgrade($self->{query}) if utf8::is_utf8($self->{query});
+ my $class = shift;
+ my $param = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $user;
+ if (ref $param) {
+ $user = $param->{user} || Bugzilla->user;
+ my $name = $param->{name};
+ if (!defined $name) {
+ ThrowCodeError('bad_arg', {argument => 'name', function => "${class}::new"});
}
- return $self;
+ my $condition = 'userid = ? AND name = ?';
+ my $user_id = blessed $user ? $user->id : $user;
+ detaint_natural($user_id)
+ || ThrowCodeError('param_must_be_numeric',
+ {function => $class . '::_init', param => 'user'});
+ my @values = ($user_id, $name);
+ $param = {condition => $condition, values => \@values};
+ }
+
+ unshift @_, $param;
+ my $self = $class->SUPER::new(@_);
+ if ($self) {
+ $self->{user} = $user if blessed $user;
+
+ # Some DBs (read: Oracle) incorrectly mark the query string as UTF-8
+ # when it's coming out of the database, even though it has no UTF-8
+ # characters in it, which prevents Bugzilla::CGI from later reading
+ # it correctly.
+ utf8::downgrade($self->{query}) if utf8::is_utf8($self->{query});
+ }
+ return $self;
}
sub check {
- my $class = shift;
- my $search = $class->SUPER::check(@_);
- my $user = Bugzilla->user;
- return $search if $search->user->id == $user->id;
-
- if (!$search->shared_with_group
- or !$user->in_group($search->shared_with_group))
- {
- ThrowUserError('missing_query', { name => $search->name,
- sharer_id => $search->user->id });
- }
-
- return $search;
+ my $class = shift;
+ my $search = $class->SUPER::check(@_);
+ my $user = Bugzilla->user;
+ return $search if $search->user->id == $user->id;
+
+ if (!$search->shared_with_group or !$user->in_group($search->shared_with_group))
+ {
+ ThrowUserError('missing_query',
+ {name => $search->name, sharer_id => $search->user->id});
+ }
+
+ return $search;
}
##############
@@ -112,24 +110,25 @@ sub check {
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;
+ 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;
+ 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;
}
#########################
@@ -137,170 +136,180 @@ sub _check_query {
#########################
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;
+ 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 rename_field_value {
- my ($class, $field, $old_value, $new_value) = @_;
-
- my $old = url_quote($old_value);
- my $new = url_quote($new_value);
- my $old_sql = $old;
- $old_sql =~ s/([_\%])/\\$1/g;
-
- my $table = $class->DB_TABLE;
- my $id_field = $class->ID_FIELD;
-
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
-
- my %queries = @{ $dbh->selectcol_arrayref(
- "SELECT $id_field, query FROM $table WHERE query LIKE ?",
- {Columns=>[1,2]}, "\%$old_sql\%") };
- foreach my $id (keys %queries) {
- my $query = $queries{$id};
- $query =~ s/\b$field=\Q$old\E\b/$field=$new/gi;
- # Fix boolean charts.
- while ($query =~ /\bfield(\d+-\d+-\d+)=\Q$field\E\b/gi) {
- my $chart_id = $1;
- # Note that this won't handle lists or substrings inside of
- # boolean charts. Users will have to fix those themselves.
- $query =~ s/\bvalue\Q$chart_id\E=\Q$old\E\b/value$chart_id=$new/i;
- }
- $dbh->do("UPDATE $table SET query = ? WHERE $id_field = ?",
- undef, $query, $id);
- Bugzilla->memcached->clear({ table => $table, id => $id });
+ my ($class, $field, $old_value, $new_value) = @_;
+
+ my $old = url_quote($old_value);
+ my $new = url_quote($new_value);
+ my $old_sql = $old;
+ $old_sql =~ s/([_\%])/\\$1/g;
+
+ my $table = $class->DB_TABLE;
+ my $id_field = $class->ID_FIELD;
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+
+ my %queries = @{
+ $dbh->selectcol_arrayref(
+ "SELECT $id_field, query FROM $table WHERE query LIKE ?",
+ {Columns => [1, 2]},
+ "\%$old_sql\%"
+ )
+ };
+ foreach my $id (keys %queries) {
+ my $query = $queries{$id};
+ $query =~ s/\b$field=\Q$old\E\b/$field=$new/gi;
+
+ # Fix boolean charts.
+ while ($query =~ /\bfield(\d+-\d+-\d+)=\Q$field\E\b/gi) {
+ my $chart_id = $1;
+
+ # Note that this won't handle lists or substrings inside of
+ # boolean charts. Users will have to fix those themselves.
+ $query =~ s/\bvalue\Q$chart_id\E=\Q$old\E\b/value$chart_id=$new/i;
}
+ $dbh->do("UPDATE $table SET query = ? WHERE $id_field = ?", undef, $query, $id);
+ Bugzilla->memcached->clear({table => $table, id => $id});
+ }
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
}
sub preload {
- my ($searches) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($searches) = @_;
+ my $dbh = Bugzilla->dbh;
- return unless scalar @$searches;
+ return unless scalar @$searches;
- my @query_ids = map { $_->id } @$searches;
- my $queries_in_footer = $dbh->selectcol_arrayref(
- 'SELECT namedquery_id
+ 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);
+ 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;
- }
+ 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};
+ 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
+ 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};
+ 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;
+ 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};
+ 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;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!exists $self->{shared_with_users}) {
- $self->{shared_with_users} =
- $dbh->selectrow_array('SELECT COUNT(*)
+ 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};
+ AND user_id != userid', undef, $self->id
+ );
+ }
+ return $self->{shared_with_users};
}
####################
# Simple Accessors #
####################
-sub url { return $_[0]->{'query'}; }
+sub url { return $_[0]->{'query'}; }
sub user {
- my ($self) = @_;
- return $self->{user} ||=
- Bugzilla::User->new({ id => $self->{userid}, cache => 1 });
+ my ($self) = @_;
+ return $self->{user}
+ ||= Bugzilla::User->new({id => $self->{userid}, cache => 1});
}
############
# Mutators #
############
-sub set_name { $_[0]->set('name', $_[1]); }
-sub set_url { $_[0]->set('query', $_[1]); }
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_url { $_[0]->set('query', $_[1]); }
1;
diff --git a/Bugzilla/Sender/Transport/Sendmail.pm b/Bugzilla/Sender/Transport/Sendmail.pm
index 49f00777f..d2be2cded 100644
--- a/Bugzilla/Sender/Transport/Sendmail.pm
+++ b/Bugzilla/Sender/Transport/Sendmail.pm
@@ -16,72 +16,93 @@ use parent qw(Email::Sender::Transport::Sendmail);
use Email::Sender::Failure;
sub send_email {
- my ($self, $email, $envelope) = @_;
-
- my $pipe = $self->_sendmail_pipe($envelope);
-
- my $string = $email->as_string;
- $string =~ s/\x0D\x0A/\x0A/g unless $^O eq 'MSWin32';
-
- print $pipe $string
- or Email::Sender::Failure->throw("couldn't send message to sendmail: $!");
-
- unless (close $pipe) {
- Email::Sender::Failure->throw("error when closing pipe to sendmail: $!") if $!;
- my ($error_message, $is_transient) = _map_exitcode($? >> 8);
- if (Bugzilla->params->{'use_mailer_queue'}) {
- # Return success for errors which are fatal so Bugzilla knows to
- # remove them from the queue.
- if ($is_transient) {
- Email::Sender::Failure->throw("error when closing pipe to sendmail: $error_message");
- } else {
- warn "error when closing pipe to sendmail: $error_message\n";
- return $self->success;
- }
- } else {
- Email::Sender::Failure->throw("error when closing pipe to sendmail: $error_message");
- }
+ my ($self, $email, $envelope) = @_;
+
+ my $pipe = $self->_sendmail_pipe($envelope);
+
+ my $string = $email->as_string;
+ $string =~ s/\x0D\x0A/\x0A/g unless $^O eq 'MSWin32';
+
+ print $pipe $string
+ or Email::Sender::Failure->throw("couldn't send message to sendmail: $!");
+
+ unless (close $pipe) {
+ Email::Sender::Failure->throw("error when closing pipe to sendmail: $!") if $!;
+ my ($error_message, $is_transient) = _map_exitcode($? >> 8);
+ if (Bugzilla->params->{'use_mailer_queue'}) {
+
+ # Return success for errors which are fatal so Bugzilla knows to
+ # remove them from the queue.
+ if ($is_transient) {
+ Email::Sender::Failure->throw(
+ "error when closing pipe to sendmail: $error_message");
+ }
+ else {
+ warn "error when closing pipe to sendmail: $error_message\n";
+ return $self->success;
+ }
+ }
+ else {
+ Email::Sender::Failure->throw(
+ "error when closing pipe to sendmail: $error_message");
}
- return $self->success;
+ }
+ return $self->success;
}
sub _map_exitcode {
- # Returns (error message, is_transient)
- # from the sendmail source (sendmail/sysexits.h)
- my $code = shift;
- if ($code == 64) {
- return ("Command line usage error (EX_USAGE)", 1);
- } elsif ($code == 65) {
- return ("Data format error (EX_DATAERR)", 1);
- } elsif ($code == 66) {
- return ("Cannot open input (EX_NOINPUT)", 1);
- } elsif ($code == 67) {
- return ("Addressee unknown (EX_NOUSER)", 0);
- } elsif ($code == 68) {
- return ("Host name unknown (EX_NOHOST)", 0);
- } elsif ($code == 69) {
- return ("Service unavailable (EX_UNAVAILABLE)", 1);
- } elsif ($code == 70) {
- return ("Internal software error (EX_SOFTWARE)", 1);
- } elsif ($code == 71) {
- return ("System error (EX_OSERR)", 1);
- } elsif ($code == 72) {
- return ("Critical OS file missing (EX_OSFILE)", 1);
- } elsif ($code == 73) {
- return ("Can't create output file (EX_CANTCREAT)", 1);
- } elsif ($code == 74) {
- return ("Input/output error (EX_IOERR)", 1);
- } elsif ($code == 75) {
- return ("Temp failure (EX_TEMPFAIL)", 1);
- } elsif ($code == 76) {
- return ("Remote error in protocol (EX_PROTOCOL)", 1);
- } elsif ($code == 77) {
- return ("Permission denied (EX_NOPERM)", 1);
- } elsif ($code == 78) {
- return ("Configuration error (EX_CONFIG)", 1);
- } else {
- return ("Unknown Error ($code)", 1);
- }
+
+ # Returns (error message, is_transient)
+ # from the sendmail source (sendmail/sysexits.h)
+ my $code = shift;
+ if ($code == 64) {
+ return ("Command line usage error (EX_USAGE)", 1);
+ }
+ elsif ($code == 65) {
+ return ("Data format error (EX_DATAERR)", 1);
+ }
+ elsif ($code == 66) {
+ return ("Cannot open input (EX_NOINPUT)", 1);
+ }
+ elsif ($code == 67) {
+ return ("Addressee unknown (EX_NOUSER)", 0);
+ }
+ elsif ($code == 68) {
+ return ("Host name unknown (EX_NOHOST)", 0);
+ }
+ elsif ($code == 69) {
+ return ("Service unavailable (EX_UNAVAILABLE)", 1);
+ }
+ elsif ($code == 70) {
+ return ("Internal software error (EX_SOFTWARE)", 1);
+ }
+ elsif ($code == 71) {
+ return ("System error (EX_OSERR)", 1);
+ }
+ elsif ($code == 72) {
+ return ("Critical OS file missing (EX_OSFILE)", 1);
+ }
+ elsif ($code == 73) {
+ return ("Can't create output file (EX_CANTCREAT)", 1);
+ }
+ elsif ($code == 74) {
+ return ("Input/output error (EX_IOERR)", 1);
+ }
+ elsif ($code == 75) {
+ return ("Temp failure (EX_TEMPFAIL)", 1);
+ }
+ elsif ($code == 76) {
+ return ("Remote error in protocol (EX_PROTOCOL)", 1);
+ }
+ elsif ($code == 77) {
+ return ("Permission denied (EX_NOPERM)", 1);
+ }
+ elsif ($code == 78) {
+ return ("Configuration error (EX_CONFIG)", 1);
+ }
+ else {
+ return ("Unknown Error ($code)", 1);
+ }
}
1;
diff --git a/Bugzilla/Series.pm b/Bugzilla/Series.pm
index 22202c6f1..ec0a16e34 100644
--- a/Bugzilla/Series.pm
+++ b/Bugzilla/Series.pm
@@ -7,9 +7,9 @@
# 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
+# 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;
@@ -27,224 +27,255 @@ use constant DB_TABLE => 'series';
use constant ID_FIELD => 'series_id';
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.
- # XXX - You cannot set category_id and subcategory_id from here.
- $self->initFromParameters(@_);
+ 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 {
- die("Bad parameters passed in - invalid number of args: $arg_count");
+ # We've been given a series_id, which should represent an existing
+ # Series.
+ $retval = $self->initFromDatabase($_[0]);
}
-
- return $retval;
+ }
+ 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.
+ # XXX - You cannot set category_id and subcategory_id from here.
+ $self->initFromParameters(@_);
+ }
+ else {
+ die("Bad parameters passed in - invalid number of args: $arg_count");
+ }
+
+ return $retval;
}
sub initFromDatabase {
- my ($self, $series_id) = @_;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
-
- detaint_natural($series_id)
- || ThrowCodeError("invalid_series_id", { 'series_id' => $series_id });
-
- my $grouplist = $user->groups_as_string;
-
- my @series = $dbh->selectrow_array("SELECT series.series_id, cc1.name, " .
- "cc2.name, series.name, series.creator, series.frequency, " .
- "series.query, series.is_public, series.category, series.subcategory " .
- "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 series.series_id = ? " .
- " AND (creator = ? OR (is_public = 1 AND cgm.category_id IS NULL))",
- undef, ($series_id, $user->id));
-
- if (@series) {
- $self->initFromParameters(@series);
- return $self;
- }
- else {
- return undef;
- }
+ my ($self, $series_id) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ detaint_natural($series_id)
+ || ThrowCodeError("invalid_series_id", {'series_id' => $series_id});
+
+ my $grouplist = $user->groups_as_string;
+
+ my @series = $dbh->selectrow_array(
+ "SELECT series.series_id, cc1.name, "
+ . "cc2.name, series.name, series.creator, series.frequency, "
+ . "series.query, series.is_public, series.category, series.subcategory "
+ . "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 series.series_id = ? "
+ . " AND (creator = ? OR (is_public = 1 AND cgm.category_id IS NULL))",
+ undef,
+ ($series_id, $user->id)
+ );
+
+ 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_id'}, $self->{'frequency'},
- $self->{'query'}, $self->{'public'}, $self->{'category_id'},
- $self->{'subcategory_id'}) = @_;
+ # 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_id'}, $self->{'frequency'},
+ $self->{'query'}, $self->{'public'}, $self->{'category_id'},
+ $self->{'subcategory_id'}
+ ) = @_;
- # If the first parameter is undefined, check if this series already
- # exists and update it series_id accordingly
- $self->{'series_id'} ||= $self->existsInDatabase();
+ # 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_id'} = 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');
+ 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_id'} = 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 $self = shift;
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
- my $category_id = getCategoryID($self->{'category'});
- my $subcategory_id = getCategoryID($self->{'subcategory'});
+ 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_id'}, $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();
+ 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_id'},
+ $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 $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $category_id = getCategoryID($self->{'category'});
+ my $subcategory_id = getCategoryID($self->{'subcategory'});
- 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);
+ 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;
+ 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);
+ # This seems for the best idiom for "Do A. Then maybe do B and A again."
+ while (1) {
- $category_id = $dbh->selectrow_array("SELECT id " .
- "from series_categories " .
- "WHERE name =" . $dbh->quote($category));
+ # We are quoting this to put it in the DB, so we can remove taint
+ trick_taint($category);
- last if defined($category_id);
+ $category_id
+ = $dbh->selectrow_array("SELECT id "
+ . "from series_categories "
+ . "WHERE name ="
+ . $dbh->quote($category));
- $dbh->do("INSERT INTO series_categories (name) " .
- "VALUES (" . $dbh->quote($category) . ")");
- }
+ last if defined($category_id);
+
+ $dbh->do("INSERT INTO series_categories (name) "
+ . "VALUES ("
+ . $dbh->quote($category)
+ . ")");
+ }
- return $category_id;
+ return $category_id;
}
##########
@@ -254,20 +285,20 @@ sub id { return $_[0]->{'series_id'}; }
sub name { return $_[0]->{'name'}; }
sub creator {
- my $self = shift;
+ my $self = shift;
- if (!$self->{creator} && $self->{creator_id}) {
- require Bugzilla::User;
- $self->{creator} = new Bugzilla::User($self->{creator_id});
- }
- return $self->{creator};
+ if (!$self->{creator} && $self->{creator_id}) {
+ require Bugzilla::User;
+ $self->{creator} = new Bugzilla::User($self->{creator_id});
+ }
+ return $self->{creator};
}
sub remove_from_db {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- $dbh->do('DELETE FROM series WHERE series_id = ?', undef, $self->id);
+ $dbh->do('DELETE FROM series WHERE series_id = ?', undef, $self->id);
}
1;
diff --git a/Bugzilla/Status.pm b/Bugzilla/Status.pm
index 275510216..615c7b250 100644
--- a/Bugzilla/Status.pm
+++ b/Bugzilla/Status.pm
@@ -11,17 +11,17 @@ use 5.10.1;
use strict;
use warnings;
-# This subclasses Bugzilla::Field::Choice instead of implementing
+# This subclasses Bugzilla::Field::Choice instead of implementing
# ChoiceInterface, because a bug status literally is a special type
# of Field::Choice, not just an object that happens to have the same
# methods.
use parent qw(Bugzilla::Field::Choice Exporter);
@Bugzilla::Status::EXPORT = qw(
- BUG_STATE_OPEN
- SPECIAL_STATUS_WORKFLOW_ACTIONS
+ BUG_STATE_OPEN
+ SPECIAL_STATUS_WORKFLOW_ACTIONS
- is_open_state
- closed_bug_statuses
+ is_open_state
+ closed_bug_statuses
);
use Bugzilla::Error;
@@ -31,25 +31,25 @@ use Bugzilla::Error;
################################
use constant SPECIAL_STATUS_WORKFLOW_ACTIONS => qw(
- none
- duplicate
- change_resolution
- clearresolution
+ none
+ duplicate
+ change_resolution
+ clearresolution
);
use constant DB_TABLE => 'bug_status';
# This has all the standard Bugzilla::Field::Choice columns plus "is_open"
sub DB_COLUMNS {
- return ($_[0]->SUPER::DB_COLUMNS, 'is_open');
+ return ($_[0]->SUPER::DB_COLUMNS, 'is_open');
}
sub VALIDATORS {
- my $invocant = shift;
- my $validators = $invocant->SUPER::VALIDATORS;
- $validators->{is_open} = \&Bugzilla::Object::check_boolean;
- $validators->{value} = \&_check_value;
- return $validators;
+ my $invocant = shift;
+ my $validators = $invocant->SUPER::VALIDATORS;
+ $validators->{is_open} = \&Bugzilla::Object::check_boolean;
+ $validators->{value} = \&_check_value;
+ return $validators;
}
#########################
@@ -57,17 +57,17 @@ sub VALIDATORS {
#########################
sub create {
- my $class = shift;
- my $self = $class->SUPER::create(@_);
- delete Bugzilla->request_cache->{status_bug_state_open};
- add_missing_bug_status_transitions();
- return $self;
+ my $class = shift;
+ my $self = $class->SUPER::create(@_);
+ delete Bugzilla->request_cache->{status_bug_state_open};
+ add_missing_bug_status_transitions();
+ return $self;
}
sub remove_from_db {
- my $self = shift;
- $self->SUPER::remove_from_db();
- delete Bugzilla->request_cache->{status_bug_state_open};
+ my $self = shift;
+ $self->SUPER::remove_from_db();
+ delete Bugzilla->request_cache->{status_bug_state_open};
}
###############################
@@ -75,16 +75,16 @@ sub remove_from_db {
###############################
sub is_active { return $_[0]->{'isactive'}; }
-sub is_open { return $_[0]->{'is_open'}; }
+sub is_open { return $_[0]->{'is_open'}; }
sub is_static {
- my $self = shift;
- if ($self->name eq 'UNCONFIRMED'
- || $self->name eq Bugzilla->params->{'duplicate_or_move_bug_status'})
- {
- return 1;
- }
- return 0;
+ my $self = shift;
+ if ( $self->name eq 'UNCONFIRMED'
+ || $self->name eq Bugzilla->params->{'duplicate_or_move_bug_status'})
+ {
+ return 1;
+ }
+ return 0;
}
##############
@@ -92,14 +92,14 @@ sub is_static {
##############
sub _check_value {
- my $invocant = shift;
- my $value = $invocant->SUPER::_check_value(@_);
-
- if (grep { lc($value) eq lc($_) } SPECIAL_STATUS_WORKFLOW_ACTIONS) {
- ThrowUserError('fieldvalue_reserved_word',
- { field => $invocant->field, value => $value });
- }
- return $value;
+ my $invocant = shift;
+ my $value = $invocant->SUPER::_check_value(@_);
+
+ if (grep { lc($value) eq lc($_) } SPECIAL_STATUS_WORKFLOW_ACTIONS) {
+ ThrowUserError('fieldvalue_reserved_word',
+ {field => $invocant->field, value => $value});
+ }
+ return $value;
}
@@ -108,118 +108,125 @@ sub _check_value {
###############################
sub BUG_STATE_OPEN {
- my $dbh = Bugzilla->dbh;
- my $request_cache = Bugzilla->request_cache;
- my $cache_key = 'status_bug_state_open';
- return @{ $request_cache->{$cache_key} }
- if exists $request_cache->{$cache_key};
-
- my $rows = Bugzilla->memcached->get_config({ key => $cache_key });
- if (!$rows) {
- $rows = $dbh->selectcol_arrayref(
- 'SELECT value FROM bug_status WHERE is_open = 1'
- );
- Bugzilla->memcached->set_config({ key => $cache_key, data => $rows });
- }
-
- $request_cache->{$cache_key} = $rows;
- return @$rows;
+ my $dbh = Bugzilla->dbh;
+ my $request_cache = Bugzilla->request_cache;
+ my $cache_key = 'status_bug_state_open';
+ return @{$request_cache->{$cache_key}} if exists $request_cache->{$cache_key};
+
+ my $rows = Bugzilla->memcached->get_config({key => $cache_key});
+ if (!$rows) {
+ $rows
+ = $dbh->selectcol_arrayref('SELECT value FROM bug_status WHERE is_open = 1');
+ Bugzilla->memcached->set_config({key => $cache_key, data => $rows});
+ }
+
+ $request_cache->{$cache_key} = $rows;
+ return @$rows;
}
# 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);
+ 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;
+ 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
+ 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);
+ 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);
- }
+ # 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'};
+ return $self->{'can_change_to'};
}
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;
+ 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';
- # We may pass a fake status object to represent the initial unset state.
- if ($old_status && $old_status->id) {
- $cond = 'old_status = ?';
- push(@values, $old_status->id);
- }
- $cond .= " AND new_status = ?";
- push(@values, $self->id);
- return ($cond, \@values);
+ my ($self, $old_status) = @_;
+ my @values;
+ my $cond = 'old_status IS NULL';
+
+ # We may pass a fake status object to represent the initial unset state.
+ if ($old_status && $old_status->id) {
+ $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 $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
+ 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);
- }
+ 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;
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index 463d38620..b23d711be 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -16,8 +16,8 @@ use Bugzilla::Constants;
use Bugzilla::WebService::Constants;
use Bugzilla::Hook;
use Bugzilla::Install::Requirements;
-use Bugzilla::Install::Util qw(install_string template_include_path
- include_languages);
+use Bugzilla::Install::Util qw(install_string template_include_path
+ include_languages);
use Bugzilla::Classification;
use Bugzilla::Keyword;
use Bugzilla::Util;
@@ -40,47 +40,49 @@ use Scalar::Util qw(blessed);
use parent qw(Template);
use constant FORMAT_TRIPLE => '%19s|%-28s|%-28s';
-use constant FORMAT_3_SIZE => [19,28,28];
+use constant FORMAT_3_SIZE => [19, 28, 28];
use constant FORMAT_DOUBLE => '%19s %-55s';
-use constant FORMAT_2_SIZE => [19,55];
+use constant FORMAT_2_SIZE => [19, 55];
# Pseudo-constant.
sub SAFE_URL_REGEXP {
- my $safe_protocols = join('|', SAFE_PROTOCOLS);
- return qr/($safe_protocols):[^:\s<>\"][^\s<>\"]+[\w\/]/i;
+ my $safe_protocols = join('|', SAFE_PROTOCOLS);
+ return qr/($safe_protocols):[^:\s<>\"][^\s<>\"]+[\w\/]/i;
}
# Convert the constants in the Bugzilla::Constants and Bugzilla::WebService::Constants
-# modules into a hash we can pass to the template object for reflection into its "constants"
+# modules 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;
- }
+ my %constants;
+ foreach
+ my $constant (@Bugzilla::Constants::EXPORT, @Bugzilla::Constants::EXPORT_OK)
+ {
+ if (ref Bugzilla::Constants->$constant) {
+ $constants{$constant} = Bugzilla::Constants->$constant;
}
-
- foreach my $constant (@Bugzilla::WebService::Constants::EXPORT,
- @Bugzilla::WebService::Constants::EXPORT_OK)
- {
- if (ref Bugzilla::WebService::Constants->$constant) {
- $constants{$constant} = Bugzilla::WebService::Constants->$constant;
- }
- else {
- my @list = (Bugzilla::WebService::Constants->$constant);
- $constants{$constant} = (scalar(@list) == 1) ? $list[0] : \@list;
- }
+ else {
+ my @list = (Bugzilla::Constants->$constant);
+ $constants{$constant} = (scalar(@list) == 1) ? $list[0] : \@list;
+ }
+ }
+
+ foreach my $constant (
+ @Bugzilla::WebService::Constants::EXPORT,
+ @Bugzilla::WebService::Constants::EXPORT_OK
+ )
+ {
+ if (ref Bugzilla::WebService::Constants->$constant) {
+ $constants{$constant} = Bugzilla::WebService::Constants->$constant;
}
- return \%constants;
+ else {
+ my @list = (Bugzilla::WebService::Constants->$constant);
+ $constants{$constant} = (scalar(@list) == 1) ? $list[0] : \@list;
+ }
+ }
+ return \%constants;
}
# Returns the path to the templates based on the Accept-Language
@@ -88,55 +90,51 @@ sub _load_constants {
# If no Accept-Language is present it uses the defined default
# Templates may also be found in the extensions/ tree
sub _include_path {
- my $lang = shift || '';
- my $cache = Bugzilla->request_cache;
- $cache->{"template_include_path_$lang"} ||=
- template_include_path({ language => $lang });
- return $cache->{"template_include_path_$lang"};
+ my $lang = shift || '';
+ my $cache = Bugzilla->request_cache;
+ $cache->{"template_include_path_$lang"}
+ ||= template_include_path({language => $lang});
+ return $cache->{"template_include_path_$lang"};
}
sub get_format {
- my $self = shift;
- my ($template, $format, $ctype) = @_;
-
- $ctype //= 'html';
- $format //= '';
-
- # ctype and format can have letters and a hyphen only.
- if ($ctype =~ /[^a-zA-Z\-]/ || $format =~ /[^a-zA-Z\-]/) {
- ThrowUserError('format_not_found', {'format' => $format,
- 'ctype' => $ctype,
- 'invalid' => 1});
- }
- 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,
- 'format' => $format,
- 'extension' => $ctype,
- 'ctype' => Bugzilla::Constants::contenttypes->{$ctype}
- };
+ my $self = shift;
+ my ($template, $format, $ctype) = @_;
+
+ $ctype //= 'html';
+ $format //= '';
+
+ # ctype and format can have letters and a hyphen only.
+ if ($ctype =~ /[^a-zA-Z\-]/ || $format =~ /[^a-zA-Z\-]/) {
+ ThrowUserError('format_not_found',
+ {'format' => $format, 'ctype' => $ctype, 'invalid' => 1});
+ }
+ 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,
+ 'format' => $format,
+ 'extension' => $ctype,
+ 'ctype' => Bugzilla::Constants::contenttypes->{$ctype}
+ };
}
# This routine quoteUrls contains inspirations from the HTML::FromText CPAN
@@ -147,203 +145,216 @@ sub get_format {
# If you want to modify this routine, read the comments carefully
sub quoteUrls {
- my ($text, $bug, $comment, $user) = @_;
- return $text unless $text;
- $user ||= Bugzilla->user;
-
- # 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 replacing matches with \x{FDD2}$count\x{FDD3}
- # \x{FDDx} is used because it's unlikely to occur in the text
- # and are reserved unicode characters. We disable warnings for now
- # until we require Perl 5.13.9 or newer.
- no warnings 'utf8';
-
- # If the comment is already wrapped, we should ignore newlines when
- # looking for matching regexps. Else we should take them into account.
- my $s = ($comment && $comment->already_wrapped) ? qr/\s/ : qr/\h/;
-
- # 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;
-
- my @hook_regexes;
- Bugzilla::Hook::process('bug_format_comment',
- { text => \$text, bug => $bug, regexes => \@hook_regexes,
- comment => $comment, user => $user });
-
- foreach my $re (@hook_regexes) {
- my ($match, $replace) = @$re{qw(match replace)};
- if (ref($replace) eq 'CODE') {
- $text =~ s/$match/($things[$count++] = $replace->({matches => [
+ my ($text, $bug, $comment, $user) = @_;
+ return $text unless $text;
+ $user ||= Bugzilla->user;
+
+ # 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 replacing matches with \x{FDD2}$count\x{FDD3}
+ # \x{FDDx} is used because it's unlikely to occur in the text
+ # and are reserved unicode characters. We disable warnings for now
+ # until we require Perl 5.13.9 or newer.
+ no warnings 'utf8';
+
+ # If the comment is already wrapped, we should ignore newlines when
+ # looking for matching regexps. Else we should take them into account.
+ my $s = ($comment && $comment->already_wrapped) ? qr/\s/ : qr/\h/;
+
+ # 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;
+
+ my @hook_regexes;
+ Bugzilla::Hook::process(
+ 'bug_format_comment',
+ {
+ text => \$text,
+ bug => $bug,
+ regexes => \@hook_regexes,
+ comment => $comment,
+ user => $user
+ }
+ );
+
+ foreach my $re (@hook_regexes) {
+ my ($match, $replace) = @$re{qw(match replace)};
+ if (ref($replace) eq 'CODE') {
+ $text =~ s/$match/($things[$count++] = $replace->({matches => [
$1, $2, $3, $4,
$5, $6, $7, $8,
$9, $10]}))
&& ("\x{FDD2}" . ($count-1) . "\x{FDD3}")/egx;
- }
- else {
- $text =~ s/$match/($things[$count++] = $replace)
+ }
+ else {
+ $text =~ s/$match/($things[$count++] = $replace)
&& ("\x{FDD2}" . ($count-1) . "\x{FDD3}")/egx;
- }
}
-
- # 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
+ }
+
+ # 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, { comment_num => $5, user => $user })) &&
("\x{FDD2}" . ($count-1) . "\x{FDD3}")
~egox;
- # non-mailto protocols
- my $safe_protocols = SAFE_URL_REGEXP();
- $text =~ s~\b($safe_protocols)
+ # non-mailto protocols
+ my $safe_protocols = SAFE_URL_REGEXP();
+ $text =~ s~\b($safe_protocols)
~($tmp = html_quote($1)) &&
($things[$count++] = "<a href=\"$tmp\">$tmp</a>") &&
("\x{FDD2}" . ($count-1) . "\x{FDD3}")
~egox;
- # We have to quote now, otherwise the html itself is escaped
- # THIS MEANS THAT A LITERAL ", <, >, ' MUST BE ESCAPED FOR A MATCH
+ # 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);
+ $text = html_quote($text);
- # Color quoted text
- $text =~ s~^(&gt;.+)$~<span class="quote">$1</span >~mg;
- $text =~ s~</span >\n<span class="quote">~\n~g;
+ # Color quoted text
+ $text =~ s~^(&gt;.+)$~<span class="quote">$1</span >~mg;
+ $text =~ s~</span >\n<span class="quote">~\n~g;
- # mailto:
- # Use |<nothing> so that $1 is defined regardless
- # &#64; is the encoded '@' character.
- $text =~ s~\b(mailto:|)?([\w\.\-\+\=]+&\#64;[\w\-]+(?:\.[\w\-]+)+)\b
+ # mailto:
+ # Use |<nothing> so that $1 is defined regardless
+ # &#64; is the encoded '@' character.
+ $text =~ s~\b(mailto:|)?([\w\.\-\+\=]+&\#64;[\w\-]+(?:\.[\w\-]+)+)\b
~<a href=\"mailto:$2\">$1$2</a>~igx;
- # attachment links
- $text =~ s~\b(attachment$s*\#?$s*([0-9]+)(?:$s+\[details\])?)
+ # attachment links
+ $text =~ s~\b(attachment$s*\#?$s*([0-9]+)(?:$s+\[details\])?)
~($things[$count++] = get_attachment_link($2, $1, $user)) &&
("\x{FDD2}" . ($count-1) . "\x{FDD3}")
~egmxi;
- # Current bug ID this comment belongs to
- my $current_bugurl = $bug ? ("show_bug.cgi?id=" . $bug->id) : "";
-
- # 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 = template_var('terms')->{bug};
- my $bug_re = qr/\Q$bug_word\E$s*\#?$s*([0-9]+)/i;
- my $comment_word = template_var('terms')->{comment};
- my $comment_re = qr/(?:\Q$comment_word\E|comment)$s*\#?$s*([0-9]+)/i;
- $text =~ s~\b($bug_re(?:$s*,?$s*$comment_re)?|$comment_re)
+ # Current bug ID this comment belongs to
+ my $current_bugurl = $bug ? ("show_bug.cgi?id=" . $bug->id) : "";
+
+ # 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 = template_var('terms')->{bug};
+ my $bug_re = qr/\Q$bug_word\E$s*\#?$s*([0-9]+)/i;
+ my $comment_word = template_var('terms')->{comment};
+ my $comment_re = qr/(?:\Q$comment_word\E|comment)$s*\#?$s*([0-9]+)/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, { comment_num => $3, user => $user }) :
"<a href=\"$current_bugurl#c$4\">$1</a>")
~egx;
- # Handle a list of bug ids: bugs 1, #2, 3, 4
- # Currently, the only delimiter supported is comma.
- # Concluding "and" and "or" are not supported.
- my $bugs_word = template_var('terms')->{bugs};
+ # Handle a list of bug ids: bugs 1, #2, 3, 4
+ # Currently, the only delimiter supported is comma.
+ # Concluding "and" and "or" are not supported.
+ my $bugs_word = template_var('terms')->{bugs};
- my $bugs_re = qr/\Q$bugs_word\E$s*\#?$s*
+ my $bugs_re = qr/\Q$bugs_word\E$s*\#?$s*
[0-9]+(?:$s*,$s*\#?$s*[0-9]+)+/ix;
- $text =~ s{($bugs_re)}{
+ $text =~ s{($bugs_re)}{
my $match = $1;
$match =~ s/((?:#$s*)?([0-9]+))/get_bug_link($2, $1);/eg;
$match;
}eg;
- my $comments_word = template_var('terms')->{comments};
+ my $comments_word = template_var('terms')->{comments};
- my $comments_re = qr/(?:comments|\Q$comments_word\E)$s*\#?$s*
+ my $comments_re = qr/(?:comments|\Q$comments_word\E)$s*\#?$s*
[0-9]+(?:$s*,$s*\#?$s*[0-9]+)+/ix;
- $text =~ s{($comments_re)}{
+ $text =~ s{($comments_re)}{
my $match = $1;
$match =~ s|((?:#$s*)?([0-9]+))|<a href="$current_bugurl#c$2">$1</a>|g;
$match;
}eg;
- # 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\ )
+ # 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\ )
([0-9]+)
(?=\ \*\*\*\Z)
~get_bug_link($1, $1, { user => $user })
~egmx;
- # Now remove the encoding hacks in reverse order
- for (my $i = $#things; $i >= 0; $i--) {
- $text =~ s/\x{FDD2}($i)\x{FDD3}/$things[$i]/eg;
- }
+ # Now remove the encoding hacks in reverse order
+ for (my $i = $#things; $i >= 0; $i--) {
+ $text =~ s/\x{FDD2}($i)\x{FDD3}/$things[$i]/eg;
+ }
- return $text;
+ return $text;
}
# Creates a link to an attachment, including its title.
sub get_attachment_link {
- my ($attachid, $link_text, $user) = @_;
- $user ||= Bugzilla->user;
-
- my $attachment = new Bugzilla::Attachment({ id => $attachid, cache => 1 });
-
- if ($attachment) {
- my $title = "";
- my $className = "";
- if ($user->can_see_bug($attachment->bug_id)
- && (!$attachment->isprivate || $user->is_insider))
- {
- $title = $attachment->description;
- }
- if ($attachment->isobsolete) {
- $className = "bz_obsolete";
- }
- # Prevent code injection in the title.
- $title = html_quote(clean_text($title));
+ my ($attachid, $link_text, $user) = @_;
+ $user ||= Bugzilla->user;
- $link_text =~ s/ \[details(?:, diff)?\]$//;
- my $linkval = "attachment.cgi?id=$attachid";
+ my $attachment = new Bugzilla::Attachment({id => $attachid, cache => 1});
- # If the attachment is a patch, try to link to the diff rather
- # than the text, by default.
- my $patchlink = "";
- if ($attachment->ispatch and Bugzilla->feature('patch_viewer')) {
- $patchlink = '&amp;action=diff';
- }
+ if ($attachment) {
+ my $title = "";
+ my $className = "";
+ if ($user->can_see_bug($attachment->bug_id)
+ && (!$attachment->isprivate || $user->is_insider))
+ {
+ $title = $attachment->description;
+ }
+ if ($attachment->isobsolete) {
+ $className = "bz_obsolete";
+ }
- if ($patchlink) {
- # 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}&amp;action=edit" title="$title">details</a>, <a href="${linkval}${patchlink}" title="$title">diff</a>]|
- . qq|</span>|;
- }
- else {
- # 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}&amp;action=edit" title="$title">details</a>]|
- . qq|</span>|;
- }
+ # Prevent code injection in the title.
+ $title = html_quote(clean_text($title));
+
+ $link_text =~ s/ \[details(?:, diff)?\]$//;
+ my $linkval = "attachment.cgi?id=$attachid";
+
+ # If the attachment is a patch, try to link to the diff rather
+ # than the text, by default.
+ my $patchlink = "";
+ if ($attachment->ispatch and Bugzilla->feature('patch_viewer')) {
+ $patchlink = '&amp;action=diff';
+ }
+
+ if ($patchlink) {
+
+ # 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}&amp;action=edit" title="$title">details</a>, <a href="${linkval}${patchlink}" title="$title">diff</a>]|
+ . qq|</span>|;
}
else {
- return qq{$link_text};
+ # 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}&amp;action=edit" title="$title">details</a>]|
+ . qq|</span>|;
}
+ }
+ else {
+ return qq{$link_text};
+ }
}
# Creates a link to a bug, including its title.
@@ -354,53 +365,59 @@ sub get_attachment_link {
# comment in the bug
sub get_bug_link {
- my ($bug, $link_text, $options) = @_;
- $options ||= {};
- $options->{user} ||= Bugzilla->user;
-
- if (defined $bug && $bug ne '') {
- if (!blessed($bug)) {
- require Bugzilla::Bug;
- $bug = new Bugzilla::Bug({ id => $bug, cache => 1 });
- }
- return $link_text if $bug->{error};
+ my ($bug, $link_text, $options) = @_;
+ $options ||= {};
+ $options->{user} ||= Bugzilla->user;
+
+ if (defined $bug && $bug ne '') {
+ if (!blessed($bug)) {
+ require Bugzilla::Bug;
+ $bug = new Bugzilla::Bug({id => $bug, cache => 1});
}
-
- my $template = Bugzilla->template_inner;
- my $linkified;
- $template->process('bug/link.html.tmpl',
- { bug => $bug, link_text => $link_text, %$options }, \$linkified);
- return $linkified;
+ return $link_text if $bug->{error};
+ }
+
+ my $template = Bugzilla->template_inner;
+ my $linkified;
+ $template->process('bug/link.html.tmpl',
+ {bug => $bug, link_text => $link_text, %$options},
+ \$linkified);
+ return $linkified;
}
# 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;
+ 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;
}
#####################
@@ -412,17 +429,18 @@ sub multiline_sprintf {
sub _mtime { return (stat($_[0]))[9] }
sub mtime_filter {
- my ($file_url, $mtime) = @_;
- # This environment var is set in the .htaccess if we have mod_headers
- # and mod_expires installed, to make sure that JS and CSS with "?"
- # after them will still be cached by clients.
- return $file_url if !$ENV{BZ_CACHE_CONTROL};
- if (!$mtime) {
- my $cgi_path = bz_locations()->{'cgi_path'};
- my $file_path = "$cgi_path/$file_url";
- $mtime = _mtime($file_path);
- }
- return "$file_url?$mtime";
+ my ($file_url, $mtime) = @_;
+
+ # This environment var is set in the .htaccess if we have mod_headers
+ # and mod_expires installed, to make sure that JS and CSS with "?"
+ # after them will still be cached by clients.
+ return $file_url if !$ENV{BZ_CACHE_CONTROL};
+ if (!$mtime) {
+ my $cgi_path = bz_locations()->{'cgi_path'};
+ my $file_path = "$cgi_path/$file_url";
+ $mtime = _mtime($file_path);
+ }
+ return "$file_url?$mtime";
}
# Set up the skin CSS cascade:
@@ -435,183 +453,186 @@ sub mtime_filter {
# 6. Custom Bugzilla stylesheet set
sub css_files {
- my ($style_urls, $yui, $yui_css) = @_;
+ my ($style_urls, $yui, $yui_css) = @_;
- # global.css goes on every page.
- my @requested_css = ('skins/standard/global.css', @$style_urls);
+ # global.css goes on every page.
+ my @requested_css = ('skins/standard/global.css', @$style_urls);
- my @yui_required_css;
- foreach my $yui_name (@$yui) {
- next if !$yui_css->{$yui_name};
- push(@yui_required_css, "js/yui/assets/skins/sam/$yui_name.css");
- }
- unshift(@requested_css, @yui_required_css);
-
- my @css_sets = map { _css_link_set($_) } @requested_css;
-
- my %by_type = (standard => [], skin => [], custom => []);
- foreach my $set (@css_sets) {
- foreach my $key (keys %$set) {
- push(@{ $by_type{$key} }, $set->{$key});
- }
+ my @yui_required_css;
+ foreach my $yui_name (@$yui) {
+ next if !$yui_css->{$yui_name};
+ push(@yui_required_css, "js/yui/assets/skins/sam/$yui_name.css");
+ }
+ unshift(@requested_css, @yui_required_css);
+
+ my @css_sets = map { _css_link_set($_) } @requested_css;
+
+ my %by_type = (standard => [], skin => [], custom => []);
+ foreach my $set (@css_sets) {
+ foreach my $key (keys %$set) {
+ push(@{$by_type{$key}}, $set->{$key});
}
+ }
- # build unified
- $by_type{unified_standard_skin} = _concatenate_css($by_type{standard},
- $by_type{skin});
- $by_type{unified_custom} = _concatenate_css($by_type{custom});
+ # build unified
+ $by_type{unified_standard_skin}
+ = _concatenate_css($by_type{standard}, $by_type{skin});
+ $by_type{unified_custom} = _concatenate_css($by_type{custom});
- return \%by_type;
+ return \%by_type;
}
sub _css_link_set {
- my ($file_name) = @_;
-
- my %set = (standard => mtime_filter($file_name));
-
- # We use (?:^|/) to allow Extensions to use the skins system if they want.
- if ($file_name !~ m{(?:^|/)skins/standard/}) {
- return \%set;
- }
+ my ($file_name) = @_;
- my $skin = Bugzilla->user->settings->{skin}->{value};
- my $cgi_path = bz_locations()->{'cgi_path'};
- my $skin_file_name = $file_name;
- $skin_file_name =~ s{(?:^|/)skins/standard/}{skins/contrib/$skin/};
- if (my $mtime = _mtime("$cgi_path/$skin_file_name")) {
- $set{skin} = mtime_filter($skin_file_name, $mtime);
- }
-
- my $custom_file_name = $file_name;
- $custom_file_name =~ s{(?:^|/)skins/standard/}{skins/custom/};
- if (my $custom_mtime = _mtime("$cgi_path/$custom_file_name")) {
- $set{custom} = mtime_filter($custom_file_name, $custom_mtime);
- }
+ my %set = (standard => mtime_filter($file_name));
+ # We use (?:^|/) to allow Extensions to use the skins system if they want.
+ if ($file_name !~ m{(?:^|/)skins/standard/}) {
return \%set;
+ }
+
+ my $skin = Bugzilla->user->settings->{skin}->{value};
+ my $cgi_path = bz_locations()->{'cgi_path'};
+ my $skin_file_name = $file_name;
+ $skin_file_name =~ s{(?:^|/)skins/standard/}{skins/contrib/$skin/};
+ if (my $mtime = _mtime("$cgi_path/$skin_file_name")) {
+ $set{skin} = mtime_filter($skin_file_name, $mtime);
+ }
+
+ my $custom_file_name = $file_name;
+ $custom_file_name =~ s{(?:^|/)skins/standard/}{skins/custom/};
+ if (my $custom_mtime = _mtime("$cgi_path/$custom_file_name")) {
+ $set{custom} = mtime_filter($custom_file_name, $custom_mtime);
+ }
+
+ return \%set;
}
sub _concatenate_css {
- my @sources = map { @$_ } @_;
- return unless @sources;
-
- my %files =
- map {
- (my $file = $_) =~ s/(^[^\?]+)\?.+/$1/;
- $_ => $file;
- } @sources;
-
- my $cgi_path = bz_locations()->{cgi_path};
- my $skins_path = bz_locations()->{assetsdir};
-
- # build minified files
- my @minified;
- foreach my $source (@sources) {
- next unless -e "$cgi_path/$files{$source}";
- my $file = $skins_path . '/' . md5_hex($source) . '.css';
- if (!-e $file) {
- my $content = read_text("$cgi_path/$files{$source}");
-
- # minify
- $content =~ s{/\*.*?\*/}{}sg; # comments
- $content =~ s{(^\s+|\s+$)}{}mg; # leading/trailing whitespace
- $content =~ s{\n}{}g; # single line
-
- # rewrite urls
- $content =~ s{url\(([^\)]+)\)}{_css_url_rewrite($source, $1)}eig;
-
- write_text($file, "/* $files{$source} */\n" . $content . "\n");
- }
- push @minified, $file;
- }
-
- # concat files
- my $file = $skins_path . '/' . md5_hex(join(' ', @sources)) . '.css';
+ my @sources = map {@$_} @_;
+ return unless @sources;
+
+ my %files = map {
+ (my $file = $_) =~ s/(^[^\?]+)\?.+/$1/;
+ $_ => $file;
+ } @sources;
+
+ my $cgi_path = bz_locations()->{cgi_path};
+ my $skins_path = bz_locations()->{assetsdir};
+
+ # build minified files
+ my @minified;
+ foreach my $source (@sources) {
+ next unless -e "$cgi_path/$files{$source}";
+ my $file = $skins_path . '/' . md5_hex($source) . '.css';
if (!-e $file) {
- my $content = '';
- foreach my $source (@minified) {
- $content .= read_text($source);
- }
- write_text($file, $content);
+ my $content = read_text("$cgi_path/$files{$source}");
+
+ # minify
+ $content =~ s{/\*.*?\*/}{}sg; # comments
+ $content =~ s{(^\s+|\s+$)}{}mg; # leading/trailing whitespace
+ $content =~ s{\n}{}g; # single line
+
+ # rewrite urls
+ $content =~ s{url\(([^\)]+)\)}{_css_url_rewrite($source, $1)}eig;
+
+ write_text($file, "/* $files{$source} */\n" . $content . "\n");
}
+ push @minified, $file;
+ }
+
+ # concat files
+ my $file = $skins_path . '/' . md5_hex(join(' ', @sources)) . '.css';
+ if (!-e $file) {
+ my $content = '';
+ foreach my $source (@minified) {
+ $content .= read_text($source);
+ }
+ write_text($file, $content);
+ }
- $file =~ s/^\Q$cgi_path\E\///o;
- return mtime_filter($file);
+ $file =~ s/^\Q$cgi_path\E\///o;
+ return mtime_filter($file);
}
sub _css_url_rewrite {
- my ($source, $url) = @_;
- # rewrite relative urls as the unified stylesheet lives in a different
- # directory from the source
- $url =~ s/(^['"]|['"]$)//g;
- if (substr($url, 0, 1) eq '/' || substr($url, 0, 5) eq 'data:') {
- return 'url(' . $url . ')';
- }
- return 'url(../../' . ($ENV{'PROJECT'} ? '../' : '') . dirname($source) . '/' . $url . ')';
+ my ($source, $url) = @_;
+
+ # rewrite relative urls as the unified stylesheet lives in a different
+ # directory from the source
+ $url =~ s/(^['"]|['"]$)//g;
+ if (substr($url, 0, 1) eq '/' || substr($url, 0, 5) eq 'data:') {
+ return 'url(' . $url . ')';
+ }
+ return
+ 'url(../../'
+ . ($ENV{'PROJECT'} ? '../' : '')
+ . dirname($source) . '/'
+ . $url . ')';
}
sub _concatenate_js {
- return @_ unless CONCATENATE_ASSETS;
- my ($sources) = @_;
- return [] unless $sources;
- $sources = ref($sources) ? $sources : [ $sources ];
-
- my %files =
- map {
- (my $file = $_) =~ s/(^[^\?]+)\?.+/$1/;
- $_ => $file;
- } @$sources;
-
- my $cgi_path = bz_locations()->{cgi_path};
- my $skins_path = bz_locations()->{assetsdir};
-
- # build minified files
- my @minified;
- foreach my $source (@$sources) {
- next unless -e "$cgi_path/$files{$source}";
- my $file = $skins_path . '/' . md5_hex($source) . '.js';
- if (!-e $file) {
- my $content = read_text("$cgi_path/$files{$source}");
-
- # minimal minification
- $content =~ s#/\*.*?\*/##sg; # block comments
- $content =~ s#(^ +| +$)##gm; # leading/trailing spaces
- $content =~ s#^//.+$##gm; # single line comments
- $content =~ s#\n{2,}#\n#g; # blank lines
- $content =~ s#(^\s+|\s+$)##g; # whitespace at the start/end of file
-
- write_text($file, ";/* $files{$source} */\n" . $content . "\n");
- }
- push @minified, $file;
- }
-
- # concat files
- my $file = $skins_path . '/' . md5_hex(join(' ', @$sources)) . '.js';
+ return @_ unless CONCATENATE_ASSETS;
+ my ($sources) = @_;
+ return [] unless $sources;
+ $sources = ref($sources) ? $sources : [$sources];
+
+ my %files = map {
+ (my $file = $_) =~ s/(^[^\?]+)\?.+/$1/;
+ $_ => $file;
+ } @$sources;
+
+ my $cgi_path = bz_locations()->{cgi_path};
+ my $skins_path = bz_locations()->{assetsdir};
+
+ # build minified files
+ my @minified;
+ foreach my $source (@$sources) {
+ next unless -e "$cgi_path/$files{$source}";
+ my $file = $skins_path . '/' . md5_hex($source) . '.js';
if (!-e $file) {
- my $content = '';
- foreach my $source (@minified) {
- $content .= read_text($source);
- }
- write_text($file, $content);
+ my $content = read_text("$cgi_path/$files{$source}");
+
+ # minimal minification
+ $content =~ s#/\*.*?\*/##sg; # block comments
+ $content =~ s#(^ +| +$)##gm; # leading/trailing spaces
+ $content =~ s#^//.+$##gm; # single line comments
+ $content =~ s#\n{2,}#\n#g; # blank lines
+ $content =~ s#(^\s+|\s+$)##g; # whitespace at the start/end of file
+
+ write_text($file, ";/* $files{$source} */\n" . $content . "\n");
}
+ push @minified, $file;
+ }
+
+ # concat files
+ my $file = $skins_path . '/' . md5_hex(join(' ', @$sources)) . '.js';
+ if (!-e $file) {
+ my $content = '';
+ foreach my $source (@minified) {
+ $content .= read_text($source);
+ }
+ write_text($file, $content);
+ }
- $file =~ s/^\Q$cgi_path\E\///o;
- return [ $file ];
+ $file =~ s/^\Q$cgi_path\E\///o;
+ return [$file];
}
# YUI dependency resolution
sub yui_resolve_deps {
- my ($yui, $yui_deps) = @_;
-
- my @yui_resolved;
- foreach my $yui_name (@$yui) {
- my $deps = $yui_deps->{$yui_name} || [];
- foreach my $dep (reverse @$deps) {
- push(@yui_resolved, $dep) if !grep { $_ eq $dep } @yui_resolved;
- }
- push(@yui_resolved, $yui_name) if !grep { $_ eq $yui_name } @yui_resolved;
+ my ($yui, $yui_deps) = @_;
+
+ my @yui_resolved;
+ foreach my $yui_name (@$yui) {
+ my $deps = $yui_deps->{$yui_name} || [];
+ foreach my $dep (reverse @$deps) {
+ push(@yui_resolved, $dep) if !grep { $_ eq $dep } @yui_resolved;
}
- return \@yui_resolved;
+ push(@yui_resolved, $yui_name) if !grep { $_ eq $yui_name } @yui_resolved;
+ }
+ return \@yui_resolved;
}
###############################################################################
@@ -630,73 +651,75 @@ use Template::Stash;
# Allow keys to start with an underscore or a dot.
$Template::Stash::PRIVATE = undef;
-# Add "contains***" methods to list variables that search for one or more
-# items in a list and return boolean values representing whether or not
+# 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) = @_;
- if (ref $item && $item->isa('Bugzilla::Object')) {
- return grep($_->id == $item->id, @$list);
- } else {
- return grep($_ eq $item, @$list);
- }
- };
-
-$Template::Stash::LIST_OPS->{ containsany } =
- sub {
- my ($list, $items) = @_;
- foreach my $item (@$items) {
- if (ref $item && $item->isa('Bugzilla::Object')) {
- return 1 if grep($_->id == $item->id, @$list);
- } else {
- return 1 if grep($_ eq $item, @$list);
- }
- }
- return 0;
- };
+$Template::Stash::LIST_OPS->{contains} = sub {
+ my ($list, $item) = @_;
+ if (ref $item && $item->isa('Bugzilla::Object')) {
+ return grep($_->id == $item->id, @$list);
+ }
+ else {
+ return grep($_ eq $item, @$list);
+ }
+};
+
+$Template::Stash::LIST_OPS->{containsany} = sub {
+ my ($list, $items) = @_;
+ foreach my $item (@$items) {
+ if (ref $item && $item->isa('Bugzilla::Object')) {
+ return 1 if grep($_->id == $item->id, @$list);
+ }
+ else {
+ return 1 if grep($_ eq $item, @$list);
+ }
+ }
+ return 0;
+};
# Clone the array reference to leave the original one unaltered.
-$Template::Stash::LIST_OPS->{ clone } =
- sub {
- my $list = shift;
- return [@$list];
- };
+$Template::Stash::LIST_OPS->{clone} = sub {
+ my $list = shift;
+ return [@$list];
+};
# Allow us to sort the list of fields correctly
-$Template::Stash::LIST_OPS->{ sort_by_field_name } =
- sub {
- sub field_name {
- if ($_[0] eq 'noop') {
- # Sort --- first
- return '';
- }
- # Otherwise sort by field_desc or description
- return $_[1]{$_[0]} || $_[0];
- }
- my ($list, $field_desc) = @_;
- return [ sort { lc field_name($a, $field_desc) cmp lc field_name($b, $field_desc) } @$list ];
- };
+$Template::Stash::LIST_OPS->{sort_by_field_name} = sub {
+
+ sub field_name {
+ if ($_[0] eq 'noop') {
+
+ # Sort --- first
+ return '';
+ }
+
+ # Otherwise sort by field_desc or description
+ return $_[1]{$_[0]} || $_[0];
+ }
+ my ($list, $field_desc) = @_;
+ return [
+ sort { lc field_name($a, $field_desc) cmp lc field_name($b, $field_desc) }
+ @$list
+ ];
+};
# 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];
- };
+$Template::Stash::SCALAR_OPS->{0} = sub {
+ return $_[0];
+};
# 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) = @_;
- return $string if !$length || length($string) <= $length;
-
- $ellipsis ||= '';
- my $strlen = $length - length($ellipsis);
- my $newstr = substr($string, 0, $strlen) . $ellipsis;
- return $newstr;
- };
+$Template::Stash::SCALAR_OPS->{truncate} = sub {
+ my ($string, $length, $ellipsis) = @_;
+ return $string if !$length || length($string) <= $length;
+
+ $ellipsis ||= '';
+ 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.
@@ -704,14 +727,15 @@ $Template::Stash::SCALAR_OPS->{ truncate } =
###############################################################################
sub process {
- my $self = shift;
- # All of this current_langs stuff allows template_inner to correctly
- # determine what-language Template object it should instantiate.
- my $current_langs = Bugzilla->request_cache->{template_current_lang} ||= [];
- unshift(@$current_langs, $self->context->{bz_language});
- my $retval = $self->SUPER::process(@_);
- shift @$current_langs;
- return $retval;
+ my $self = shift;
+
+ # All of this current_langs stuff allows template_inner to correctly
+ # determine what-language Template object it should instantiate.
+ my $current_langs = Bugzilla->request_cache->{template_current_lang} ||= [];
+ unshift(@$current_langs, $self->context->{bz_language});
+ my $retval = $self->SUPER::process(@_);
+ shift @$current_langs;
+ return $retval;
}
# Construct the Template object
@@ -720,603 +744,625 @@ sub process {
# since we won't have a template to use...
sub create {
- my $class = shift;
- my %opts = @_;
-
- # IMPORTANT - If you make any FILTER changes here, make sure to
- # make them in t/004.template.t also, if required.
-
- my $config = {
- # Colon-separated list of directories containing templates.
- INCLUDE_PATH => $opts{'include_path'}
- || _include_path($opts{'language'}),
-
- # 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,
-
- # Bugzilla::Template::Plugin::Hook uses the absolute (in mod_perl)
- # or relative (in mod_cgi) paths of hook files to explicitly compile
- # a specific file. Also, these paths may be absolute at any time
- # if a packager has modified bz_locations() to contain absolute
- # paths.
- ABSOLUTE => 1,
- RELATIVE => $ENV{MOD_PERL} ? 0 : 1,
-
- COMPILE_DIR => bz_locations()->{'template_cache'},
-
- # Don't check for a template update until 1 hour has passed since the
- # last check.
- STAT_TTL => 60 * 60,
-
- # Initialize templates (f.e. by loading plugins like Hook).
- PRE_PROCESS => ["global/variables.none.tmpl"],
-
- ENCODING => Bugzilla->params->{'utf8'} ? 'UTF-8' : undef,
-
- # 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/\x{2028}/\\u2028/g; # unicode line separator
- $var =~ s/\x{2029}/\\u2029/g; # unicode paragraph separator
- $var =~ s/\@/\\x40/g; # anti-spam for email addresses
- $var =~ s/</\\x3c/g;
- $var =~ s/>/\\x3e/g;
- return $var;
- },
-
- # Converts data to base64
- base64 => sub {
- my ($data) = @_;
- return encode_base64($data);
- },
-
- # Strips out control characters excepting whitespace
- strip_control_chars => sub {
- my ($data) = @_;
- state $use_utf8 = Bugzilla->params->{'utf8'};
- # Only run for utf8 to avoid issues with other multibyte encodings
- # that may be reassigning meaning to ascii characters.
- if ($use_utf8) {
- $data =~ s/(?![\t\r\n])[[:cntrl:]]//g;
- }
- return $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 &#013;
- # See bugs 4928, 22983 and 32000 for more details
- html_linebreak => sub {
- my ($var) = @_;
- $var = html_quote($var);
- $var =~ s/\r\n/\&#013;/g;
- $var =~ s/\n\r/\&#013;/g;
- $var =~ s/\r/\&#013;/g;
- $var =~ s/\n/\&#013;/g;
- return $var;
- },
-
- xml => \&Bugzilla::Util::xml_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 ,
-
- # Removes control characters and trims extra whitespace.
- clean_text => \&Bugzilla::Util::clean_text ,
-
- quoteUrls => [ sub {
- my ($context, $bug, $comment, $user) = @_;
- return sub {
- my $text = shift;
- return quoteUrls($text, $bug, $comment, $user);
- };
- },
- 1
- ],
-
- bug_link => [ sub {
- my ($context, $bug, $options) = @_;
- return sub {
- my $text = shift;
- return get_bug_link($bug, $text, $options);
- };
- },
- 1
- ],
-
- bug_list_link => sub {
- my ($buglist, $options) = @_;
- return join(", ", map(get_bug_link($_, $_, $options), split(/ *, */, $buglist)));
- },
-
- # In CSV, quotes are doubled, and any value containing a quote or a
- # comma is enclosed in quotes.
- # If a field starts with either "=", "+", "-" or "@", it is preceded
- # by a space to prevent stupid formula execution from Excel & co.
- csv => sub
- {
- my ($var) = @_;
- $var = ' ' . $var if $var =~ /^[+=@-]/;
- # backslash is not special to CSV, but it can be used to confuse some browsers...
- # so we do not allow it to happen. We only do this for logged-in users.
- $var =~ s/\\/\x{FF3C}/g if Bugzilla->user->id;
- $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 => [ sub {
- my ($context, $format, $timezone) = @_;
- return sub {
- my $time = shift;
- return format_time($time, $format, $timezone);
- };
- },
- 1
- ],
-
- html => \&Bugzilla::Util::html_quote,
-
- html_light => \&Bugzilla::Util::html_light_quote,
-
- email => \&Bugzilla::Util::email_filter,
-
- mtime => \&mtime_filter,
-
- # 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/\&#64;/@/g;
- $var =~ s/\&lt;/</g;
- $var =~ s/\&gt;/>/g;
- $var =~ s/\&quot;/\"/g;
- $var =~ s/\&amp;/\&/g;
- # Now remove extra whitespace...
- my $collapse_filter = $Template::Filters::FILTERS->{collapse};
- $var = $collapse_filter->($var);
- # And if we're not in the WebService, wrap the message.
- # (Wrapping the message in the WebService is unnecessary
- # and causes awkward things like \n's appearing in error
- # messages in JSON-RPC.)
- unless (i_am_webservice()) {
- $var = wrap_comment($var, 72);
- }
- $var =~ s/\&nbsp;/ /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]; } ,
+ my $class = shift;
+ my %opts = @_;
+
+ # IMPORTANT - If you make any FILTER changes here, make sure to
+ # make them in t/004.template.t also, if required.
+
+ my $config = {
+
+ # Colon-separated list of directories containing templates.
+ INCLUDE_PATH => $opts{'include_path'} || _include_path($opts{'language'}),
+
+ # 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,
+
+ # Bugzilla::Template::Plugin::Hook uses the absolute (in mod_perl)
+ # or relative (in mod_cgi) paths of hook files to explicitly compile
+ # a specific file. Also, these paths may be absolute at any time
+ # if a packager has modified bz_locations() to contain absolute
+ # paths.
+ ABSOLUTE => 1,
+ RELATIVE => $ENV{MOD_PERL} ? 0 : 1,
+
+ COMPILE_DIR => bz_locations()->{'template_cache'},
+
+ # Don't check for a template update until 1 hour has passed since the
+ # last check.
+ STAT_TTL => 60 * 60,
+
+ # Initialize templates (f.e. by loading plugins like Hook).
+ PRE_PROCESS => ["global/variables.none.tmpl"],
+
+ ENCODING => Bugzilla->params->{'utf8'} ? 'UTF-8' : undef,
+
+ # 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/\x{2028}/\\u2028/g; # unicode line separator
+ $var =~ s/\x{2029}/\\u2029/g; # unicode paragraph separator
+ $var =~ s/\@/\\x40/g; # anti-spam for email addresses
+ $var =~ s/</\\x3c/g;
+ $var =~ s/>/\\x3e/g;
+ return $var;
+ },
+
+ # Converts data to base64
+ base64 => sub {
+ my ($data) = @_;
+ return encode_base64($data);
+ },
+
+ # Strips out control characters excepting whitespace
+ strip_control_chars => sub {
+ my ($data) = @_;
+ state $use_utf8 = Bugzilla->params->{'utf8'};
+
+ # Only run for utf8 to avoid issues with other multibyte encodings
+ # that may be reassigning meaning to ascii characters.
+ if ($use_utf8) {
+ $data =~ s/(?![\t\r\n])[[:cntrl:]]//g;
+ }
+ return $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 &#013;
+ # See bugs 4928, 22983 and 32000 for more details
+ html_linebreak => sub {
+ my ($var) = @_;
+ $var = html_quote($var);
+ $var =~ s/\r\n/\&#013;/g;
+ $var =~ s/\n\r/\&#013;/g;
+ $var =~ s/\r/\&#013;/g;
+ $var =~ s/\n/\&#013;/g;
+ return $var;
+ },
+
+ xml => \&Bugzilla::Util::xml_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,
+
+ # Removes control characters and trims extra whitespace.
+ clean_text => \&Bugzilla::Util::clean_text,
+
+ quoteUrls => [
+ sub {
+ my ($context, $bug, $comment, $user) = @_;
+ return sub {
+ my $text = shift;
+ return quoteUrls($text, $bug, $comment, $user);
+ };
+ },
+ 1
+ ],
+
+ bug_link => [
+ sub {
+ my ($context, $bug, $options) = @_;
+ return sub {
+ my $text = shift;
+ return get_bug_link($bug, $text, $options);
+ };
+ },
+ 1
+ ],
+
+ bug_list_link => sub {
+ my ($buglist, $options) = @_;
+ return
+ join(", ", map(get_bug_link($_, $_, $options), split(/ *, */, $buglist)));
+ },
+
+ # In CSV, quotes are doubled, and any value containing a quote or a
+ # comma is enclosed in quotes.
+ # If a field starts with either "=", "+", "-" or "@", it is preceded
+ # by a space to prevent stupid formula execution from Excel & co.
+ csv => sub {
+ my ($var) = @_;
+ $var = ' ' . $var if $var =~ /^[+=@-]/;
+
+ # backslash is not special to CSV, but it can be used to confuse some browsers...
+ # so we do not allow it to happen. We only do this for logged-in users.
+ $var =~ s/\\/\x{FF3C}/g if Bugzilla->user->id;
+ $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 => [
+ sub {
+ my ($context, $format, $timezone) = @_;
+ return sub {
+ my $time = shift;
+ return format_time($time, $format, $timezone);
+ };
},
+ 1
+ ],
+
+ html => \&Bugzilla::Util::html_quote,
+
+ html_light => \&Bugzilla::Util::html_light_quote,
+
+ email => \&Bugzilla::Util::email_filter,
+
+ mtime => \&mtime_filter,
+
+ # 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;
- 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,
-
- # Fixed size column formatting for bugmail.
- 'format_columns' => sub {
- my $cols = shift;
- my $format = ($cols == 3) ? FORMAT_TRIPLE : FORMAT_DOUBLE;
- my $col_size = ($cols == 3) ? FORMAT_3_SIZE : FORMAT_2_SIZE;
- return multiline_sprintf($format, \@_, $col_size);
- },
-
- # Generic linear search function
- 'lsearch' => sub {
- my ($array, $item) = @_;
- return firstidx { $_ eq $item } @$array;
- },
-
- # 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; },
-
- # Currenly active language
- 'current_language' => sub { return Bugzilla->current_language; },
-
- # If an sudo session is in progress, this is the user who
- # started the session.
- 'sudoer' => sub { return Bugzilla->sudoer; },
-
- # Allow templates to access the "correct" URLBase value
- 'urlbase' => sub { return Bugzilla::Util::correct_urlbase(); },
- 'httpbase' => sub { return Bugzilla->params->{'urlbase'}; },
- 'sslbase' => sub { return Bugzilla->params->{'sslbase'}; },
- 'ssl_redirect' => sub { return Bugzilla->params->{'ssl_redirect'}; },
-
- # Allow templates to access docs url with users' preferred language
- # We fall back to English if documentation in the preferred
- # language is not available
- 'docs_urlbase' => sub {
- my $docs_urlbase;
- my $lang = Bugzilla->current_language;
- # Translations currently available on readthedocs.org
- my @rtd_translations = ('en', 'fr');
-
- if ($lang ne 'en' && -f "docs/$lang/html/index.html") {
- $docs_urlbase = "docs/$lang/html/";
- }
- elsif (-f "docs/en/html/index.html") {
- $docs_urlbase = "docs/en/html/";
- }
- else {
- if (!grep { $_ eq $lang } @rtd_translations) {
- $lang = "en";
- }
-
- my $version = BUGZILLA_VERSION;
- $version =~ /^(\d+)\.(\d+)/;
- if ($2 % 2 == 1) {
- # second number is odd; development version
- $version = 'latest';
- }
- else {
- $version = "$1.$2";
- }
-
- $docs_urlbase = "https://bugzilla.readthedocs.org/$lang/$version/";
- }
-
- return $docs_urlbase;
- },
-
- # Check whether the URL is safe.
- 'is_safe_url' => sub {
- my $url = shift;
- return 0 unless $url;
-
- my $safe_url_regexp = SAFE_URL_REGEXP();
- return 1 if $url =~ /^$safe_url_regexp$/;
- # Pointing to a local file with no colon in its name is fine.
- return 1 if $url =~ /^[^\s<>\":]+[\w\/]$/i;
- # If we come here, then we cannot guarantee it's safe.
- return 0;
- },
-
- # Allow templates to generate a token themselves.
- 'issue_hash_token' => \&Bugzilla::Token::issue_hash_token,
-
- 'get_login_request_token' => sub {
- my $cookie = Bugzilla->cgi->cookie('Bugzilla_login_request_cookie');
- return $cookie ? issue_hash_token(['login_request', $cookie]) : '';
- },
-
- 'get_api_token' => sub {
- return '' unless Bugzilla->user->id;
- my $cache = Bugzilla->request_cache;
- return $cache->{api_token} //= issue_api_token();
- },
-
- # A way for all templates to get at Field data, cached.
- 'bug_fields' => sub {
- my $cache = Bugzilla->request_cache;
- $cache->{template_bug_fields} ||=
- Bugzilla->fields({ by_name => 1 });
- return $cache->{template_bug_fields};
- },
-
- # A general purpose cache to store rendered templates for reuse.
- # Make sure to not mix language-specific data.
- 'template_cache' => sub {
- my $cache = Bugzilla->request_cache->{template_cache} ||= {};
- $cache->{users} ||= {};
- return $cache;
- },
-
- 'css_files' => \&css_files,
- yui_resolve_deps => \&yui_resolve_deps,
- concatenate_js => \&_concatenate_js,
-
- # All classifications (sorted by sortkey, name)
- 'all_classifications' => sub {
- return [map { $_->name } Bugzilla::Classification->get_all()];
- },
-
- # Whether or not keywords are enabled, in this Bugzilla.
- 'use_keywords' => sub { return Bugzilla::Keyword->any_exist; },
-
- # All the keywords.
- 'all_keywords' => sub {
- return [map { $_->name } Bugzilla::Keyword->get_all()];
- },
-
- 'feature_enabled' => sub { return Bugzilla->feature(@_); },
-
- # field_descs can be somewhat slow to generate, so we generate
- # it only once per-language no matter how many times
- # $template->process() is called.
- 'field_descs' => sub { return template_var('field_descs') },
-
- # Calling bug/field-help.none.tmpl once per label is very
- # expensive, so we generate it once per-language.
- 'help_html' => sub { return template_var('help_html') },
-
- # This way we don't have to load field-descs.none.tmpl in
- # many templates.
- 'display_value' => \&Bugzilla::Util::display_value,
-
- 'install_string' => \&Bugzilla::Install::Util::install_string,
-
- 'report_columns' => \&Bugzilla::Search::REPORT_COLUMNS,
-
- # 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()};
- foreach my $item (@optional) {
- my @features;
- foreach my $feat_id (@{ $item->{feature} }) {
- push(@features, install_string("feature_$feat_id"));
- }
- $item->{feature} = \@features;
- }
- return \@optional;
- },
- 'default_authorizer' => sub { return Bugzilla::Auth->new() },
-
- 'login_not_email' => sub {
- my $params = Bugzilla->params;
- my $cache = Bugzilla->request_cache;
-
- return $cache->{login_not_email} //=
- ($params->{emailsuffix}
- || ($params->{user_verify_class} =~ /LDAP/ && $params->{LDAPmailattribute})
- || ($params->{user_verify_class} =~ /RADIUS/ && $params->{RADIUS_email_suffix}))
- ? 1 : 0;
- },
+ return $output;
+ };
},
- };
- # Use a per-process provider to cache compiled templates in memory across
- # requests.
- my $provider_key = join(':', @{ $config->{INCLUDE_PATH} });
- my $shared_providers = Bugzilla->process_cache->{shared_providers} ||= {};
- $shared_providers->{$provider_key} ||= Template::Provider->new($config);
- $config->{LOAD_TEMPLATES} = [ $shared_providers->{$provider_key} ];
+ 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/\&#64;/@/g;
+ $var =~ s/\&lt;/</g;
+ $var =~ s/\&gt;/>/g;
+ $var =~ s/\&quot;/\"/g;
+ $var =~ s/\&amp;/\&/g;
+
+ # Now remove extra whitespace...
+ my $collapse_filter = $Template::Filters::FILTERS->{collapse};
+ $var = $collapse_filter->($var);
+
+ # And if we're not in the WebService, wrap the message.
+ # (Wrapping the message in the WebService is unnecessary
+ # and causes awkward things like \n's appearing in error
+ # messages in JSON-RPC.)
+ unless (i_am_webservice()) {
+ $var = wrap_comment($var, 72);
+ }
+ $var =~ s/\&nbsp;/ /g;
+
+ return $var;
+ },
- local $Template::Config::CONTEXT = 'Bugzilla::Template::Context';
+ # 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,
+
+ # Fixed size column formatting for bugmail.
+ 'format_columns' => sub {
+ my $cols = shift;
+ my $format = ($cols == 3) ? FORMAT_TRIPLE : FORMAT_DOUBLE;
+ my $col_size = ($cols == 3) ? FORMAT_3_SIZE : FORMAT_2_SIZE;
+ return multiline_sprintf($format, \@_, $col_size);
+ },
+
+ # Generic linear search function
+ 'lsearch' => sub {
+ my ($array, $item) = @_;
+ return firstidx { $_ eq $item } @$array;
+ },
+
+ # 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; },
+
+ # Currenly active language
+ 'current_language' => sub { return Bugzilla->current_language; },
+
+ # If an sudo session is in progress, this is the user who
+ # started the session.
+ 'sudoer' => sub { return Bugzilla->sudoer; },
+
+ # Allow templates to access the "correct" URLBase value
+ 'urlbase' => sub { return Bugzilla::Util::correct_urlbase(); },
+ 'httpbase' => sub { return Bugzilla->params->{'urlbase'}; },
+ 'sslbase' => sub { return Bugzilla->params->{'sslbase'}; },
+ 'ssl_redirect' => sub { return Bugzilla->params->{'ssl_redirect'}; },
+
+ # Allow templates to access docs url with users' preferred language
+ # We fall back to English if documentation in the preferred
+ # language is not available
+ 'docs_urlbase' => sub {
+ my $docs_urlbase;
+ my $lang = Bugzilla->current_language;
+
+ # Translations currently available on readthedocs.org
+ my @rtd_translations = ('en', 'fr');
+
+ if ($lang ne 'en' && -f "docs/$lang/html/index.html") {
+ $docs_urlbase = "docs/$lang/html/";
+ }
+ elsif (-f "docs/en/html/index.html") {
+ $docs_urlbase = "docs/en/html/";
+ }
+ else {
+ if (!grep { $_ eq $lang } @rtd_translations) {
+ $lang = "en";
+ }
- Bugzilla::Hook::process('template_before_create', { config => $config });
- my $template = $class->new($config)
- || die("Template creation failed: " . $class->error());
+ my $version = BUGZILLA_VERSION;
+ $version =~ /^(\d+)\.(\d+)/;
+ if ($2 % 2 == 1) {
- # Pass on our current language to any template hooks or inner templates
- # called by this Template object.
- $template->context->{bz_language} = $opts{language} || '';
+ # second number is odd; development version
+ $version = 'latest';
+ }
+ else {
+ $version = "$1.$2";
+ }
- return $template;
+ $docs_urlbase = "https://bugzilla.readthedocs.org/$lang/$version/";
+ }
+
+ return $docs_urlbase;
+ },
+
+ # Check whether the URL is safe.
+ 'is_safe_url' => sub {
+ my $url = shift;
+ return 0 unless $url;
+
+ my $safe_url_regexp = SAFE_URL_REGEXP();
+ return 1 if $url =~ /^$safe_url_regexp$/;
+
+ # Pointing to a local file with no colon in its name is fine.
+ return 1 if $url =~ /^[^\s<>\":]+[\w\/]$/i;
+
+ # If we come here, then we cannot guarantee it's safe.
+ return 0;
+ },
+
+ # Allow templates to generate a token themselves.
+ 'issue_hash_token' => \&Bugzilla::Token::issue_hash_token,
+
+ 'get_login_request_token' => sub {
+ my $cookie = Bugzilla->cgi->cookie('Bugzilla_login_request_cookie');
+ return $cookie ? issue_hash_token(['login_request', $cookie]) : '';
+ },
+
+ 'get_api_token' => sub {
+ return '' unless Bugzilla->user->id;
+ my $cache = Bugzilla->request_cache;
+ return $cache->{api_token} //= issue_api_token();
+ },
+
+ # A way for all templates to get at Field data, cached.
+ 'bug_fields' => sub {
+ my $cache = Bugzilla->request_cache;
+ $cache->{template_bug_fields} ||= Bugzilla->fields({by_name => 1});
+ return $cache->{template_bug_fields};
+ },
+
+ # A general purpose cache to store rendered templates for reuse.
+ # Make sure to not mix language-specific data.
+ 'template_cache' => sub {
+ my $cache = Bugzilla->request_cache->{template_cache} ||= {};
+ $cache->{users} ||= {};
+ return $cache;
+ },
+
+ 'css_files' => \&css_files,
+ yui_resolve_deps => \&yui_resolve_deps,
+ concatenate_js => \&_concatenate_js,
+
+ # All classifications (sorted by sortkey, name)
+ 'all_classifications' => sub {
+ return [map { $_->name } Bugzilla::Classification->get_all()];
+ },
+
+ # Whether or not keywords are enabled, in this Bugzilla.
+ 'use_keywords' => sub { return Bugzilla::Keyword->any_exist; },
+
+ # All the keywords.
+ 'all_keywords' => sub {
+ return [map { $_->name } Bugzilla::Keyword->get_all()];
+ },
+
+ 'feature_enabled' => sub { return Bugzilla->feature(@_); },
+
+ # field_descs can be somewhat slow to generate, so we generate
+ # it only once per-language no matter how many times
+ # $template->process() is called.
+ 'field_descs' => sub { return template_var('field_descs') },
+
+ # Calling bug/field-help.none.tmpl once per label is very
+ # expensive, so we generate it once per-language.
+ 'help_html' => sub { return template_var('help_html') },
+
+ # This way we don't have to load field-descs.none.tmpl in
+ # many templates.
+ 'display_value' => \&Bugzilla::Util::display_value,
+
+ 'install_string' => \&Bugzilla::Install::Util::install_string,
+
+ 'report_columns' => \&Bugzilla::Search::REPORT_COLUMNS,
+
+ # 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()};
+ foreach my $item (@optional) {
+ my @features;
+ foreach my $feat_id (@{$item->{feature}}) {
+ push(@features, install_string("feature_$feat_id"));
+ }
+ $item->{feature} = \@features;
+ }
+ return \@optional;
+ },
+ 'default_authorizer' => sub { return Bugzilla::Auth->new() },
+
+ 'login_not_email' => sub {
+ my $params = Bugzilla->params;
+ my $cache = Bugzilla->request_cache;
+
+ return $cache->{login_not_email}
+ //= ($params->{emailsuffix}
+ || ($params->{user_verify_class} =~ /LDAP/ && $params->{LDAPmailattribute})
+ || ($params->{user_verify_class} =~ /RADIUS/
+ && $params->{RADIUS_email_suffix})) ? 1 : 0;
+ },
+ },
+ };
+
+ # Use a per-process provider to cache compiled templates in memory across
+ # requests.
+ my $provider_key = join(':', @{$config->{INCLUDE_PATH}});
+ my $shared_providers = Bugzilla->process_cache->{shared_providers} ||= {};
+ $shared_providers->{$provider_key} ||= Template::Provider->new($config);
+ $config->{LOAD_TEMPLATES} = [$shared_providers->{$provider_key}];
+
+ local $Template::Config::CONTEXT = 'Bugzilla::Template::Context';
+
+ Bugzilla::Hook::process('template_before_create', {config => $config});
+ my $template = $class->new($config)
+ || die("Template creation failed: " . $class->error());
+ Bugzilla::Hook::process('template_after_create', {template => $template});
+
+ # Pass on our current language to any template hooks or inner templates
+ # called by this Template object.
+ $template->context->{bz_language} = $opts{language} || '';
+
+ return $template;
}
# Used as part of the two subroutines below.
our %_templates_to_precompile;
+
sub precompile_templates {
- my ($output) = @_;
+ my ($output) = @_;
- # Remove the compiled templates.
- my $cache_dir = bz_locations()->{'template_cache'};
- my $datadir = bz_locations()->{'datadir'};
+ # Remove the compiled templates.
+ my $cache_dir = bz_locations()->{'template_cache'};
+ my $datadir = bz_locations()->{'datadir'};
+ if (-e $cache_dir) {
+ print install_string('template_removing_dir') . "\n" if $output;
+
+ # This frequently fails if the webserver made the files, because
+ # then the webserver owns the directories.
+ rmtree($cache_dir);
+
+ # Check that the directory was really removed, and if not, move it
+ # into data/deleteme/.
if (-e $cache_dir) {
- print install_string('template_removing_dir') . "\n" if $output;
-
- # This frequently fails if the webserver made the files, because
- # then the webserver owns the directories.
- rmtree($cache_dir);
-
- # Check that the directory was really removed, and if not, move it
- # into data/deleteme/.
- if (-e $cache_dir) {
- my $deleteme = "$datadir/deleteme";
-
- print STDERR "\n\n",
- install_string('template_removal_failed',
- { deleteme => $deleteme,
- template_cache => $cache_dir }), "\n\n";
- mkpath($deleteme);
- my $random = generate_random_password();
- rename($cache_dir, "$deleteme/$random")
- or die "move failed: $!";
- }
+ my $deleteme = "$datadir/deleteme";
+
+ print STDERR "\n\n",
+ install_string('template_removal_failed',
+ {deleteme => $deleteme, template_cache => $cache_dir}),
+ "\n\n";
+ mkpath($deleteme);
+ my $random = generate_random_password();
+ rename($cache_dir, "$deleteme/$random") or die "move failed: $!";
}
+ }
- print install_string('template_precompile') if $output;
+ print install_string('template_precompile') if $output;
- # Pre-compile all available languages.
- my $paths = template_include_path({ language => Bugzilla->languages });
+ # Pre-compile all available languages.
+ my $paths = template_include_path({language => Bugzilla->languages});
- foreach my $dir (@$paths) {
- my $template = Bugzilla::Template->create(include_path => [$dir]);
+ foreach my $dir (@$paths) {
+ my $template = Bugzilla::Template->create(include_path => [$dir]);
- %_templates_to_precompile = ();
- # Traverse the template hierarchy.
- find({ wanted => \&_precompile_push, no_chdir => 1 }, $dir);
- # 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) {
- $file =~ s{^\Q$dir\E/}{};
- # Compile the template but throw away the result. This has the side-
- # effect of writing the compiled version to disk.
- $template->context->template($file);
- }
+ %_templates_to_precompile = ();
- # Clear out the cached Provider object
- Bugzilla->process_cache->{shared_providers} = undef;
- }
+ # Traverse the template hierarchy.
+ find({wanted => \&_precompile_push, no_chdir => 1}, $dir);
- # 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/bugzilla/.) To avoid
- # re-compiling templates under mod_perl, we symlink to the
- # already-compiled templates. This doesn't work on Windows.
- if (!ON_WINDOWS) {
- # We do these separately in case they're in different locations.
- _do_template_symlink(bz_locations()->{'templatedir'});
- _do_template_symlink(bz_locations()->{'extensionsdir'});
+ # 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) {
+ $file =~ s{^\Q$dir\E/}{};
+
+ # Compile the template but throw away the result. This has the side-
+ # effect of writing the compiled version to disk.
+ $template->context->template($file);
}
- # If anything created a Template object before now, clear it out.
- delete Bugzilla->request_cache->{template};
+ # Clear out the cached Provider object
+ Bugzilla->process_cache->{shared_providers} = undef;
+ }
+
+ # 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/bugzilla/.) To avoid
+ # re-compiling templates under mod_perl, we symlink to the
+ # already-compiled templates. This doesn't work on Windows.
+ if (!ON_WINDOWS) {
+
+ # We do these separately in case they're in different locations.
+ _do_template_symlink(bz_locations()->{'templatedir'});
+ _do_template_symlink(bz_locations()->{'extensionsdir'});
+ }
- print install_string('done') . "\n" if $output;
+ # If anything created a Template object before now, clear it out.
+ delete Bugzilla->request_cache->{template};
+
+ 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$/);
- $_templates_to_precompile{$name} = 1;
+ my $name = $File::Find::name;
+ return if (-d $name);
+ return if ($name =~ /\/CVS\//);
+ return if ($name !~ /\.tmpl$/);
+ $_templates_to_precompile{$name} = 1;
}
# Helper for precompile_templates
sub _do_template_symlink {
- my $dir_to_symlink = shift;
-
- my $abs_path = abs_path($dir_to_symlink);
-
- # If $dir_to_symlink is already an absolute path (as might happen
- # with packagers who set $libpath to an absolute path), then we don't
- # need to do this symlink.
- return if ($abs_path eq $dir_to_symlink);
-
- my $abs_root = dirname($abs_path);
- my $dir_name = basename($abs_path);
- my $cache_dir = bz_locations()->{'template_cache'};
- my $container = "$cache_dir$abs_root";
- mkpath($container);
- my $target = "$cache_dir/$dir_name";
- # Check if the directory exists, because if there are no extensions,
- # there won't be an "data/template/extensions" directory to link to.
- if (-d $target) {
- # We use abs2rel so that the symlink will look like
- # "../../../../template" which works, while just
- # "data/template/template/" doesn't work.
- my $relative_target = File::Spec->abs2rel($target, $container);
-
- my $link_name = "$container/$dir_name";
- symlink($relative_target, $link_name)
- or warn "Could not make $link_name a symlink to $relative_target: $!";
- }
+ my $dir_to_symlink = shift;
+
+ my $abs_path = abs_path($dir_to_symlink);
+
+ # If $dir_to_symlink is already an absolute path (as might happen
+ # with packagers who set $libpath to an absolute path), then we don't
+ # need to do this symlink.
+ return if ($abs_path eq $dir_to_symlink);
+
+ my $abs_root = dirname($abs_path);
+ my $dir_name = basename($abs_path);
+ my $cache_dir = bz_locations()->{'template_cache'};
+ my $container = "$cache_dir$abs_root";
+ mkpath($container);
+ my $target = "$cache_dir/$dir_name";
+
+ # Check if the directory exists, because if there are no extensions,
+ # there won't be an "data/template/extensions" directory to link to.
+ if (-d $target) {
+
+ # We use abs2rel so that the symlink will look like
+ # "../../../../template" which works, while just
+ # "data/template/template/" doesn't work.
+ my $relative_target = File::Spec->abs2rel($target, $container);
+
+ my $link_name = "$container/$dir_name";
+ symlink($relative_target, $link_name)
+ or warn "Could not make $link_name a symlink to $relative_target: $!";
+ }
}
1;
diff --git a/Bugzilla/Template/Context.pm b/Bugzilla/Template/Context.pm
index 470e6a9ee..01c8c5981 100644
--- a/Bugzilla/Template/Context.pm
+++ b/Bugzilla/Template/Context.pm
@@ -18,23 +18,24 @@ use Bugzilla::Hook;
use Scalar::Util qw(blessed);
sub process {
- my $self = shift;
- # We don't want to run the template_before_process hook for
- # template hooks (but we do want it to run if a hook calls
- # PROCESS inside itself). The problem is that the {component}->{name} of
- # hooks is unreliable--sometimes it starts with ./ and it's the
- # full path to the hook template, and sometimes it's just the relative
- # name (like hook/global/field-descs-end.none.tmpl). Also, calling
- # template_before_process for hook templates doesn't seem too useful,
- # because that's already part of the extension and they should be able
- # to modify their hook if they want (or just modify the variables in the
- # calling template).
- if (not delete $self->{bz_in_hook}) {
- $self->{bz_in_process} = 1;
- }
- my $result = $self->SUPER::process(@_);
- delete $self->{bz_in_process};
- return $result;
+ my $self = shift;
+
+ # We don't want to run the template_before_process hook for
+ # template hooks (but we do want it to run if a hook calls
+ # PROCESS inside itself). The problem is that the {component}->{name} of
+ # hooks is unreliable--sometimes it starts with ./ and it's the
+ # full path to the hook template, and sometimes it's just the relative
+ # name (like hook/global/field-descs-end.none.tmpl). Also, calling
+ # template_before_process for hook templates doesn't seem too useful,
+ # because that's already part of the extension and they should be able
+ # to modify their hook if they want (or just modify the variables in the
+ # calling template).
+ if (not delete $self->{bz_in_hook}) {
+ $self->{bz_in_process} = 1;
+ }
+ my $result = $self->SUPER::process(@_);
+ delete $self->{bz_in_process};
+ return $result;
}
# This method is called by Template-Toolkit exactly once per template or
@@ -46,58 +47,59 @@ sub process {
# in the PROCESS or INCLUDE directive haven't been set, and if we're
# in an INCLUDE, the stash is not yet localized during process().
sub stash {
- my $self = shift;
- my $stash = $self->SUPER::stash(@_);
-
- my $name = $stash->{component}->{name};
- my $pre_process = $self->config->{PRE_PROCESS};
-
- # Checking bz_in_process tells us that we were indeed called as part of a
- # Context::process, and not at some other point.
- #
- # Checking $name makes sure that we're processing a file, and not just a
- # block, by checking that the name has a period in it. We don't allow
- # blocks because their names are too unreliable--an extension could have
- # a block with the same name, or multiple files could have a same-named
- # block, and then your extension would malfunction.
- #
- # We also make sure that we don't run, ever, during the PRE_PROCESS
- # templates, because if somebody calls Throw*Error globally inside of
- # template_before_process, that causes an infinite recursion into
- # the PRE_PROCESS templates (because Bugzilla, while inside
- # global/intialize.none.tmpl, loads the template again to create the
- # template object for Throw*Error).
- #
- # Checking Bugzilla::Hook::in prevents infinite recursion on this hook.
- if ($self->{bz_in_process} and $name =~ /\./
- and !grep($_ eq $name, @$pre_process)
- and !Bugzilla::Hook::in('template_before_process'))
- {
- Bugzilla::Hook::process("template_before_process",
- { vars => $stash, context => $self,
- file => $name });
- }
-
- # This prevents other calls to stash() that might somehow happen
- # later in the file from also triggering the hook.
- delete $self->{bz_in_process};
-
- return $stash;
+ my $self = shift;
+ my $stash = $self->SUPER::stash(@_);
+
+ my $name = $stash->{component}->{name};
+ my $pre_process = $self->config->{PRE_PROCESS};
+
+ # Checking bz_in_process tells us that we were indeed called as part of a
+ # Context::process, and not at some other point.
+ #
+ # Checking $name makes sure that we're processing a file, and not just a
+ # block, by checking that the name has a period in it. We don't allow
+ # blocks because their names are too unreliable--an extension could have
+ # a block with the same name, or multiple files could have a same-named
+ # block, and then your extension would malfunction.
+ #
+ # We also make sure that we don't run, ever, during the PRE_PROCESS
+ # templates, because if somebody calls Throw*Error globally inside of
+ # template_before_process, that causes an infinite recursion into
+ # the PRE_PROCESS templates (because Bugzilla, while inside
+ # global/intialize.none.tmpl, loads the template again to create the
+ # template object for Throw*Error).
+ #
+ # Checking Bugzilla::Hook::in prevents infinite recursion on this hook.
+ if ( $self->{bz_in_process}
+ and $name =~ /\./
+ and !grep($_ eq $name, @$pre_process)
+ and !Bugzilla::Hook::in('template_before_process'))
+ {
+ Bugzilla::Hook::process("template_before_process",
+ {vars => $stash, context => $self, file => $name});
+ }
+
+ # This prevents other calls to stash() that might somehow happen
+ # later in the file from also triggering the hook.
+ delete $self->{bz_in_process};
+
+ return $stash;
}
sub filter {
- my ($self, $name, $args) = @_;
- # If we pass an alias for the filter name, the filter code is cached
- # instead of looking for it at each call.
- # If the filter has arguments, then we can't cache it.
- $self->SUPER::filter($name, $args, $args ? undef : $name);
+ my ($self, $name, $args) = @_;
+
+ # If we pass an alias for the filter name, the filter code is cached
+ # instead of looking for it at each call.
+ # If the filter has arguments, then we can't cache it.
+ $self->SUPER::filter($name, $args, $args ? undef : $name);
}
# We need a DESTROY sub for the same reason that Bugzilla::CGI does.
sub DESTROY {
- my $self = shift;
- $self->SUPER::DESTROY(@_);
-};
+ my $self = shift;
+ $self->SUPER::DESTROY(@_);
+}
1;
diff --git a/Bugzilla/Template/Plugin/Bugzilla.pm b/Bugzilla/Template/Plugin/Bugzilla.pm
index 806dd903b..0734fb942 100644
--- a/Bugzilla/Template/Plugin/Bugzilla.pm
+++ b/Bugzilla/Template/Plugin/Bugzilla.pm
@@ -16,20 +16,20 @@ use parent qw(Template::Plugin);
use Bugzilla;
sub new {
- my ($class, $context) = @_;
+ my ($class, $context) = @_;
- return bless {}, $class;
+ return bless {}, $class;
}
sub AUTOLOAD {
- my $class = shift;
- our $AUTOLOAD;
+ my $class = shift;
+ our $AUTOLOAD;
- $AUTOLOAD =~ s/^.*:://;
+ $AUTOLOAD =~ s/^.*:://;
- return if $AUTOLOAD eq 'DESTROY';
+ return if $AUTOLOAD eq 'DESTROY';
- return Bugzilla->$AUTOLOAD(@_);
+ return Bugzilla->$AUTOLOAD(@_);
}
1;
diff --git a/Bugzilla/Template/Plugin/Hook.pm b/Bugzilla/Template/Plugin/Hook.pm
index 669c77614..c57db4223 100644
--- a/Bugzilla/Template/Plugin/Hook.pm
+++ b/Bugzilla/Template/Plugin/Hook.pm
@@ -14,81 +14,80 @@ use warnings;
use parent qw(Template::Plugin);
use Bugzilla::Constants;
-use Bugzilla::Install::Util qw(template_include_path);
+use Bugzilla::Install::Util qw(template_include_path);
use Bugzilla::Util;
use Bugzilla::Error;
use File::Spec;
sub new {
- my ($class, $context) = @_;
- return bless { _CONTEXT => $context }, $class;
+ my ($class, $context) = @_;
+ return bless {_CONTEXT => $context}, $class;
}
sub _context { return $_[0]->{_CONTEXT} }
sub process {
- my ($self, $hook_name, $template) = @_;
- my $context = $self->_context();
- $template ||= $context->stash->{component}->{name};
-
- # sanity check:
- if (!$template =~ /[\w\.\/\-_\\]+/) {
- ThrowCodeError('template_invalid', { name => $template });
- }
-
- my (undef, $path, $filename) = File::Spec->splitpath($template);
- $path ||= '';
- $filename =~ m/(.+)\.(.+)\.tmpl$/;
- my $template_name = $1;
- my $type = $2;
-
- # Hooks are named like this:
- my $extension_template = "$path$template_name-$hook_name.$type.tmpl";
-
- # Get the hooks out of the cache if they exist. Otherwise, read them
- # from the disk.
- my $cache = Bugzilla->request_cache->{template_plugin_hook_cache} ||= {};
- my $lang = $context->{bz_language} || '';
- $cache->{"${lang}__$extension_template"}
- ||= $self->_get_hooks($extension_template);
-
- # process() accepts an arrayref of templates, so we just pass the whole
- # arrayref.
- $context->{bz_in_hook} = 1; # See Bugzilla::Template::Context
- return $context->process($cache->{"${lang}__$extension_template"});
+ my ($self, $hook_name, $template) = @_;
+ my $context = $self->_context();
+ $template ||= $context->stash->{component}->{name};
+
+ # sanity check:
+ if (!$template =~ /[\w\.\/\-_\\]+/) {
+ ThrowCodeError('template_invalid', {name => $template});
+ }
+
+ my (undef, $path, $filename) = File::Spec->splitpath($template);
+ $path ||= '';
+ $filename =~ m/(.+)\.(.+)\.tmpl$/;
+ my $template_name = $1;
+ my $type = $2;
+
+ # Hooks are named like this:
+ my $extension_template = "$path$template_name-$hook_name.$type.tmpl";
+
+ # Get the hooks out of the cache if they exist. Otherwise, read them
+ # from the disk.
+ my $cache = Bugzilla->request_cache->{template_plugin_hook_cache} ||= {};
+ my $lang = $context->{bz_language} || '';
+ $cache->{"${lang}__$extension_template"}
+ ||= $self->_get_hooks($extension_template);
+
+ # process() accepts an arrayref of templates, so we just pass the whole
+ # arrayref.
+ $context->{bz_in_hook} = 1; # See Bugzilla::Template::Context
+ return $context->process($cache->{"${lang}__$extension_template"});
}
sub _get_hooks {
- my ($self, $extension_template) = @_;
-
- my $template_sets = $self->_template_hook_include_path();
- my @hooks;
- foreach my $dir_set (@$template_sets) {
- foreach my $template_dir (@$dir_set) {
- my $file = "$template_dir/hook/$extension_template";
- if (-e $file) {
- my $template = $self->_context->template($file);
- push(@hooks, $template);
- # Don't run the hook for more than one language.
- last;
- }
- }
+ my ($self, $extension_template) = @_;
+
+ my $template_sets = $self->_template_hook_include_path();
+ my @hooks;
+ foreach my $dir_set (@$template_sets) {
+ foreach my $template_dir (@$dir_set) {
+ my $file = "$template_dir/hook/$extension_template";
+ if (-e $file) {
+ my $template = $self->_context->template($file);
+ push(@hooks, $template);
+
+ # Don't run the hook for more than one language.
+ last;
+ }
}
+ }
- return \@hooks;
+ return \@hooks;
}
sub _template_hook_include_path {
- my $self = shift;
- my $cache = Bugzilla->request_cache;
- my $language = $self->_context->{bz_language} || '';
- my $cache_key = "template_plugin_hook_include_path_$language";
- $cache->{$cache_key} ||= template_include_path({
- language => $language,
- hook => 1,
- });
- return $cache->{$cache_key};
+ my $self = shift;
+ my $cache = Bugzilla->request_cache;
+ my $language = $self->_context->{bz_language} || '';
+ my $cache_key = "template_plugin_hook_include_path_$language";
+ $cache->{$cache_key}
+ ||= template_include_path({language => $language, hook => 1,});
+ return $cache->{$cache_key};
}
1;
diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm
index 28122e818..62106c5e5 100644
--- a/Bugzilla/Token.pm
+++ b/Bugzilla/Token.pm
@@ -25,8 +25,8 @@ use Digest::SHA qw(hmac_sha256_base64);
use parent qw(Exporter);
@Bugzilla::Token::EXPORT = qw(issue_api_token issue_session_token
- check_token_data delete_token
- issue_hash_token check_hash_token);
+ check_token_data delete_token
+ issue_hash_token check_hash_token);
use constant SEND_NOW => 1;
@@ -36,394 +36,420 @@ use constant SEND_NOW => 1;
# Create a token used for internal API authentication
sub issue_api_token {
- # Generates a random token, adds it to the tokens table if one does not
- # already exist, and returns the token to the caller.
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
- my ($token) = $dbh->selectrow_array("
+
+ # Generates a random token, adds it to the tokens table if one does not
+ # already exist, and returns the token to the caller.
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ my ($token) = $dbh->selectrow_array("
SELECT token FROM tokens
WHERE userid = ? AND tokentype = 'api_token'
- AND (" . $dbh->sql_date_math('issuedate', '+', (MAX_TOKEN_AGE * 24 - 12), 'HOUR') . ") > NOW()",
- undef, $user->id);
- return $token // _create_token($user->id, 'api_token', '');
+ AND ("
+ . $dbh->sql_date_math('issuedate', '+', (MAX_TOKEN_AGE * 24 - 12), 'HOUR')
+ . ") > NOW()", undef, $user->id);
+ return $token // _create_token($user->id, 'api_token', '');
}
# 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 their email with the token inside.
- # But to prevent using this way to mailbomb an email address, make sure
- # the last request is old enough before sending a new email (default: 10 minutes).
-
- my $pending_requests = $dbh->selectrow_array(
- 'SELECT COUNT(*)
+ 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 their email with the token inside.
+# But to prevent using this way to mailbomb an email address, make sure
+# the last request is old enough before sending a new email (default: 10 minutes).
+
+ my $pending_requests = $dbh->selectrow_array(
+ 'SELECT COUNT(*)
FROM tokens
WHERE tokentype = ?
AND ' . $dbh->sql_istrcmp('eventdata', '?') . '
AND issuedate > '
- . $dbh->sql_date_math('NOW()', '-', ACCOUNT_CHANGE_INTERVAL, 'MINUTE'),
- undef, ('account', $login_name));
+ . $dbh->sql_date_math('NOW()', '-', ACCOUNT_CHANGE_INTERVAL, 'MINUTE'),
+ undef, ('account', $login_name)
+ );
- ThrowUserError('too_soon_for_new_token', {'type' => 'account'}) if $pending_requests;
+ ThrowUserError('too_soon_for_new_token', {'type' => 'account'})
+ if $pending_requests;
- my ($token, $token_ts) = _create_token(undef, 'account', $login_name);
+ my ($token, $token_ts) = _create_token(undef, 'account', $login_name);
- $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
- $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
- $vars->{'token'} = $token;
+ $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
+ $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
+ $vars->{'token'} = $token;
- my $message;
- $template->process('account/email/request-new.txt.tmpl', $vars, \$message)
- || ThrowTemplateError($template->error());
+ 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 their
- # user prefs as the user has no account yet!).
- MessageToMTA($message, SEND_NOW);
+ # 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 their
+ # user prefs as the user has no account yet!).
+ MessageToMTA($message, SEND_NOW);
}
sub IssueEmailChangeToken {
- my $new_email = shift;
- my $user = Bugzilla->user;
+ my $new_email = shift;
+ my $user = Bugzilla->user;
- my ($token, $token_ts) = _create_token($user->id, 'emailold', $user->login . ":$new_email");
- my $newtoken = _create_token($user->id, 'emailnew', $user->login . ":$new_email");
+ my ($token, $token_ts)
+ = _create_token($user->id, 'emailold', $user->login . ":$new_email");
+ my $newtoken
+ = _create_token($user->id, 'emailnew', $user->login . ":$new_email");
- # Mail the user the token along with instructions for using it.
+ # Mail the user the token along with instructions for using it.
- my $template = Bugzilla->template_inner($user->setting('lang'));
- my $vars = {};
+ my $template = Bugzilla->template_inner($user->setting('lang'));
+ my $vars = {};
- $vars->{'newemailaddress'} = $new_email . Bugzilla->params->{'emailsuffix'};
- $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
+ $vars->{'newemailaddress'} = $new_email . Bugzilla->params->{'emailsuffix'};
+ $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
- # First send an email to the new address. If this one doesn't exist,
- # then the whole process must stop immediately. This means the email must
- # be sent immediately and must not be stored in the queue.
- $vars->{'token'} = $newtoken;
+ # First send an email to the new address. If this one doesn't exist,
+ # then the whole process must stop immediately. This means the email must
+ # be sent immediately and must not be stored in the queue.
+ $vars->{'token'} = $newtoken;
- my $message;
- $template->process('account/email/change-new.txt.tmpl', $vars, \$message)
- || ThrowTemplateError($template->error());
+ my $message;
+ $template->process('account/email/change-new.txt.tmpl', $vars, \$message)
+ || ThrowTemplateError($template->error());
- MessageToMTA($message, SEND_NOW);
+ MessageToMTA($message, SEND_NOW);
- # If we come here, then the new address exists. We now email the current
- # address, but we don't want to stop the process if it no longer exists,
- # to give a chance to the user to confirm the email address change.
- $vars->{'token'} = $token;
+ # If we come here, then the new address exists. We now email the current
+ # address, but we don't want to stop the process if it no longer exists,
+ # to give a chance to the user to confirm the email address change.
+ $vars->{'token'} = $token;
- $message = '';
- $template->process('account/email/change-old.txt.tmpl', $vars, \$message)
- || ThrowTemplateError($template->error());
+ $message = '';
+ $template->process('account/email/change-old.txt.tmpl', $vars, \$message)
+ || ThrowTemplateError($template->error());
- eval { MessageToMTA($message, SEND_NOW); };
+ eval { MessageToMTA($message, SEND_NOW); };
- # Give the user a chance to cancel the process even if he never got
- # the email above. The token is required.
- return $token;
+ # Give the user a chance to cancel the process even if he never got
+ # the email above. The token is required.
+ return $token;
}
# 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 $user = shift;
+ my $dbh = Bugzilla->dbh;
- my $too_soon = $dbh->selectrow_array(
- 'SELECT 1 FROM tokens
+ my $too_soon = $dbh->selectrow_array(
+ 'SELECT 1 FROM tokens
WHERE userid = ? AND tokentype = ?
- AND issuedate > '
- . $dbh->sql_date_math('NOW()', '-', ACCOUNT_CHANGE_INTERVAL, 'MINUTE'),
- undef, ($user->id, 'password'));
+ AND issuedate > '
+ . $dbh->sql_date_math('NOW()', '-', ACCOUNT_CHANGE_INTERVAL, 'MINUTE'),
+ undef, ($user->id, 'password')
+ );
- ThrowUserError('too_soon_for_new_token', {'type' => 'password'}) if $too_soon;
+ ThrowUserError('too_soon_for_new_token', {'type' => 'password'}) if $too_soon;
- my $ip_addr = remote_ip();
- my ($token, $token_ts) = _create_token($user->id, 'password', $ip_addr);
+ my $ip_addr = remote_ip();
+ my ($token, $token_ts) = _create_token($user->id, 'password', $ip_addr);
- # Mail the user the token along with instructions for using it.
- my $template = Bugzilla->template_inner($user->setting('lang'));
- my $vars = {};
+ # Mail the user the token along with instructions for using it.
+ my $template = Bugzilla->template_inner($user->setting('lang'));
+ my $vars = {};
- $vars->{'token'} = $token;
- $vars->{'ip_addr'} = $ip_addr;
- $vars->{'emailaddress'} = $user->email;
- $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
- # The user is not logged in (else they wouldn't request a new password).
- # So we have to pass this information to the template.
- $vars->{'timezone'} = $user->timezone;
-
- my $message = "";
- $template->process("account/password/forgotten-password.txt.tmpl",
- $vars, \$message)
- || ThrowTemplateError($template->error());
+ $vars->{'token'} = $token;
+ $vars->{'ip_addr'} = $ip_addr;
+ $vars->{'emailaddress'} = $user->email;
+ $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
+
+ # The user is not logged in (else they wouldn't request a new password).
+ # So we have to pass this information to the template.
+ $vars->{'timezone'} = $user->timezone;
- MessageToMTA($message);
+ my $message = "";
+ $template->process("account/password/forgotten-password.txt.tmpl",
+ $vars, \$message)
+ || ThrowTemplateError($template->error());
+
+ 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);
+ # 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 issue_hash_token {
- my ($data, $time) = @_;
- $data ||= [];
- $time ||= time();
-
- # For the user ID, use the actual ID if the user is logged in.
- # Otherwise, use the remote IP, in case this is for something
- # such as creating an account or logging in.
- my $user_id = Bugzilla->user->id || remote_ip();
-
- # The concatenated string is of the form
- # token creation time + user ID (either ID or remote IP) + data
- my @args = ($time, $user_id, @$data);
-
- my $token = join('*', @args);
- # Wide characters cause Digest::SHA to die.
- if (Bugzilla->params->{'utf8'}) {
- utf8::encode($token) if utf8::is_utf8($token);
- }
- $token = hmac_sha256_base64($token, Bugzilla->localconfig->{'site_wide_secret'});
- $token =~ s/\+/-/g;
- $token =~ s/\//_/g;
-
- # Prepend the token creation time, unencrypted, so that the token
- # lifetime can be validated.
- return $time . '-' . $token;
+ my ($data, $time) = @_;
+ $data ||= [];
+ $time ||= time();
+
+ # For the user ID, use the actual ID if the user is logged in.
+ # Otherwise, use the remote IP, in case this is for something
+ # such as creating an account or logging in.
+ my $user_id = Bugzilla->user->id || remote_ip();
+
+ # The concatenated string is of the form
+ # token creation time + user ID (either ID or remote IP) + data
+ my @args = ($time, $user_id, @$data);
+
+ my $token = join('*', @args);
+
+ # Wide characters cause Digest::SHA to die.
+ if (Bugzilla->params->{'utf8'}) {
+ utf8::encode($token) if utf8::is_utf8($token);
+ }
+ $token
+ = hmac_sha256_base64($token, Bugzilla->localconfig->{'site_wide_secret'});
+ $token =~ s/\+/-/g;
+ $token =~ s/\//_/g;
+
+ # Prepend the token creation time, unencrypted, so that the token
+ # lifetime can be validated.
+ return $time . '-' . $token;
}
sub check_hash_token {
- my ($token, $data) = @_;
- $data ||= [];
- my ($time, $expected_token);
-
- if ($token) {
- ($time, undef) = split(/-/, $token);
- # Regenerate the token based on the information we have.
- $expected_token = issue_hash_token($data, $time);
- }
+ my ($token, $data) = @_;
+ $data ||= [];
+ my ($time, $expected_token);
- if (!$token
- || $expected_token ne $token
- || time() - $time > MAX_TOKEN_AGE * 86400)
- {
- my $template = Bugzilla->template;
- my $vars = {};
- $vars->{'script_name'} = basename($0);
- $vars->{'token'} = issue_hash_token($data);
- $vars->{'reason'} = (!$token) ? 'missing_token' :
- ($expected_token ne $token) ? 'invalid_token' :
- 'expired_token';
- print Bugzilla->cgi->header();
- $template->process('global/confirm-action.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
+ if ($token) {
+ ($time, undef) = split(/-/, $token);
+
+ # Regenerate the token based on the information we have.
+ $expected_token = issue_hash_token($data, $time);
+ }
+
+ if (!$token
+ || $expected_token ne $token
+ || time() - $time > MAX_TOKEN_AGE * 86400)
+ {
+ my $template = Bugzilla->template;
+ my $vars = {};
+ $vars->{'script_name'} = basename($0);
+ $vars->{'token'} = issue_hash_token($data);
+ $vars->{'reason'}
+ = (!$token) ? 'missing_token'
+ : ($expected_token ne $token) ? 'invalid_token'
+ : 'expired_token';
+ print Bugzilla->cgi->header();
+ $template->process('global/confirm-action.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
- # If we come here, then the token is valid and not too old.
- return 1;
+ # If we come here, then the token is valid and not too old.
+ return 1;
}
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);
+ 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 1 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;
+
+ # 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 1 FROM $table WHERE $column = ?");
+
+ while ($duplicate) {
+ ++$tries;
+ if ($tries > 100) {
+ ThrowCodeError("token_generation_error");
}
- return $token;
+ $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 ($db_token, $issuedate, $tokentype, $eventdata, $userid) =
- $dbh->selectrow_array('SELECT token, ' . $dbh->sql_date_format('issuedate') . ',
+ my ($token, $cancelaction, $vars) = @_;
+ my $dbh = Bugzilla->dbh;
+ $vars ||= {};
+
+ # Get information about the token being canceled.
+ trick_taint($token);
+ my ($db_token, $issuedate, $tokentype, $eventdata, $userid)
+ = $dbh->selectrow_array(
+ 'SELECT token, '
+ . $dbh->sql_date_format('issuedate') . ',
tokentype, eventdata, userid
FROM tokens
- WHERE token = ?',
- undef, $token);
-
- # Some DBs such as MySQL are case-insensitive by default so we do
- # a quick comparison to make sure the tokens are indeed the same.
- (defined $db_token && $db_token eq $token)
- || ThrowCodeError("cancel_token_does_not_exist");
-
- # 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'} = remote_ip();
- $vars->{'token'} = $token;
- $vars->{'tokentype'} = $tokentype;
- $vars->{'issuedate'} = $issuedate;
- # The user is probably not logged in.
- # So we have to pass this information to the template.
- $vars->{'timezone'} = $user->timezone;
- $vars->{'eventdata'} = $eventdata;
- $vars->{'cancelaction'} = $cancelaction;
-
- # Notify the user via email about the cancellation.
- my $template = Bugzilla->template_inner($user->setting('lang'));
-
- my $message;
- $template->process("account/cancel-token.txt.tmpl", $vars, \$message)
- || ThrowTemplateError($template->error());
+ WHERE token = ?', undef, $token
+ );
- MessageToMTA($message);
+ # Some DBs such as MySQL are case-insensitive by default so we do
+ # a quick comparison to make sure the tokens are indeed the same.
+ (defined $db_token && $db_token eq $token)
+ || ThrowCodeError("cancel_token_does_not_exist");
- # Delete the token from the database.
- delete_token($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'} = remote_ip();
+ $vars->{'token'} = $token;
+ $vars->{'tokentype'} = $tokentype;
+ $vars->{'issuedate'} = $issuedate;
+
+ # The user is probably not logged in.
+ # So we have to pass this information to the template.
+ $vars->{'timezone'} = $user->timezone;
+ $vars->{'eventdata'} = $eventdata;
+ $vars->{'cancelaction'} = $cancelaction;
+
+ # Notify the user via email about the cancellation.
+ my $template = Bugzilla->template_inner($user->setting('lang'));
+
+ my $message;
+ $template->process("account/cancel-token.txt.tmpl", $vars, \$message)
+ || ThrowTemplateError($template->error());
+
+ MessageToMTA($message);
+
+ # Delete the token from the database.
+ delete_token($token);
}
sub DeletePasswordTokens {
- my ($userid, $reason) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($userid, $reason) = @_;
+ my $dbh = Bugzilla->dbh;
- detaint_natural($userid);
- my $tokens = $dbh->selectcol_arrayref('SELECT token FROM tokens
+ detaint_natural($userid);
+ my $tokens = $dbh->selectcol_arrayref(
+ 'SELECT token FROM tokens
WHERE userid = ? AND tokentype = ?',
- undef, ($userid, 'password'));
+ undef, ($userid, 'password')
+ );
- foreach my $token (@$tokens) {
- Bugzilla::Token::Cancel($token, $reason);
- }
+ foreach my $token (@$tokens) {
+ Bugzilla::Token::Cancel($token, $reason);
+ }
}
-# Returns an email change token if the user has one.
+# Returns an email change token if the user has one.
sub HasEmailChangeToken {
- my $userid = shift;
- my $dbh = Bugzilla->dbh;
+ my $userid = shift;
+ my $dbh = Bugzilla->dbh;
- my $token = $dbh->selectrow_array('SELECT token FROM tokens
+ 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;
+ 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;
+ my ($token) = @_;
+ my $dbh = Bugzilla->dbh;
- return unless defined $token;
- $token = clean_text($token);
- trick_taint($token);
+ return unless defined $token;
+ $token = clean_text($token);
+ trick_taint($token);
- my @token_data = $dbh->selectrow_array(
- "SELECT token, userid, " . $dbh->sql_date_format('issuedate') . ", eventdata, tokentype
+ my @token_data = $dbh->selectrow_array(
+ "SELECT token, userid, "
+ . $dbh->sql_date_format('issuedate')
+ . ", eventdata, tokentype
FROM tokens
- WHERE token = ?", undef, $token);
+ WHERE token = ?", undef, $token
+ );
- # Some DBs such as MySQL are case-insensitive by default so we do
- # a quick comparison to make sure the tokens are indeed the same.
- my $db_token = shift @token_data;
- return undef if (!defined $db_token || $db_token ne $token);
+ # Some DBs such as MySQL are case-insensitive by default so we do
+ # a quick comparison to make sure the tokens are indeed the same.
+ my $db_token = shift @token_data;
+ return undef if (!defined $db_token || $db_token ne $token);
- return @token_data;
+ return @token_data;
}
# Deletes specified token
sub delete_token {
- my ($token) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($token) = @_;
+ my $dbh = Bugzilla->dbh;
- return unless defined $token;
- trick_taint($token);
+ return unless defined $token;
+ trick_taint($token);
- $dbh->do("DELETE FROM tokens WHERE token = ?", undef, $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.
sub check_token_data {
- my ($token, $expected_action, $alternate_script) = @_;
- 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 their 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);
- $vars->{'alternate_script'} = $alternate_script || 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;
+ my ($token, $expected_action, $alternate_script) = @_;
+ 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 their 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);
+ $vars->{'alternate_script'} = $alternate_script || 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);
}
- return 1;
+
+ print $cgi->header();
+
+ $template->process('admin/confirm-action.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+ return 1;
}
################################################################################
@@ -433,34 +459,38 @@ sub check_token_data {
# 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;
+ my ($userid, $tokentype, $eventdata) = @_;
+ my $dbh = Bugzilla->dbh;
- detaint_natural($userid) if defined $userid;
- trick_taint($tokentype);
- trick_taint($eventdata);
+ detaint_natural($userid) if defined $userid;
+ trick_taint($tokentype);
+ trick_taint($eventdata);
- my $is_shadow = Bugzilla->is_shadow_db;
- $dbh = Bugzilla->switch_to_main_db() if $is_shadow;
+ my $is_shadow = Bugzilla->is_shadow_db;
+ $dbh = Bugzilla->switch_to_main_db() if $is_shadow;
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- my $token = GenerateUniqueToken();
+ my $token = GenerateUniqueToken();
- $dbh->do("INSERT INTO tokens (userid, issuedate, token, tokentype, eventdata)
- VALUES (?, NOW(), ?, ?, ?)", undef, ($userid, $token, $tokentype, $eventdata));
+ $dbh->do(
+ "INSERT INTO tokens (userid, issuedate, token, tokentype, eventdata)
+ VALUES (?, NOW(), ?, ?, ?)", undef,
+ ($userid, $token, $tokentype, $eventdata)
+ );
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
- if (wantarray) {
- my (undef, $token_ts, undef) = GetTokenData($token);
- $token_ts = str2time($token_ts);
- Bugzilla->switch_to_shadow_db() if $is_shadow;
- return ($token, $token_ts);
- } else {
- Bugzilla->switch_to_shadow_db() if $is_shadow;
- return $token;
- }
+ if (wantarray) {
+ my (undef, $token_ts, undef) = GetTokenData($token);
+ $token_ts = str2time($token_ts);
+ Bugzilla->switch_to_shadow_db() if $is_shadow;
+ return ($token, $token_ts);
+ }
+ else {
+ Bugzilla->switch_to_shadow_db() if $is_shadow;
+ return $token;
+ }
}
1;
diff --git a/Bugzilla/Update.pm b/Bugzilla/Update.pm
index 72a7108a8..9f9288162 100644
--- a/Bugzilla/Update.pm
+++ b/Bugzilla/Update.pm
@@ -13,149 +13,159 @@ use warnings;
use Bugzilla::Constants;
-use constant TIME_INTERVAL => 86400; # Default is one day, in seconds.
-use constant TIMEOUT => 5; # Number of seconds before timeout.
+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->feature('updates');
- return if (Bugzilla->params->{'upgrade_notification'} eq 'disabled');
-
- 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)) {
- unlink $local_file; # Make sure the old copy is away.
- return { 'error' => 'no_update' } if (-e $local_file);
-
- my $error = _synchronize_data();
- # If an error is returned, leave now.
- return $error if $error;
+ return if !Bugzilla->feature('updates');
+ return if (Bugzilla->params->{'upgrade_notification'} eq 'disabled');
+
+ 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)) {
+ unlink $local_file; # Make sure the old copy is away.
+ return {'error' => 'no_update'} if (-e $local_file);
+
+ my $error = _synchronize_data();
+
+ # If an error is returned, leave now.
+ return $error if $error;
+ }
+
+ # If we cannot access the local XML file, ignore it.
+ return {'error' => 'no_access'} unless (-r $local_file);
+
+ my $twig = XML::Twig->new();
+ $twig->safe_parsefile($local_file);
+
+ # If the XML file is invalid, return.
+ return {'error' => 'corrupted'} 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;
}
-
- # If we cannot access the local XML file, ignore it.
- return { 'error' => 'no_access' } unless (-r $local_file);
-
- my $twig = XML::Twig->new();
- $twig->safe_parsefile($local_file);
- # If the XML file is invalid, return.
- return { 'error' => 'corrupted' } 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};
- }
+ }
+ 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;
+ }
+ 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 {
- 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;
- }
- my $response = eval { $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' };
- }
- elsif ($response && $response->is_error) {
- # We have been unable to download the file.
- return { 'error' => 'cannot_download', 'reason' => $response->status_line };
- }
- else {
- return { 'error' => 'no_write', 'reason' => $@ };
- }
-
- # Everything went well.
- return 0;
+ 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;
+ }
+ my $response = eval { $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'};
+ }
+ elsif ($response && $response->is_error) {
+
+ # We have been unable to download the file.
+ return {'error' => 'cannot_download', 'reason' => $response->status_line};
+ }
+ else {
+ return {'error' => 'no_write', 'reason' => $@};
+ }
+
+ # 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);
+ 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);
}
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index 77e6cebb0..420aaf0fa 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -33,9 +33,9 @@ use URI::QueryParam;
use parent qw(Bugzilla::Object Exporter);
@Bugzilla::User::EXPORT = qw(is_available_username
- login_to_id validate_password validate_password_check
- USER_MATCH_MULTIPLE USER_MATCH_FAILED USER_MATCH_SUCCESS
- MATCH_SKIP_CONFIRM
+ login_to_id validate_password validate_password_check
+ USER_MATCH_MULTIPLE USER_MATCH_FAILED USER_MATCH_SUCCESS
+ MATCH_SKIP_CONFIRM
);
#####################################################################
@@ -46,16 +46,16 @@ use constant USER_MATCH_MULTIPLE => -1;
use constant USER_MATCH_FAILED => 0;
use constant USER_MATCH_SUCCESS => 1;
-use constant MATCH_SKIP_CONFIRM => 1;
+use constant MATCH_SKIP_CONFIRM => 1;
use constant DEFAULT_USER => {
- 'userid' => 0,
- 'realname' => '',
- 'login_name' => '',
- 'showmybugslink' => 0,
- 'disabledtext' => '',
- 'disable_mail' => 0,
- 'is_enabled' => 1,
+ 'userid' => 0,
+ 'realname' => '',
+ 'login_name' => '',
+ 'showmybugslink' => 0,
+ 'disabledtext' => '',
+ 'disable_mail' => 0,
+ 'is_enabled' => 1,
};
use constant DB_TABLE => 'profiles';
@@ -65,18 +65,19 @@ use constant DB_TABLE => 'profiles';
# Bugzilla::User used "name" for the realname field. This should be
# fixed one day.
sub DB_COLUMNS {
- my $dbh = Bugzilla->dbh;
- return (
- 'profiles.userid',
- 'profiles.login_name',
- 'profiles.realname',
- 'profiles.mybugslink AS showmybugslink',
- 'profiles.disabledtext',
- 'profiles.disable_mail',
- 'profiles.extern_id',
- 'profiles.is_enabled',
- $dbh->sql_date_format('last_seen_date', '%Y-%m-%d') . ' AS last_seen_date',
+ my $dbh = Bugzilla->dbh;
+ return (
+ 'profiles.userid',
+ 'profiles.login_name',
+ 'profiles.realname',
+ 'profiles.mybugslink AS showmybugslink',
+ 'profiles.disabledtext',
+ 'profiles.disable_mail',
+ 'profiles.extern_id',
+ 'profiles.is_enabled',
+ $dbh->sql_date_format('last_seen_date', '%Y-%m-%d') . ' AS last_seen_date',
),
+ ;
}
use constant NAME_FIELD => 'login_name';
@@ -84,32 +85,30 @@ use constant ID_FIELD => 'userid';
use constant LIST_ORDER => NAME_FIELD;
use constant VALIDATORS => {
- cryptpassword => \&_check_password,
- disable_mail => \&_check_disable_mail,
- disabledtext => \&_check_disabledtext,
- login_name => \&check_login_name,
- realname => \&_check_realname,
- extern_id => \&_check_extern_id,
- is_enabled => \&_check_is_enabled,
+ cryptpassword => \&_check_password,
+ disable_mail => \&_check_disable_mail,
+ disabledtext => \&_check_disabledtext,
+ login_name => \&check_login_name,
+ realname => \&_check_realname,
+ extern_id => \&_check_extern_id,
+ is_enabled => \&_check_is_enabled,
};
sub UPDATE_COLUMNS {
- my $self = shift;
- my @cols = qw(
- disable_mail
- disabledtext
- login_name
- realname
- extern_id
- is_enabled
- );
- push(@cols, 'cryptpassword') if exists $self->{cryptpassword};
- return @cols;
-};
+ my $self = shift;
+ my @cols = qw(
+ disable_mail
+ disabledtext
+ login_name
+ realname
+ extern_id
+ is_enabled
+ );
+ push(@cols, 'cryptpassword') if exists $self->{cryptpassword};
+ return @cols;
+}
-use constant VALIDATOR_DEPENDENCIES => {
- is_enabled => ['disabledtext'],
-};
+use constant VALIDATOR_DEPENDENCIES => {is_enabled => ['disabledtext'],};
use constant EXTRA_REQUIRED_FIELDS => qw(is_enabled);
@@ -118,129 +117,125 @@ use constant EXTRA_REQUIRED_FIELDS => qw(is_enabled);
################################################################################
sub new {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my ($param) = @_;
-
- my $user = { %{ DEFAULT_USER() } };
- bless ($user, $class);
- return $user unless $param;
-
- if (ref($param) eq 'HASH') {
- if (defined $param->{extern_id}) {
- $param = { condition => 'extern_id = ?' , values => [$param->{extern_id}] };
- $_[0] = $param;
- }
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my ($param) = @_;
+
+ my $user = {%{DEFAULT_USER()}};
+ bless($user, $class);
+ return $user unless $param;
+
+ if (ref($param) eq 'HASH') {
+ if (defined $param->{extern_id}) {
+ $param = {condition => 'extern_id = ?', values => [$param->{extern_id}]};
+ $_[0] = $param;
}
- return $class->SUPER::new(@_);
+ }
+ return $class->SUPER::new(@_);
}
sub super_user {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my ($param) = @_;
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my ($param) = @_;
- my $user = { %{ DEFAULT_USER() } };
- $user->{groups} = [Bugzilla::Group->get_all];
- $user->{bless_groups} = [Bugzilla::Group->get_all];
- bless $user, $class;
- return $user;
+ my $user = {%{DEFAULT_USER()}};
+ $user->{groups} = [Bugzilla::Group->get_all];
+ $user->{bless_groups} = [Bugzilla::Group->get_all];
+ bless $user, $class;
+ return $user;
}
sub _update_groups {
- my $self = shift;
- my $group_changes = shift;
- my $changes = shift;
- my $dbh = Bugzilla->dbh;
-
- # Update group settings.
- my $sth_add_mapping = $dbh->prepare(
- qq{INSERT INTO user_group_map (
+ my $self = shift;
+ my $group_changes = shift;
+ my $changes = shift;
+ my $dbh = Bugzilla->dbh;
+
+ # Update group settings.
+ my $sth_add_mapping = $dbh->prepare(qq{INSERT INTO user_group_map (
user_id, group_id, isbless, grant_type
) VALUES (
?, ?, ?, ?
)
- });
- my $sth_remove_mapping = $dbh->prepare(
- qq{DELETE FROM user_group_map
+ }
+ );
+ my $sth_remove_mapping = $dbh->prepare(qq{DELETE FROM user_group_map
WHERE user_id = ?
AND group_id = ?
AND isbless = ?
AND grant_type = ?
- });
+ }
+ );
- foreach my $is_bless (keys %$group_changes) {
- my ($removed, $added) = @{$group_changes->{$is_bless}};
+ foreach my $is_bless (keys %$group_changes) {
+ my ($removed, $added) = @{$group_changes->{$is_bless}};
- foreach my $group (@$removed) {
- $sth_remove_mapping->execute(
- $self->id, $group->id, $is_bless, GRANT_DIRECT
- );
- }
- foreach my $group (@$added) {
- $sth_add_mapping->execute(
- $self->id, $group->id, $is_bless, GRANT_DIRECT
- );
- }
+ foreach my $group (@$removed) {
+ $sth_remove_mapping->execute($self->id, $group->id, $is_bless, GRANT_DIRECT);
+ }
+ foreach my $group (@$added) {
+ $sth_add_mapping->execute($self->id, $group->id, $is_bless, GRANT_DIRECT);
+ }
- if (! $is_bless) {
- my $query = qq{
+ if (!$is_bless) {
+ my $query = qq{
INSERT INTO profiles_activity
(userid, who, profiles_when, fieldid, oldvalue, newvalue)
VALUES ( ?, ?, now(), ?, ?, ?)
};
- $dbh->do(
- $query, undef,
- $self->id, Bugzilla->user->id,
- get_field_id('bug_group'),
- join(', ', map { $_->name } @$removed),
- join(', ', map { $_->name } @$added)
- );
- }
- else {
- # XXX: should create profiles_activity entries for blesser changes.
- }
+ $dbh->do(
+ $query, undef, $self->id, Bugzilla->user->id,
+ get_field_id('bug_group'),
+ join(', ', map { $_->name } @$removed),
+ join(', ', map { $_->name } @$added)
+ );
+ }
+ else {
+ # XXX: should create profiles_activity entries for blesser changes.
+ }
- Bugzilla->memcached->clear_config({ key => 'user_groups.' . $self->id });
+ Bugzilla->memcached->clear_config({key => 'user_groups.' . $self->id});
- my $type = $is_bless ? 'bless_groups' : 'groups';
- $changes->{$type} = [
- [ map { $_->name } @$removed ],
- [ map { $_->name } @$added ],
- ];
- }
+ my $type = $is_bless ? 'bless_groups' : 'groups';
+ $changes->{$type} = [[map { $_->name } @$removed], [map { $_->name } @$added],];
+ }
}
sub update {
- my $self = shift;
- my $options = shift;
+ my $self = shift;
+ my $options = shift;
- my $group_changes = delete $self->{_group_changes};
+ my $group_changes = delete $self->{_group_changes};
- my $changes = $self->SUPER::update(@_);
- my $dbh = Bugzilla->dbh;
- $self->_update_groups($group_changes, $changes);
+ my $changes = $self->SUPER::update(@_);
+ my $dbh = Bugzilla->dbh;
+ $self->_update_groups($group_changes, $changes);
- if (exists $changes->{login_name}) {
- # Delete all the tokens related to the userid
- $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $self->id)
- unless $options->{keep_tokens};
- # And rederive regex groups
- $self->derive_regexp_groups();
- }
+ if (exists $changes->{login_name}) {
- # Logout the user if necessary.
- Bugzilla->logout_user($self)
- if (!$options->{keep_session}
- && (exists $changes->{login_name}
- || exists $changes->{disabledtext}
- || exists $changes->{cryptpassword}));
+ # Delete all the tokens related to the userid
+ $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $self->id)
+ unless $options->{keep_tokens};
- # XXX Can update profiles_activity here as soon as it understands
- # field names like login_name.
-
- return $changes;
+ # And rederive regex groups
+ $self->derive_regexp_groups();
+ }
+
+ # Logout the user if necessary.
+ Bugzilla->logout_user($self)
+ if (
+ !$options->{keep_session}
+ && ( exists $changes->{login_name}
+ || exists $changes->{disabledtext}
+ || exists $changes->{cryptpassword})
+ );
+
+ # XXX Can update profiles_activity here as soon as it understands
+ # field names like login_name.
+
+ return $changes;
}
################################################################################
@@ -252,62 +247,63 @@ sub _check_disabledtext { return trim($_[1]) || ''; }
# Check whether the extern_id is unique.
sub _check_extern_id {
- my ($invocant, $extern_id) = @_;
- $extern_id = trim($extern_id);
- return undef unless defined($extern_id) && $extern_id ne "";
- if (!ref($invocant) || $invocant->extern_id ne $extern_id) {
- my $existing_login = $invocant->new({ extern_id => $extern_id });
- if ($existing_login) {
- ThrowUserError( 'extern_id_exists',
- { extern_id => $extern_id,
- existing_login_name => $existing_login->login });
- }
+ my ($invocant, $extern_id) = @_;
+ $extern_id = trim($extern_id);
+ return undef unless defined($extern_id) && $extern_id ne "";
+ if (!ref($invocant) || $invocant->extern_id ne $extern_id) {
+ my $existing_login = $invocant->new({extern_id => $extern_id});
+ if ($existing_login) {
+ ThrowUserError('extern_id_exists',
+ {extern_id => $extern_id, existing_login_name => $existing_login->login});
}
- return $extern_id;
+ }
+ return $extern_id;
}
# This is public since createaccount.cgi needs to use it before issuing
# a token for account creation.
sub check_login_name {
- my ($invocant, $name) = @_;
- $name = trim($name);
- $name || ThrowUserError('user_login_required');
- check_email_syntax($name);
-
- # Check the name if it's a new user, or if we're changing the name.
- if (!ref($invocant) || lc($invocant->login) ne lc($name)) {
- my @params = ($name);
- push(@params, $invocant->login) if ref($invocant);
- is_available_username(@params)
- || ThrowUserError('account_exists', { email => $name });
- }
+ my ($invocant, $name) = @_;
+ $name = trim($name);
+ $name || ThrowUserError('user_login_required');
+ check_email_syntax($name);
+
+ # Check the name if it's a new user, or if we're changing the name.
+ if (!ref($invocant) || lc($invocant->login) ne lc($name)) {
+ my @params = ($name);
+ push(@params, $invocant->login) if ref($invocant);
+ is_available_username(@params)
+ || ThrowUserError('account_exists', {email => $name});
+ }
- return $name;
+ return $name;
}
sub _check_password {
- my ($self, $pass) = @_;
+ my ($self, $pass) = @_;
- # If the password is '*', do not encrypt it or validate it further--we
- # are creating a user who should not be able to log in using DB
- # authentication.
- return $pass if $pass eq '*';
+ # If the password is '*', do not encrypt it or validate it further--we
+ # are creating a user who should not be able to log in using DB
+ # authentication.
+ return $pass if $pass eq '*';
- validate_password($pass);
- my $cryptpassword = bz_crypt($pass);
- return $cryptpassword;
+ validate_password($pass);
+ my $cryptpassword = bz_crypt($pass);
+ return $cryptpassword;
}
sub _check_realname { return trim($_[1]) || ''; }
sub _check_is_enabled {
- my ($invocant, $is_enabled, undef, $params) = @_;
- # is_enabled is set automatically on creation depending on whether
- # disabledtext is empty (enabled) or not empty (disabled).
- # When updating the user, is_enabled is set by calling set_disabledtext().
- # Any value passed into this validator is ignored.
- my $disabledtext = ref($invocant) ? $invocant->disabledtext : $params->{disabledtext};
- return $disabledtext ? 0 : 1;
+ my ($invocant, $is_enabled, undef, $params) = @_;
+
+ # is_enabled is set automatically on creation depending on whether
+ # disabledtext is empty (enabled) or not empty (disabled).
+ # When updating the user, is_enabled is set by calling set_disabledtext().
+ # Any value passed into this validator is ignored.
+ my $disabledtext
+ = ref($invocant) ? $invocant->disabledtext : $params->{disabledtext};
+ return $disabledtext ? 0 : 1;
}
################################################################################
@@ -316,150 +312,151 @@ sub _check_is_enabled {
sub set_disable_mail { $_[0]->set('disable_mail', $_[1]); }
sub set_email_enabled { $_[0]->set('disable_mail', !$_[1]); }
-sub set_extern_id { $_[0]->set('extern_id', $_[1]); }
+sub set_extern_id { $_[0]->set('extern_id', $_[1]); }
sub set_login {
- my ($self, $login) = @_;
- $self->set('login_name', $login);
- delete $self->{identity};
- delete $self->{nick};
+ my ($self, $login) = @_;
+ $self->set('login_name', $login);
+ delete $self->{identity};
+ delete $self->{nick};
}
sub set_name {
- my ($self, $name) = @_;
- $self->set('realname', $name);
- delete $self->{identity};
+ my ($self, $name) = @_;
+ $self->set('realname', $name);
+ delete $self->{identity};
}
sub set_password { $_[0]->set('cryptpassword', $_[1]); }
sub set_disabledtext {
- $_[0]->set('disabledtext', $_[1]);
- $_[0]->set('is_enabled', $_[1] ? 0 : 1);
+ $_[0]->set('disabledtext', $_[1]);
+ $_[0]->set('is_enabled', $_[1] ? 0 : 1);
}
sub set_groups {
- my $self = shift;
- $self->_set_groups(GROUP_MEMBERSHIP, @_);
+ my $self = shift;
+ $self->_set_groups(GROUP_MEMBERSHIP, @_);
}
sub set_bless_groups {
- my $self = shift;
+ my $self = shift;
- # The person making the change needs to be in the editusers group
- Bugzilla->user->in_group('editusers')
- || ThrowUserError("auth_failure", {group => "editusers",
- reason => "cant_bless",
- action => "edit",
- object => "users"});
+ # The person making the change needs to be in the editusers group
+ Bugzilla->user->in_group('editusers') || ThrowUserError(
+ "auth_failure",
+ {
+ group => "editusers",
+ reason => "cant_bless",
+ action => "edit",
+ object => "users"
+ }
+ );
- $self->_set_groups(GROUP_BLESS, @_);
+ $self->_set_groups(GROUP_BLESS, @_);
}
sub _set_groups {
- my $self = shift;
- my $is_bless = shift;
- my $changes = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $is_bless = shift;
+ my $changes = shift;
+ my $dbh = Bugzilla->dbh;
- # The person making the change is $user, $self is the person being changed
- my $user = Bugzilla->user;
+ # The person making the change is $user, $self is the person being changed
+ my $user = Bugzilla->user;
- # Input is a hash of arrays. Key is 'set', 'add' or 'remove'. The array
- # is a list of group ids and/or names.
+ # Input is a hash of arrays. Key is 'set', 'add' or 'remove'. The array
+ # is a list of group ids and/or names.
- # First turn the arrays into group objects.
- $changes = $self->_set_groups_to_object($changes);
+ # First turn the arrays into group objects.
+ $changes = $self->_set_groups_to_object($changes);
- # Get a list of the groups the user currently is a member of
- my $ids = $dbh->selectcol_arrayref(
- q{SELECT DISTINCT group_id
+ # Get a list of the groups the user currently is a member of
+ my $ids = $dbh->selectcol_arrayref(
+ q{SELECT DISTINCT group_id
FROM user_group_map
- WHERE user_id = ? AND isbless = ? AND grant_type = ?},
- undef, $self->id, $is_bless, GRANT_DIRECT);
-
- my $current_groups = Bugzilla::Group->new_from_list($ids);
- my $new_groups = dclone($current_groups);
-
- # Record the changes
- if (exists $changes->{set}) {
- $new_groups = $changes->{set};
-
- # We need to check the user has bless rights on the existing groups
- # If they don't, then we need to add them back to new_groups
- foreach my $group (@$current_groups) {
- if (! $user->can_bless($group->id)) {
- push @$new_groups, $group
- unless grep { $_->id eq $group->id } @$new_groups;
- }
- }
+ WHERE user_id = ? AND isbless = ? AND grant_type = ?}, undef, $self->id,
+ $is_bless, GRANT_DIRECT
+ );
+
+ my $current_groups = Bugzilla::Group->new_from_list($ids);
+ my $new_groups = dclone($current_groups);
+
+ # Record the changes
+ if (exists $changes->{set}) {
+ $new_groups = $changes->{set};
+
+ # We need to check the user has bless rights on the existing groups
+ # If they don't, then we need to add them back to new_groups
+ foreach my $group (@$current_groups) {
+ if (!$user->can_bless($group->id)) {
+ push @$new_groups, $group unless grep { $_->id eq $group->id } @$new_groups;
+ }
}
- else {
- foreach my $group (@{$changes->{remove} // []}) {
- @$new_groups = grep { $_->id ne $group->id } @$new_groups;
- }
- foreach my $group (@{$changes->{add} // []}) {
- push @$new_groups, $group
- unless grep { $_->id eq $group->id } @$new_groups;
- }
+ }
+ else {
+ foreach my $group (@{$changes->{remove} // []}) {
+ @$new_groups = grep { $_->id ne $group->id } @$new_groups;
}
-
- # Stash the changes, so self->update can actually make them
- my @diffs = diff_arrays($current_groups, $new_groups, 'id');
- if (scalar(@{$diffs[0]}) || scalar(@{$diffs[1]})) {
- $self->{_group_changes}{$is_bless} = \@diffs;
+ foreach my $group (@{$changes->{add} // []}) {
+ push @$new_groups, $group unless grep { $_->id eq $group->id } @$new_groups;
}
+ }
+
+ # Stash the changes, so self->update can actually make them
+ my @diffs = diff_arrays($current_groups, $new_groups, 'id');
+ if (scalar(@{$diffs[0]}) || scalar(@{$diffs[1]})) {
+ $self->{_group_changes}{$is_bless} = \@diffs;
+ }
}
sub _set_groups_to_object {
- my $self = shift;
- my $changes = shift;
- my $user = Bugzilla->user;
-
- foreach my $key (keys %$changes) {
- # Check we were given an array
- unless (ref($changes->{$key}) eq 'ARRAY') {
- ThrowCodeError(
- 'param_invalid',
- { param => $changes->{$key}, function => $key }
- );
- }
+ my $self = shift;
+ my $changes = shift;
+ my $user = Bugzilla->user;
- # Go through the array, and turn items into group objects
- my @groups = ();
- foreach my $value (@{$changes->{$key}}) {
- my $type = $value =~ /^\d+$/ ? 'id' : 'name';
- my $group = Bugzilla::Group->new({$type => $value});
-
- if (! $group || ! $user->can_bless($group->id)) {
- ThrowUserError('auth_failure',
- { group => $value, reason => 'cant_bless',
- action => 'edit', object => 'users' });
- }
- push @groups, $group;
- }
- $changes->{$key} = \@groups;
+ foreach my $key (keys %$changes) {
+
+ # Check we were given an array
+ unless (ref($changes->{$key}) eq 'ARRAY') {
+ ThrowCodeError('param_invalid', {param => $changes->{$key}, function => $key});
+ }
+
+ # Go through the array, and turn items into group objects
+ my @groups = ();
+ foreach my $value (@{$changes->{$key}}) {
+ my $type = $value =~ /^\d+$/ ? 'id' : 'name';
+ my $group = Bugzilla::Group->new({$type => $value});
+
+ if (!$group || !$user->can_bless($group->id)) {
+ ThrowUserError('auth_failure',
+ {group => $value, reason => 'cant_bless', action => 'edit', object => 'users'});
+ }
+ push @groups, $group;
}
+ $changes->{$key} = \@groups;
+ }
- return $changes;
+ return $changes;
}
sub update_last_seen_date {
- my $self = shift;
- return unless $self->id;
- my $dbh = Bugzilla->dbh;
- my $date = $dbh->selectrow_array(
- 'SELECT ' . $dbh->sql_date_format('NOW()', '%Y-%m-%d'));
-
- if (!$self->last_seen_date or $date ne $self->last_seen_date) {
- $self->{last_seen_date} = $date;
- # We don't use the normal update() routine here as we only
- # want to update the last_seen_date column, not any other
- # pending changes
- $dbh->do("UPDATE profiles SET last_seen_date = ? WHERE userid = ?",
- undef, $date, $self->id);
- Bugzilla->memcached->clear({ table => 'profiles', id => $self->id });
- }
+ my $self = shift;
+ return unless $self->id;
+ my $dbh = Bugzilla->dbh;
+ my $date = $dbh->selectrow_array(
+ 'SELECT ' . $dbh->sql_date_format('NOW()', '%Y-%m-%d'));
+
+ if (!$self->last_seen_date or $date ne $self->last_seen_date) {
+ $self->{last_seen_date} = $date;
+
+ # We don't use the normal update() routine here as we only
+ # want to update the last_seen_date column, not any other
+ # pending changes
+ $dbh->do("UPDATE profiles SET last_seen_date = ? WHERE userid = ?",
+ undef, $date, $self->id);
+ Bugzilla->memcached->clear({table => 'profiles', id => $self->id});
+ }
}
################################################################################
@@ -467,171 +464,181 @@ sub update_last_seen_date {
################################################################################
# Accessors for user attributes
-sub name { $_[0]->{realname}; }
-sub login { $_[0]->{login_name}; }
-sub extern_id { $_[0]->{extern_id}; }
-sub email { $_[0]->login . Bugzilla->params->{'emailsuffix'}; }
-sub disabledtext { $_[0]->{'disabledtext'}; }
-sub is_enabled { $_[0]->{'is_enabled'} ? 1 : 0; }
+sub name { $_[0]->{realname}; }
+sub login { $_[0]->{login_name}; }
+sub extern_id { $_[0]->{extern_id}; }
+sub email { $_[0]->login . Bugzilla->params->{'emailsuffix'}; }
+sub disabledtext { $_[0]->{'disabledtext'}; }
+sub is_enabled { $_[0]->{'is_enabled'} ? 1 : 0; }
sub showmybugslink { $_[0]->{showmybugslink}; }
sub email_disabled { $_[0]->{disable_mail}; }
-sub email_enabled { !($_[0]->{disable_mail}); }
+sub email_enabled { !($_[0]->{disable_mail}); }
sub last_seen_date { $_[0]->{last_seen_date}; }
+
sub cryptpassword {
- my $self = shift;
- # We don't store it because we never want it in the object (we
- # don't want to accidentally dump even the hash somewhere).
- my ($pw) = Bugzilla->dbh->selectrow_array(
- 'SELECT cryptpassword FROM profiles WHERE userid = ?',
- undef, $self->id);
- return $pw;
+ my $self = shift;
+
+ # We don't store it because we never want it in the object (we
+ # don't want to accidentally dump even the hash somewhere).
+ my ($pw)
+ = Bugzilla->dbh->selectrow_array(
+ 'SELECT cryptpassword FROM profiles WHERE userid = ?',
+ undef, $self->id);
+ return $pw;
}
sub set_authorizer {
- my ($self, $authorizer) = @_;
- $self->{authorizer} = $authorizer;
+ my ($self, $authorizer) = @_;
+ $self->{authorizer} = $authorizer;
}
+
sub authorizer {
- my ($self) = @_;
- if (!$self->{authorizer}) {
- require Bugzilla::Auth;
- $self->{authorizer} = new Bugzilla::Auth();
- }
- return $self->{authorizer};
+ my ($self) = @_;
+ if (!$self->{authorizer}) {
+ require Bugzilla::Auth;
+ $self->{authorizer} = new Bugzilla::Auth();
+ }
+ return $self->{authorizer};
}
# Generate a string to identify the user by name + login if the user
# has a name or by login only if they don't.
sub identity {
- my $self = shift;
+ my $self = shift;
- return "" unless $self->id;
+ return "" unless $self->id;
- if (!defined $self->{identity}) {
- $self->{identity} =
- $self->name ? $self->name . " <" . $self->login. ">" : $self->login;
- }
+ if (!defined $self->{identity}) {
+ $self->{identity}
+ = $self->name ? $self->name . " <" . $self->login . ">" : $self->login;
+ }
- return $self->{identity};
+ return $self->{identity};
}
sub nick {
- my $self = shift;
+ my $self = shift;
- return "" unless $self->id;
+ return "" unless $self->id;
- if (!defined $self->{nick}) {
- $self->{nick} = (split(/@/, $self->login, 2))[0];
- }
+ if (!defined $self->{nick}) {
+ $self->{nick} = (split(/@/, $self->login, 2))[0];
+ }
- return $self->{nick};
+ return $self->{nick};
}
sub queries {
- my $self = shift;
- return $self->{queries} if defined $self->{queries};
- return [] unless $self->id;
+ my $self = shift;
+ return $self->{queries} if defined $self->{queries};
+ return [] unless $self->id;
- my $dbh = Bugzilla->dbh;
- my $query_ids = $dbh->selectcol_arrayref(
- 'SELECT id FROM namedqueries WHERE userid = ?', undef, $self->id);
- require Bugzilla::Search::Saved;
- $self->{queries} = Bugzilla::Search::Saved->new_from_list($query_ids);
+ my $dbh = Bugzilla->dbh;
+ my $query_ids
+ = $dbh->selectcol_arrayref('SELECT id FROM namedqueries WHERE userid = ?',
+ undef, $self->id);
+ require Bugzilla::Search::Saved;
+ $self->{queries} = Bugzilla::Search::Saved->new_from_list($query_ids);
- # We preload link_in_footer from here as this information is always requested.
- # This only works if the user object represents the current logged in user.
- Bugzilla::Search::Saved::preload($self->{queries}) if $self->id == Bugzilla->user->id;
+ # We preload link_in_footer from here as this information is always requested.
+ # This only works if the user object represents the current logged in user.
+ Bugzilla::Search::Saved::preload($self->{queries})
+ if $self->id == Bugzilla->user->id;
- return $self->{queries};
+ return $self->{queries};
}
sub queries_subscribed {
- my $self = shift;
- return $self->{queries_subscribed} if defined $self->{queries_subscribed};
- return [] unless $self->id;
-
- # Exclude the user's own queries.
- my @my_query_ids = map($_->id, @{$self->queries});
- my $query_id_string = join(',', @my_query_ids) || '-1';
-
- # Only show subscriptions that we can still actually see. If a
- # user changes the shared group of a query, our subscription
- # will remain but we won't have access to the query anymore.
- my $subscribed_query_ids = Bugzilla->dbh->selectcol_arrayref(
- "SELECT lif.namedquery_id
+ my $self = shift;
+ return $self->{queries_subscribed} if defined $self->{queries_subscribed};
+ return [] unless $self->id;
+
+ # Exclude the user's own queries.
+ my @my_query_ids = map($_->id, @{$self->queries});
+ my $query_id_string = join(',', @my_query_ids) || '-1';
+
+ # Only show subscriptions that we can still actually see. If a
+ # user changes the shared group of a query, our subscription
+ # will remain but we won't have access to the query anymore.
+ my $subscribed_query_ids = Bugzilla->dbh->selectcol_arrayref(
+ "SELECT lif.namedquery_id
FROM namedqueries_link_in_footer lif
INNER JOIN namedquery_group_map ngm
ON ngm.namedquery_id = lif.namedquery_id
WHERE lif.user_id = ?
AND lif.namedquery_id NOT IN ($query_id_string)
- AND " . $self->groups_in_sql,
- undef, $self->id);
- require Bugzilla::Search::Saved;
- $self->{queries_subscribed} =
- Bugzilla::Search::Saved->new_from_list($subscribed_query_ids);
- return $self->{queries_subscribed};
+ AND " . $self->groups_in_sql, undef, $self->id
+ );
+ require Bugzilla::Search::Saved;
+ $self->{queries_subscribed}
+ = Bugzilla::Search::Saved->new_from_list($subscribed_query_ids);
+ return $self->{queries_subscribed};
}
sub queries_available {
- my $self = shift;
- return $self->{queries_available} if defined $self->{queries_available};
- return [] unless $self->id;
+ my $self = shift;
+ return $self->{queries_available} if defined $self->{queries_available};
+ return [] unless $self->id;
- # Exclude the user's own queries.
- my @my_query_ids = map($_->id, @{$self->queries});
- my $query_id_string = join(',', @my_query_ids) || '-1';
+ # Exclude the user's own queries.
+ my @my_query_ids = map($_->id, @{$self->queries});
+ my $query_id_string = join(',', @my_query_ids) || '-1';
- my $avail_query_ids = Bugzilla->dbh->selectcol_arrayref(
- 'SELECT namedquery_id FROM namedquery_group_map
- WHERE ' . $self->groups_in_sql . "
- AND namedquery_id NOT IN ($query_id_string)");
- require Bugzilla::Search::Saved;
- $self->{queries_available} =
- Bugzilla::Search::Saved->new_from_list($avail_query_ids);
- return $self->{queries_available};
+ my $avail_query_ids = Bugzilla->dbh->selectcol_arrayref(
+ 'SELECT namedquery_id FROM namedquery_group_map
+ WHERE ' . $self->groups_in_sql . "
+ AND namedquery_id NOT IN ($query_id_string)"
+ );
+ require Bugzilla::Search::Saved;
+ $self->{queries_available}
+ = Bugzilla::Search::Saved->new_from_list($avail_query_ids);
+ return $self->{queries_available};
}
sub tags {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- if (!defined $self->{tags}) {
- # We must use LEFT JOIN instead of INNER JOIN as we may be
- # in the process of inserting a new tag to some bugs,
- # in which case there are no bugs with this tag yet.
- $self->{tags} = $dbh->selectall_hashref(
- 'SELECT name, id, COUNT(bug_id) AS bug_count
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{tags}) {
+
+ # We must use LEFT JOIN instead of INNER JOIN as we may be
+ # in the process of inserting a new tag to some bugs,
+ # in which case there are no bugs with this tag yet.
+ $self->{tags} = $dbh->selectall_hashref(
+ 'SELECT name, id, COUNT(bug_id) AS bug_count
FROM tag
LEFT JOIN bug_tag ON bug_tag.tag_id = tag.id
- WHERE user_id = ? ' . $dbh->sql_group_by('id', 'name'),
- 'name', undef, $self->id);
- }
- return $self->{tags};
+ WHERE user_id = ? ' . $dbh->sql_group_by('id', 'name'), 'name', undef,
+ $self->id
+ );
+ }
+ return $self->{tags};
}
sub bugs_ignored {
- my ($self) = @_;
- my $dbh = Bugzilla->dbh;
- if (!defined $self->{'bugs_ignored'}) {
- $self->{'bugs_ignored'} = $dbh->selectall_arrayref(
- 'SELECT bugs.bug_id AS id,
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ if (!defined $self->{'bugs_ignored'}) {
+ $self->{'bugs_ignored'} = $dbh->selectall_arrayref(
+ 'SELECT bugs.bug_id AS id,
bugs.bug_status AS status,
bugs.short_desc AS summary
FROM bugs
INNER JOIN email_bug_ignore
ON bugs.bug_id = email_bug_ignore.bug_id
- WHERE user_id = ?',
- { Slice => {} }, $self->id);
- # Go ahead and load these into the visible bugs cache
- # to speed up can_see_bug checks later
- $self->visible_bugs([ map { $_->{'id'} } @{ $self->{'bugs_ignored'} } ]);
- }
- return $self->{'bugs_ignored'};
+ WHERE user_id = ?', {Slice => {}}, $self->id
+ );
+
+ # Go ahead and load these into the visible bugs cache
+ # to speed up can_see_bug checks later
+ $self->visible_bugs([map { $_->{'id'} } @{$self->{'bugs_ignored'}}]);
+ }
+ return $self->{'bugs_ignored'};
}
sub is_bug_ignored {
- my ($self, $bug_id) = @_;
- return (grep {$_->{'id'} == $bug_id} @{$self->bugs_ignored}) ? 1 : 0;
+ my ($self, $bug_id) = @_;
+ return (grep { $_->{'id'} == $bug_id } @{$self->bugs_ignored}) ? 1 : 0;
}
##########################
@@ -639,309 +646,312 @@ sub is_bug_ignored {
##########################
sub recent_searches {
- my $self = shift;
- $self->{recent_searches} ||=
- Bugzilla::Search::Recent->match({ user_id => $self->id });
- return $self->{recent_searches};
+ my $self = shift;
+ $self->{recent_searches}
+ ||= Bugzilla::Search::Recent->match({user_id => $self->id});
+ return $self->{recent_searches};
}
sub recent_search_containing {
- my ($self, $bug_id) = @_;
- my $searches = $self->recent_searches;
+ my ($self, $bug_id) = @_;
+ my $searches = $self->recent_searches;
- foreach my $search (@$searches) {
- return $search if grep($_ == $bug_id, @{ $search->bug_list });
- }
+ foreach my $search (@$searches) {
+ return $search if grep($_ == $bug_id, @{$search->bug_list});
+ }
- return undef;
+ return undef;
}
sub recent_search_for {
- my ($self, $bug) = @_;
- my $params = Bugzilla->input_params;
- my $cgi = Bugzilla->cgi;
-
- if ($self->id) {
- # First see if there's a list_id parameter in the query string.
- my $list_id = $params->{list_id};
- if (!$list_id) {
- # If not, check for "list_id" in the query string of the referer.
- my $referer = $cgi->referer;
- if ($referer) {
- my $uri = URI->new($referer);
- if ($uri->path =~ /buglist\.cgi$/) {
- $list_id = $uri->query_param('list_id')
- || $uri->query_param('regetlastlist');
- }
- }
+ my ($self, $bug) = @_;
+ my $params = Bugzilla->input_params;
+ my $cgi = Bugzilla->cgi;
+
+ if ($self->id) {
+
+ # First see if there's a list_id parameter in the query string.
+ my $list_id = $params->{list_id};
+ if (!$list_id) {
+
+ # If not, check for "list_id" in the query string of the referer.
+ my $referer = $cgi->referer;
+ if ($referer) {
+ my $uri = URI->new($referer);
+ if ($uri->path =~ /buglist\.cgi$/) {
+ $list_id = $uri->query_param('list_id') || $uri->query_param('regetlastlist');
}
+ }
+ }
- if ($list_id && $list_id ne 'cookie') {
- # If we got a bad list_id (either some other user's or an expired
- # one) don't crash, just don't return that list.
- my $search = Bugzilla::Search::Recent->check_quietly(
- { id => $list_id });
- return $search if $search;
- }
+ if ($list_id && $list_id ne 'cookie') {
- # If there's no list_id, see if the current bug's id is contained
- # in any of the user's saved lists.
- my $search = $self->recent_search_containing($bug->id);
- return $search if $search;
+ # If we got a bad list_id (either some other user's or an expired
+ # one) don't crash, just don't return that list.
+ my $search = Bugzilla::Search::Recent->check_quietly({id => $list_id});
+ return $search if $search;
}
- # Finally (or always, if we're logged out), if there's a BUGLIST cookie
- # and the selected bug is in the list, then return the cookie as a fake
- # Search::Recent object.
- if (my $list = $cgi->cookie('BUGLIST')) {
- # Also split on colons, which was used as a separator in old cookies.
- my @bug_ids = split(/[:-]/, $list);
- if (grep { $_ == $bug->id } @bug_ids) {
- my $search = Bugzilla::Search::Recent->new_from_cookie(\@bug_ids);
- return $search;
- }
+ # If there's no list_id, see if the current bug's id is contained
+ # in any of the user's saved lists.
+ my $search = $self->recent_search_containing($bug->id);
+ return $search if $search;
+ }
+
+ # Finally (or always, if we're logged out), if there's a BUGLIST cookie
+ # and the selected bug is in the list, then return the cookie as a fake
+ # Search::Recent object.
+ if (my $list = $cgi->cookie('BUGLIST')) {
+
+ # Also split on colons, which was used as a separator in old cookies.
+ my @bug_ids = split(/[:-]/, $list);
+ if (grep { $_ == $bug->id } @bug_ids) {
+ my $search = Bugzilla::Search::Recent->new_from_cookie(\@bug_ids);
+ return $search;
}
+ }
- return undef;
+ return undef;
}
sub save_last_search {
- my ($self, $params) = @_;
- my ($bug_ids, $order, $vars, $list_id) =
- @$params{qw(bugs order vars list_id)};
-
- my $cgi = Bugzilla->cgi;
- if ($order) {
- $cgi->send_cookie(-name => 'LASTORDER',
- -value => $order,
- -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
- }
+ my ($self, $params) = @_;
+ my ($bug_ids, $order, $vars, $list_id) = @$params{qw(bugs order vars list_id)};
+
+ my $cgi = Bugzilla->cgi;
+ if ($order) {
+ $cgi->send_cookie(
+ -name => 'LASTORDER',
+ -value => $order,
+ -expires => 'Fri, 01-Jan-2038 00:00:00 GMT'
+ );
+ }
- return if !@$bug_ids;
-
- my $search;
- if ($self->id) {
- on_main_db {
- if ($list_id) {
- $search = Bugzilla::Search::Recent->check_quietly({ id => $list_id });
- }
-
- if ($search) {
- if (join(',', @{$search->bug_list}) ne join(',', @$bug_ids)) {
- $search->set_bug_list($bug_ids);
- }
- if (!$search->list_order || $order ne $search->list_order) {
- $search->set_list_order($order);
- }
- $search->update();
- }
- else {
- # If we already have an existing search with a totally
- # identical bug list, then don't create a new one. This
- # prevents people from writing over their whole
- # recent-search list by just refreshing a saved search
- # (which doesn't have list_id in the header) over and over.
- my $list_string = join(',', @$bug_ids);
- my $existing_search = Bugzilla::Search::Recent->match({
- user_id => $self->id, bug_list => $list_string });
-
- if (!scalar(@$existing_search)) {
- $search = Bugzilla::Search::Recent->create({
- user_id => $self->id,
- bug_list => $bug_ids,
- list_order => $order });
- }
- else {
- $search = $existing_search->[0];
- }
- }
- };
- delete $self->{recent_searches};
- }
- # Logged-out users use a cookie to store a single last search. We don't
- # override that cookie with the logged-in user's latest search, because
- # if they did one search while logged out and another while logged in,
- # they may still want to navigate through the search they made while
- # logged out.
- else {
- my $bug_list = join('-', @$bug_ids);
- if (length($bug_list) < 4000) {
- $cgi->send_cookie(-name => 'BUGLIST',
- -value => $bug_list,
- -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
+ return if !@$bug_ids;
+
+ my $search;
+ if ($self->id) {
+ on_main_db {
+ if ($list_id) {
+ $search = Bugzilla::Search::Recent->check_quietly({id => $list_id});
+ }
+
+ if ($search) {
+ if (join(',', @{$search->bug_list}) ne join(',', @$bug_ids)) {
+ $search->set_bug_list($bug_ids);
+ }
+ if (!$search->list_order || $order ne $search->list_order) {
+ $search->set_list_order($order);
+ }
+ $search->update();
+ }
+ else {
+ # If we already have an existing search with a totally
+ # identical bug list, then don't create a new one. This
+ # prevents people from writing over their whole
+ # recent-search list by just refreshing a saved search
+ # (which doesn't have list_id in the header) over and over.
+ my $list_string = join(',', @$bug_ids);
+ my $existing_search = Bugzilla::Search::Recent->match(
+ {user_id => $self->id, bug_list => $list_string});
+
+ if (!scalar(@$existing_search)) {
+ $search = Bugzilla::Search::Recent->create(
+ {user_id => $self->id, bug_list => $bug_ids, list_order => $order});
}
else {
- $cgi->remove_cookie('BUGLIST');
- $vars->{'toolong'} = 1;
+ $search = $existing_search->[0];
}
+ }
+ };
+ delete $self->{recent_searches};
+ }
+
+ # Logged-out users use a cookie to store a single last search. We don't
+ # override that cookie with the logged-in user's latest search, because
+ # if they did one search while logged out and another while logged in,
+ # they may still want to navigate through the search they made while
+ # logged out.
+ else {
+ my $bug_list = join('-', @$bug_ids);
+ if (length($bug_list) < 4000) {
+ $cgi->send_cookie(
+ -name => 'BUGLIST',
+ -value => $bug_list,
+ -expires => 'Fri, 01-Jan-2038 00:00:00 GMT'
+ );
}
- return $search;
+ else {
+ $cgi->remove_cookie('BUGLIST');
+ $vars->{'toolong'} = 1;
+ }
+ }
+ return $search;
}
sub reports {
- my $self = shift;
- return $self->{reports} if defined $self->{reports};
- return [] unless $self->id;
+ my $self = shift;
+ return $self->{reports} if defined $self->{reports};
+ return [] unless $self->id;
- my $dbh = Bugzilla->dbh;
- my $report_ids = $dbh->selectcol_arrayref(
- 'SELECT id FROM reports WHERE user_id = ?', undef, $self->id);
- require Bugzilla::Report;
- $self->{reports} = Bugzilla::Report->new_from_list($report_ids);
- return $self->{reports};
+ my $dbh = Bugzilla->dbh;
+ my $report_ids
+ = $dbh->selectcol_arrayref('SELECT id FROM reports WHERE user_id = ?',
+ undef, $self->id);
+ require Bugzilla::Report;
+ $self->{reports} = Bugzilla::Report->new_from_list($report_ids);
+ return $self->{reports};
}
sub flush_reports_cache {
- my $self = shift;
+ my $self = shift;
- delete $self->{reports};
+ delete $self->{reports};
}
sub settings {
- my ($self) = @_;
+ my ($self) = @_;
- return $self->{'settings'} if (defined $self->{'settings'});
+ return $self->{'settings'} if (defined $self->{'settings'});
- # IF the user is logged in
- # THEN get the user's settings
- # ELSE get default settings
- if ($self->id) {
- $self->{'settings'} = get_all_settings($self->id);
- } else {
- $self->{'settings'} = get_defaults();
- }
+ # IF the user is logged in
+ # THEN get the user's settings
+ # ELSE get default settings
+ if ($self->id) {
+ $self->{'settings'} = get_all_settings($self->id);
+ }
+ else {
+ $self->{'settings'} = get_defaults();
+ }
- return $self->{'settings'};
+ return $self->{'settings'};
}
sub setting {
- my ($self, $name) = @_;
- return $self->settings->{$name}->{'value'};
+ my ($self, $name) = @_;
+ return $self->settings->{$name}->{'value'};
}
sub timezone {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{timezone}) {
- my $tz = $self->setting('timezone');
- if ($tz eq 'local') {
- # The user wants the local timezone of the server.
- $self->{timezone} = Bugzilla->local_timezone;
- }
- else {
- $self->{timezone} = DateTime::TimeZone->new(name => $tz);
- }
+ if (!defined $self->{timezone}) {
+ my $tz = $self->setting('timezone');
+ if ($tz eq 'local') {
+
+ # The user wants the local timezone of the server.
+ $self->{timezone} = Bugzilla->local_timezone;
}
- return $self->{timezone};
+ else {
+ $self->{timezone} = DateTime::TimeZone->new(name => $tz);
+ }
+ }
+ return $self->{timezone};
}
sub flush_queries_cache {
- my $self = shift;
+ my $self = shift;
- delete $self->{queries};
- delete $self->{queries_subscribed};
- delete $self->{queries_available};
+ delete $self->{queries};
+ delete $self->{queries_subscribed};
+ delete $self->{queries_available};
}
sub groups {
- my $self = shift;
+ my $self = shift;
- return $self->{groups} if defined $self->{groups};
- return [] unless $self->id;
+ return $self->{groups} if defined $self->{groups};
+ return [] unless $self->id;
- my $user_groups_key = "user_groups." . $self->id;
- my $groups = Bugzilla->memcached->get_config({
- key => $user_groups_key
- });
+ my $user_groups_key = "user_groups." . $self->id;
+ my $groups = Bugzilla->memcached->get_config({key => $user_groups_key});
- if (!$groups) {
- my $dbh = Bugzilla->dbh;
- my $groups_to_check = $dbh->selectcol_arrayref(
- "SELECT DISTINCT group_id
+ if (!$groups) {
+ my $dbh = Bugzilla->dbh;
+ my $groups_to_check = $dbh->selectcol_arrayref(
+ "SELECT DISTINCT group_id
FROM user_group_map
- WHERE user_id = ? AND isbless = 0", undef, $self->id);
-
- my $grant_type_key = 'group_grant_type_' . GROUP_MEMBERSHIP;
- my $membership_rows = Bugzilla->memcached->get_config({
- key => $grant_type_key,
- });
- if (!$membership_rows) {
- $membership_rows = $dbh->selectall_arrayref(
- "SELECT DISTINCT grantor_id, member_id
+ WHERE user_id = ? AND isbless = 0", undef, $self->id
+ );
+
+ my $grant_type_key = 'group_grant_type_' . GROUP_MEMBERSHIP;
+ my $membership_rows
+ = Bugzilla->memcached->get_config({key => $grant_type_key,});
+ if (!$membership_rows) {
+ $membership_rows = $dbh->selectall_arrayref(
+ "SELECT DISTINCT grantor_id, member_id
FROM group_group_map
- WHERE grant_type = " . GROUP_MEMBERSHIP);
- Bugzilla->memcached->set_config({
- key => $grant_type_key,
- data => $membership_rows,
- });
- }
+ WHERE grant_type = " . GROUP_MEMBERSHIP
+ );
+ Bugzilla->memcached->set_config(
+ {key => $grant_type_key, data => $membership_rows,});
+ }
- my %group_membership;
- foreach my $row (@$membership_rows) {
- my ($grantor_id, $member_id) = @$row;
- push (@{ $group_membership{$member_id} }, $grantor_id);
- }
+ my %group_membership;
+ foreach my $row (@$membership_rows) {
+ my ($grantor_id, $member_id) = @$row;
+ push(@{$group_membership{$member_id}}, $grantor_id);
+ }
- # Let's walk the groups hierarchy tree (using FIFO)
- # On the first iteration it's pre-filled with direct groups
- # membership. Later on, each group can add its own members into the
- # FIFO. Circular dependencies are eliminated by checking
- # $checked_groups{$member_id} hash values.
- # As a result, %groups will have all the groups we are the member of.
- my %checked_groups;
- my %groups;
- while (scalar(@$groups_to_check) > 0) {
- # Pop the head group from FIFO
- my $member_id = shift @$groups_to_check;
-
- # Skip the group if we have already checked it
- if (!$checked_groups{$member_id}) {
- # Mark group as checked
- $checked_groups{$member_id} = 1;
-
- # Add all its members to the FIFO check list
- # %group_membership contains arrays of group members
- # for all groups. Accessible by group number.
- my $members = $group_membership{$member_id};
- my @new_to_check = grep(!$checked_groups{$_}, @$members);
- push(@$groups_to_check, @new_to_check);
-
- $groups{$member_id} = 1;
- }
- }
- $groups = [ keys %groups ];
+ # Let's walk the groups hierarchy tree (using FIFO)
+ # On the first iteration it's pre-filled with direct groups
+ # membership. Later on, each group can add its own members into the
+ # FIFO. Circular dependencies are eliminated by checking
+ # $checked_groups{$member_id} hash values.
+ # As a result, %groups will have all the groups we are the member of.
+ my %checked_groups;
+ my %groups;
+ while (scalar(@$groups_to_check) > 0) {
- Bugzilla->memcached->set_config({
- key => $user_groups_key,
- data => $groups,
- });
+ # Pop the head group from FIFO
+ my $member_id = shift @$groups_to_check;
+
+ # Skip the group if we have already checked it
+ if (!$checked_groups{$member_id}) {
+
+ # Mark group as checked
+ $checked_groups{$member_id} = 1;
+
+ # Add all its members to the FIFO check list
+ # %group_membership contains arrays of group members
+ # for all groups. Accessible by group number.
+ my $members = $group_membership{$member_id};
+ my @new_to_check = grep(!$checked_groups{$_}, @$members);
+ push(@$groups_to_check, @new_to_check);
+
+ $groups{$member_id} = 1;
+ }
}
+ $groups = [keys %groups];
+
+ Bugzilla->memcached->set_config({key => $user_groups_key, data => $groups,});
+ }
- $self->{groups} = Bugzilla::Group->new_from_list($groups);
- return $self->{groups};
+ $self->{groups} = Bugzilla::Group->new_from_list($groups);
+ return $self->{groups};
}
sub last_visited {
- my ($self, $ids) = @_;
+ my ($self, $ids) = @_;
- return Bugzilla::BugUserLastVisit->match({ user_id => $self->id,
- $ids ? ( bug_id => $ids ) : () });
+ return Bugzilla::BugUserLastVisit->match(
+ {user_id => $self->id, $ids ? (bug_id => $ids) : ()});
}
sub is_involved_in_bug {
- my ($self, $bug) = @_;
- my $user_id = $self->id;
- my $user_login = $self->login;
+ my ($self, $bug) = @_;
+ my $user_id = $self->id;
+ my $user_login = $self->login;
- return unless $user_id;
- return 1 if $user_id == $bug->assigned_to->id;
- return 1 if $user_id == $bug->reporter->id;
+ return unless $user_id;
+ return 1 if $user_id == $bug->assigned_to->id;
+ return 1 if $user_id == $bug->reporter->id;
- if (Bugzilla->params->{'useqacontact'} and $bug->qa_contact) {
- return 1 if $user_id == $bug->qa_contact->id;
- }
+ if (Bugzilla->params->{'useqacontact'} and $bug->qa_contact) {
+ return 1 if $user_id == $bug->qa_contact->id;
+ }
- return any { $user_login eq $_ } @{ $bug->cc };
+ return any { $user_login eq $_ } @{$bug->cc};
}
# It turns out that calling ->id on objects a few hundred thousand
@@ -949,296 +959,311 @@ sub is_involved_in_bug {
# when profiling xt/search.t.) So we cache the group ids separately from
# groups for functions that need the group ids.
sub _group_ids {
- my ($self) = @_;
- $self->{group_ids} ||= [map { $_->id } @{ $self->groups }];
- return $self->{group_ids};
+ my ($self) = @_;
+ $self->{group_ids} ||= [map { $_->id } @{$self->groups}];
+ return $self->{group_ids};
}
sub groups_as_string {
- my $self = shift;
- my $ids = $self->_group_ids;
- return scalar(@$ids) ? join(',', @$ids) : '-1';
+ my $self = shift;
+ my $ids = $self->_group_ids;
+ return scalar(@$ids) ? join(',', @$ids) : '-1';
}
sub groups_in_sql {
- my ($self, $field) = @_;
- $field ||= 'group_id';
- my $ids = $self->_group_ids;
- $ids = [-1] if !scalar @$ids;
- return Bugzilla->dbh->sql_in($field, $ids);
+ my ($self, $field) = @_;
+ $field ||= 'group_id';
+ my $ids = $self->_group_ids;
+ $ids = [-1] if !scalar @$ids;
+ return Bugzilla->dbh->sql_in($field, $ids);
}
sub bless_groups {
- my $self = shift;
+ my $self = shift;
- return $self->{'bless_groups'} if defined $self->{'bless_groups'};
- return [] unless $self->id;
+ return $self->{'bless_groups'} if defined $self->{'bless_groups'};
+ return [] unless $self->id;
- if ($self->in_group('editusers')) {
- # Users having editusers permissions may bless all groups.
- $self->{'bless_groups'} = [Bugzilla::Group->get_all];
- return $self->{'bless_groups'};
- }
+ if ($self->in_group('editusers')) {
- if (Bugzilla->params->{usevisibilitygroups}
- && !@{ $self->visible_groups_inherited }) {
- return [];
- }
+ # Users having editusers permissions may bless all groups.
+ $self->{'bless_groups'} = [Bugzilla::Group->get_all];
+ return $self->{'bless_groups'};
+ }
- my $dbh = Bugzilla->dbh;
+ if (Bugzilla->params->{usevisibilitygroups}
+ && !@{$self->visible_groups_inherited})
+ {
+ return [];
+ }
- # Get all groups for the user where they have direct bless privileges.
- my $query = "
+ my $dbh = Bugzilla->dbh;
+
+ # Get all groups for the user where they have direct bless privileges.
+ my $query = "
SELECT DISTINCT group_id
FROM user_group_map
WHERE user_id = ?
AND isbless = 1";
- if (Bugzilla->params->{usevisibilitygroups}) {
- $query .= " AND "
- . $dbh->sql_in('group_id', $self->visible_groups_inherited);
- }
-
- # Get all groups for the user where they are a member of a group that
- # inherits bless privs.
- my @group_ids = map { $_->id } @{ $self->groups };
- if (@group_ids) {
- $query .= "
+ if (Bugzilla->params->{usevisibilitygroups}) {
+ $query .= " AND " . $dbh->sql_in('group_id', $self->visible_groups_inherited);
+ }
+
+ # Get all groups for the user where they are a member of a group that
+ # inherits bless privs.
+ my @group_ids = map { $_->id } @{$self->groups};
+ if (@group_ids) {
+ $query .= "
UNION
SELECT DISTINCT grantor_id
FROM group_group_map
WHERE grant_type = " . GROUP_BLESS . "
AND " . $dbh->sql_in('member_id', \@group_ids);
- if (Bugzilla->params->{usevisibilitygroups}) {
- $query .= " AND "
- . $dbh->sql_in('grantor_id', $self->visible_groups_inherited);
- }
+ if (Bugzilla->params->{usevisibilitygroups}) {
+ $query .= " AND " . $dbh->sql_in('grantor_id', $self->visible_groups_inherited);
}
+ }
- my $ids = $dbh->selectcol_arrayref($query, undef, $self->id);
- return $self->{bless_groups} = Bugzilla::Group->new_from_list($ids);
+ my $ids = $dbh->selectcol_arrayref($query, undef, $self->id);
+ return $self->{bless_groups} = Bugzilla::Group->new_from_list($ids);
}
sub in_group {
- my ($self, $group, $product_id) = @_;
- $group = $group->name if blessed $group;
- if (scalar grep($_->name eq $group, @{ $self->groups })) {
- return 1;
- }
- elsif ($product_id && detaint_natural($product_id)) {
- # Make sure $group exists on a per-product basis.
- return 0 unless (grep {$_ eq $group} PER_PRODUCT_PRIVILEGES);
-
- $self->{"product_$product_id"} = {} unless exists $self->{"product_$product_id"};
- if (!defined $self->{"product_$product_id"}->{$group}) {
- my $dbh = Bugzilla->dbh;
- my $in_group = $dbh->selectrow_array(
- "SELECT 1
+ my ($self, $group, $product_id) = @_;
+ $group = $group->name if blessed $group;
+ if (scalar grep($_->name eq $group, @{$self->groups})) {
+ return 1;
+ }
+ elsif ($product_id && detaint_natural($product_id)) {
+
+ # Make sure $group exists on a per-product basis.
+ return 0 unless (grep { $_ eq $group } PER_PRODUCT_PRIVILEGES);
+
+ $self->{"product_$product_id"} = {}
+ unless exists $self->{"product_$product_id"};
+ if (!defined $self->{"product_$product_id"}->{$group}) {
+ my $dbh = Bugzilla->dbh;
+ my $in_group = $dbh->selectrow_array(
+ "SELECT 1
FROM group_control_map
WHERE product_id = ?
AND $group != 0
- AND " . $self->groups_in_sql . ' ' .
- $dbh->sql_limit(1),
- undef, $product_id);
+ AND "
+ . $self->groups_in_sql . ' ' . $dbh->sql_limit(1), undef, $product_id
+ );
- $self->{"product_$product_id"}->{$group} = $in_group ? 1 : 0;
- }
- return $self->{"product_$product_id"}->{$group};
+ $self->{"product_$product_id"}->{$group} = $in_group ? 1 : 0;
}
- # If we come here, then the user is not in the requested group.
- return 0;
+ return $self->{"product_$product_id"}->{$group};
+ }
+
+ # If we come here, then the user is not in the requested group.
+ return 0;
}
sub in_group_id {
- my ($self, $id) = @_;
- return grep($_->id == $id, @{ $self->groups }) ? 1 : 0;
+ my ($self, $id) = @_;
+ return grep($_->id == $id, @{$self->groups}) ? 1 : 0;
}
# This is a helper to get all groups which have an icon to be displayed
# besides the name of the commenter.
sub groups_with_icon {
- my $self = shift;
+ my $self = shift;
- return $self->{groups_with_icon} //= [grep { $_->icon_url } @{ $self->groups }];
+ return $self->{groups_with_icon} //= [grep { $_->icon_url } @{$self->groups}];
}
sub get_products_by_permission {
- my ($self, $group) = @_;
- # Make sure $group exists on a per-product basis.
- return [] unless (grep {$_ eq $group} PER_PRODUCT_PRIVILEGES);
+ my ($self, $group) = @_;
- my $product_ids = Bugzilla->dbh->selectcol_arrayref(
- "SELECT DISTINCT product_id
+ # Make sure $group exists on a per-product basis.
+ return [] unless (grep { $_ eq $group } PER_PRODUCT_PRIVILEGES);
+
+ my $product_ids = Bugzilla->dbh->selectcol_arrayref(
+ "SELECT DISTINCT product_id
FROM group_control_map
WHERE $group != 0
- AND " . $self->groups_in_sql);
+ AND " . $self->groups_in_sql
+ );
- # No need to go further if the user has no "special" privs.
- return [] unless scalar(@$product_ids);
- my %product_map = map { $_ => 1 } @$product_ids;
+ # No need to go further if the user has no "special" privs.
+ return [] unless scalar(@$product_ids);
+ my %product_map = map { $_ => 1 } @$product_ids;
- # We will restrict the list to products the user can see.
- my $selectable_products = $self->get_selectable_products;
- my @products = grep { $product_map{$_->id} } @$selectable_products;
- return \@products;
+ # We will restrict the list to products the user can see.
+ my $selectable_products = $self->get_selectable_products;
+ my @products = grep { $product_map{$_->id} } @$selectable_products;
+ return \@products;
}
sub can_see_user {
- my ($self, $otherUser) = @_;
- my $query;
+ my ($self, $otherUser) = @_;
+ my $query;
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- # If the user can see no groups, then no users are visible either.
- my $visibleGroups = $self->visible_groups_as_string() || return 0;
- $query = qq{SELECT COUNT(DISTINCT userid)
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+
+ # If the user can see no groups, then no users are visible either.
+ my $visibleGroups = $self->visible_groups_as_string() || return 0;
+ $query = qq{SELECT COUNT(DISTINCT userid)
FROM profiles, user_group_map
WHERE userid = ?
AND user_id = userid
AND isbless = 0
AND group_id IN ($visibleGroups)
};
- } else {
- $query = qq{SELECT COUNT(userid)
+ }
+ else {
+ $query = qq{SELECT COUNT(userid)
FROM profiles
WHERE userid = ?
};
- }
- return Bugzilla->dbh->selectrow_array($query, undef, $otherUser->id);
+ }
+ return Bugzilla->dbh->selectrow_array($query, undef, $otherUser->id);
}
sub can_edit_product {
- my ($self, $prod_id) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($self, $prod_id) = @_;
+ my $dbh = Bugzilla->dbh;
- if (Bugzilla->params->{'or_groups'}) {
- my $groups = $self->groups_as_string;
- # For or-groups, we check if there are any can_edit groups for the
- # product, and if the user is in any of them. If there are none or
- # the user is in at least one of them, they can edit the product
- my ($cnt_can_edit, $cnt_group_member) = $dbh->selectrow_array(
- "SELECT SUM(p.cnt_can_edit),
+ if (Bugzilla->params->{'or_groups'}) {
+ my $groups = $self->groups_as_string;
+
+ # For or-groups, we check if there are any can_edit groups for the
+ # product, and if the user is in any of them. If there are none or
+ # the user is in at least one of them, they can edit the product
+ my ($cnt_can_edit, $cnt_group_member) = $dbh->selectrow_array(
+ "SELECT SUM(p.cnt_can_edit),
SUM(p.cnt_group_member)
FROM (SELECT CASE WHEN canedit = 1 THEN 1 ELSE 0 END AS cnt_can_edit,
CASE WHEN canedit = 1 AND group_id IN ($groups) THEN 1 ELSE 0 END AS cnt_group_member
FROM group_control_map
- WHERE product_id = $prod_id) AS p");
- return (!$cnt_can_edit or $cnt_group_member);
- }
- else {
- # For and-groups, a user needs to be in all canedit groups. Therefore
- # if the user is not in a can_edit group for the product, they cannot
- # edit the product.
- my $has_external_groups =
- $dbh->selectrow_array('SELECT 1
+ WHERE product_id = $prod_id) AS p"
+ );
+ return (!$cnt_can_edit or $cnt_group_member);
+ }
+ else {
+ # For and-groups, a user needs to be in all canedit groups. Therefore
+ # if the user is not in a can_edit group for the product, they cannot
+ # edit the product.
+ my $has_external_groups = $dbh->selectrow_array(
+ 'SELECT 1
FROM group_control_map
WHERE product_id = ?
AND canedit != 0
- AND group_id NOT IN(' . $self->groups_as_string . ')',
- undef, $prod_id);
+ AND group_id NOT IN('
+ . $self->groups_as_string . ')', undef, $prod_id
+ );
- return !$has_external_groups;
- }
+ return !$has_external_groups;
+ }
}
sub can_see_bug {
- my ($self, $bug_id) = @_;
- return @{ $self->visible_bugs([$bug_id]) } ? 1 : 0;
+ my ($self, $bug_id) = @_;
+ return @{$self->visible_bugs([$bug_id])} ? 1 : 0;
}
sub visible_bugs {
- my ($self, $bugs) = @_;
- # Allow users to pass in Bug objects and bug ids both.
- my @bug_ids = map { blessed $_ ? $_->id : $_ } @$bugs;
-
- # We only check the visibility of bugs that we haven't
- # checked yet.
- # Bugzilla::Bug->update automatically removes updated bugs
- # from the cache to force them to be checked again.
- my $visible_cache = $self->{_visible_bugs_cache} ||= {};
- my @check_ids = grep(!exists $visible_cache->{$_}, @bug_ids);
-
- if (@check_ids) {
- foreach my $id (@check_ids) {
- my $orig_id = $id;
- detaint_natural($id)
- || ThrowCodeError('param_must_be_numeric', { param => $orig_id,
- function => 'Bugzilla::User->visible_bugs'});
- }
+ my ($self, $bugs) = @_;
+
+ # Allow users to pass in Bug objects and bug ids both.
+ my @bug_ids = map { blessed $_ ? $_->id : $_ } @$bugs;
- Bugzilla->params->{'or_groups'}
- ? $self->_visible_bugs_check_or(\@check_ids)
- : $self->_visible_bugs_check_and(\@check_ids);
+ # We only check the visibility of bugs that we haven't
+ # checked yet.
+ # Bugzilla::Bug->update automatically removes updated bugs
+ # from the cache to force them to be checked again.
+ my $visible_cache = $self->{_visible_bugs_cache} ||= {};
+ my @check_ids = grep(!exists $visible_cache->{$_}, @bug_ids);
+
+ if (@check_ids) {
+ foreach my $id (@check_ids) {
+ my $orig_id = $id;
+ detaint_natural($id)
+ || ThrowCodeError('param_must_be_numeric',
+ {param => $orig_id, function => 'Bugzilla::User->visible_bugs'});
}
- return [grep { $visible_cache->{blessed $_ ? $_->id : $_} } @$bugs];
+ Bugzilla->params->{'or_groups'}
+ ? $self->_visible_bugs_check_or(\@check_ids)
+ : $self->_visible_bugs_check_and(\@check_ids);
+ }
+
+ return [grep { $visible_cache->{blessed $_ ? $_->id : $_} } @$bugs];
}
sub _visible_bugs_check_or {
- my ($self, $check_ids) = @_;
- my $visible_cache = $self->{_visible_bugs_cache};
- my $dbh = Bugzilla->dbh;
- my $user_id = $self->id;
-
- my $sth;
- # Speed up the can_see_bug case.
- if (scalar(@$check_ids) == 1) {
- $sth = $self->{_sth_one_visible_bug};
- }
- my $query = qq{
+ my ($self, $check_ids) = @_;
+ my $visible_cache = $self->{_visible_bugs_cache};
+ my $dbh = Bugzilla->dbh;
+ my $user_id = $self->id;
+
+ my $sth;
+
+ # Speed up the can_see_bug case.
+ if (scalar(@$check_ids) == 1) {
+ $sth = $self->{_sth_one_visible_bug};
+ }
+ my $query = qq{
SELECT DISTINCT bugs.bug_id
FROM bugs
LEFT JOIN bug_group_map AS security_map ON bugs.bug_id = security_map.bug_id
LEFT JOIN cc AS security_cc ON bugs.bug_id = security_cc.bug_id AND security_cc.who = $user_id
WHERE bugs.bug_id IN (} . join(',', ('?') x @$check_ids) . qq{)
- AND ((security_map.group_id IS NULL OR security_map.group_id IN (} . $self->groups_as_string . qq{))
+ AND ((security_map.group_id IS NULL OR security_map.group_id IN (}
+ . $self->groups_as_string . qq{))
OR (bugs.reporter_accessible = 1 AND bugs.reporter = $user_id)
OR (bugs.cclist_accessible = 1 AND security_cc.who IS NOT NULL)
OR bugs.assigned_to = $user_id
};
- if (Bugzilla->params->{'useqacontact'}) {
- $query .= " OR bugs.qa_contact = $user_id";
- }
- $query .= ')';
+ if (Bugzilla->params->{'useqacontact'}) {
+ $query .= " OR bugs.qa_contact = $user_id";
+ }
+ $query .= ')';
- $sth ||= $dbh->prepare($query);
- if (scalar(@$check_ids) == 1) {
- $self->{_sth_one_visible_bug} = $sth;
- }
+ $sth ||= $dbh->prepare($query);
+ if (scalar(@$check_ids) == 1) {
+ $self->{_sth_one_visible_bug} = $sth;
+ }
- # Set all bugs as non visible
- foreach my $bug_id (@$check_ids) {
- $visible_cache->{$bug_id} = 0;
- }
+ # Set all bugs as non visible
+ foreach my $bug_id (@$check_ids) {
+ $visible_cache->{$bug_id} = 0;
+ }
- # Now get the bugs the user can see
- my $visible_bug_ids = $dbh->selectcol_arrayref($sth, undef, @$check_ids);
- foreach my $bug_id (@$visible_bug_ids) {
- $visible_cache->{$bug_id} = 1;
- }
+ # Now get the bugs the user can see
+ my $visible_bug_ids = $dbh->selectcol_arrayref($sth, undef, @$check_ids);
+ foreach my $bug_id (@$visible_bug_ids) {
+ $visible_cache->{$bug_id} = 1;
+ }
}
sub _visible_bugs_check_and {
- my ($self, $check_ids) = @_;
- my $visible_cache = $self->{_visible_bugs_cache};
- my $dbh = Bugzilla->dbh;
- my $user_id = $self->id;
-
- my $sth;
- # Speed up the can_see_bug case.
- if (scalar(@$check_ids) == 1) {
- $sth = $self->{_sth_one_visible_bug};
- }
- $sth ||= $dbh->prepare(
- # This checks for groups that the bug is in that the user
- # *isn't* in. Then, in the Perl code below, we check if
- # the user can otherwise access the bug (for example, by being
- # the assignee or QA Contact).
- #
- # The DISTINCT exists because the bug could be in *several*
- # groups that the user isn't in, but they will all return the
- # same result for bug_group_map.bug_id (so DISTINCT filters
- # out duplicate rows).
- "SELECT DISTINCT bugs.bug_id, reporter, assigned_to, qa_contact,
+ my ($self, $check_ids) = @_;
+ my $visible_cache = $self->{_visible_bugs_cache};
+ my $dbh = Bugzilla->dbh;
+ my $user_id = $self->id;
+
+ my $sth;
+
+ # Speed up the can_see_bug case.
+ if (scalar(@$check_ids) == 1) {
+ $sth = $self->{_sth_one_visible_bug};
+ }
+ $sth ||= $dbh->prepare(
+
+ # This checks for groups that the bug is in that the user
+ # *isn't* in. Then, in the Perl code below, we check if
+ # the user can otherwise access the bug (for example, by being
+ # the assignee or QA Contact).
+ #
+ # The DISTINCT exists because the bug could be in *several*
+ # groups that the user isn't in, but they will all return the
+ # same result for bug_group_map.bug_id (so DISTINCT filters
+ # out duplicate rows).
+ "SELECT DISTINCT bugs.bug_id, reporter, assigned_to, qa_contact,
reporter_accessible, cclist_accessible, cc.who,
bug_group_map.bug_id
FROM bugs
@@ -1248,1056 +1273,1122 @@ sub _visible_bugs_check_and {
LEFT JOIN bug_group_map
ON bugs.bug_id = bug_group_map.bug_id
AND bug_group_map.group_id NOT IN ("
- . $self->groups_as_string . ')
+ . $self->groups_as_string . ')
WHERE bugs.bug_id IN (' . join(',', ('?') x @$check_ids) . ')
- AND creation_ts IS NOT NULL ');
- if (scalar(@$check_ids) == 1) {
- $self->{_sth_one_visible_bug} = $sth;
- }
-
- $sth->execute(@$check_ids);
- my $use_qa_contact = Bugzilla->params->{'useqacontact'};
- while (my $row = $sth->fetchrow_arrayref) {
- my ($bug_id, $reporter, $owner, $qacontact, $reporter_access,
- $cclist_access, $isoncclist, $missinggroup) = @$row;
- $visible_cache->{$bug_id} ||=
- ((($reporter == $user_id) && $reporter_access)
- || ($use_qa_contact
- && $qacontact && ($qacontact == $user_id))
- || ($owner == $user_id)
- || ($isoncclist && $cclist_access)
- || !$missinggroup) ? 1 : 0;
- }
+ AND creation_ts IS NOT NULL '
+ );
+ if (scalar(@$check_ids) == 1) {
+ $self->{_sth_one_visible_bug} = $sth;
+ }
+
+ $sth->execute(@$check_ids);
+ my $use_qa_contact = Bugzilla->params->{'useqacontact'};
+ while (my $row = $sth->fetchrow_arrayref) {
+ my ($bug_id, $reporter, $owner, $qacontact, $reporter_access, $cclist_access,
+ $isoncclist, $missinggroup)
+ = @$row;
+ $visible_cache->{$bug_id}
+ ||= ((($reporter == $user_id) && $reporter_access)
+ || ($use_qa_contact && $qacontact && ($qacontact == $user_id))
+ || ($owner == $user_id)
+ || ($isoncclist && $cclist_access)
+ || !$missinggroup) ? 1 : 0;
+ }
}
sub clear_product_cache {
- my $self = shift;
- delete $self->{enterable_products};
- delete $self->{selectable_products};
- delete $self->{selectable_classifications};
+ my $self = shift;
+ delete $self->{enterable_products};
+ delete $self->{selectable_products};
+ delete $self->{selectable_classifications};
}
sub can_see_product {
- my ($self, $product_name) = @_;
+ my ($self, $product_name) = @_;
- return scalar(grep {$_->name eq $product_name} @{$self->get_selectable_products});
+ return
+ scalar(grep { $_->name eq $product_name } @{$self->get_selectable_products});
}
sub get_selectable_products {
- my $self = shift;
- my $class_id = shift;
- my $class_restricted = Bugzilla->params->{'useclassification'} && $class_id;
+ my $self = shift;
+ my $class_id = shift;
+ my $class_restricted = Bugzilla->params->{'useclassification'} && $class_id;
- if (!defined $self->{selectable_products}) {
- my $query = "SELECT id
+ if (!defined $self->{selectable_products}) {
+ my $query = "SELECT id
FROM products
LEFT JOIN group_control_map
ON group_control_map.product_id = products.id
- AND group_control_map.membercontrol = " . CONTROLMAPMANDATORY;
-
- if (Bugzilla->params->{'or_groups'}) {
- # Either the user is in at least one of the MANDATORY groups, or
- # there are no such groups for the product.
- $query .= " WHERE group_id IN (" . $self->groups_as_string . ")
+ AND group_control_map.membercontrol = "
+ . CONTROLMAPMANDATORY;
+
+ if (Bugzilla->params->{'or_groups'}) {
+
+ # Either the user is in at least one of the MANDATORY groups, or
+ # there are no such groups for the product.
+ $query .= " WHERE group_id IN (" . $self->groups_as_string . ")
OR group_id IS NULL";
- }
- else {
- # There must be no MANDATORY groups that the user is not in.
- $query .= " AND group_id NOT IN (" . $self->groups_as_string . ")
+ }
+ else {
+ # There must be no MANDATORY groups that the user is not in.
+ $query .= " AND group_id NOT IN (" . $self->groups_as_string . ")
WHERE group_id IS NULL";
- }
-
- my $prod_ids = Bugzilla->dbh->selectcol_arrayref($query);
- $self->{selectable_products} = Bugzilla::Product->new_from_list($prod_ids);
}
- # Restrict the list of products to those being in the classification, if any.
- if ($class_restricted) {
- return [grep {$_->classification_id == $class_id} @{$self->{selectable_products}}];
- }
- # If we come here, then we want all selectable products.
- return $self->{selectable_products};
+ my $prod_ids = Bugzilla->dbh->selectcol_arrayref($query);
+ $self->{selectable_products} = Bugzilla::Product->new_from_list($prod_ids);
+ }
+
+ # Restrict the list of products to those being in the classification, if any.
+ if ($class_restricted) {
+ return [grep { $_->classification_id == $class_id }
+ @{$self->{selectable_products}}];
+ }
+
+ # If we come here, then we want all selectable products.
+ return $self->{selectable_products};
}
sub get_selectable_classifications {
- my ($self) = @_;
+ my ($self) = @_;
- if (!defined $self->{selectable_classifications}) {
- my $products = $self->get_selectable_products;
- my %class_ids = map { $_->classification_id => 1 } @$products;
+ if (!defined $self->{selectable_classifications}) {
+ my $products = $self->get_selectable_products;
+ my %class_ids = map { $_->classification_id => 1 } @$products;
- $self->{selectable_classifications} = Bugzilla::Classification->new_from_list([keys %class_ids]);
- }
- return $self->{selectable_classifications};
+ $self->{selectable_classifications}
+ = Bugzilla::Classification->new_from_list([keys %class_ids]);
+ }
+ return $self->{selectable_classifications};
}
sub can_enter_product {
- my ($self, $input, $warn) = @_;
- my $dbh = Bugzilla->dbh;
- $warn ||= 0;
-
- $input = trim($input) if !ref $input;
- if (!defined $input or $input eq '') {
- return unless $warn == THROW_ERROR;
- ThrowUserError('object_not_specified',
- { class => 'Bugzilla::Product' });
- }
+ my ($self, $input, $warn) = @_;
+ my $dbh = Bugzilla->dbh;
+ $warn ||= 0;
- if (!scalar @{ $self->get_enterable_products }) {
- return unless $warn == THROW_ERROR;
- ThrowUserError('no_products');
- }
+ $input = trim($input) if !ref $input;
+ if (!defined $input or $input eq '') {
+ return unless $warn == THROW_ERROR;
+ ThrowUserError('object_not_specified', {class => 'Bugzilla::Product'});
+ }
- my $product = blessed($input) ? $input
- : new Bugzilla::Product({ name => $input });
- my $can_enter =
- $product && grep($_->name eq $product->name,
- @{ $self->get_enterable_products });
+ if (!scalar @{$self->get_enterable_products}) {
+ return unless $warn == THROW_ERROR;
+ ThrowUserError('no_products');
+ }
- return $product if $can_enter;
+ my $product
+ = blessed($input) ? $input : new Bugzilla::Product({name => $input});
+ my $can_enter = $product
+ && grep($_->name eq $product->name, @{$self->get_enterable_products});
- return 0 unless $warn == THROW_ERROR;
+ return $product if $can_enter;
- # Check why access was denied. These checks are slow,
- # but that's fine, because they only happen if we fail.
+ return 0 unless $warn == THROW_ERROR;
- # We don't just use $product->name for error messages, because if it
- # changes case from $input, then that's a clue that the product does
- # exist but is hidden.
- my $name = blessed($input) ? $input->name : $input;
+ # Check why access was denied. These checks are slow,
+ # but that's fine, because they only happen if we fail.
- # The product could not exist or you could be denied...
- if (!$product || !$product->user_has_access($self)) {
- ThrowUserError('entry_access_denied', { product => $name });
- }
- # It could be closed for bug entry...
- elsif (!$product->is_active) {
- ThrowUserError('product_disabled', { product => $product });
- }
- # It could have no components...
- elsif (!@{$product->components}
- || !grep { $_->is_active } @{$product->components})
- {
- ThrowUserError('missing_component', { product => $product });
- }
- # It could have no versions...
- elsif (!@{$product->versions}
- || !grep { $_->is_active } @{$product->versions})
- {
- ThrowUserError ('missing_version', { product => $product });
- }
+ # We don't just use $product->name for error messages, because if it
+ # changes case from $input, then that's a clue that the product does
+ # exist but is hidden.
+ my $name = blessed($input) ? $input->name : $input;
+
+ # The product could not exist or you could be denied...
+ if (!$product || !$product->user_has_access($self)) {
+ ThrowUserError('entry_access_denied', {product => $name});
+ }
+
+ # It could be closed for bug entry...
+ elsif (!$product->is_active) {
+ ThrowUserError('product_disabled', {product => $product});
+ }
+
+ # It could have no components...
+ elsif (!@{$product->components}
+ || !grep { $_->is_active } @{$product->components})
+ {
+ ThrowUserError('missing_component', {product => $product});
+ }
- die "can_enter_product reached an unreachable location.";
+ # It could have no versions...
+ elsif (!@{$product->versions} || !grep { $_->is_active } @{$product->versions})
+ {
+ ThrowUserError('missing_version', {product => $product});
+ }
+
+ die "can_enter_product reached an unreachable location.";
}
sub get_enterable_products {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (defined $self->{enterable_products}) {
- return $self->{enterable_products};
- }
+ if (defined $self->{enterable_products}) {
+ return $self->{enterable_products};
+ }
- # All products which the user has "Entry" access to.
- my $query =
- 'SELECT products.id FROM products
+ # All products which the user has "Entry" access to.
+ my $query = 'SELECT products.id FROM products
LEFT JOIN group_control_map
ON group_control_map.product_id = products.id
AND group_control_map.entry != 0';
- if (Bugzilla->params->{'or_groups'}) {
- $query .= " WHERE (group_id IN (" . $self->groups_as_string . ")" .
- " OR group_id IS NULL)";
- } else {
- $query .= " AND group_id NOT IN (" . $self->groups_as_string . ")" .
- " WHERE group_id IS NULL"
- }
- $query .= " AND products.isactive = 1";
- my $enterable_ids = $dbh->selectcol_arrayref($query);
-
- if (scalar @$enterable_ids) {
- # And all of these products must have at least one component
- # and one version.
- $enterable_ids = $dbh->selectcol_arrayref(
- 'SELECT DISTINCT products.id FROM products
- WHERE ' . $dbh->sql_in('products.id', $enterable_ids) .
- ' AND products.id IN (SELECT DISTINCT components.product_id
+ if (Bugzilla->params->{'or_groups'}) {
+ $query
+ .= " WHERE (group_id IN ("
+ . $self->groups_as_string . ")"
+ . " OR group_id IS NULL)";
+ }
+ else {
+ $query
+ .= " AND group_id NOT IN ("
+ . $self->groups_as_string . ")"
+ . " WHERE group_id IS NULL";
+ }
+ $query .= " AND products.isactive = 1";
+ my $enterable_ids = $dbh->selectcol_arrayref($query);
+
+ if (scalar @$enterable_ids) {
+
+ # And all of these products must have at least one component
+ # and one version.
+ $enterable_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT products.id FROM products
+ WHERE '
+ . $dbh->sql_in('products.id', $enterable_ids)
+ . ' AND products.id IN (SELECT DISTINCT components.product_id
FROM components
WHERE components.isactive = 1)
AND products.id IN (SELECT DISTINCT versions.product_id
FROM versions
- WHERE versions.isactive = 1)');
- }
+ WHERE versions.isactive = 1)'
+ );
+ }
- $self->{enterable_products} =
- Bugzilla::Product->new_from_list($enterable_ids);
- return $self->{enterable_products};
+ $self->{enterable_products} = Bugzilla::Product->new_from_list($enterable_ids);
+ return $self->{enterable_products};
}
sub can_access_product {
- my ($self, $product) = @_;
- my $product_name = blessed($product) ? $product->name : $product;
- return scalar(grep {$_->name eq $product_name} @{$self->get_accessible_products});
+ my ($self, $product) = @_;
+ my $product_name = blessed($product) ? $product->name : $product;
+ return
+ scalar(grep { $_->name eq $product_name } @{$self->get_accessible_products});
}
sub get_accessible_products {
- my $self = shift;
-
- # Map the objects into a hash using the ids as keys
- my %products = map { $_->id => $_ }
- @{$self->get_selectable_products},
- @{$self->get_enterable_products};
-
- return [ sort { $a->name cmp $b->name } values %products ];
+ my $self = shift;
+
+ # Map the objects into a hash using the ids as keys
+ my %products = map { $_->id => $_ } @{$self->get_selectable_products},
+ @{$self->get_enterable_products};
+
+ return [sort { $a->name cmp $b->name } values %products];
}
sub can_administer {
- my $self = shift;
-
- if (not defined $self->{can_administer}) {
- my $can_administer = 0;
-
- $can_administer = 1 if $self->in_group('admin')
- || $self->in_group('tweakparams')
- || $self->in_group('editusers')
- || $self->can_bless
- || (Bugzilla->params->{'useclassification'} && $self->in_group('editclassifications'))
- || $self->in_group('editcomponents')
- || scalar(@{$self->get_products_by_permission('editcomponents')})
- || $self->in_group('creategroups')
- || $self->in_group('editkeywords')
- || $self->in_group('bz_canusewhines');
-
- Bugzilla::Hook::process('user_can_administer', { can_administer => \$can_administer });
- $self->{can_administer} = $can_administer;
- }
+ my $self = shift;
+
+ if (not defined $self->{can_administer}) {
+ my $can_administer = 0;
+
+ $can_administer = 1
+ if $self->in_group('admin')
+ || $self->in_group('tweakparams')
+ || $self->in_group('editusers')
+ || $self->can_bless
+ || (Bugzilla->params->{'useclassification'}
+ && $self->in_group('editclassifications'))
+ || $self->in_group('editcomponents')
+ || scalar(@{$self->get_products_by_permission('editcomponents')})
+ || $self->in_group('creategroups')
+ || $self->in_group('editkeywords')
+ || $self->in_group('bz_canusewhines');
- return $self->{can_administer};
+ Bugzilla::Hook::process('user_can_administer',
+ {can_administer => \$can_administer});
+ $self->{can_administer} = $can_administer;
+ }
+
+ return $self->{can_administer};
}
sub check_can_admin_product {
- my ($self, $product_name) = @_;
+ my ($self, $product_name) = @_;
- # First make sure the product name is valid.
- my $product = Bugzilla::Product->check($product_name);
+ # First make sure the product name is valid.
+ my $product = Bugzilla::Product->check($product_name);
- ($self->in_group('editcomponents', $product->id)
- && $self->can_see_product($product->name))
- || ThrowUserError('product_admin_denied', {product => $product->name});
+ ( $self->in_group('editcomponents', $product->id)
+ && $self->can_see_product($product->name))
+ || ThrowUserError('product_admin_denied', {product => $product->name});
- # Return the validated product object.
- return $product;
+ # Return the validated product object.
+ return $product;
}
sub check_can_admin_flagtype {
- my ($self, $flagtype_id) = @_;
-
- my $flagtype = Bugzilla::FlagType->check({ id => $flagtype_id });
- my $can_fully_edit = 1;
-
- if (!$self->in_group('editcomponents')) {
- my $products = $self->get_products_by_permission('editcomponents');
- # You need editcomponents privs for at least one product to have
- # a chance to edit the flagtype.
- scalar(@$products)
- || ThrowUserError('auth_failure', {group => 'editcomponents',
- action => 'edit',
- object => 'flagtypes'});
- my $can_admin = 0;
- my $i = $flagtype->inclusions_as_hash;
- my $e = $flagtype->exclusions_as_hash;
-
- # If there is at least one product for which the user doesn't have
- # editcomponents privs, then don't allow them to do everything with
- # this flagtype, independently of whether this product is in the
- # exclusion list or not.
- my %product_ids;
- map { $product_ids{$_->id} = 1 } @$products;
- $can_fully_edit = 0 if grep { !$product_ids{$_} } keys %$i;
-
- unless ($e->{0}->{0}) {
- foreach my $product (@$products) {
- my $id = $product->id;
- next if $e->{$id}->{0};
- # If we are here, the product has not been explicitly excluded.
- # Check whether it's explicitly included, or at least one of
- # its components.
- $can_admin = ($i->{0}->{0} || $i->{$id}->{0}
- || scalar(grep { !$e->{$id}->{$_} } keys %{$i->{$id}}));
- last if $can_admin;
- }
- }
- $can_admin || ThrowUserError('flag_type_not_editable', { flagtype => $flagtype });
- }
- return wantarray ? ($flagtype, $can_fully_edit) : $flagtype;
+ my ($self, $flagtype_id) = @_;
+
+ my $flagtype = Bugzilla::FlagType->check({id => $flagtype_id});
+ my $can_fully_edit = 1;
+
+ if (!$self->in_group('editcomponents')) {
+ my $products = $self->get_products_by_permission('editcomponents');
+
+ # You need editcomponents privs for at least one product to have
+ # a chance to edit the flagtype.
+ scalar(@$products)
+ || ThrowUserError('auth_failure',
+ {group => 'editcomponents', action => 'edit', object => 'flagtypes'});
+ my $can_admin = 0;
+ my $i = $flagtype->inclusions_as_hash;
+ my $e = $flagtype->exclusions_as_hash;
+
+ # If there is at least one product for which the user doesn't have
+ # editcomponents privs, then don't allow them to do everything with
+ # this flagtype, independently of whether this product is in the
+ # exclusion list or not.
+ my %product_ids;
+ map { $product_ids{$_->id} = 1 } @$products;
+ $can_fully_edit = 0 if grep { !$product_ids{$_} } keys %$i;
+
+ unless ($e->{0}->{0}) {
+ foreach my $product (@$products) {
+ my $id = $product->id;
+ next if $e->{$id}->{0};
+
+ # If we are here, the product has not been explicitly excluded.
+ # Check whether it's explicitly included, or at least one of
+ # its components.
+ $can_admin
+ = ( $i->{0}->{0}
+ || $i->{$id}->{0}
+ || scalar(grep { !$e->{$id}->{$_} } keys %{$i->{$id}}));
+ last if $can_admin;
+ }
+ }
+ $can_admin || ThrowUserError('flag_type_not_editable', {flagtype => $flagtype});
+ }
+ return wantarray ? ($flagtype, $can_fully_edit) : $flagtype;
}
sub can_request_flag {
- my ($self, $flag_type) = @_;
+ my ($self, $flag_type) = @_;
- return ($self->can_set_flag($flag_type)
- || !$flag_type->request_group_id
- || $self->in_group_id($flag_type->request_group_id)) ? 1 : 0;
+ return ($self->can_set_flag($flag_type)
+ || !$flag_type->request_group_id
+ || $self->in_group_id($flag_type->request_group_id)) ? 1 : 0;
}
sub can_set_flag {
- my ($self, $flag_type) = @_;
+ my ($self, $flag_type) = @_;
- return (!$flag_type->grant_group_id
- || $self->in_group_id($flag_type->grant_group_id)) ? 1 : 0;
+ return (!$flag_type->grant_group_id
+ || $self->in_group_id($flag_type->grant_group_id)) ? 1 : 0;
}
# visible_groups_inherited returns a reference to a list of all the groups
# whose members are visible to this user.
sub visible_groups_inherited {
- my $self = shift;
- return $self->{visible_groups_inherited} if defined $self->{visible_groups_inherited};
- return [] unless $self->id;
- my @visgroups = @{$self->visible_groups_direct};
- @visgroups = @{Bugzilla::Group->flatten_group_membership(@visgroups)};
- $self->{visible_groups_inherited} = \@visgroups;
- return $self->{visible_groups_inherited};
+ my $self = shift;
+ return $self->{visible_groups_inherited}
+ if defined $self->{visible_groups_inherited};
+ return [] unless $self->id;
+ my @visgroups = @{$self->visible_groups_direct};
+ @visgroups = @{Bugzilla::Group->flatten_group_membership(@visgroups)};
+ $self->{visible_groups_inherited} = \@visgroups;
+ return $self->{visible_groups_inherited};
}
# visible_groups_direct returns a reference to a list of all the groups that
# are visible to this user.
sub visible_groups_direct {
- my $self = shift;
- my @visgroups = ();
- return $self->{visible_groups_direct} if defined $self->{visible_groups_direct};
- return [] unless $self->id;
-
- my $dbh = Bugzilla->dbh;
- my $sth;
-
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $sth = $dbh->prepare("SELECT DISTINCT grantor_id
+ my $self = shift;
+ my @visgroups = ();
+ return $self->{visible_groups_direct} if defined $self->{visible_groups_direct};
+ return [] unless $self->id;
+
+ my $dbh = Bugzilla->dbh;
+ my $sth;
+
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $sth = $dbh->prepare(
+ "SELECT DISTINCT grantor_id
FROM group_group_map
WHERE " . $self->groups_in_sql('member_id') . "
- AND grant_type=" . GROUP_VISIBLE);
- }
- else {
- # All groups are visible if usevisibilitygroups is off.
- $sth = $dbh->prepare('SELECT id FROM groups');
- }
- $sth->execute();
+ AND grant_type=" . GROUP_VISIBLE
+ );
+ }
+ else {
+ # All groups are visible if usevisibilitygroups is off.
+ $sth = $dbh->prepare('SELECT id FROM groups');
+ }
+ $sth->execute();
- while (my ($row) = $sth->fetchrow_array) {
- push @visgroups,$row;
- }
- $self->{visible_groups_direct} = \@visgroups;
+ while (my ($row) = $sth->fetchrow_array) {
+ push @visgroups, $row;
+ }
+ $self->{visible_groups_direct} = \@visgroups;
- return $self->{visible_groups_direct};
+ return $self->{visible_groups_direct};
}
sub visible_groups_as_string {
- my $self = shift;
- return join(', ', @{$self->visible_groups_inherited()});
+ my $self = shift;
+ return join(', ', @{$self->visible_groups_inherited()});
}
# This function defines the groups a user may share a query with.
# More restrictive sites may want to build this reference to a list of group IDs
# from bless_groups instead of mirroring visible_groups_inherited, perhaps.
sub queryshare_groups {
- my $self = shift;
- my @queryshare_groups;
-
- return $self->{queryshare_groups} if defined $self->{queryshare_groups};
-
- if ($self->in_group(Bugzilla->params->{'querysharegroup'})) {
- # We want to be allowed to share with groups we're in only.
- # If usevisibilitygroups is on, then we need to restrict this to groups
- # we may see.
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- foreach(@{$self->visible_groups_inherited()}) {
- next unless $self->in_group_id($_);
- push(@queryshare_groups, $_);
- }
- }
- else {
- @queryshare_groups = @{ $self->_group_ids };
- }
+ my $self = shift;
+ my @queryshare_groups;
+
+ return $self->{queryshare_groups} if defined $self->{queryshare_groups};
+
+ if ($self->in_group(Bugzilla->params->{'querysharegroup'})) {
+
+ # We want to be allowed to share with groups we're in only.
+ # If usevisibilitygroups is on, then we need to restrict this to groups
+ # we may see.
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ foreach (@{$self->visible_groups_inherited()}) {
+ next unless $self->in_group_id($_);
+ push(@queryshare_groups, $_);
+ }
}
+ else {
+ @queryshare_groups = @{$self->_group_ids};
+ }
+ }
- return $self->{queryshare_groups} = \@queryshare_groups;
+ return $self->{queryshare_groups} = \@queryshare_groups;
}
sub queryshare_groups_as_string {
- my $self = shift;
- return join(', ', @{$self->queryshare_groups()});
+ my $self = shift;
+ return join(', ', @{$self->queryshare_groups()});
}
sub derive_regexp_groups {
- my ($self) = @_;
+ my ($self) = @_;
- my $id = $self->id;
- return unless $id;
+ my $id = $self->id;
+ return unless $id;
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- my $sth;
+ my $sth;
- # add derived records for any matching regexps
+ # add derived records for any matching regexps
- $sth = $dbh->prepare("SELECT id, userregexp, user_group_map.group_id
+ $sth = $dbh->prepare(
+ "SELECT id, userregexp, user_group_map.group_id
FROM groups
LEFT JOIN user_group_map
ON groups.id = user_group_map.group_id
AND user_group_map.user_id = ?
- AND user_group_map.grant_type = ?");
- $sth->execute($id, GRANT_REGEXP);
+ AND user_group_map.grant_type = ?"
+ );
+ $sth->execute($id, GRANT_REGEXP);
- my $group_insert = $dbh->prepare(q{INSERT INTO user_group_map
+ my $group_insert = $dbh->prepare(q{INSERT INTO user_group_map
(user_id, group_id, isbless, grant_type)
- VALUES (?, ?, 0, ?)});
- my $group_delete = $dbh->prepare(q{DELETE FROM user_group_map
+ VALUES (?, ?, 0, ?)}
+ );
+ my $group_delete = $dbh->prepare(q{DELETE FROM user_group_map
WHERE user_id = ?
AND group_id = ?
AND isbless = 0
- AND grant_type = ?});
- while (my ($group, $regexp, $present) = $sth->fetchrow_array()) {
- if (($regexp ne '') && ($self->login =~ m/$regexp/i)) {
- $group_insert->execute($id, $group, GRANT_REGEXP) unless $present;
- } else {
- $group_delete->execute($id, $group, GRANT_REGEXP) if $present;
- }
+ AND grant_type = ?}
+ );
+ while (my ($group, $regexp, $present) = $sth->fetchrow_array()) {
+ if (($regexp ne '') && ($self->login =~ m/$regexp/i)) {
+ $group_insert->execute($id, $group, GRANT_REGEXP) unless $present;
+ }
+ else {
+ $group_delete->execute($id, $group, GRANT_REGEXP) if $present;
}
+ }
- Bugzilla->memcached->clear_config({ key => "user_groups.$id" });
+ Bugzilla->memcached->clear_config({key => "user_groups.$id"});
}
sub product_responsibilities {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- return $self->{'product_resp'} if defined $self->{'product_resp'};
- return [] unless $self->id;
+ return $self->{'product_resp'} if defined $self->{'product_resp'};
+ return [] unless $self->id;
- my $list = $dbh->selectall_arrayref('SELECT components.product_id, components.id
+ my $list = $dbh->selectall_arrayref(
+ 'SELECT components.product_id, components.id
FROM components
LEFT JOIN component_cc
ON components.id = component_cc.component_id
WHERE components.initialowner = ?
OR components.initialqacontact = ?
OR component_cc.user_id = ?',
- {Slice => {}}, ($self->id, $self->id, $self->id));
-
- unless ($list) {
- $self->{'product_resp'} = [];
- return $self->{'product_resp'};
- }
+ {Slice => {}}, ($self->id, $self->id, $self->id)
+ );
- my @prod_ids = map {$_->{'product_id'}} @$list;
- my $products = Bugzilla::Product->new_from_list(\@prod_ids);
- # We cannot |use| it, because Component.pm already |use|s User.pm.
- require Bugzilla::Component;
- my @comp_ids = map {$_->{'id'}} @$list;
- my $components = Bugzilla::Component->new_from_list(\@comp_ids);
-
- my @prod_list;
- # @$products is already sorted alphabetically.
- foreach my $prod (@$products) {
- # We use @components instead of $prod->components because we only want
- # components where the user is either the default assignee or QA contact.
- push(@prod_list, {product => $prod,
- components => [grep {$_->product_id == $prod->id} @$components]});
- }
- $self->{'product_resp'} = \@prod_list;
+ unless ($list) {
+ $self->{'product_resp'} = [];
return $self->{'product_resp'};
+ }
+
+ my @prod_ids = map { $_->{'product_id'} } @$list;
+ my $products = Bugzilla::Product->new_from_list(\@prod_ids);
+
+ # We cannot |use| it, because Component.pm already |use|s User.pm.
+ require Bugzilla::Component;
+ my @comp_ids = map { $_->{'id'} } @$list;
+ my $components = Bugzilla::Component->new_from_list(\@comp_ids);
+
+ my @prod_list;
+
+ # @$products is already sorted alphabetically.
+ foreach my $prod (@$products) {
+
+ # We use @components instead of $prod->components because we only want
+ # components where the user is either the default assignee or QA contact.
+ push(
+ @prod_list,
+ {
+ product => $prod,
+ components => [grep { $_->product_id == $prod->id } @$components]
+ }
+ );
+ }
+ $self->{'product_resp'} = \@prod_list;
+ return $self->{'product_resp'};
}
sub can_bless {
- my $self = shift;
+ my $self = shift;
- if (!scalar(@_)) {
- # If we're called without an argument, just return
- # whether or not we can bless at all.
- return scalar(@{ $self->bless_groups }) ? 1 : 0;
- }
+ if (!scalar(@_)) {
- # Otherwise, we're checking a specific group
- my $group_id = shift;
- return grep($_->id == $group_id, @{ $self->bless_groups }) ? 1 : 0;
+ # If we're called without an argument, just return
+ # whether or not we can bless at all.
+ return scalar(@{$self->bless_groups}) ? 1 : 0;
+ }
+
+ # Otherwise, we're checking a specific group
+ my $group_id = shift;
+ return grep($_->id == $group_id, @{$self->bless_groups}) ? 1 : 0;
}
sub match {
- # Generates a list of users whose login name (email address) or real name
- # matches a substring or wildcard.
- # This is also called if matches are disabled (for error checking), but
- # in this case only the exact match code will end up running.
-
- # $str contains the string to match, while $limit contains the
- # maximum number of records to retrieve.
- my ($str, $limit, $exclude_disabled) = @_;
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
-
- $str = trim($str);
-
- my @users = ();
- return \@users if $str =~ /^\s*$/;
-
- # The search order is wildcards, then exact match, then substring search.
- # Wildcard matching is skipped if there is no '*', and exact matches will
- # not (?) have a '*' in them. If any search comes up with something, the
- # ones following it will not execute.
-
- # first try wildcards
- my $wildstr = $str;
-
- # Do not do wildcards if there is no '*' in the string.
- if ($wildstr =~ s/\*/\%/g && $user->id) {
- # Build the query.
- trick_taint($wildstr);
- my $query = "SELECT DISTINCT userid FROM profiles ";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= "INNER JOIN user_group_map
- ON user_group_map.user_id = profiles.userid ";
- }
- $query .= "WHERE ("
- . $dbh->sql_istrcmp('login_name', '?', "LIKE") . " OR " .
- $dbh->sql_istrcmp('realname', '?', "LIKE") . ") ";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= "AND isbless = 0 " .
- "AND group_id IN(" .
- join(', ', (-1, @{$user->visible_groups_inherited})) . ") ";
- }
- $query .= " AND is_enabled = 1 " if $exclude_disabled;
- $query .= $dbh->sql_limit($limit) if $limit;
- # Execute the query, retrieve the results, and make them into
- # User objects.
- my $user_ids = $dbh->selectcol_arrayref($query, undef, ($wildstr, $wildstr));
- @users = @{Bugzilla::User->new_from_list($user_ids)};
- }
- else { # try an exact match
- # Exact matches don't care if a user is disabled.
- trick_taint($str);
- my $user_id = $dbh->selectrow_array('SELECT userid FROM profiles
- WHERE ' . $dbh->sql_istrcmp('login_name', '?'),
- undef, $str);
-
- push(@users, new Bugzilla::User($user_id)) if $user_id;
+ # Generates a list of users whose login name (email address) or real name
+ # matches a substring or wildcard.
+ # This is also called if matches are disabled (for error checking), but
+ # in this case only the exact match code will end up running.
+
+ # $str contains the string to match, while $limit contains the
+ # maximum number of records to retrieve.
+ my ($str, $limit, $exclude_disabled) = @_;
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+
+ $str = trim($str);
+
+ my @users = ();
+ return \@users if $str =~ /^\s*$/;
+
+ # The search order is wildcards, then exact match, then substring search.
+ # Wildcard matching is skipped if there is no '*', and exact matches will
+ # not (?) have a '*' in them. If any search comes up with something, the
+ # ones following it will not execute.
+
+ # first try wildcards
+ my $wildstr = $str;
+
+ # Do not do wildcards if there is no '*' in the string.
+ if ($wildstr =~ s/\*/\%/g && $user->id) {
+
+ # Build the query.
+ trick_taint($wildstr);
+ my $query = "SELECT DISTINCT userid FROM profiles ";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query .= "INNER JOIN user_group_map
+ ON user_group_map.user_id = profiles.userid ";
}
+ $query
+ .= "WHERE ("
+ . $dbh->sql_istrcmp('login_name', '?', "LIKE") . " OR "
+ . $dbh->sql_istrcmp('realname', '?', "LIKE") . ") ";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query
+ .= "AND isbless = 0 "
+ . "AND group_id IN("
+ . join(', ', (-1, @{$user->visible_groups_inherited})) . ") ";
+ }
+ $query .= " AND is_enabled = 1 " if $exclude_disabled;
+ $query .= $dbh->sql_limit($limit) if $limit;
+
+ # Execute the query, retrieve the results, and make them into
+ # User objects.
+ my $user_ids = $dbh->selectcol_arrayref($query, undef, ($wildstr, $wildstr));
+ @users = @{Bugzilla::User->new_from_list($user_ids)};
+ }
+ else { # try an exact match
+ # Exact matches don't care if a user is disabled.
+ trick_taint($str);
+ my $user_id = $dbh->selectrow_array(
+ 'SELECT userid FROM profiles
+ WHERE '
+ . $dbh->sql_istrcmp('login_name', '?'), undef, $str
+ );
- # then try substring search
- if (!scalar(@users) && length($str) >= 3 && $user->id) {
- trick_taint($str);
+ push(@users, new Bugzilla::User($user_id)) if $user_id;
+ }
- my $query = "SELECT DISTINCT userid FROM profiles ";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= "INNER JOIN user_group_map
+ # then try substring search
+ if (!scalar(@users) && length($str) >= 3 && $user->id) {
+ trick_taint($str);
+
+ my $query = "SELECT DISTINCT userid FROM profiles ";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query .= "INNER JOIN user_group_map
ON user_group_map.user_id = profiles.userid ";
- }
- $query .= " WHERE (" .
- $dbh->sql_iposition('?', 'login_name') . " > 0" . " OR " .
- $dbh->sql_iposition('?', 'realname') . " > 0) ";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= " AND isbless = 0" .
- " AND group_id IN(" .
- join(', ', (-1, @{$user->visible_groups_inherited})) . ") ";
- }
- $query .= " AND is_enabled = 1 " if $exclude_disabled;
- $query .= $dbh->sql_limit($limit) if $limit;
- my $user_ids = $dbh->selectcol_arrayref($query, undef, ($str, $str));
- @users = @{Bugzilla::User->new_from_list($user_ids)};
}
- return \@users;
+ $query
+ .= " WHERE ("
+ . $dbh->sql_iposition('?', 'login_name') . " > 0" . " OR "
+ . $dbh->sql_iposition('?', 'realname')
+ . " > 0) ";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query
+ .= " AND isbless = 0"
+ . " AND group_id IN("
+ . join(', ', (-1, @{$user->visible_groups_inherited})) . ") ";
+ }
+ $query .= " AND is_enabled = 1 " if $exclude_disabled;
+ $query .= $dbh->sql_limit($limit) if $limit;
+ my $user_ids = $dbh->selectcol_arrayref($query, undef, ($str, $str));
+ @users = @{Bugzilla::User->new_from_list($user_ids)};
+ }
+ return \@users;
}
sub match_field {
- my $fields = shift; # arguments as a hash
- my $data = shift || Bugzilla->input_params; # hash to look up fields in
- my $behavior = shift || 0; # A constant that tells us how to act
- my $matches = {}; # the values sent to the template
- my $matchsuccess = 1; # did the match fail?
- my $need_confirm = 0; # whether to display confirmation screen
- my $match_multiple = 0; # whether we ever matched more than one user
- my @non_conclusive_fields; # fields which don't have a unique user.
-
- my $params = Bugzilla->params;
-
- # prepare default form values
-
- # Fields can be regular expressions matching multiple form fields
- # (f.e. "requestee-(\d+)"), so expand each non-literal field
- # into the list of form fields it matches.
- my $expanded_fields = {};
- foreach my $field_pattern (keys %{$fields}) {
- # Check if the field has any non-word characters. Only those fields
- # can be regular expressions, so don't expand the field if it doesn't
- # have any of those characters.
- if ($field_pattern =~ /^\w+$/) {
- $expanded_fields->{$field_pattern} = $fields->{$field_pattern};
- }
- else {
- my @field_names = grep(/$field_pattern/, keys %$data);
-
- foreach my $field_name (@field_names) {
- $expanded_fields->{$field_name} =
- { type => $fields->{$field_pattern}->{'type'} };
-
- # The field is a requestee field; in order for its name
- # to show up correctly on the confirmation page, we need
- # to find out the name of its flag type.
- if ($field_name =~ /^requestee(_type)?-(\d+)$/) {
- my $flag_type;
- if ($1) {
- require Bugzilla::FlagType;
- $flag_type = new Bugzilla::FlagType($2);
- }
- else {
- require Bugzilla::Flag;
- my $flag = new Bugzilla::Flag($2);
- $flag_type = $flag->type if $flag;
- }
- if ($flag_type) {
- $expanded_fields->{$field_name}->{'flag_type'} = $flag_type;
- }
- else {
- # No need to look for a valid requestee if the flag(type)
- # has been deleted (may occur in race conditions).
- delete $expanded_fields->{$field_name};
- delete $data->{$field_name};
- }
- }
- }
+ my $fields = shift; # arguments as a hash
+ my $data = shift || Bugzilla->input_params; # hash to look up fields in
+ my $behavior = shift || 0; # A constant that tells us how to act
+ my $matches = {}; # the values sent to the template
+ my $matchsuccess = 1; # did the match fail?
+ my $need_confirm = 0; # whether to display confirmation screen
+ my $match_multiple = 0; # whether we ever matched more than one user
+ my @non_conclusive_fields; # fields which don't have a unique user.
+
+ my $params = Bugzilla->params;
+
+ # prepare default form values
+
+ # Fields can be regular expressions matching multiple form fields
+ # (f.e. "requestee-(\d+)"), so expand each non-literal field
+ # into the list of form fields it matches.
+ my $expanded_fields = {};
+ foreach my $field_pattern (keys %{$fields}) {
+
+ # Check if the field has any non-word characters. Only those fields
+ # can be regular expressions, so don't expand the field if it doesn't
+ # have any of those characters.
+ if ($field_pattern =~ /^\w+$/) {
+ $expanded_fields->{$field_pattern} = $fields->{$field_pattern};
+ }
+ else {
+ my @field_names = grep(/$field_pattern/, keys %$data);
+
+ foreach my $field_name (@field_names) {
+ $expanded_fields->{$field_name} = {type => $fields->{$field_pattern}->{'type'}};
+
+ # The field is a requestee field; in order for its name
+ # to show up correctly on the confirmation page, we need
+ # to find out the name of its flag type.
+ if ($field_name =~ /^requestee(_type)?-(\d+)$/) {
+ my $flag_type;
+ if ($1) {
+ require Bugzilla::FlagType;
+ $flag_type = new Bugzilla::FlagType($2);
+ }
+ else {
+ require Bugzilla::Flag;
+ my $flag = new Bugzilla::Flag($2);
+ $flag_type = $flag->type if $flag;
+ }
+ if ($flag_type) {
+ $expanded_fields->{$field_name}->{'flag_type'} = $flag_type;
+ }
+ else {
+ # No need to look for a valid requestee if the flag(type)
+ # has been deleted (may occur in race conditions).
+ delete $expanded_fields->{$field_name};
+ delete $data->{$field_name};
+ }
}
+ }
}
- $fields = $expanded_fields;
+ }
+ $fields = $expanded_fields;
- foreach my $field (keys %{$fields}) {
- next unless defined $data->{$field};
+ foreach my $field (keys %{$fields}) {
+ next unless defined $data->{$field};
- #Concatenate login names, so that we have a common way to handle them.
- my $raw_field;
- if (ref $data->{$field}) {
- $raw_field = join(",", @{$data->{$field}});
- }
- else {
- $raw_field = $data->{$field};
- }
- $raw_field = clean_text($raw_field || '');
-
- # Now we either split $raw_field by spaces/commas and put the list
- # into @queries, or in the case of fields which only accept single
- # entries, we simply use the verbatim text.
- my @queries;
- if ($fields->{$field}->{'type'} eq 'single') {
- @queries = ($raw_field);
- # We will repopulate it later if a match is found, else it must
- # be set to an empty string so that the field remains defined.
- $data->{$field} = '';
- }
- elsif ($fields->{$field}->{'type'} eq 'multi') {
- @queries = split(/[,;]+/, $raw_field);
- # We will repopulate it later if a match is found, else it must
- # be undefined.
- delete $data->{$field};
- }
- else {
- # bad argument
- ThrowCodeError('bad_arg',
- { argument => $fields->{$field}->{'type'},
- function => 'Bugzilla::User::match_field',
- });
- }
+ #Concatenate login names, so that we have a common way to handle them.
+ my $raw_field;
+ if (ref $data->{$field}) {
+ $raw_field = join(",", @{$data->{$field}});
+ }
+ else {
+ $raw_field = $data->{$field};
+ }
+ $raw_field = clean_text($raw_field || '');
- # Tolerate fields that do not exist (in case you specify
- # e.g. the QA contact, and it's currently not in use).
- next unless (defined $raw_field && $raw_field ne '');
+ # Now we either split $raw_field by spaces/commas and put the list
+ # into @queries, or in the case of fields which only accept single
+ # entries, we simply use the verbatim text.
+ my @queries;
+ if ($fields->{$field}->{'type'} eq 'single') {
+ @queries = ($raw_field);
- my $limit = 0;
- if ($params->{'maxusermatches'}) {
- $limit = $params->{'maxusermatches'} + 1;
- }
+ # We will repopulate it later if a match is found, else it must
+ # be set to an empty string so that the field remains defined.
+ $data->{$field} = '';
+ }
+ elsif ($fields->{$field}->{'type'} eq 'multi') {
+ @queries = split(/[,;]+/, $raw_field);
- my @logins;
- for my $query (@queries) {
- $query = trim($query);
- next if $query eq '';
-
- my $users = match(
- $query, # match string
- $limit, # match limit
- 1 # exclude_disabled
- );
-
- # here is where it checks for multiple matches
- if (scalar(@{$users}) == 1) { # exactly one match
- push(@logins, @{$users}[0]->login);
-
- # skip confirmation for exact matches
- next if (lc(@{$users}[0]->login) eq lc($query));
-
- $matches->{$field}->{$query}->{'status'} = 'success';
- $need_confirm = 1 if $params->{'confirmuniqueusermatch'};
-
- }
- elsif ((scalar(@{$users}) > 1)
- && ($params->{'maxusermatches'} != 1)) {
- $need_confirm = 1;
- $match_multiple = 1;
- push(@non_conclusive_fields, $field);
-
- if (($params->{'maxusermatches'})
- && (scalar(@{$users}) > $params->{'maxusermatches'}))
- {
- $matches->{$field}->{$query}->{'status'} = 'trunc';
- pop @{$users}; # take the last one out
- }
- else {
- $matches->{$field}->{$query}->{'status'} = 'success';
- }
-
- }
- else {
- # everything else fails
- $matchsuccess = 0; # fail
- push(@non_conclusive_fields, $field);
- $matches->{$field}->{$query}->{'status'} = 'fail';
- $need_confirm = 1; # confirmation screen shows failures
- }
-
- $matches->{$field}->{$query}->{'users'} = $users;
+ # We will repopulate it later if a match is found, else it must
+ # be undefined.
+ delete $data->{$field};
+ }
+ else {
+ # bad argument
+ ThrowCodeError(
+ 'bad_arg',
+ {
+ argument => $fields->{$field}->{'type'},
+ function => 'Bugzilla::User::match_field',
}
+ );
+ }
+
+ # Tolerate fields that do not exist (in case you specify
+ # e.g. the QA contact, and it's currently not in use).
+ next unless (defined $raw_field && $raw_field ne '');
+
+ my $limit = 0;
+ if ($params->{'maxusermatches'}) {
+ $limit = $params->{'maxusermatches'} + 1;
+ }
+
+ my @logins;
+ for my $query (@queries) {
+ $query = trim($query);
+ next if $query eq '';
+
+ my $users = match(
+ $query, # match string
+ $limit, # match limit
+ 1 # exclude_disabled
+ );
+
+ # here is where it checks for multiple matches
+ if (scalar(@{$users}) == 1) { # exactly one match
+ push(@logins, @{$users}[0]->login);
+
+ # skip confirmation for exact matches
+ next if (lc(@{$users}[0]->login) eq lc($query));
+
+ $matches->{$field}->{$query}->{'status'} = 'success';
+ $need_confirm = 1 if $params->{'confirmuniqueusermatch'};
- # If no match or more than one match has been found for a field
- # expecting only one match (type eq "single"), we set it back to ''
- # so that the caller of this function can still check whether this
- # field was defined or not (and it was if we came here).
- if ($fields->{$field}->{'type'} eq 'single') {
- $data->{$field} = $logins[0] || '';
+ }
+ elsif ((scalar(@{$users}) > 1) && ($params->{'maxusermatches'} != 1)) {
+ $need_confirm = 1;
+ $match_multiple = 1;
+ push(@non_conclusive_fields, $field);
+
+ if ( ($params->{'maxusermatches'})
+ && (scalar(@{$users}) > $params->{'maxusermatches'}))
+ {
+ $matches->{$field}->{$query}->{'status'} = 'trunc';
+ pop @{$users}; # take the last one out
}
- elsif (scalar @logins) {
- $data->{$field} = \@logins;
+ else {
+ $matches->{$field}->{$query}->{'status'} = 'success';
}
+
+ }
+ else {
+ # everything else fails
+ $matchsuccess = 0; # fail
+ push(@non_conclusive_fields, $field);
+ $matches->{$field}->{$query}->{'status'} = 'fail';
+ $need_confirm = 1; # confirmation screen shows failures
+ }
+
+ $matches->{$field}->{$query}->{'users'} = $users;
}
- my $retval;
- if (!$matchsuccess) {
- $retval = USER_MATCH_FAILED;
+ # If no match or more than one match has been found for a field
+ # expecting only one match (type eq "single"), we set it back to ''
+ # so that the caller of this function can still check whether this
+ # field was defined or not (and it was if we came here).
+ if ($fields->{$field}->{'type'} eq 'single') {
+ $data->{$field} = $logins[0] || '';
}
- elsif ($match_multiple) {
- $retval = USER_MATCH_MULTIPLE;
- }
- else {
- $retval = USER_MATCH_SUCCESS;
+ elsif (scalar @logins) {
+ $data->{$field} = \@logins;
}
+ }
- # Skip confirmation if we were told to, or if we don't need to confirm.
- if ($behavior == MATCH_SKIP_CONFIRM || !$need_confirm) {
- return wantarray ? ($retval, \@non_conclusive_fields) : $retval;
- }
+ my $retval;
+ if (!$matchsuccess) {
+ $retval = USER_MATCH_FAILED;
+ }
+ elsif ($match_multiple) {
+ $retval = USER_MATCH_MULTIPLE;
+ }
+ else {
+ $retval = USER_MATCH_SUCCESS;
+ }
+
+ # Skip confirmation if we were told to, or if we don't need to confirm.
+ if ($behavior == MATCH_SKIP_CONFIRM || !$need_confirm) {
+ return wantarray ? ($retval, \@non_conclusive_fields) : $retval;
+ }
- my $template = Bugzilla->template;
- my $cgi = Bugzilla->cgi;
- my $vars = {};
+ my $template = Bugzilla->template;
+ my $cgi = Bugzilla->cgi;
+ my $vars = {};
- $vars->{'script'} = $cgi->url(-relative => 1); # for self-referencing URLs
- $vars->{'fields'} = $fields; # fields being matched
- $vars->{'matches'} = $matches; # matches that were made
- $vars->{'matchsuccess'} = $matchsuccess; # continue or fail
- $vars->{'matchmultiple'} = $match_multiple;
+ $vars->{'script'} = $cgi->url(-relative => 1); # for self-referencing URLs
+ $vars->{'fields'} = $fields; # fields being matched
+ $vars->{'matches'} = $matches; # matches that were made
+ $vars->{'matchsuccess'} = $matchsuccess; # continue or fail
+ $vars->{'matchmultiple'} = $match_multiple;
- print $cgi->header();
+ print $cgi->header();
- $template->process("global/confirm-user-match.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("global/confirm-user-match.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
# Changes in some fields automatically trigger events. The field names are
# from the fielddefs table.
our %names_to_events = (
- 'resolution' => EVT_OPENED_CLOSED,
- 'keywords' => EVT_KEYWORD,
- 'cc' => EVT_CC,
- 'bug_severity' => EVT_PROJ_MANAGEMENT,
- 'priority' => EVT_PROJ_MANAGEMENT,
- 'bug_status' => EVT_PROJ_MANAGEMENT,
- 'target_milestone' => EVT_PROJ_MANAGEMENT,
- 'attachments.description' => EVT_ATTACHMENT_DATA,
- 'attachments.mimetype' => EVT_ATTACHMENT_DATA,
- 'attachments.ispatch' => EVT_ATTACHMENT_DATA,
- 'dependson' => EVT_DEPEND_BLOCK,
- 'blocked' => EVT_DEPEND_BLOCK,
- 'product' => EVT_COMPONENT,
- 'component' => EVT_COMPONENT);
+ 'resolution' => EVT_OPENED_CLOSED,
+ 'keywords' => EVT_KEYWORD,
+ 'cc' => EVT_CC,
+ 'bug_severity' => EVT_PROJ_MANAGEMENT,
+ 'priority' => EVT_PROJ_MANAGEMENT,
+ 'bug_status' => EVT_PROJ_MANAGEMENT,
+ 'target_milestone' => EVT_PROJ_MANAGEMENT,
+ 'attachments.description' => EVT_ATTACHMENT_DATA,
+ 'attachments.mimetype' => EVT_ATTACHMENT_DATA,
+ 'attachments.ispatch' => EVT_ATTACHMENT_DATA,
+ 'dependson' => EVT_DEPEND_BLOCK,
+ 'blocked' => EVT_DEPEND_BLOCK,
+ 'product' => EVT_COMPONENT,
+ 'component' => EVT_COMPONENT
+);
# Returns true if the user wants mail for a given bug change.
# Note: the "+" signs before the constants suppress bareword quoting.
sub wants_bug_mail {
- my $self = shift;
- my ($bug, $relationship, $fieldDiffs, $comments, $dep_mail, $changer) = @_;
-
- # Make a list of the events which have happened during this bug change,
- # from the point of view of this user.
- my %events;
- foreach my $change (@$fieldDiffs) {
- my $fieldName = $change->{field_name};
- # A change to any of the above fields sets the corresponding event
- if (defined($names_to_events{$fieldName})) {
- $events{$names_to_events{$fieldName}} = 1;
- }
- else {
- # Catch-all for any change not caught by a more specific event
- $events{+EVT_OTHER} = 1;
- }
+ my $self = shift;
+ my ($bug, $relationship, $fieldDiffs, $comments, $dep_mail, $changer) = @_;
- # If the user is in a particular role and the value of that role
- # changed, we need the ADDED_REMOVED event.
- if (($fieldName eq "assigned_to" && $relationship == REL_ASSIGNEE) ||
- ($fieldName eq "qa_contact" && $relationship == REL_QA))
- {
- $events{+EVT_ADDED_REMOVED} = 1;
- }
-
- if ($fieldName eq "cc") {
- my $login = $self->login;
- my $inold = ($change->{old} =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
- my $innew = ($change->{new} =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
- if ($inold != $innew)
- {
- $events{+EVT_ADDED_REMOVED} = 1;
- }
- }
- }
+ # Make a list of the events which have happened during this bug change,
+ # from the point of view of this user.
+ my %events;
+ foreach my $change (@$fieldDiffs) {
+ my $fieldName = $change->{field_name};
- if (!$bug->lastdiffed) {
- # Notify about new bugs.
- $events{+EVT_BUG_CREATED} = 1;
-
- # You role is new if the bug itself is.
- # Only makes sense for the assignee, QA contact and the CC list.
- if ($relationship == REL_ASSIGNEE
- || $relationship == REL_QA
- || $relationship == REL_CC)
- {
- $events{+EVT_ADDED_REMOVED} = 1;
- }
+ # A change to any of the above fields sets the corresponding event
+ if (defined($names_to_events{$fieldName})) {
+ $events{$names_to_events{$fieldName}} = 1;
}
-
- if (grep { $_->type == CMT_ATTACHMENT_CREATED } @$comments) {
- $events{+EVT_ATTACHMENT} = 1;
+ else {
+ # Catch-all for any change not caught by a more specific event
+ $events{+EVT_OTHER} = 1;
}
- elsif (defined($$comments[0])) {
- $events{+EVT_COMMENT} = 1;
+
+ # If the user is in a particular role and the value of that role
+ # changed, we need the ADDED_REMOVED event.
+ if ( ($fieldName eq "assigned_to" && $relationship == REL_ASSIGNEE)
+ || ($fieldName eq "qa_contact" && $relationship == REL_QA))
+ {
+ $events{+EVT_ADDED_REMOVED} = 1;
}
-
- # Dependent changed bugmails must have an event to ensure the bugmail is
- # emailed.
- if ($dep_mail) {
- $events{+EVT_DEPEND_BLOCK} = 1;
+
+ if ($fieldName eq "cc") {
+ my $login = $self->login;
+ my $inold = ($change->{old} =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
+ my $innew = ($change->{new} =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
+ if ($inold != $innew) {
+ $events{+EVT_ADDED_REMOVED} = 1;
+ }
}
+ }
- my @event_list = keys %events;
-
- my $wants_mail = $self->wants_mail(\@event_list, $relationship);
-
- # The negative events are handled separately - they can't be incorporated
- # into the first wants_mail call, because they are of the opposite sense.
- #
- # We do them separately because if _any_ of them are set, we don't want
- # the mail.
- if ($wants_mail && $changer && ($self->id == $changer->id)) {
- $wants_mail &= $self->wants_mail([EVT_CHANGED_BY_ME], $relationship);
- }
-
- if ($wants_mail && $bug->bug_status eq 'UNCONFIRMED') {
- $wants_mail &= $self->wants_mail([EVT_UNCONFIRMED], $relationship);
+ if (!$bug->lastdiffed) {
+
+ # Notify about new bugs.
+ $events{+EVT_BUG_CREATED} = 1;
+
+ # You role is new if the bug itself is.
+ # Only makes sense for the assignee, QA contact and the CC list.
+ if ( $relationship == REL_ASSIGNEE
+ || $relationship == REL_QA
+ || $relationship == REL_CC)
+ {
+ $events{+EVT_ADDED_REMOVED} = 1;
}
-
- return $wants_mail;
+ }
+
+ if (grep { $_->type == CMT_ATTACHMENT_CREATED } @$comments) {
+ $events{+EVT_ATTACHMENT} = 1;
+ }
+ elsif (defined($$comments[0])) {
+ $events{+EVT_COMMENT} = 1;
+ }
+
+ # Dependent changed bugmails must have an event to ensure the bugmail is
+ # emailed.
+ if ($dep_mail) {
+ $events{+EVT_DEPEND_BLOCK} = 1;
+ }
+
+ my @event_list = keys %events;
+
+ my $wants_mail = $self->wants_mail(\@event_list, $relationship);
+
+ # The negative events are handled separately - they can't be incorporated
+ # into the first wants_mail call, because they are of the opposite sense.
+ #
+ # We do them separately because if _any_ of them are set, we don't want
+ # the mail.
+ if ($wants_mail && $changer && ($self->id == $changer->id)) {
+ $wants_mail &= $self->wants_mail([EVT_CHANGED_BY_ME], $relationship);
+ }
+
+ if ($wants_mail && $bug->bug_status eq 'UNCONFIRMED') {
+ $wants_mail &= $self->wants_mail([EVT_UNCONFIRMED], $relationship);
+ }
+
+ return $wants_mail;
}
# Returns true if the user wants mail for a given set of events.
sub wants_mail {
- my $self = shift;
- my ($events, $relationship) = @_;
-
- # Don't send any mail, ever, if account is disabled
- # XXX Temporary Compatibility Change 1 of 2:
- # This code is disabled for the moment to make the behaviour like the old
- # system, which sent bugmail to disabled accounts.
- # return 0 if $self->{'disabledtext'};
-
- # No mail if there are no events
- return 0 if !scalar(@$events);
-
- # If a relationship isn't given, default to REL_ANY.
- if (!defined($relationship)) {
- $relationship = REL_ANY;
- }
+ my $self = shift;
+ my ($events, $relationship) = @_;
+
+ # Don't send any mail, ever, if account is disabled
+ # XXX Temporary Compatibility Change 1 of 2:
+ # This code is disabled for the moment to make the behaviour like the old
+ # system, which sent bugmail to disabled accounts.
+ # return 0 if $self->{'disabledtext'};
+
+ # No mail if there are no events
+ return 0 if !scalar(@$events);
- # Skip DB query if relationship is explicit
- return 1 if $relationship == REL_GLOBAL_WATCHER;
+ # If a relationship isn't given, default to REL_ANY.
+ if (!defined($relationship)) {
+ $relationship = REL_ANY;
+ }
- my $wants_mail = grep { $self->mail_settings->{$relationship}{$_} } @$events;
- return $wants_mail ? 1 : 0;
+ # Skip DB query if relationship is explicit
+ return 1 if $relationship == REL_GLOBAL_WATCHER;
+
+ my $wants_mail = grep { $self->mail_settings->{$relationship}{$_} } @$events;
+ return $wants_mail ? 1 : 0;
}
sub mail_settings {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- if (!defined $self->{'mail_settings'}) {
- my $data =
- $dbh->selectall_arrayref('SELECT relationship, event FROM email_setting
- WHERE user_id = ?', undef, $self->id);
- my %mail;
- # The hash is of the form $mail{$relationship}{$event} = 1.
- $mail{$_->[0]}{$_->[1]} = 1 foreach @$data;
-
- $self->{'mail_settings'} = \%mail;
- }
- return $self->{'mail_settings'};
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{'mail_settings'}) {
+ my $data = $dbh->selectall_arrayref(
+ 'SELECT relationship, event FROM email_setting
+ WHERE user_id = ?', undef, $self->id
+ );
+ my %mail;
+
+ # The hash is of the form $mail{$relationship}{$event} = 1.
+ $mail{$_->[0]}{$_->[1]} = 1 foreach @$data;
+
+ $self->{'mail_settings'} = \%mail;
+ }
+ return $self->{'mail_settings'};
}
sub has_audit_entries {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!exists $self->{'has_audit_entries'}) {
- $self->{'has_audit_entries'} =
- $dbh->selectrow_array('SELECT 1 FROM audit_log WHERE user_id = ? ' .
- $dbh->sql_limit(1), undef, $self->id);
- }
- return $self->{'has_audit_entries'};
+ if (!exists $self->{'has_audit_entries'}) {
+ $self->{'has_audit_entries'}
+ = $dbh->selectrow_array(
+ 'SELECT 1 FROM audit_log WHERE user_id = ? ' . $dbh->sql_limit(1),
+ undef, $self->id);
+ }
+ return $self->{'has_audit_entries'};
}
sub is_insider {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{'is_insider'}) {
- my $insider_group = Bugzilla->params->{'insidergroup'};
- $self->{'is_insider'} =
- ($insider_group && $self->in_group($insider_group)) ? 1 : 0;
- }
- return $self->{'is_insider'};
+ if (!defined $self->{'is_insider'}) {
+ my $insider_group = Bugzilla->params->{'insidergroup'};
+ $self->{'is_insider'}
+ = ($insider_group && $self->in_group($insider_group)) ? 1 : 0;
+ }
+ return $self->{'is_insider'};
}
sub is_global_watcher {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{'is_global_watcher'}) {
- my @watchers = split(/[,;]+/, Bugzilla->params->{'globalwatchers'});
- $self->{'is_global_watcher'} = scalar(grep { $_ eq $self->login } @watchers) ? 1 : 0;
- }
- return $self->{'is_global_watcher'};
+ if (!defined $self->{'is_global_watcher'}) {
+ my @watchers = split(/[,;]+/, Bugzilla->params->{'globalwatchers'});
+ $self->{'is_global_watcher'}
+ = scalar(grep { $_ eq $self->login } @watchers) ? 1 : 0;
+ }
+ return $self->{'is_global_watcher'};
}
sub is_timetracker {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{'is_timetracker'}) {
- my $tt_group = Bugzilla->params->{'timetrackinggroup'};
- $self->{'is_timetracker'} =
- ($tt_group && $self->in_group($tt_group)) ? 1 : 0;
- }
- return $self->{'is_timetracker'};
+ if (!defined $self->{'is_timetracker'}) {
+ my $tt_group = Bugzilla->params->{'timetrackinggroup'};
+ $self->{'is_timetracker'} = ($tt_group && $self->in_group($tt_group)) ? 1 : 0;
+ }
+ return $self->{'is_timetracker'};
}
sub can_tag_comments {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{'can_tag_comments'}) {
- my $group = Bugzilla->params->{'comment_taggers_group'};
- $self->{'can_tag_comments'} =
- ($group && $self->in_group($group)) ? 1 : 0;
- }
- return $self->{'can_tag_comments'};
+ if (!defined $self->{'can_tag_comments'}) {
+ my $group = Bugzilla->params->{'comment_taggers_group'};
+ $self->{'can_tag_comments'} = ($group && $self->in_group($group)) ? 1 : 0;
+ }
+ return $self->{'can_tag_comments'};
}
sub get_userlist {
- my $self = shift;
-
- return $self->{'userlist'} if defined $self->{'userlist'};
-
- my $dbh = Bugzilla->dbh;
- my $query = "SELECT DISTINCT login_name, realname,";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= " COUNT(group_id) ";
- } else {
- $query .= " 1 ";
- }
- $query .= "FROM profiles ";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= "LEFT JOIN user_group_map " .
- "ON user_group_map.user_id = userid AND isbless = 0 " .
- "AND group_id IN(" .
- join(', ', (-1, @{$self->visible_groups_inherited})) . ")";
- }
- $query .= " WHERE is_enabled = 1 ";
- $query .= $dbh->sql_group_by('userid', 'login_name, realname');
-
- my $sth = $dbh->prepare($query);
- $sth->execute;
-
- my @userlist;
- while (my($login, $name, $visible) = $sth->fetchrow_array) {
- push @userlist, {
- login => $login,
- identity => $name ? "$name <$login>" : $login,
- visible => $visible,
- };
- }
- @userlist = sort { lc $$a{'identity'} cmp lc $$b{'identity'} } @userlist;
-
- $self->{'userlist'} = \@userlist;
- return $self->{'userlist'};
+ my $self = shift;
+
+ return $self->{'userlist'} if defined $self->{'userlist'};
+
+ my $dbh = Bugzilla->dbh;
+ my $query = "SELECT DISTINCT login_name, realname,";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query .= " COUNT(group_id) ";
+ }
+ else {
+ $query .= " 1 ";
+ }
+ $query .= "FROM profiles ";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query
+ .= "LEFT JOIN user_group_map "
+ . "ON user_group_map.user_id = userid AND isbless = 0 "
+ . "AND group_id IN("
+ . join(', ', (-1, @{$self->visible_groups_inherited})) . ")";
+ }
+ $query .= " WHERE is_enabled = 1 ";
+ $query .= $dbh->sql_group_by('userid', 'login_name, realname');
+
+ my $sth = $dbh->prepare($query);
+ $sth->execute;
+
+ my @userlist;
+ while (my ($login, $name, $visible) = $sth->fetchrow_array) {
+ push @userlist,
+ {
+ login => $login,
+ identity => $name ? "$name <$login>" : $login,
+ visible => $visible,
+ };
+ }
+ @userlist = sort { lc $$a{'identity'} cmp lc $$b{'identity'} } @userlist;
+
+ $self->{'userlist'} = \@userlist;
+ return $self->{'userlist'};
}
sub create {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $dbh = Bugzilla->dbh;
-
- $dbh->bz_start_transaction();
-
- my $user = $class->SUPER::create(@_);
-
- # Turn on all email for the new user
- require Bugzilla::BugMail;
- my %relationships = Bugzilla::BugMail::relationships();
- foreach my $rel (keys %relationships) {
- foreach my $event (POS_EVENTS, NEG_EVENTS) {
- # These "exceptions" define the default email preferences.
- #
- # We enable mail unless the change was made by the user, or it's
- # just a CC list addition and the user is not the reporter.
- next if ($event == EVT_CHANGED_BY_ME);
- next if (($event == EVT_CC) && ($rel != REL_REPORTER));
-
- $dbh->do('INSERT INTO email_setting (user_id, relationship, event)
- VALUES (?, ?, ?)', undef, ($user->id, $rel, $event));
- }
- }
-
- foreach my $event (GLOBAL_EVENTS) {
- $dbh->do('INSERT INTO email_setting (user_id, relationship, event)
- VALUES (?, ?, ?)', undef, ($user->id, REL_ANY, $event));
- }
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+
+ my $user = $class->SUPER::create(@_);
+
+ # Turn on all email for the new user
+ require Bugzilla::BugMail;
+ my %relationships = Bugzilla::BugMail::relationships();
+ foreach my $rel (keys %relationships) {
+ foreach my $event (POS_EVENTS, NEG_EVENTS) {
+
+ # These "exceptions" define the default email preferences.
+ #
+ # We enable mail unless the change was made by the user, or it's
+ # just a CC list addition and the user is not the reporter.
+ next if ($event == EVT_CHANGED_BY_ME);
+ next if (($event == EVT_CC) && ($rel != REL_REPORTER));
+
+ $dbh->do(
+ 'INSERT INTO email_setting (user_id, relationship, event)
+ VALUES (?, ?, ?)', undef, ($user->id, $rel, $event)
+ );
+ }
+ }
+
+ foreach my $event (GLOBAL_EVENTS) {
+ $dbh->do(
+ 'INSERT INTO email_setting (user_id, relationship, event)
+ VALUES (?, ?, ?)', undef, ($user->id, REL_ANY, $event)
+ );
+ }
- $user->derive_regexp_groups();
+ $user->derive_regexp_groups();
- # Add the creation date to the profiles_activity table.
- # $who is the user who created the new user account, i.e. either an
- # admin or the new user himself.
- my $who = Bugzilla->user->id || $user->id;
- my $creation_date_fieldid = get_field_id('creation_ts');
+ # Add the creation date to the profiles_activity table.
+ # $who is the user who created the new user account, i.e. either an
+ # admin or the new user himself.
+ my $who = Bugzilla->user->id || $user->id;
+ my $creation_date_fieldid = get_field_id('creation_ts');
- $dbh->do('INSERT INTO profiles_activity
+ $dbh->do(
+ 'INSERT INTO profiles_activity
(userid, who, profiles_when, fieldid, newvalue)
- VALUES (?, ?, NOW(), ?, NOW())',
- undef, ($user->id, $who, $creation_date_fieldid));
+ VALUES (?, ?, NOW(), ?, NOW())', undef,
+ ($user->id, $who, $creation_date_fieldid)
+ );
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
- # Return the newly created user account.
- return $user;
+ # Return the newly created user account.
+ return $user;
}
###########################
@@ -2305,44 +2396,45 @@ sub create {
###########################
sub account_is_locked_out {
- my $self = shift;
- my $login_failures = scalar @{ $self->account_ip_login_failures };
- return $login_failures >= MAX_LOGIN_ATTEMPTS ? 1 : 0;
+ my $self = shift;
+ my $login_failures = scalar @{$self->account_ip_login_failures};
+ return $login_failures >= MAX_LOGIN_ATTEMPTS ? 1 : 0;
}
sub note_login_failure {
- my $self = shift;
- my $ip_addr = remote_ip();
- trick_taint($ip_addr);
- Bugzilla->dbh->do("INSERT INTO login_failure (user_id, ip_addr, login_time)
- VALUES (?, ?, LOCALTIMESTAMP(0))",
- undef, $self->id, $ip_addr);
- delete $self->{account_ip_login_failures};
+ my $self = shift;
+ my $ip_addr = remote_ip();
+ trick_taint($ip_addr);
+ Bugzilla->dbh->do(
+ "INSERT INTO login_failure (user_id, ip_addr, login_time)
+ VALUES (?, ?, LOCALTIMESTAMP(0))", undef, $self->id, $ip_addr
+ );
+ delete $self->{account_ip_login_failures};
}
sub clear_login_failures {
- my $self = shift;
- my $ip_addr = remote_ip();
- trick_taint($ip_addr);
- Bugzilla->dbh->do(
- 'DELETE FROM login_failure WHERE user_id = ? AND ip_addr = ?',
- undef, $self->id, $ip_addr);
- delete $self->{account_ip_login_failures};
+ my $self = shift;
+ my $ip_addr = remote_ip();
+ trick_taint($ip_addr);
+ Bugzilla->dbh->do('DELETE FROM login_failure WHERE user_id = ? AND ip_addr = ?',
+ undef, $self->id, $ip_addr);
+ delete $self->{account_ip_login_failures};
}
sub account_ip_login_failures {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $time = $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-',
- LOGIN_LOCKOUT_INTERVAL, 'MINUTE');
- my $ip_addr = remote_ip();
- trick_taint($ip_addr);
- $self->{account_ip_login_failures} ||= Bugzilla->dbh->selectall_arrayref(
- "SELECT login_time, ip_addr, user_id FROM login_failure
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $time = $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', LOGIN_LOCKOUT_INTERVAL,
+ 'MINUTE');
+ my $ip_addr = remote_ip();
+ trick_taint($ip_addr);
+ $self->{account_ip_login_failures} ||= Bugzilla->dbh->selectall_arrayref(
+ "SELECT login_time, ip_addr, user_id FROM login_failure
WHERE user_id = ? AND login_time > $time
AND ip_addr = ?
- ORDER BY login_time", {Slice => {}}, $self->id, $ip_addr);
- return $self->{account_ip_login_failures};
+ ORDER BY login_time", {Slice => {}}, $self->id, $ip_addr
+ );
+ return $self->{account_ip_login_failures};
}
###############
@@ -2350,145 +2442,162 @@ sub account_ip_login_failures {
###############
sub is_available_username {
- my ($username, $old_username) = @_;
-
- if(login_to_id($username) != 0) {
- return 0;
- }
+ my ($username, $old_username) = @_;
- my $dbh = Bugzilla->dbh;
- # $username is safe because it is only used in SELECT placeholders.
- trick_taint($username);
- # Reject if the new login is part of an email change which is
- # still in progress
- #
- # substring/locate stuff: bug 165221; this used to use regexes, but that
- # was unsafe and required weird escaping; using substring to pull out
- # the new/old email addresses and sql_position() to find the delimiter (':')
- # is cleaner/safer
- my ($tokentype, $eventdata) = $dbh->selectrow_array(
- "SELECT tokentype, eventdata
+ if (login_to_id($username) != 0) {
+ return 0;
+ }
+
+ my $dbh = Bugzilla->dbh;
+
+ # $username is safe because it is only used in SELECT placeholders.
+ trick_taint($username);
+
+ # Reject if the new login is part of an email change which is
+ # still in progress
+ #
+ # substring/locate stuff: bug 165221; this used to use regexes, but that
+ # was unsafe and required weird escaping; using substring to pull out
+ # the new/old email addresses and sql_position() to find the delimiter (':')
+ # is cleaner/safer
+ my ($tokentype, $eventdata) = $dbh->selectrow_array(
+ "SELECT tokentype, eventdata
FROM tokens
WHERE (tokentype = 'emailold'
- AND SUBSTRING(eventdata, 1, (" .
- $dbh->sql_position(q{':'}, 'eventdata') . "- 1)) = ?)
+ AND SUBSTRING(eventdata, 1, ("
+ . $dbh->sql_position(q{':'}, 'eventdata') . "- 1)) = ?)
OR (tokentype = 'emailnew'
- AND SUBSTRING(eventdata, (" .
- $dbh->sql_position(q{':'}, 'eventdata') . "+ 1), LENGTH(eventdata)) = ?)",
- undef, ($username, $username));
-
- if ($eventdata) {
- # Allow thru owner of token
- if ($old_username
- && (($tokentype eq 'emailnew' && $eventdata eq "$old_username:$username")
- || ($tokentype eq 'emailold' && $eventdata eq "$username:$old_username")))
- {
- return 1;
- }
- return 0;
+ AND SUBSTRING(eventdata, ("
+ . $dbh->sql_position(q{':'}, 'eventdata')
+ . "+ 1), LENGTH(eventdata)) = ?)",
+ undef, ($username, $username)
+ );
+
+ if ($eventdata) {
+
+ # Allow thru owner of token
+ if (
+ $old_username
+ && ( ($tokentype eq 'emailnew' && $eventdata eq "$old_username:$username")
+ || ($tokentype eq 'emailold' && $eventdata eq "$username:$old_username"))
+ )
+ {
+ return 1;
}
+ return 0;
+ }
- return 1;
+ return 1;
}
sub check_account_creation_enabled {
- my $self = shift;
+ my $self = shift;
- # If we're using e.g. LDAP for login, then we can't create a new account.
- $self->authorizer->user_can_create_account
- || ThrowUserError('auth_cant_create_account');
+ # If we're using e.g. LDAP for login, then we can't create a new account.
+ $self->authorizer->user_can_create_account
+ || ThrowUserError('auth_cant_create_account');
- Bugzilla->params->{'createemailregexp'}
- || ThrowUserError('account_creation_disabled');
+ Bugzilla->params->{'createemailregexp'}
+ || ThrowUserError('account_creation_disabled');
}
sub check_and_send_account_creation_confirmation {
- my ($self, $login) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($self, $login) = @_;
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction;
+ $dbh->bz_start_transaction;
- $login = $self->check_login_name($login);
- my $creation_regexp = Bugzilla->params->{'createemailregexp'};
+ $login = $self->check_login_name($login);
+ my $creation_regexp = Bugzilla->params->{'createemailregexp'};
- if ($login !~ /$creation_regexp/i) {
- ThrowUserError('account_creation_restricted');
- }
+ if ($login !~ /$creation_regexp/i) {
+ ThrowUserError('account_creation_restricted');
+ }
- # Allow extensions to do extra checks.
- Bugzilla::Hook::process('user_check_account_creation', { login => $login });
+ # Allow extensions to do extra checks.
+ Bugzilla::Hook::process('user_check_account_creation', {login => $login});
- # Create and send a token for this new account.
- require Bugzilla::Token;
- Bugzilla::Token::issue_new_user_account_token($login);
+ # Create and send a token for this new account.
+ require Bugzilla::Token;
+ Bugzilla::Token::issue_new_user_account_token($login);
- $dbh->bz_commit_transaction;
+ $dbh->bz_commit_transaction;
}
# This is used in a few performance-critical areas where we don't want to
# do check() and pull all the user data from the database.
sub login_to_id {
- my ($login, $throw_error) = @_;
- my $dbh = Bugzilla->dbh;
- my $cache = Bugzilla->request_cache->{user_login_to_id} ||= {};
-
- # We cache lookups because this function showed up as taking up a
- # significant amount of time in profiles of xt/search.t. However,
- # for users that don't exist, we re-do the check every time, because
- # otherwise we break is_available_username.
- my $user_id;
- if (defined $cache->{$login}) {
- $user_id = $cache->{$login};
- }
- else {
- # No need to validate $login -- it will be used by the following SELECT
- # statement only, so it's safe to simply trick_taint.
- trick_taint($login);
- $user_id = $dbh->selectrow_array(
- "SELECT userid FROM profiles
- WHERE " . $dbh->sql_istrcmp('login_name', '?'), undef, $login);
- $cache->{$login} = $user_id;
- }
-
- if ($user_id) {
- return $user_id;
- } elsif ($throw_error) {
- ThrowUserError('invalid_username', { name => $login });
- } else {
- return 0;
- }
+ my ($login, $throw_error) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $cache = Bugzilla->request_cache->{user_login_to_id} ||= {};
+
+ # We cache lookups because this function showed up as taking up a
+ # significant amount of time in profiles of xt/search.t. However,
+ # for users that don't exist, we re-do the check every time, because
+ # otherwise we break is_available_username.
+ my $user_id;
+ if (defined $cache->{$login}) {
+ $user_id = $cache->{$login};
+ }
+ else {
+ # No need to validate $login -- it will be used by the following SELECT
+ # statement only, so it's safe to simply trick_taint.
+ trick_taint($login);
+ $user_id = $dbh->selectrow_array(
+ "SELECT userid FROM profiles
+ WHERE " . $dbh->sql_istrcmp('login_name', '?'), undef, $login
+ );
+ $cache->{$login} = $user_id;
+ }
+
+ if ($user_id) {
+ return $user_id;
+ }
+ elsif ($throw_error) {
+ ThrowUserError('invalid_username', {name => $login});
+ }
+ else {
+ return 0;
+ }
}
sub validate_password {
- my $check = validate_password_check(@_);
- ThrowUserError($check) if $check;
- return 1;
+ my $check = validate_password_check(@_);
+ ThrowUserError($check) if $check;
+ return 1;
}
sub validate_password_check {
- my ($password, $matchpassword) = @_;
-
- if (length($password) < USER_PASSWORD_MIN_LENGTH) {
- return 'password_too_short';
- } elsif ((defined $matchpassword) && ($password ne $matchpassword)) {
- return 'passwords_dont_match';
- }
-
- my $complexity_level = Bugzilla->params->{password_complexity};
- if ($complexity_level eq 'letters_numbers_specialchars') {
- return 'password_not_complex'
- if ($password !~ /[[:alpha:]]/ || $password !~ /\d/ || $password !~ /[[:punct:]]/);
- } elsif ($complexity_level eq 'letters_numbers') {
- return 'password_not_complex'
- if ($password !~ /[[:lower:]]/ || $password !~ /[[:upper:]]/ || $password !~ /\d/);
- } elsif ($complexity_level eq 'mixed_letters') {
- return 'password_not_complex'
- if ($password !~ /[[:lower:]]/ || $password !~ /[[:upper:]]/);
- }
-
- # Having done these checks makes us consider the password untainted.
- trick_taint($_[0]);
- return;
+ my ($password, $matchpassword) = @_;
+
+ if (length($password) < USER_PASSWORD_MIN_LENGTH) {
+ return 'password_too_short';
+ }
+ elsif ((defined $matchpassword) && ($password ne $matchpassword)) {
+ return 'passwords_dont_match';
+ }
+
+ my $complexity_level = Bugzilla->params->{password_complexity};
+ if ($complexity_level eq 'letters_numbers_specialchars') {
+ return 'password_not_complex'
+ if ($password !~ /[[:alpha:]]/
+ || $password !~ /\d/
+ || $password !~ /[[:punct:]]/);
+ }
+ elsif ($complexity_level eq 'letters_numbers') {
+ return 'password_not_complex'
+ if ($password !~ /[[:lower:]]/
+ || $password !~ /[[:upper:]]/
+ || $password !~ /\d/);
+ }
+ elsif ($complexity_level eq 'mixed_letters') {
+ return 'password_not_complex'
+ if ($password !~ /[[:lower:]]/ || $password !~ /[[:upper:]]/);
+ }
+
+ # Having done these checks makes us consider the password untainted.
+ trick_taint($_[0]);
+ return;
}
diff --git a/Bugzilla/User/APIKey.pm b/Bugzilla/User/APIKey.pm
index d268a0a93..d2e337c5e 100644
--- a/Bugzilla/User/APIKey.pm
+++ b/Bugzilla/User/APIKey.pm
@@ -20,52 +20,54 @@ use Bugzilla::Util qw(generate_random_password trim);
# Overriden Constants that are used as methods
#####################################################################
-use constant DB_TABLE => 'user_api_keys';
-use constant DB_COLUMNS => qw(
- id
- user_id
- api_key
- description
- revoked
- last_used
+use constant DB_TABLE => 'user_api_keys';
+use constant DB_COLUMNS => qw(
+ id
+ user_id
+ api_key
+ description
+ revoked
+ last_used
);
use constant UPDATE_COLUMNS => qw(description revoked last_used);
use constant VALIDATORS => {
- api_key => \&_check_api_key,
- description => \&_check_description,
- revoked => \&Bugzilla::Object::check_boolean,
+ api_key => \&_check_api_key,
+ description => \&_check_description,
+ revoked => \&Bugzilla::Object::check_boolean,
};
-use constant LIST_ORDER => 'id';
-use constant NAME_FIELD => 'api_key';
+use constant LIST_ORDER => 'id';
+use constant NAME_FIELD => 'api_key';
# turn off auditing and exclude these objects from memcached
-use constant { AUDIT_CREATES => 0,
- AUDIT_UPDATES => 0,
- AUDIT_REMOVES => 0,
- USE_MEMCACHED => 0 };
+use constant {
+ AUDIT_CREATES => 0,
+ AUDIT_UPDATES => 0,
+ AUDIT_REMOVES => 0,
+ USE_MEMCACHED => 0
+};
# Accessors
-sub id { return $_[0]->{id} }
-sub user_id { return $_[0]->{user_id} }
-sub api_key { return $_[0]->{api_key} }
-sub description { return $_[0]->{description} }
-sub revoked { return $_[0]->{revoked} }
-sub last_used { return $_[0]->{last_used} }
+sub id { return $_[0]->{id} }
+sub user_id { return $_[0]->{user_id} }
+sub api_key { return $_[0]->{api_key} }
+sub description { return $_[0]->{description} }
+sub revoked { return $_[0]->{revoked} }
+sub last_used { return $_[0]->{last_used} }
# Helpers
sub user {
- my $self = shift;
- $self->{user} //= Bugzilla::User->new({name => $self->user_id, cache => 1});
- return $self->{user};
+ my $self = shift;
+ $self->{user} //= Bugzilla::User->new({name => $self->user_id, cache => 1});
+ return $self->{user};
}
sub update_last_used {
- my $self = shift;
- my $timestamp = shift
- || Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- $self->set('last_used', $timestamp);
- $self->update;
+ my $self = shift;
+ my $timestamp
+ = shift || Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $self->set('last_used', $timestamp);
+ $self->update;
}
# Setters
@@ -73,8 +75,8 @@ sub set_description { $_[0]->set('description', $_[1]); }
sub set_revoked { $_[0]->set('revoked', $_[1]); }
# Validators
-sub _check_api_key { return generate_random_password(40); }
-sub _check_description { return trim($_[1]) || ''; }
+sub _check_api_key { return generate_random_password(40); }
+sub _check_description { return trim($_[1]) || ''; }
1;
__END__
diff --git a/Bugzilla/User/Setting.pm b/Bugzilla/User/Setting.pm
index aece3b7de..67f5c5684 100644
--- a/Bugzilla/User/Setting.pm
+++ b/Bugzilla/User/Setting.pm
@@ -17,10 +17,10 @@ use parent qw(Exporter);
# Module stuff
@Bugzilla::User::Setting::EXPORT = qw(
- get_all_settings
- get_defaults
- add_setting
- clear_settings_cache
+ get_all_settings
+ get_defaults
+ add_setting
+ clear_settings_cache
);
use Bugzilla::Error;
@@ -31,88 +31,84 @@ use Bugzilla::Util qw(trick_taint get_text);
###############################
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
+ 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
+ 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;
+ WHERE name = ?}, undef, $setting_name
+ );
}
- bless($self, $class);
- $self->{'_setting_name'} = $setting_name;
- $self->{'_user_id'} = $user_id;
+ $self->{'is_enabled'} = $is_enabled;
+ $self->{'default_value'} = $default;
- return $self;
+ # 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;
}
###############################
@@ -120,191 +116,203 @@ sub new {
###############################
sub add_setting {
- my ($name, $values, $default_value, $subclass, $force_check,
- $silently) = @_;
- my $dbh = Bugzilla->dbh;
-
- my $exists = _setting_exists($name);
- return if ($exists && !$force_check);
-
- ($name && length( $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);
- }
- }
- elsif (!$silently) {
- 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 ($name, $values, $default_value, $subclass, $force_check, $silently) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $exists = _setting_exists($name);
+ return if ($exists && !$force_check);
+
+ ($name && length($default_value // ''))
+ || ThrowCodeError("setting_info_invalid");
- my $sth = $dbh->prepare(q{INSERT INTO setting_value (name, value, sortindex)
- VALUES (?, ?, ?)});
+ if ($exists) {
- my $sortindex = 5;
- foreach my $key (@$values){
- $sth->execute($name, $key, $sortindex);
- $sortindex += 5;
+ # 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
+ );
}
+ }
+ elsif (!$silently) {
+ 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 = {};
- my $dbh = Bugzilla->dbh;
-
- my $cache_key = "user_settings.$user_id";
- my $rows = Bugzilla->memcached->get_config({ key => $cache_key });
- if (!$rows) {
- $rows = $dbh->selectall_arrayref(
- q{SELECT name, default_value, is_enabled, setting_value, subclass
+ my ($user_id) = @_;
+ my $settings = {};
+ my $dbh = Bugzilla->dbh;
+
+ my $cache_key = "user_settings.$user_id";
+ my $rows = Bugzilla->memcached->get_config({key => $cache_key});
+ if (!$rows) {
+ $rows = $dbh->selectall_arrayref(
+ q{SELECT name, default_value, is_enabled, setting_value, subclass
FROM setting
LEFT JOIN profile_setting
ON setting.name = profile_setting.setting_name
- AND profile_setting.user_id = ?}, undef, ($user_id));
- Bugzilla->memcached->set_config({ key => $cache_key, data => $rows });
- }
+ AND profile_setting.user_id = ?}, undef, ($user_id)
+ );
+ Bugzilla->memcached->set_config({key => $cache_key, data => $rows});
+ }
- foreach my $row (@$rows) {
- my ($name, $default_value, $is_enabled, $value, $subclass) = @$row;
+ foreach my $row (@$rows) {
+ my ($name, $default_value, $is_enabled, $value, $subclass) = @$row;
- my $is_default;
+ 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);
+ 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;
+ return $settings;
}
sub clear_settings_cache {
- my ($user_id) = @_;
- Bugzilla->memcached->clear_config({ key => "user_settings.$user_id" });
+ my ($user_id) = @_;
+ Bugzilla->memcached->clear_config({key => "user_settings.$user_id"});
}
sub get_defaults {
- my ($user_id) = @_;
- my $dbh = Bugzilla->dbh;
- my $default_settings = {};
+ my ($user_id) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $default_settings = {};
- $user_id ||= 0;
+ $user_id ||= 0;
- my $rows = $dbh->selectall_arrayref(q{SELECT name, default_value, is_enabled, subclass
- FROM setting});
+ my $rows = $dbh->selectall_arrayref(
+ q{SELECT name, default_value, is_enabled, subclass
+ FROM setting}
+ );
- foreach my $row (@$rows) {
- my ($name, $default_value, $is_enabled, $subclass) = @$row;
+ foreach my $row (@$rows) {
+ my ($name, $default_value, $is_enabled, $subclass) = @$row;
- $default_settings->{$name} = new Bugzilla::User::Setting(
- $name, $user_id, $is_enabled, $default_value, $default_value, 1,
- $subclass);
- }
+ $default_settings->{$name}
+ = new Bugzilla::User::Setting($name, $user_id, $is_enabled, $default_value,
+ $default_value, 1, $subclass);
+ }
- return $default_settings;
+ return $default_settings;
}
sub set_default {
- my ($setting_name, $default_value, $is_enabled) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($setting_name, $default_value, $is_enabled) = @_;
+ my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare(q{UPDATE setting
+ my $sth = $dbh->prepare(q{UPDATE setting
SET default_value = ?, is_enabled = ?
- WHERE name = ?});
- $sth->execute($default_value, $is_enabled, $setting_name);
+ 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;
+ 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) = @_;
+ my ($self) = @_;
- return $self->{'legal_values'} if defined $self->{'legal_values'};
+ return $self->{'legal_values'} if defined $self->{'legal_values'};
- my $dbh = Bugzilla->dbh;
- $self->{'legal_values'} = $dbh->selectcol_arrayref(
- q{SELECT value
+ 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'});
+ ORDER BY sortindex}, undef, $self->{'_setting_name'}
+ );
- return $self->{'legal_values'};
+ 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]});
- }
+ 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 ($self) = @_;
- my $dbh = Bugzilla->dbh;
- my $sth = $dbh->do(q{ DELETE
+ 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;
+ 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;
+ my ($self, $value) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $query;
- if ($self->{'is_default'}) {
- $query = q{INSERT INTO profile_setting
+ if ($self->{'is_default'}) {
+ $query = q{INSERT INTO profile_setting
(setting_value, setting_name, user_id)
VALUES (?,?,?)};
- } else {
- $query = q{UPDATE profile_setting
+ }
+ 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'});
+ }
+ $dbh->do($query, undef, $value, $self->{'_setting_name'}, $self->{'_user_id'});
- $self->{'value'} = $value;
- $self->{'is_default'} = 0;
+ $self->{'value'} = $value;
+ $self->{'is_default'} = 0;
}
-
1;
__END__
diff --git a/Bugzilla/User/Setting/Lang.pm b/Bugzilla/User/Setting/Lang.pm
index d980b7a92..d1aeb3421 100644
--- a/Bugzilla/User/Setting/Lang.pm
+++ b/Bugzilla/User/Setting/Lang.pm
@@ -16,11 +16,11 @@ use parent qw(Bugzilla::User::Setting);
use Bugzilla::Constants;
sub legal_values {
- my ($self) = @_;
+ my ($self) = @_;
- return $self->{'legal_values'} if defined $self->{'legal_values'};
+ return $self->{'legal_values'} if defined $self->{'legal_values'};
- return $self->{'legal_values'} = Bugzilla->languages;
+ return $self->{'legal_values'} = Bugzilla->languages;
}
1;
diff --git a/Bugzilla/User/Setting/Skin.pm b/Bugzilla/User/Setting/Skin.pm
index 7b0688c0c..0447b02ab 100644
--- a/Bugzilla/User/Setting/Skin.pm
+++ b/Bugzilla/User/Setting/Skin.pm
@@ -21,24 +21,26 @@ use File::Basename;
use constant BUILTIN_SKIN_NAMES => ['standard'];
sub legal_values {
- my ($self) = @_;
+ my ($self) = @_;
- return $self->{'legal_values'} if defined $self->{'legal_values'};
+ 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)};
+ my $dirbase = bz_locations()->{'skinsdir'} . '/contrib';
- foreach my $direntry (glob(catdir($dirbase, '*'))) {
- if (-d $direntry) {
- next if basename($direntry) =~ /^cvs$/i;
- # Stylesheet set found
- push(@legal_values, basename($direntry));
- }
+ # 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) {
+ next if basename($direntry) =~ /^cvs$/i;
+
+ # Stylesheet set found
+ push(@legal_values, basename($direntry));
}
+ }
- return $self->{'legal_values'} = \@legal_values;
+ return $self->{'legal_values'} = \@legal_values;
}
1;
diff --git a/Bugzilla/User/Setting/Timezone.pm b/Bugzilla/User/Setting/Timezone.pm
index 8959d1dda..b6b2503b5 100644
--- a/Bugzilla/User/Setting/Timezone.pm
+++ b/Bugzilla/User/Setting/Timezone.pm
@@ -18,19 +18,21 @@ use parent qw(Bugzilla::User::Setting);
use Bugzilla::Constants;
sub legal_values {
- my ($self) = @_;
+ my ($self) = @_;
- return $self->{'legal_values'} if defined $self->{'legal_values'};
+ return $self->{'legal_values'} if defined $self->{'legal_values'};
- my @timezones = DateTime::TimeZone->all_names;
- # Remove old formats, such as CST6CDT, EST, EST5EDT.
- @timezones = grep { $_ =~ m#.+/.+#} @timezones;
- # Append 'local' to the list, which will use the timezone
- # given by the server.
- push(@timezones, 'local');
- push(@timezones, 'UTC');
+ my @timezones = DateTime::TimeZone->all_names;
- return $self->{'legal_values'} = \@timezones;
+ # Remove old formats, such as CST6CDT, EST, EST5EDT.
+ @timezones = grep { $_ =~ m#.+/.+# } @timezones;
+
+ # Append 'local' to the list, which will use the timezone
+ # given by the server.
+ push(@timezones, 'local');
+ push(@timezones, 'UTC');
+
+ return $self->{'legal_values'} = \@timezones;
}
1;
diff --git a/Bugzilla/UserAgent.pm b/Bugzilla/UserAgent.pm
index 14637038c..1995cc82f 100644
--- a/Bugzilla/UserAgent.pm
+++ b/Bugzilla/UserAgent.pm
@@ -20,176 +20,200 @@ use List::MoreUtils qw(natatime);
use constant DEFAULT_VALUE => 'Other';
use constant PLATFORMS_MAP => (
- # PowerPC
- qr/\(.*PowerPC.*\)/i => ["PowerPC", "Macintosh"],
- # AMD64, Intel x86_64
- qr/\(.*[ix0-9]86 (?:on |\()x86_64.*\)/ => ["IA32", "x86", "PC"],
- qr/\(.*amd64.*\)/ => ["AMD64", "x86_64", "PC"],
- qr/\(.*x86_64.*\)/ => ["AMD64", "x86_64", "PC"],
- # Intel IA64
- qr/\(.*IA64.*\)/ => ["IA64", "PC"],
- # Intel x86
- qr/\(.*Intel.*\)/ => ["IA32", "x86", "PC"],
- qr/\(.*[ix0-9]86.*\)/ => ["IA32", "x86", "PC"],
- # Versions of Windows that only run on Intel x86
- qr/\(.*Win(?:dows |)[39M].*\)/ => ["IA32", "x86", "PC"],
- qr/\(.*Win(?:dows |)16.*\)/ => ["IA32", "x86", "PC"],
- # Sparc
- qr/\(.*sparc.*\)/ => ["Sparc", "Sun"],
- qr/\(.*sun4.*\)/ => ["Sparc", "Sun"],
- # Alpha
- qr/\(.*AXP.*\)/i => ["Alpha", "DEC"],
- qr/\(.*[ _]Alpha.\D/i => ["Alpha", "DEC"],
- qr/\(.*[ _]Alpha\)/i => ["Alpha", "DEC"],
- # MIPS
- qr/\(.*IRIX.*\)/i => ["MIPS", "SGI"],
- qr/\(.*MIPS.*\)/i => ["MIPS", "SGI"],
- # 68k
- qr/\(.*68K.*\)/ => ["68k", "Macintosh"],
- qr/\(.*680[x0]0.*\)/ => ["68k", "Macintosh"],
- # HP
- qr/\(.*9000.*\)/ => ["PA-RISC", "HP"],
- # ARM
- qr/\(.*(?:iPod|iPad|iPhone).*\)/ => ["ARM"],
- qr/\(.*ARM.*\)/ => ["ARM", "PocketPC"],
- # PocketPC intentionally before PowerPC
- qr/\(.*Windows CE.*PPC.*\)/ => ["ARM", "PocketPC"],
- # PowerPC
- qr/\(.*PPC.*\)/ => ["PowerPC", "Macintosh"],
- qr/\(.*AIX.*\)/ => ["PowerPC", "Macintosh"],
- # Stereotypical and broken
- qr/\(.*Windows CE.*\)/ => ["ARM", "PocketPC"],
- qr/\(.*Macintosh.*\)/ => ["68k", "Macintosh"],
- qr/\(.*Mac OS [89].*\)/ => ["68k", "Macintosh"],
- qr/\(.*WOW64.*\)/ => ["x86_64"],
- qr/\(.*Win64.*\)/ => ["IA64"],
- qr/\(Win.*\)/ => ["IA32", "x86", "PC"],
- qr/\(.*Win(?:dows[ -])NT.*\)/ => ["IA32", "x86", "PC"],
- qr/\(.*OSF.*\)/ => ["Alpha", "DEC"],
- qr/\(.*HP-?UX.*\)/i => ["PA-RISC", "HP"],
- qr/\(.*IRIX.*\)/i => ["MIPS", "SGI"],
- qr/\(.*(SunOS|Solaris).*\)/ => ["Sparc", "Sun"],
- # Braindead old browsers who didn't follow convention:
- qr/Amiga/ => ["68k", "Macintosh"],
- qr/WinMosaic/ => ["IA32", "x86", "PC"],
+
+ # PowerPC
+ qr/\(.*PowerPC.*\)/i => ["PowerPC", "Macintosh"],
+
+ # AMD64, Intel x86_64
+ qr/\(.*[ix0-9]86 (?:on |\()x86_64.*\)/ => ["IA32", "x86", "PC"],
+ qr/\(.*amd64.*\)/ => ["AMD64", "x86_64", "PC"],
+ qr/\(.*x86_64.*\)/ => ["AMD64", "x86_64", "PC"],
+
+ # Intel IA64
+ qr/\(.*IA64.*\)/ => ["IA64", "PC"],
+
+ # Intel x86
+ qr/\(.*Intel.*\)/ => ["IA32", "x86", "PC"],
+ qr/\(.*[ix0-9]86.*\)/ => ["IA32", "x86", "PC"],
+
+ # Versions of Windows that only run on Intel x86
+ qr/\(.*Win(?:dows |)[39M].*\)/ => ["IA32", "x86", "PC"],
+ qr/\(.*Win(?:dows |)16.*\)/ => ["IA32", "x86", "PC"],
+
+ # Sparc
+ qr/\(.*sparc.*\)/ => ["Sparc", "Sun"],
+ qr/\(.*sun4.*\)/ => ["Sparc", "Sun"],
+
+ # Alpha
+ qr/\(.*AXP.*\)/i => ["Alpha", "DEC"],
+ qr/\(.*[ _]Alpha.\D/i => ["Alpha", "DEC"],
+ qr/\(.*[ _]Alpha\)/i => ["Alpha", "DEC"],
+
+ # MIPS
+ qr/\(.*IRIX.*\)/i => ["MIPS", "SGI"],
+ qr/\(.*MIPS.*\)/i => ["MIPS", "SGI"],
+
+ # 68k
+ qr/\(.*68K.*\)/ => ["68k", "Macintosh"],
+ qr/\(.*680[x0]0.*\)/ => ["68k", "Macintosh"],
+
+ # HP
+ qr/\(.*9000.*\)/ => ["PA-RISC", "HP"],
+
+ # ARM
+ qr/\(.*(?:iPod|iPad|iPhone).*\)/ => ["ARM"],
+ qr/\(.*ARM.*\)/ => ["ARM", "PocketPC"],
+
+ # PocketPC intentionally before PowerPC
+ qr/\(.*Windows CE.*PPC.*\)/ => ["ARM", "PocketPC"],
+
+ # PowerPC
+ qr/\(.*PPC.*\)/ => ["PowerPC", "Macintosh"],
+ qr/\(.*AIX.*\)/ => ["PowerPC", "Macintosh"],
+
+ # Stereotypical and broken
+ qr/\(.*Windows CE.*\)/ => ["ARM", "PocketPC"],
+ qr/\(.*Macintosh.*\)/ => ["68k", "Macintosh"],
+ qr/\(.*Mac OS [89].*\)/ => ["68k", "Macintosh"],
+ qr/\(.*WOW64.*\)/ => ["x86_64"],
+ qr/\(.*Win64.*\)/ => ["IA64"],
+ qr/\(Win.*\)/ => ["IA32", "x86", "PC"],
+ qr/\(.*Win(?:dows[ -])NT.*\)/ => ["IA32", "x86", "PC"],
+ qr/\(.*OSF.*\)/ => ["Alpha", "DEC"],
+ qr/\(.*HP-?UX.*\)/i => ["PA-RISC", "HP"],
+ qr/\(.*IRIX.*\)/i => ["MIPS", "SGI"],
+ qr/\(.*(SunOS|Solaris).*\)/ => ["Sparc", "Sun"],
+
+ # Braindead old browsers who didn't follow convention:
+ qr/Amiga/ => ["68k", "Macintosh"],
+ qr/WinMosaic/ => ["IA32", "x86", "PC"],
);
use constant OS_MAP => (
- # Sun
- qr/\(.*Solaris.*\)/ => ["Solaris"],
- qr/\(.*SunOS 5.11.*\)/ => [("OpenSolaris", "Opensolaris", "Solaris 11")],
- qr/\(.*SunOS 5.10.*\)/ => ["Solaris 10"],
- qr/\(.*SunOS 5.9.*\)/ => ["Solaris 9"],
- qr/\(.*SunOS 5.8.*\)/ => ["Solaris 8"],
- qr/\(.*SunOS 5.7.*\)/ => ["Solaris 7"],
- qr/\(.*SunOS 5.6.*\)/ => ["Solaris 6"],
- qr/\(.*SunOS 5.5.*\)/ => ["Solaris 5"],
- qr/\(.*SunOS 5.*\)/ => ["Solaris"],
- qr/\(.*SunOS.*sun4u.*\)/ => ["Solaris"],
- qr/\(.*SunOS.*i86pc.*\)/ => ["Solaris"],
- qr/\(.*SunOS.*\)/ => ["SunOS"],
- # BSD
- qr/\(.*BSD\/(?:OS|386).*\)/ => ["BSDI"],
- qr/\(.*FreeBSD.*\)/ => ["FreeBSD"],
- qr/\(.*OpenBSD.*\)/ => ["OpenBSD"],
- qr/\(.*NetBSD.*\)/ => ["NetBSD"],
- # Misc POSIX
- qr/\(.*IRIX.*\)/ => ["IRIX"],
- qr/\(.*OSF.*\)/ => ["OSF/1"],
- qr/\(.*Linux.*\)/ => ["Linux"],
- qr/\(.*BeOS.*\)/ => ["BeOS"],
- qr/\(.*AIX.*\)/ => ["AIX"],
- qr/\(.*OS\/2.*\)/ => ["OS/2"],
- qr/\(.*QNX.*\)/ => ["Neutrino"],
- qr/\(.*VMS.*\)/ => ["OpenVMS"],
- qr/\(.*HP-?UX.*\)/ => ["HP-UX"],
- qr/\(.*Android.*\)/ => ["Android"],
- # Windows
- qr/\(.*Windows XP.*\)/ => ["Windows XP"],
- qr/\(.*Windows NT 10\.0.*\)/ => ["Windows 10"],
- qr/\(.*Windows NT 6\.4.*\)/ => ["Windows 10"],
- qr/\(.*Windows NT 6\.3.*\)/ => ["Windows 8.1"],
- qr/\(.*Windows NT 6\.2.*\)/ => ["Windows 8"],
- qr/\(.*Windows NT 6\.1.*\)/ => ["Windows 7"],
- qr/\(.*Windows NT 6\.0.*\)/ => ["Windows Vista"],
- qr/\(.*Windows NT 5\.2.*\)/ => ["Windows Server 2003"],
- qr/\(.*Windows NT 5\.1.*\)/ => ["Windows XP"],
- qr/\(.*Windows 2000.*\)/ => ["Windows 2000"],
- qr/\(.*Windows NT 5.*\)/ => ["Windows 2000"],
- qr/\(.*Win.*9[8x].*4\.9.*\)/ => ["Windows ME"],
- qr/\(.*Win(?:dows |)M[Ee].*\)/ => ["Windows ME"],
- qr/\(.*Win(?:dows |)98.*\)/ => ["Windows 98"],
- qr/\(.*Win(?:dows |)95.*\)/ => ["Windows 95"],
- qr/\(.*Win(?:dows |)16.*\)/ => ["Windows 3.1"],
- qr/\(.*Win(?:dows[ -]|)NT.*\)/ => ["Windows NT"],
- qr/\(.*Windows.*NT.*\)/ => ["Windows NT"],
- # OS X
- qr/\(.*(?:iPad|iPhone).*OS 7.*\)/ => ["iOS 7"],
- qr/\(.*(?:iPad|iPhone).*OS 6.*\)/ => ["iOS 6"],
- qr/\(.*(?:iPad|iPhone).*OS 5.*\)/ => ["iOS 5"],
- qr/\(.*(?:iPad|iPhone).*OS 4.*\)/ => ["iOS 4"],
- qr/\(.*(?:iPad|iPhone).*OS 3.*\)/ => ["iOS 3"],
- qr/\(.*(?:iPod|iPad|iPhone).*\)/ => ["iOS"],
- qr/\(.*Mac OS X (?:|Mach-O |\()10.8.*\)/ => ["Mac OS X 10.8"],
- qr/\(.*Mac OS X (?:|Mach-O |\()10.7.*\)/ => ["Mac OS X 10.7"],
- qr/\(.*Mac OS X (?:|Mach-O |\()10.6.*\)/ => ["Mac OS X 10.6"],
- qr/\(.*Mac OS X (?:|Mach-O |\()10.5.*\)/ => ["Mac OS X 10.5"],
- qr/\(.*Mac OS X (?:|Mach-O |\()10.4.*\)/ => ["Mac OS X 10.4"],
- qr/\(.*Mac OS X (?:|Mach-O |\()10.3.*\)/ => ["Mac OS X 10.3"],
- qr/\(.*Mac OS X (?:|Mach-O |\()10.2.*\)/ => ["Mac OS X 10.2"],
- qr/\(.*Mac OS X (?:|Mach-O |\()10.1.*\)/ => ["Mac OS X 10.1"],
- # Unfortunately, OS X 10.4 was the first to support Intel. This is fallback
- # support because some browsers refused to include the OS Version.
- qr/\(.*Intel.*Mac OS X.*\)/ => ["Mac OS X 10.4"],
- # OS X 10.3 is the most likely default version of PowerPC Macs
- # OS X 10.0 is more for configurations which didn't setup 10.x versions
- qr/\(.*Mac OS X.*\)/ => [("Mac OS X 10.3", "Mac OS X 10.0", "Mac OS X")],
- qr/\(.*Mac OS 9.*\)/ => [("Mac System 9.x", "Mac System 9.0")],
- qr/\(.*Mac OS 8\.6.*\)/ => [("Mac System 8.6", "Mac System 8.5")],
- qr/\(.*Mac OS 8\.5.*\)/ => ["Mac System 8.5"],
- qr/\(.*Mac OS 8\.1.*\)/ => [("Mac System 8.1", "Mac System 8.0")],
- qr/\(.*Mac OS 8\.0.*\)/ => ["Mac System 8.0"],
- qr/\(.*Mac OS 8[^.].*\)/ => ["Mac System 8.0"],
- qr/\(.*Mac OS 8.*\)/ => ["Mac System 8.6"],
- qr/\(.*Darwin.*\)/ => [("Mac OS X 10.0", "Mac OS X")],
- # Silly
- qr/\(.*Mac.*PowerPC.*\)/ => ["Mac System 9.x"],
- qr/\(.*Mac.*PPC.*\)/ => ["Mac System 9.x"],
- qr/\(.*Mac.*68k.*\)/ => ["Mac System 8.0"],
- # Evil
- qr/Amiga/i => ["Other"],
- qr/WinMosaic/ => ["Windows 95"],
- qr/\(.*32bit.*\)/ => ["Windows 95"],
- qr/\(.*16bit.*\)/ => ["Windows 3.1"],
- qr/\(.*PowerPC.*\)/ => ["Mac System 9.x"],
- qr/\(.*PPC.*\)/ => ["Mac System 9.x"],
- qr/\(.*68K.*\)/ => ["Mac System 8.0"],
+
+ # Sun
+ qr/\(.*Solaris.*\)/ => ["Solaris"],
+ qr/\(.*SunOS 5.11.*\)/ => [("OpenSolaris", "Opensolaris", "Solaris 11")],
+ qr/\(.*SunOS 5.10.*\)/ => ["Solaris 10"],
+ qr/\(.*SunOS 5.9.*\)/ => ["Solaris 9"],
+ qr/\(.*SunOS 5.8.*\)/ => ["Solaris 8"],
+ qr/\(.*SunOS 5.7.*\)/ => ["Solaris 7"],
+ qr/\(.*SunOS 5.6.*\)/ => ["Solaris 6"],
+ qr/\(.*SunOS 5.5.*\)/ => ["Solaris 5"],
+ qr/\(.*SunOS 5.*\)/ => ["Solaris"],
+ qr/\(.*SunOS.*sun4u.*\)/ => ["Solaris"],
+ qr/\(.*SunOS.*i86pc.*\)/ => ["Solaris"],
+ qr/\(.*SunOS.*\)/ => ["SunOS"],
+
+ # BSD
+ qr/\(.*BSD\/(?:OS|386).*\)/ => ["BSDI"],
+ qr/\(.*FreeBSD.*\)/ => ["FreeBSD"],
+ qr/\(.*OpenBSD.*\)/ => ["OpenBSD"],
+ qr/\(.*NetBSD.*\)/ => ["NetBSD"],
+
+ # Misc POSIX
+ qr/\(.*IRIX.*\)/ => ["IRIX"],
+ qr/\(.*OSF.*\)/ => ["OSF/1"],
+ qr/\(.*Linux.*\)/ => ["Linux"],
+ qr/\(.*BeOS.*\)/ => ["BeOS"],
+ qr/\(.*AIX.*\)/ => ["AIX"],
+ qr/\(.*OS\/2.*\)/ => ["OS/2"],
+ qr/\(.*QNX.*\)/ => ["Neutrino"],
+ qr/\(.*VMS.*\)/ => ["OpenVMS"],
+ qr/\(.*HP-?UX.*\)/ => ["HP-UX"],
+ qr/\(.*Android.*\)/ => ["Android"],
+
+ # Windows
+ qr/\(.*Windows XP.*\)/ => ["Windows XP"],
+ qr/\(.*Windows NT 10\.0.*\)/ => ["Windows 10"],
+ qr/\(.*Windows NT 6\.4.*\)/ => ["Windows 10"],
+ qr/\(.*Windows NT 6\.3.*\)/ => ["Windows 8.1"],
+ qr/\(.*Windows NT 6\.2.*\)/ => ["Windows 8"],
+ qr/\(.*Windows NT 6\.1.*\)/ => ["Windows 7"],
+ qr/\(.*Windows NT 6\.0.*\)/ => ["Windows Vista"],
+ qr/\(.*Windows NT 5\.2.*\)/ => ["Windows Server 2003"],
+ qr/\(.*Windows NT 5\.1.*\)/ => ["Windows XP"],
+ qr/\(.*Windows 2000.*\)/ => ["Windows 2000"],
+ qr/\(.*Windows NT 5.*\)/ => ["Windows 2000"],
+ qr/\(.*Win.*9[8x].*4\.9.*\)/ => ["Windows ME"],
+ qr/\(.*Win(?:dows |)M[Ee].*\)/ => ["Windows ME"],
+ qr/\(.*Win(?:dows |)98.*\)/ => ["Windows 98"],
+ qr/\(.*Win(?:dows |)95.*\)/ => ["Windows 95"],
+ qr/\(.*Win(?:dows |)16.*\)/ => ["Windows 3.1"],
+ qr/\(.*Win(?:dows[ -]|)NT.*\)/ => ["Windows NT"],
+ qr/\(.*Windows.*NT.*\)/ => ["Windows NT"],
+
+ # OS X
+ qr/\(.*(?:iPad|iPhone).*OS 7.*\)/ => ["iOS 7"],
+ qr/\(.*(?:iPad|iPhone).*OS 6.*\)/ => ["iOS 6"],
+ qr/\(.*(?:iPad|iPhone).*OS 5.*\)/ => ["iOS 5"],
+ qr/\(.*(?:iPad|iPhone).*OS 4.*\)/ => ["iOS 4"],
+ qr/\(.*(?:iPad|iPhone).*OS 3.*\)/ => ["iOS 3"],
+ qr/\(.*(?:iPod|iPad|iPhone).*\)/ => ["iOS"],
+ qr/\(.*Mac OS X (?:|Mach-O |\()10.8.*\)/ => ["Mac OS X 10.8"],
+ qr/\(.*Mac OS X (?:|Mach-O |\()10.7.*\)/ => ["Mac OS X 10.7"],
+ qr/\(.*Mac OS X (?:|Mach-O |\()10.6.*\)/ => ["Mac OS X 10.6"],
+ qr/\(.*Mac OS X (?:|Mach-O |\()10.5.*\)/ => ["Mac OS X 10.5"],
+ qr/\(.*Mac OS X (?:|Mach-O |\()10.4.*\)/ => ["Mac OS X 10.4"],
+ qr/\(.*Mac OS X (?:|Mach-O |\()10.3.*\)/ => ["Mac OS X 10.3"],
+ qr/\(.*Mac OS X (?:|Mach-O |\()10.2.*\)/ => ["Mac OS X 10.2"],
+ qr/\(.*Mac OS X (?:|Mach-O |\()10.1.*\)/ => ["Mac OS X 10.1"],
+
+ # Unfortunately, OS X 10.4 was the first to support Intel. This is fallback
+ # support because some browsers refused to include the OS Version.
+ qr/\(.*Intel.*Mac OS X.*\)/ => ["Mac OS X 10.4"],
+
+ # OS X 10.3 is the most likely default version of PowerPC Macs
+ # OS X 10.0 is more for configurations which didn't setup 10.x versions
+ qr/\(.*Mac OS X.*\)/ => [("Mac OS X 10.3", "Mac OS X 10.0", "Mac OS X")],
+ qr/\(.*Mac OS 9.*\)/ => [("Mac System 9.x", "Mac System 9.0")],
+ qr/\(.*Mac OS 8\.6.*\)/ => [("Mac System 8.6", "Mac System 8.5")],
+ qr/\(.*Mac OS 8\.5.*\)/ => ["Mac System 8.5"],
+ qr/\(.*Mac OS 8\.1.*\)/ => [("Mac System 8.1", "Mac System 8.0")],
+ qr/\(.*Mac OS 8\.0.*\)/ => ["Mac System 8.0"],
+ qr/\(.*Mac OS 8[^.].*\)/ => ["Mac System 8.0"],
+ qr/\(.*Mac OS 8.*\)/ => ["Mac System 8.6"],
+ qr/\(.*Darwin.*\)/ => [("Mac OS X 10.0", "Mac OS X")],
+
+ # Silly
+ qr/\(.*Mac.*PowerPC.*\)/ => ["Mac System 9.x"],
+ qr/\(.*Mac.*PPC.*\)/ => ["Mac System 9.x"],
+ qr/\(.*Mac.*68k.*\)/ => ["Mac System 8.0"],
+
+ # Evil
+ qr/Amiga/i => ["Other"],
+ qr/WinMosaic/ => ["Windows 95"],
+ qr/\(.*32bit.*\)/ => ["Windows 95"],
+ qr/\(.*16bit.*\)/ => ["Windows 3.1"],
+ qr/\(.*PowerPC.*\)/ => ["Mac System 9.x"],
+ qr/\(.*PPC.*\)/ => ["Mac System 9.x"],
+ qr/\(.*68K.*\)/ => ["Mac System 8.0"],
);
sub detect_platform {
- my $userAgent = $ENV{'HTTP_USER_AGENT'};
- my @detected;
- my $iterator = natatime(2, PLATFORMS_MAP);
- while (my($re, $ra) = $iterator->()) {
- if ($userAgent =~ $re) {
- push @detected, @$ra;
- }
+ my $userAgent = $ENV{'HTTP_USER_AGENT'};
+ my @detected;
+ my $iterator = natatime(2, PLATFORMS_MAP);
+ while (my ($re, $ra) = $iterator->()) {
+ if ($userAgent =~ $re) {
+ push @detected, @$ra;
}
- return _pick_valid_field_value('rep_platform', @detected);
+ }
+ return _pick_valid_field_value('rep_platform', @detected);
}
sub detect_op_sys {
- my $userAgent = $ENV{'HTTP_USER_AGENT'} || '';
- my @detected;
- my $iterator = natatime(2, OS_MAP);
- while (my($re, $ra) = $iterator->()) {
- if ($userAgent =~ $re) {
- push @detected, @$ra;
- }
+ my $userAgent = $ENV{'HTTP_USER_AGENT'} || '';
+ my @detected;
+ my $iterator = natatime(2, OS_MAP);
+ while (my ($re, $ra) = $iterator->()) {
+ if ($userAgent =~ $re) {
+ push @detected, @$ra;
}
- push(@detected, "Windows") if grep(/^Windows /, @detected);
- push(@detected, "Mac OS") if grep(/^Mac /, @detected);
- return _pick_valid_field_value('op_sys', @detected);
+ }
+ push(@detected, "Windows") if grep(/^Windows /, @detected);
+ push(@detected, "Mac OS") if grep(/^Mac /, @detected);
+ return _pick_valid_field_value('op_sys', @detected);
}
# Takes the name of a field and a list of possible values for that field.
@@ -197,11 +221,11 @@ sub detect_op_sys {
# field.
# Returns 'Other' if none of the values match.
sub _pick_valid_field_value {
- my ($field, @values) = @_;
- foreach my $value (@values) {
- return $value if check_field($field, $value, undef, 1);
- }
- return DEFAULT_VALUE;
+ my ($field, @values) = @_;
+ foreach my $value (@values) {
+ return $value if check_field($field, $value, undef, 1);
+ }
+ return DEFAULT_VALUE;
}
1;
diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm
index 57ce5f6b6..4a6e89d18 100644
--- a/Bugzilla/Util.pm
+++ b/Bugzilla/Util.pm
@@ -13,18 +13,18 @@ use warnings;
use parent qw(Exporter);
@Bugzilla::Util::EXPORT = qw(trick_taint detaint_natural detaint_signed
- html_quote url_quote xml_quote
- css_class_quote html_light_quote
- i_am_cgi i_am_webservice correct_urlbase remote_ip
- validate_ip do_ssl_redirect_if_required use_attachbase
- diff_arrays on_main_db
- trim wrap_hard wrap_comment find_wrap_point
- format_time validate_date validate_time datetime_from
- is_7bit_clean bz_crypt generate_random_password
- validate_email_syntax check_email_syntax clean_text
- get_text template_var display_value disable_utf8
- detect_encoding email_filter
- join_activity_entries read_text write_text);
+ html_quote url_quote xml_quote
+ css_class_quote html_light_quote
+ i_am_cgi i_am_webservice correct_urlbase remote_ip
+ validate_ip do_ssl_redirect_if_required use_attachbase
+ diff_arrays on_main_db
+ trim wrap_hard wrap_comment find_wrap_point
+ format_time validate_date validate_time datetime_from
+ is_7bit_clean bz_crypt generate_random_password
+ validate_email_syntax check_email_syntax clean_text
+ get_text template_var display_value disable_utf8
+ detect_encoding email_filter
+ join_activity_entries read_text write_text);
use Bugzilla::Constants;
use Bugzilla::RNG qw(irand);
@@ -43,642 +43,691 @@ use File::Basename qw(dirname);
use File::Temp qw(tempfile);
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]));
+ 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] =~ /^([0-9]+)$/;
- $_[0] = $match ? int($1) : undef;
- return (defined($_[0]));
+ my $match = $_[0] =~ /^([0-9]+)$/;
+ $_[0] = $match ? int($1) : undef;
+ return (defined($_[0]));
}
sub detaint_signed {
- my $match = $_[0] =~ /^([-+]?[0-9]+)$/;
- # The "int()" call removes any leading plus sign.
- $_[0] = $match ? int($1) : undef;
- return (defined($_[0]));
+ my $match = $_[0] =~ /^([-+]?[0-9]+)$/;
+
+ # The "int()" call removes any leading plus sign.
+ $_[0] = $match ? int($1) : undef;
+ return (defined($_[0]));
}
# Bug 120030: Override html filter to obscure the '@' in user
# visible strings.
# Bug 319331: Handle BiDi disruptions.
sub html_quote {
- my $var = shift;
- $var =~ s/&/&amp;/g;
- $var =~ s/</&lt;/g;
- $var =~ s/>/&gt;/g;
- $var =~ s/"/&quot;/g;
- # Obscure '@'.
- $var =~ s/\@/\&#64;/g;
-
- state $use_utf8 = Bugzilla->params->{'utf8'};
-
- if ($use_utf8) {
- # Remove control characters if the encoding is utf8.
- # Other multibyte encodings may be using this range; so ignore if not utf8.
- $var =~ s/(?![\t\r\n])[[:cntrl:]]//g;
-
- # 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 =~ tr/\x{202a}-\x{202e}//d;
- }
- return $var;
+ my $var = shift;
+ $var =~ s/&/&amp;/g;
+ $var =~ s/</&lt;/g;
+ $var =~ s/>/&gt;/g;
+ $var =~ s/"/&quot;/g;
+
+ # Obscure '@'.
+ $var =~ s/\@/\&#64;/g;
+
+ state $use_utf8 = Bugzilla->params->{'utf8'};
+
+ if ($use_utf8) {
+
+ # Remove control characters if the encoding is utf8.
+ # Other multibyte encodings may be using this range; so ignore if not utf8.
+ $var =~ s/(?![\t\r\n])[[:cntrl:]]//g;
+
+ # 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 |
+ # --------------------------------------------------------
+ #
+ # Perl Safe.pm and 5.30 don't interact well, the following expression is
+ # disabled. This introduces a small risk of BiDi characters being added to
+ # bugs.
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1588175
+ # https://rt.perl.org/Public/Bug/Display.html?id=72942
+ # https://github.com/Perl/perl5/issues/17271
+ #$var =~ tr/\x{202a}-\x{202e}//d;
+ }
+ return $var;
}
sub read_text {
- my ($filename) = @_;
- open my $fh, '<:encoding(utf-8)', $filename;
- local $/ = undef;
- my $content = <$fh>;
- close $fh;
- return $content;
+ my ($filename) = @_;
+ open my $fh, '<:encoding(utf-8)', $filename;
+ local $/ = undef;
+ my $content = <$fh>;
+ close $fh;
+ return $content;
}
sub write_text {
- my ($filename, $content) = @_;
- my ($tmp_fh, $tmp_filename) = tempfile('.tmp.XXXXXXXXXX',
- DIR => dirname($filename),
- UNLINK => 0,
- );
- binmode $tmp_fh, ':encoding(utf-8)';
- print $tmp_fh $content;
- close $tmp_fh;
- # File::Temp tries for secure files, but File::Slurp used the umask.
- chmod(0666 & ~umask, $tmp_filename);
- rename $tmp_filename, $filename;
+ my ($filename, $content) = @_;
+ my ($tmp_fh, $tmp_filename)
+ = tempfile('.tmp.XXXXXXXXXX', DIR => dirname($filename), UNLINK => 0,);
+ binmode $tmp_fh, ':encoding(utf-8)';
+ print $tmp_fh $content;
+ close $tmp_fh;
+
+ # File::Temp tries for secure files, but File::Slurp used the umask.
+ chmod(0666 & ~umask, $tmp_filename);
+ rename $tmp_filename, $filename;
}
sub html_light_quote {
- my ($text) = @_;
- # admin/table.html.tmpl calls |FILTER html_light| many times.
- # There is no need to recreate the HTML::Scrubber object again and again.
- my $scrubber = Bugzilla->process_cache->{html_scrubber};
-
- # 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);
-
- if (!Bugzilla->feature('html_desc')) {
- 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#<#&lt;#g;
- $text =~ s#>#&gt;#g;
- # Restore safe elements.
- $text =~ s#$chr/($safe)$chr#</$1>#go;
- $text =~ s#$chr($safe)$chr#<$1>#go;
- return $text;
- }
- elsif (!$scrubber) {
- # 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,
- target => qr{^(?:_blank|_parent|_self|_top)$}i,
- 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.
- },
- );
-
- Bugzilla->process_cache->{html_scrubber} = $scrubber =
- HTML::Scrubber->new(default => \@default,
- allow => \@allow,
- rules => \@rules,
- comment => 0,
- process => 0);
- }
- return $scrubber->scrub($text);
+ my ($text) = @_;
+
+ # admin/table.html.tmpl calls |FILTER html_light| many times.
+ # There is no need to recreate the HTML::Scrubber object again and again.
+ my $scrubber = Bugzilla->process_cache->{html_scrubber};
+
+ # 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);
+
+ if (!Bugzilla->feature('html_desc')) {
+ 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#<#&lt;#g;
+ $text =~ s#>#&gt;#g;
+
+ # Restore safe elements.
+ $text =~ s#$chr/($safe)$chr#</$1>#go;
+ $text =~ s#$chr($safe)$chr#<$1>#go;
+ return $text;
+ }
+ elsif (!$scrubber) {
+
+ # 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,
+ target => qr{^(?:_blank|_parent|_self|_top)$}i,
+ 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.
+ },
+ );
+
+ Bugzilla->process_cache->{html_scrubber} = $scrubber = HTML::Scrubber->new(
+ default => \@default,
+ allow => \@allow,
+ rules => \@rules,
+ comment => 0,
+ process => 0
+ );
+ }
+ return $scrubber->scrub($text);
}
sub email_filter {
- my ($toencode) = @_;
- if (!Bugzilla->user->id) {
- my @emails = Email::Address->parse($toencode);
- if (scalar @emails) {
- my @hosts = map { quotemeta($_->host) } @emails;
- my $hosts_re = join('|', @hosts);
- $toencode =~ s/\@(?:$hosts_re)//g;
- return $toencode;
- }
+ my ($toencode) = @_;
+ if (!Bugzilla->user->id) {
+ my @emails = Email::Address->parse($toencode);
+ if (scalar @emails) {
+ my @hosts = map { quotemeta($_->host) } @emails;
+ my $hosts_re = join('|', @hosts);
+ $toencode =~ s/\@(?:$hosts_re)//g;
+ return $toencode;
}
- return $toencode;
+ }
+ return $toencode;
}
# 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;
+ 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;
+ 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/\&/\&amp;/g;
- $var =~ s/</\&lt;/g;
- $var =~ s/>/\&gt;/g;
- $var =~ s/\"/\&quot;/g;
- $var =~ s/\'/\&apos;/g;
-
- # the following nukes characters disallowed by the XML 1.0
- # spec, Production 2.2. 1.0 declares that only the following
- # are valid:
- # (#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF])
- $var =~ s/([\x{0001}-\x{0008}]|
+ my ($var) = (@_);
+ $var =~ s/\&/\&amp;/g;
+ $var =~ s/</\&lt;/g;
+ $var =~ s/>/\&gt;/g;
+ $var =~ s/\"/\&quot;/g;
+ $var =~ s/\'/\&apos;/g;
+
+ # the following nukes characters disallowed by the XML 1.0
+ # spec, Production 2.2. 1.0 declares that only the following
+ # are valid:
+ # (#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF])
+ $var =~ s/([\x{0001}-\x{0008}]|
[\x{000B}-\x{000C}]|
[\x{000E}-\x{001F}]|
[\x{D800}-\x{DFFF}]|
[\x{FFFE}-\x{FFFF}])//gx;
- return $var;
+ return $var;
}
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;
+
+ # 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 i_am_webservice {
- my $usage_mode = Bugzilla->usage_mode;
- return $usage_mode == USAGE_MODE_XMLRPC
- || $usage_mode == USAGE_MODE_JSON
- || $usage_mode == USAGE_MODE_REST;
+ my $usage_mode = Bugzilla->usage_mode;
+ return
+ $usage_mode == USAGE_MODE_XMLRPC
+ || $usage_mode == USAGE_MODE_JSON
+ || $usage_mode == USAGE_MODE_REST;
}
# This exists as a separate function from Bugzilla::CGI::redirect_to_https
# because we don't want to create a CGI object during XML-RPC calls
# (doing so can mess up XML-RPC).
sub do_ssl_redirect_if_required {
- return if !i_am_cgi();
- return if !Bugzilla->params->{'ssl_redirect'};
-
- my $sslbase = Bugzilla->params->{'sslbase'};
-
- # If we're already running under SSL, never redirect.
- return if uc($ENV{HTTPS} || '') eq 'ON';
- # Never redirect if there isn't an sslbase.
- return if !$sslbase;
- Bugzilla->cgi->redirect_to_https();
+ return if !i_am_cgi();
+ return if !Bugzilla->params->{'ssl_redirect'};
+
+ my $sslbase = Bugzilla->params->{'sslbase'};
+
+ # If we're already running under SSL, never redirect.
+ return if uc($ENV{HTTPS} || '') eq 'ON';
+
+ # Never redirect if there isn't an sslbase.
+ return if !$sslbase;
+ Bugzilla->cgi->redirect_to_https();
}
sub correct_urlbase {
- my $ssl = Bugzilla->params->{'ssl_redirect'};
- my $urlbase = Bugzilla->params->{'urlbase'};
- my $sslbase = Bugzilla->params->{'sslbase'};
-
- if (!$sslbase) {
- return $urlbase;
- }
- elsif ($ssl) {
- return $sslbase;
- }
- else {
- # Return what the user currently uses.
- return (uc($ENV{HTTPS} || '') eq 'ON') ? $sslbase : $urlbase;
- }
+ my $ssl = Bugzilla->params->{'ssl_redirect'};
+ my $urlbase = Bugzilla->params->{'urlbase'};
+ my $sslbase = Bugzilla->params->{'sslbase'};
+
+ if (!$sslbase) {
+ return $urlbase;
+ }
+ elsif ($ssl) {
+ return $sslbase;
+ }
+ else {
+ # Return what the user currently uses.
+ return (uc($ENV{HTTPS} || '') eq 'ON') ? $sslbase : $urlbase;
+ }
}
sub remote_ip {
- my $ip = $ENV{'REMOTE_ADDR'} || '127.0.0.1';
- my @proxies = split(/[\s,]+/, Bugzilla->params->{'inbound_proxies'});
-
- # If the IP address is one of our trusted proxies, then we look at
- # the X-Forwarded-For header to determine the real remote IP address.
- if ($ENV{'HTTP_X_FORWARDED_FOR'} && first { $_ eq $ip } @proxies) {
- my @ips = split(/[\s,]+/, $ENV{'HTTP_X_FORWARDED_FOR'});
- # This header can contain several IP addresses. We want the
- # IP address of the machine which connected to our proxies as
- # all other IP addresses may be fake or internal ones.
- # Note that this may block a whole external proxy, but we have
- # no way to determine if this proxy is malicious or trustable.
- foreach my $remote_ip (reverse @ips) {
- if (!first { $_ eq $remote_ip } @proxies) {
- # Keep the original IP address if the remote IP is invalid.
- $ip = validate_ip($remote_ip) || $ip;
- last;
- }
- }
+ my $ip = $ENV{'REMOTE_ADDR'} || '127.0.0.1';
+ my @proxies = split(/[\s,]+/, Bugzilla->params->{'inbound_proxies'});
+
+ # If the IP address is one of our trusted proxies, then we look at
+ # the X-Forwarded-For header to determine the real remote IP address.
+ if ($ENV{'HTTP_X_FORWARDED_FOR'} && first { $_ eq $ip } @proxies) {
+ my @ips = split(/[\s,]+/, $ENV{'HTTP_X_FORWARDED_FOR'});
+
+ # This header can contain several IP addresses. We want the
+ # IP address of the machine which connected to our proxies as
+ # all other IP addresses may be fake or internal ones.
+ # Note that this may block a whole external proxy, but we have
+ # no way to determine if this proxy is malicious or trustable.
+ foreach my $remote_ip (reverse @ips) {
+ if (!first { $_ eq $remote_ip } @proxies) {
+
+ # Keep the original IP address if the remote IP is invalid.
+ $ip = validate_ip($remote_ip) || $ip;
+ last;
+ }
}
- return $ip;
+ }
+ return $ip;
}
sub validate_ip {
- my $ip = shift;
- return is_ipv4($ip) || is_ipv6($ip);
+ my $ip = shift;
+ return is_ipv4($ip) || is_ipv6($ip);
}
# Copied from Data::Validate::IP::is_ipv4().
sub is_ipv4 {
- my $ip = shift;
- return unless defined $ip;
+ my $ip = shift;
+ return unless defined $ip;
- my @octets = $ip =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
- return unless scalar(@octets) == 4;
+ my @octets = $ip =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
+ return unless scalar(@octets) == 4;
- foreach my $octet (@octets) {
- return unless ($octet >= 0 && $octet <= 255 && $octet !~ /^0\d{1,2}$/);
- }
+ foreach my $octet (@octets) {
+ return unless ($octet >= 0 && $octet <= 255 && $octet !~ /^0\d{1,2}$/);
+ }
- # The IP address is valid and can now be detainted.
- return join('.', @octets);
+ # The IP address is valid and can now be detainted.
+ return join('.', @octets);
}
# Copied from Data::Validate::IP::is_ipv6().
sub is_ipv6 {
- my $ip = shift;
- return unless defined $ip;
-
- # If there is a :: then there must be only one :: and the length
- # can be variable. Without it, the length must be 8 groups.
- my @chunks = split(':', $ip);
-
- # Need to check if the last chunk is an IPv4 address, if it is we
- # pop it off and exempt it from the normal IPv6 checking and stick
- # it back on at the end. If there is only one chunk and it's an IPv4
- # address, then it isn't an IPv6 address.
- my $ipv4;
- my $expected_chunks = 8;
- if (@chunks > 1 && is_ipv4($chunks[$#chunks])) {
- $ipv4 = pop(@chunks);
- $expected_chunks--;
- }
+ my $ip = shift;
+ return unless defined $ip;
+
+ # If there is a :: then there must be only one :: and the length
+ # can be variable. Without it, the length must be 8 groups.
+ my @chunks = split(':', $ip);
+
+ # Need to check if the last chunk is an IPv4 address, if it is we
+ # pop it off and exempt it from the normal IPv6 checking and stick
+ # it back on at the end. If there is only one chunk and it's an IPv4
+ # address, then it isn't an IPv6 address.
+ my $ipv4;
+ my $expected_chunks = 8;
+ if (@chunks > 1 && is_ipv4($chunks[$#chunks])) {
+ $ipv4 = pop(@chunks);
+ $expected_chunks--;
+ }
+
+ my $empty = 0;
+
+ # Workaround to handle trailing :: being valid.
+ if ($ip =~ /[0-9a-f]{1,4}::$/) {
+ $empty++;
- my $empty = 0;
- # Workaround to handle trailing :: being valid.
- if ($ip =~ /[0-9a-f]{1,4}::$/) {
- $empty++;
# Single trailing ':' is invalid.
- } elsif ($ip =~ /:$/) {
- return;
- }
+ }
+ elsif ($ip =~ /:$/) {
+ return;
+ }
- foreach my $chunk (@chunks) {
- return unless $chunk =~ /^[0-9a-f]{0,4}$/i;
- $empty++ if $chunk eq '';
- }
- # More than one :: block is bad, but if it starts with :: it will
- # look like two, so we need an exception.
- if ($empty == 2 && $ip =~ /^::/) {
- # This is ok
- } elsif ($empty > 1) {
- return;
- }
+ foreach my $chunk (@chunks) {
+ return unless $chunk =~ /^[0-9a-f]{0,4}$/i;
+ $empty++ if $chunk eq '';
+ }
+
+ # More than one :: block is bad, but if it starts with :: it will
+ # look like two, so we need an exception.
+ if ($empty == 2 && $ip =~ /^::/) {
+
+ # This is ok
+ }
+ elsif ($empty > 1) {
+ return;
+ }
- push(@chunks, $ipv4) if $ipv4;
- # Need 8 chunks, or we need an empty section that could be filled
- # to represent the missing '0' sections.
- return unless (@chunks == $expected_chunks || @chunks < $expected_chunks && $empty);
+ push(@chunks, $ipv4) if $ipv4;
- my $ipv6 = join(':', @chunks);
- # The IP address is valid and can now be detainted.
- trick_taint($ipv6);
+ # Need 8 chunks, or we need an empty section that could be filled
+ # to represent the missing '0' sections.
+ return
+ unless (@chunks == $expected_chunks || @chunks < $expected_chunks && $empty);
- # Need to handle the exception of trailing :: being valid.
- return "${ipv6}::" if $ip =~ /::$/;
- return $ipv6;
+ my $ipv6 = join(':', @chunks);
+
+ # The IP address is valid and can now be detainted.
+ trick_taint($ipv6);
+
+ # Need to handle the exception of trailing :: being valid.
+ return "${ipv6}::" if $ip =~ /::$/;
+ return $ipv6;
}
sub use_attachbase {
- my $attachbase = Bugzilla->params->{'attachment_base'};
- return ($attachbase ne ''
- && $attachbase ne Bugzilla->params->{'urlbase'}
- && $attachbase ne Bugzilla->params->{'sslbase'}) ? 1 : 0;
+ my $attachbase = Bugzilla->params->{'attachment_base'};
+ return ($attachbase ne ''
+ && $attachbase ne Bugzilla->params->{'urlbase'}
+ && $attachbase ne Bugzilla->params->{'sslbase'}) ? 1 : 0;
}
sub diff_arrays {
- my ($old_ref, $new_ref, $attrib) = @_;
- $attrib ||= 'name';
-
- my (%counts, %pos);
- # We are going to alter the old array.
- my @old = @$old_ref;
- my $i = 0;
-
- # $counts{foo}-- means old, $counts{foo}++ means new.
- # If $counts{foo} becomes positive, then we are adding new items,
- # else we simply cancel one old existing item. Remaining items
- # in the old list have been removed.
- foreach (@old) {
- next unless defined $_;
- my $value = blessed($_) ? $_->$attrib : $_;
- $counts{$value}--;
- push @{$pos{$value}}, $i++;
+ my ($old_ref, $new_ref, $attrib) = @_;
+ $attrib ||= 'name';
+
+ my (%counts, %pos);
+
+ # We are going to alter the old array.
+ my @old = @$old_ref;
+ my $i = 0;
+
+ # $counts{foo}-- means old, $counts{foo}++ means new.
+ # If $counts{foo} becomes positive, then we are adding new items,
+ # else we simply cancel one old existing item. Remaining items
+ # in the old list have been removed.
+ foreach (@old) {
+ next unless defined $_;
+ my $value = blessed($_) ? $_->$attrib : $_;
+ $counts{$value}--;
+ push @{$pos{$value}}, $i++;
+ }
+ my @added;
+ foreach (@$new_ref) {
+ next unless defined $_;
+ my $value = blessed($_) ? $_->$attrib : $_;
+ if (++$counts{$value} > 0) {
+
+ # Ignore empty strings, but objects having an empty string
+ # as attribute are fine.
+ push(@added, $_) unless ($value eq '' && !blessed($_));
}
- my @added;
- foreach (@$new_ref) {
- next unless defined $_;
- my $value = blessed($_) ? $_->$attrib : $_;
- if (++$counts{$value} > 0) {
- # Ignore empty strings, but objects having an empty string
- # as attribute are fine.
- push(@added, $_) unless ($value eq '' && !blessed($_));
- }
- else {
- my $old_pos = shift @{$pos{$value}};
- $old[$old_pos] = undef;
- }
+ else {
+ my $old_pos = shift @{$pos{$value}};
+ $old[$old_pos] = undef;
}
- # Ignore canceled items as well as empty strings.
- my @removed = grep { defined $_ && $_ ne '' } @old;
- return (\@removed, \@added);
+ }
+
+ # Ignore canceled items as well as empty strings.
+ my @removed = grep { defined $_ && $_ ne '' } @old;
+ return (\@removed, \@added);
}
sub trim {
- my ($str) = @_;
- if ($str) {
- $str =~ s/^\s+//g;
- $str =~ s/\s+$//g;
- }
- return $str;
+ my ($str) = @_;
+ if ($str) {
+ $str =~ s/^\s+//g;
+ $str =~ s/\s+$//g;
+ }
+ return $str;
}
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 {
- $wrappedcomment .= (wrap('', '', $line) . "\n");
- }
+ 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 {
+ $wrappedcomment .= (wrap('', '', $line) . "\n");
+ }
+ }
- chomp($wrappedcomment); # Text::Wrap adds an extra newline at the end.
- return $wrappedcomment;
+ 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
- }
- }
+ 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;
+ }
+ return $wrappoint;
}
sub join_activity_entries {
- my ($field, $current_change, $new_change) = @_;
- # We need to insert characters as these were removed by old
- # LogActivityEntry code.
-
- return $new_change if $current_change eq '';
-
- # Buglists and see_also need the comma restored
- if ($field eq 'dependson' || $field eq 'blocked' || $field eq 'see_also') {
- if (substr($new_change, 0, 1) eq ',' || substr($new_change, 0, 1) eq ' ') {
- return $current_change . $new_change;
- } else {
- return $current_change . ', ' . $new_change;
- }
- }
+ my ($field, $current_change, $new_change) = @_;
- # Assume bug_file_loc contain a single url, don't insert a delimiter
- if ($field eq 'bug_file_loc') {
- return $current_change . $new_change;
- }
+ # We need to insert characters as these were removed by old
+ # LogActivityEntry code.
+
+ return $new_change if $current_change eq '';
- # All other fields get a space unless the first character of the second
- # string is a comma or space
+ # Buglists and see_also need the comma restored
+ if ($field eq 'dependson' || $field eq 'blocked' || $field eq 'see_also') {
if (substr($new_change, 0, 1) eq ',' || substr($new_change, 0, 1) eq ' ') {
- return $current_change . $new_change;
- } else {
- return $current_change . ' ' . $new_change;
+ return $current_change . $new_change;
+ }
+ else {
+ return $current_change . ', ' . $new_change;
}
+ }
+
+ # Assume bug_file_loc contain a single url, don't insert a delimiter
+ if ($field eq 'bug_file_loc') {
+ return $current_change . $new_change;
+ }
+
+ # All other fields get a space unless the first character of the second
+ # string is a comma or space
+ if (substr($new_change, 0, 1) eq ',' || substr($new_change, 0, 1) eq ' ') {
+ return $current_change . $new_change;
+ }
+ else {
+ return $current_change . ' ' . $new_change;
+ }
}
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;
+ 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, $timezone) = @_;
-
- # If $format is not set, try to guess the correct date format.
- if (!$format) {
- if (!ref $date
- && $date =~ /^(\d{4})[-\.](\d{2})[-\.](\d{2}) (\d{2}):(\d{2})(:(\d{2}))?$/)
- {
- my $sec = $7;
- if (defined $sec) {
- $format = "%Y-%m-%d %T %Z";
- } else {
- $format = "%Y-%m-%d %R %Z";
- }
- } else {
- # Default date format. See DateTime for other formats available.
- $format = "%Y-%m-%d %R %Z";
- }
- }
-
- my $dt = ref $date ? $date : datetime_from($date, $timezone);
- $date = defined $dt ? $dt->strftime($format) : '';
- return trim($date);
-}
-
-sub datetime_from {
- my ($date, $timezone) = @_;
-
- # In the database, this is the "0" date.
- return undef if $date =~ /^0000/;
+ my ($date, $format, $timezone) = @_;
- my @time;
- # Most dates will be in this format, avoid strptime's generic parser
- if ($date =~ /^(\d{4})[\.-](\d{2})[\.-](\d{2})(?: (\d{2}):(\d{2}):(\d{2}))?$/) {
- @time = ($6, $5, $4, $3, $2 - 1, $1 - 1900, undef);
+ # If $format is not set, try to guess the correct date format.
+ if (!$format) {
+ if (!ref $date
+ && $date =~ /^(\d{4})[-\.](\d{2})[-\.](\d{2}) (\d{2}):(\d{2})(:(\d{2}))?$/)
+ {
+ my $sec = $7;
+ if (defined $sec) {
+ $format = "%Y-%m-%d %T %Z";
+ }
+ else {
+ $format = "%Y-%m-%d %R %Z";
+ }
}
else {
- @time = strptime($date);
- }
-
- unless (scalar @time) {
- # If an unknown timezone is passed (such as MSK, for Moskow),
- # strptime() is unable to parse the date. We try again, but we first
- # remove the timezone.
- $date =~ s/\s+\S+$//;
- @time = strptime($date);
+ # Default date format. See DateTime for other formats available.
+ $format = "%Y-%m-%d %R %Z";
}
+ }
- return undef if !@time;
-
- # strptime() counts years from 1900, except if they are older than 1901
- # in which case it returns the full year (so 1890 -> 1890, but 1984 -> 84,
- # and 3790 -> 1890). We make a guess and assume that 1100 <= year < 3000.
- $time[5] += 1900 if $time[5] < 1100;
-
- my %args = (
- year => $time[5],
- # Months start from 0 (January).
- month => $time[4] + 1,
- day => $time[3],
- hour => $time[2],
- minute => $time[1],
- # DateTime doesn't like fractional seconds.
- # Also, sometimes seconds are undef.
- second => defined($time[0]) ? int($time[0]) : undef,
- # If a timezone was specified, use it. Otherwise, use the
- # local timezone.
- time_zone => DateTime::TimeZone->offset_as_string($time[6])
- || Bugzilla->local_timezone,
- );
-
- # If something wasn't specified in the date, it's best to just not
- # pass it to DateTime at all. (This is important for doing datetime_from
- # on the deadline field, which is usually just a date with no time.)
- foreach my $arg (keys %args) {
- delete $args{$arg} if !defined $args{$arg};
- }
-
- # This module takes time to load and is only used here, so we
- # |require| it here rather than |use| it.
- require DateTime;
- my $dt = new DateTime(\%args);
+ my $dt = ref $date ? $date : datetime_from($date, $timezone);
+ $date = defined $dt ? $dt->strftime($format) : '';
+ return trim($date);
+}
- # Now display the date using the given timezone,
- # or the user's timezone if none is given.
- $dt->set_time_zone($timezone || Bugzilla->user->timezone);
- return $dt;
+sub datetime_from {
+ my ($date, $timezone) = @_;
+
+ # In the database, this is the "0" date.
+ return undef if $date =~ /^0000/;
+
+ my @time;
+
+ # Most dates will be in this format, avoid strptime's generic parser
+ if ($date =~ /^(\d{4})[\.-](\d{2})[\.-](\d{2})(?: (\d{2}):(\d{2}):(\d{2}))?$/) {
+ @time = ($6, $5, $4, $3, $2 - 1, $1 - 1900, undef);
+ }
+ else {
+ @time = strptime($date);
+ }
+
+ unless (scalar @time) {
+
+ # If an unknown timezone is passed (such as MSK, for Moskow),
+ # strptime() is unable to parse the date. We try again, but we first
+ # remove the timezone.
+ $date =~ s/\s+\S+$//;
+ @time = strptime($date);
+ }
+
+ return undef if !@time;
+
+ # strptime() counts years from 1900, except if they are older than 1901
+ # in which case it returns the full year (so 1890 -> 1890, but 1984 -> 84,
+ # and 3790 -> 1890). We make a guess and assume that 1100 <= year < 3000.
+ $time[5] += 1900 if $time[5] < 1100;
+
+ my %args = (
+ year => $time[5],
+
+ # Months start from 0 (January).
+ month => $time[4] + 1,
+ day => $time[3],
+ hour => $time[2],
+ minute => $time[1],
+
+ # DateTime doesn't like fractional seconds.
+ # Also, sometimes seconds are undef.
+ second => defined($time[0]) ? int($time[0]) : undef,
+
+ # If a timezone was specified, use it. Otherwise, use the
+ # local timezone.
+ time_zone => DateTime::TimeZone->offset_as_string($time[6])
+ || Bugzilla->local_timezone,
+ );
+
+ # If something wasn't specified in the date, it's best to just not
+ # pass it to DateTime at all. (This is important for doing datetime_from
+ # on the deadline field, which is usually just a date with no time.)
+ foreach my $arg (keys %args) {
+ delete $args{$arg} if !defined $args{$arg};
+ }
+
+ # This module takes time to load and is only used here, so we
+ # |require| it here rather than |use| it.
+ require DateTime;
+ my $dt = new DateTime(\%args);
+
+ # Now display the date using the given timezone,
+ # or the user's timezone if none is given.
+ $dt->set_time_zone($timezone || Bugzilla->user->timezone);
+ return $dt;
}
sub bz_crypt {
- my ($password, $salt) = @_;
-
- my $algorithm;
- if (!defined $salt) {
- # If you don't use a salt, then people can create tables of
- # hashes that map to particular passwords, and then break your
- # hashing very easily if they have a large-enough table of common
- # (or even uncommon) passwords. So we generate a unique salt for
- # each password in the database, and then just prepend it to
- # the hash.
- $salt = generate_random_password(PASSWORD_SALT_LENGTH);
- $algorithm = PASSWORD_DIGEST_ALGORITHM;
- }
-
- # We append the algorithm used to the string. This is good because then
- # we can change the algorithm being used, in the future, without
- # disrupting the validation of existing passwords. Also, this tells
- # us if a password is using the old "crypt" method of hashing passwords,
- # because the algorithm will be missing from the string.
- if ($salt =~ /{([^}]+)}$/) {
- $algorithm = $1;
- }
-
- # Wide characters cause crypt and Digest to die.
- if (Bugzilla->params->{'utf8'}) {
- utf8::encode($password) if utf8::is_utf8($password);
- }
-
- my $crypted_password;
- if (!$algorithm) {
- # Crypt the password.
- $crypted_password = crypt($password, $salt);
- }
- else {
- my $hasher = Digest->new($algorithm);
- # Newly created salts won't yet have a comma.
- ($salt) = $salt =~ /^([^,]+),?/;
- $hasher->add($password, $salt);
- $crypted_password = $salt . ',' . $hasher->b64digest . "{$algorithm}";
- }
-
- # Return the crypted password.
- return $crypted_password;
+ my ($password, $salt) = @_;
+
+ my $algorithm;
+ if (!defined $salt) {
+
+ # If you don't use a salt, then people can create tables of
+ # hashes that map to particular passwords, and then break your
+ # hashing very easily if they have a large-enough table of common
+ # (or even uncommon) passwords. So we generate a unique salt for
+ # each password in the database, and then just prepend it to
+ # the hash.
+ $salt = generate_random_password(PASSWORD_SALT_LENGTH);
+ $algorithm = PASSWORD_DIGEST_ALGORITHM;
+ }
+
+ # We append the algorithm used to the string. This is good because then
+ # we can change the algorithm being used, in the future, without
+ # disrupting the validation of existing passwords. Also, this tells
+ # us if a password is using the old "crypt" method of hashing passwords,
+ # because the algorithm will be missing from the string.
+ if ($salt =~ /{([^}]+)}$/) {
+ $algorithm = $1;
+ }
+
+ # Wide characters cause crypt and Digest to die.
+ if (Bugzilla->params->{'utf8'}) {
+ utf8::encode($password) if utf8::is_utf8($password);
+ }
+
+ my $crypted_password;
+ if (!$algorithm) {
+
+ # Crypt the password.
+ $crypted_password = crypt($password, $salt);
+ }
+ else {
+ my $hasher = Digest->new($algorithm);
+
+ # Newly created salts won't yet have a comma.
+ ($salt) = $salt =~ /^([^,]+),?/;
+ $hasher->add($password, $salt);
+ $crypted_password = $salt . ',' . $hasher->b64digest . "{$algorithm}";
+ }
+
+ # Return the crypted password.
+ return $crypted_password;
}
# If you want to understand the security of strings generated by this
@@ -688,191 +737,199 @@ sub bz_crypt {
# by the number of characters you generate, and that gets you the equivalent
# strength of the string in bits.
sub generate_random_password {
- my $size = shift || 10; # default to 10 chars if nothing specified
- return join("", map{ ('0'..'9','a'..'z','A'..'Z')[irand 62] } (1..$size));
+ my $size = shift || 10; # default to 10 chars if nothing specified
+ return
+ join("", map { ('0' .. '9', 'a' .. 'z', 'A' .. 'Z')[irand 62] } (1 .. $size));
}
sub validate_email_syntax {
- my ($addr) = @_;
- my $match = Bugzilla->params->{'emailregexp'};
- my $email = $addr . Bugzilla->params->{'emailsuffix'};
- # This regexp follows RFC 2822 section 3.4.1.
- my $addr_spec = $Email::Address::addr_spec;
- # RFC 2822 section 2.1 specifies that email addresses must
- # be made of US-ASCII characters only.
- # Email::Address::addr_spec doesn't enforce this.
- # We set the max length to 127 to ensure addresses aren't truncated when
- # inserted into the tokens.eventdata field.
- if ($addr =~ /$match/
- && $email !~ /\P{ASCII}/
- && $email =~ /^$addr_spec$/
- && length($email) <= 127)
- {
- # We assume these checks to suffice to consider the address untainted.
- trick_taint($_[0]);
- return 1;
- }
- return 0;
+ my ($addr) = @_;
+ my $match = Bugzilla->params->{'emailregexp'};
+ my $email = $addr . Bugzilla->params->{'emailsuffix'};
+
+ # This regexp follows RFC 2822 section 3.4.1.
+ my $addr_spec = $Email::Address::addr_spec;
+
+ # RFC 2822 section 2.1 specifies that email addresses must
+ # be made of US-ASCII characters only.
+ # Email::Address::addr_spec doesn't enforce this.
+ # We set the max length to 127 to ensure addresses aren't truncated when
+ # inserted into the tokens.eventdata field.
+ if ( $addr =~ /$match/
+ && $email !~ /\P{ASCII}/
+ && $email =~ /^$addr_spec$/
+ && length($email) <= 127)
+ {
+ # We assume these checks to suffice to consider the address untainted.
+ trick_taint($_[0]);
+ return 1;
+ }
+ return 0;
}
sub check_email_syntax {
- my ($addr) = @_;
+ my ($addr) = @_;
- unless (validate_email_syntax(@_)) {
- my $email = $addr . Bugzilla->params->{'emailsuffix'};
- ThrowUserError('illegal_email_address', { addr => $email });
- }
+ unless (validate_email_syntax(@_)) {
+ my $email = $addr . Bugzilla->params->{'emailsuffix'};
+ ThrowUserError('illegal_email_address', {addr => $email});
+ }
}
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;
+ 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 ($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;
+ }
+ my $ret = ($ts && $time eq $time2);
+ return $ret ? 1 : 0;
}
sub is_7bit_clean {
- return $_[0] !~ /[^\x20-\x7E\x0A\x0D]/;
+ return $_[0] !~ /[^\x20-\x7E\x0A\x0D]/;
}
sub clean_text {
- my $dtext = shift;
- if ($dtext) {
- # change control characters into a space
- $dtext =~ s/[\x00-\x1F\x7F]+/ /g;
- }
- return trim($dtext);
+ my $dtext = shift;
+ if ($dtext) {
+
+ # change control characters into a space
+ $dtext =~ s/[\x00-\x1F\x7F]+/ /g;
+ }
+ return trim($dtext);
}
sub on_main_db (&) {
- my $code = shift;
- my $original_dbh = Bugzilla->dbh;
- Bugzilla->request_cache->{dbh} = Bugzilla->dbh_main;
- $code->();
- Bugzilla->request_cache->{dbh} = $original_dbh;
+ my $code = shift;
+ my $original_dbh = Bugzilla->dbh;
+ Bugzilla->request_cache->{dbh} = Bugzilla->dbh_main;
+ $code->();
+ Bugzilla->request_cache->{dbh} = $original_dbh;
}
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;
+ 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 template_var {
- my $name = shift;
- my $request_cache = Bugzilla->request_cache;
- my $cache = $request_cache->{util_template_var} ||= {};
- my $lang = $request_cache->{template_current_lang}->[0] || '';
- return $cache->{$lang}->{$name} if defined $cache->{$lang};
-
- my $template = Bugzilla->template_inner($lang);
- my %vars;
- # Note: If we suddenly start needing a lot of template_var variables,
- # they should move into their own template, not field-descs.
- $template->process('global/field-descs.none.tmpl',
- { vars => \%vars, in_template_var => 1 })
- || ThrowTemplateError($template->error());
-
- $cache->{$lang} = \%vars;
- return $vars{$name};
+ my $name = shift;
+ my $request_cache = Bugzilla->request_cache;
+ my $cache = $request_cache->{util_template_var} ||= {};
+ my $lang = $request_cache->{template_current_lang}->[0] || '';
+ return $cache->{$lang}->{$name} if defined $cache->{$lang};
+
+ my $template = Bugzilla->template_inner($lang);
+ my %vars;
+
+ # Note: If we suddenly start needing a lot of template_var variables,
+ # they should move into their own template, not field-descs.
+ $template->process('global/field-descs.none.tmpl',
+ {vars => \%vars, in_template_var => 1})
+ || ThrowTemplateError($template->error());
+
+ $cache->{$lang} = \%vars;
+ return $vars{$name};
}
sub display_value {
- my ($field, $value) = @_;
- return template_var('value_descs')->{$field}->{$value} // $value;
+ my ($field, $value) = @_;
+ return template_var('value_descs')->{$field}->{$value} // $value;
}
sub disable_utf8 {
- if (Bugzilla->params->{'utf8'}) {
- binmode STDOUT, ':bytes'; # Turn off UTF8 encoding.
- }
+ if (Bugzilla->params->{'utf8'}) {
+ binmode STDOUT, ':bytes'; # Turn off UTF8 encoding.
+ }
}
use constant UTF8_ACCIDENTAL => qw(shiftjis big5-eten euc-kr euc-jp);
sub detect_encoding {
- my $data = shift;
-
- Bugzilla->feature('detect_charset')
- || ThrowUserError('feature_disabled', { feature => 'detect_charset' });
-
- require Encode::Detect::Detector;
- import Encode::Detect::Detector 'detect';
-
- my $encoding = detect($data);
- $encoding = resolve_alias($encoding) if $encoding;
-
- # Encode::Detect is bad at detecting certain charsets, but Encode::Guess
- # is better at them. Here's the details:
-
- # shiftjis, big5-eten, euc-kr, and euc-jp: (Encode::Detect
- # tends to accidentally mis-detect UTF-8 strings as being
- # these encodings.)
- if ($encoding && grep($_ eq $encoding, UTF8_ACCIDENTAL)) {
- $encoding = undef;
- my $decoder = guess_encoding($data, UTF8_ACCIDENTAL);
- $encoding = $decoder->name if ref $decoder;
- }
-
- # Encode::Detect sometimes mis-detects various ISO encodings as iso-8859-8,
- # or cp1255, but Encode::Guess can usually tell which one it is.
- if ($encoding && ($encoding eq 'iso-8859-8' || $encoding eq 'cp1255')) {
- my $decoded_as = _guess_iso($data, 'iso-8859-8',
- # These are ordered this way because it gives the most
- # accurate results.
- qw(cp1252 iso-8859-7 iso-8859-2));
- $encoding = $decoded_as if $decoded_as;
- }
+ my $data = shift;
+
+ Bugzilla->feature('detect_charset')
+ || ThrowUserError('feature_disabled', {feature => 'detect_charset'});
+
+ require Encode::Detect::Detector;
+ import Encode::Detect::Detector 'detect';
+
+ my $encoding = detect($data);
+ $encoding = resolve_alias($encoding) if $encoding;
+
+ # Encode::Detect is bad at detecting certain charsets, but Encode::Guess
+ # is better at them. Here's the details:
+
+ # shiftjis, big5-eten, euc-kr, and euc-jp: (Encode::Detect
+ # tends to accidentally mis-detect UTF-8 strings as being
+ # these encodings.)
+ if ($encoding && grep($_ eq $encoding, UTF8_ACCIDENTAL)) {
+ $encoding = undef;
+ my $decoder = guess_encoding($data, UTF8_ACCIDENTAL);
+ $encoding = $decoder->name if ref $decoder;
+ }
+
+ # Encode::Detect sometimes mis-detects various ISO encodings as iso-8859-8,
+ # or cp1255, but Encode::Guess can usually tell which one it is.
+ if ($encoding && ($encoding eq 'iso-8859-8' || $encoding eq 'cp1255')) {
+ my $decoded_as = _guess_iso(
+ $data, 'iso-8859-8',
+
+ # These are ordered this way because it gives the most
+ # accurate results.
+ qw(cp1252 iso-8859-7 iso-8859-2)
+ );
+ $encoding = $decoded_as if $decoded_as;
+ }
- return $encoding;
+ return $encoding;
}
# A helper for detect_encoding.
sub _guess_iso {
- my ($data, $versus, @isos) = (shift, shift, shift);
-
- my $encoding;
- foreach my $iso (@isos) {
- my $decoder = guess_encoding($data, ($iso, $versus));
- if (ref $decoder) {
- $encoding = $decoder->name if ref $decoder;
- last;
- }
+ my ($data, $versus, @isos) = (shift, shift, shift);
+
+ my $encoding;
+ foreach my $iso (@isos) {
+ my $decoder = guess_encoding($data, ($iso, $versus));
+ if (ref $decoder) {
+ $encoding = $decoder->name if ref $decoder;
+ last;
}
- return $encoding;
+ }
+ return $encoding;
}
1;
diff --git a/Bugzilla/Version.pm b/Bugzilla/Version.pm
index 4b332ff2b..dfc5a8e0f 100644
--- a/Bugzilla/Version.pm
+++ b/Bugzilla/Version.pm
@@ -26,134 +26,131 @@ use Scalar::Util qw(blessed);
use constant DEFAULT_VERSION => 'unspecified';
-use constant DB_TABLE => 'versions';
+use constant DB_TABLE => 'versions';
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';
use constant DB_COLUMNS => qw(
- id
- value
- product_id
- isactive
+ id
+ value
+ product_id
+ isactive
);
-use constant REQUIRED_FIELD_MAP => {
- product_id => 'product',
-};
+use constant REQUIRED_FIELD_MAP => {product_id => 'product',};
use constant UPDATE_COLUMNS => qw(
- value
- isactive
+ value
+ isactive
);
use constant VALIDATORS => {
- product => \&_check_product,
- value => \&_check_value,
- isactive => \&Bugzilla::Object::check_boolean,
+ product => \&_check_product,
+ value => \&_check_value,
+ isactive => \&Bugzilla::Object::check_boolean,
};
-use constant VALIDATOR_DEPENDENCIES => {
- value => ['product'],
-};
+use constant VALIDATOR_DEPENDENCIES => {value => ['product'],};
################################
# Methods
################################
sub new {
- my $class = shift;
- my $param = shift;
- my $dbh = Bugzilla->dbh;
-
- my $product;
- if (ref $param and !defined $param->{id}) {
- $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 };
+ my $class = shift;
+ my $param = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $product;
+ if (ref $param and !defined $param->{id}) {
+ $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"});
}
- unshift @_, $param;
- return $class->SUPER::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];
+ my $self = shift;
+ my $list = $self->SUPER::new_from_list(@_);
+ return [sort { vers_cmp(lc($a->name), lc($b->name)) } @$list];
}
sub run_create_validators {
- my $class = shift;
- my $params = $class->SUPER::run_create_validators(@_);
- my $product = delete $params->{product};
- $params->{product_id} = $product->id;
- return $params;
+ my $class = shift;
+ my $params = $class->SUPER::run_create_validators(@_);
+ my $product = delete $params->{product};
+ $params->{product_id} = $product->id;
+ return $params;
}
sub bug_count {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!defined $self->{'bug_count'}) {
- $self->{'bug_count'} = $dbh->selectrow_array(qq{
+ 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'};
+ ($self->product_id, $self->name)
+ ) || 0;
+ }
+ return $self->{'bug_count'};
}
sub update {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- $dbh->bz_start_transaction();
- my ($changes, $old_self) = $self->SUPER::update(@_);
-
- if (exists $changes->{value}) {
- $dbh->do('UPDATE bugs SET version = ?
- WHERE version = ? AND product_id = ?',
- undef, ($self->name, $old_self->name, $self->product_id));
- }
- $dbh->bz_commit_transaction();
-
- return $changes;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+ my ($changes, $old_self) = $self->SUPER::update(@_);
+
+ if (exists $changes->{value}) {
+ $dbh->do(
+ 'UPDATE bugs SET version = ?
+ WHERE version = ? AND product_id = ?', undef,
+ ($self->name, $old_self->name, $self->product_id)
+ );
+ }
+ $dbh->bz_commit_transaction();
+
+ return $changes;
}
sub remove_from_db {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- # Products must have at least one version.
- if (scalar(@{$self->product->versions}) == 1) {
- ThrowUserError('version_is_last', { version => $self });
- }
+ # Products must have at least one version.
+ if (scalar(@{$self->product->versions}) == 1) {
+ ThrowUserError('version_is_last', {version => $self});
+ }
- # The version cannot be removed if there are bugs
- # associated with it.
- if ($self->bug_count) {
- ThrowUserError("version_has_bugs", { nb => $self->bug_count });
- }
- $self->SUPER::remove_from_db();
+ # The version cannot be removed if there are bugs
+ # associated with it.
+ if ($self->bug_count) {
+ ThrowUserError("version_has_bugs", {nb => $self->bug_count});
+ }
+ $self->SUPER::remove_from_db();
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
}
###############################
@@ -161,45 +158,47 @@ sub remove_from_db {
###############################
sub product_id { return $_[0]->{'product_id'}; }
-sub is_active { return $_[0]->{'isactive'}; }
+sub is_active { return $_[0]->{'isactive'}; }
sub product {
- my $self = shift;
+ my $self = shift;
- require Bugzilla::Product;
- $self->{'product'} ||= new Bugzilla::Product($self->product_id);
- return $self->{'product'};
+ require Bugzilla::Product;
+ $self->{'product'} ||= new Bugzilla::Product($self->product_id);
+ return $self->{'product'};
}
################################
# Validators
################################
-sub set_value { $_[0]->set('value', $_[1]); }
+sub set_value { $_[0]->set('value', $_[1]); }
sub set_isactive { $_[0]->set('isactive', $_[1]); }
sub _check_value {
- my ($invocant, $name, undef, $params) = @_;
- my $product = blessed($invocant) ? $invocant->product : $params->{product};
-
- $name = trim($name);
- $name || ThrowUserError('version_blank_name');
- # Remove unprintable characters
- $name = clean_text($name);
-
- my $version = new Bugzilla::Version({ product => $product, name => $name });
- if ($version && (!ref $invocant || $version->id != $invocant->id)) {
- ThrowUserError('version_already_exists', { name => $version->name,
- product => $product->name });
- }
- return $name;
+ my ($invocant, $name, undef, $params) = @_;
+ my $product = blessed($invocant) ? $invocant->product : $params->{product};
+
+ $name = trim($name);
+ $name || ThrowUserError('version_blank_name');
+
+ # Remove unprintable characters
+ $name = clean_text($name);
+
+ my $version = new Bugzilla::Version({product => $product, name => $name});
+ if ($version && (!ref $invocant || $version->id != $invocant->id)) {
+ ThrowUserError('version_already_exists',
+ {name => $version->name, product => $product->name});
+ }
+ return $name;
}
sub _check_product {
- my ($invocant, $product) = @_;
- $product || ThrowCodeError('param_required',
- { function => "$invocant->create", param => 'product' });
- return Bugzilla->user->check_can_admin_product($product->name);
+ my ($invocant, $product) = @_;
+ $product
+ || ThrowCodeError('param_required',
+ {function => "$invocant->create", param => 'product'});
+ return Bugzilla->user->check_can_admin_product($product->name);
}
###############################
@@ -209,44 +208,52 @@ sub _check_product {
# 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;
- }
+ 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;
}
- return @A <=> @B;
+ }
+ return @A <=> @B;
}
1;
diff --git a/Bugzilla/WebService.pm b/Bugzilla/WebService.pm
index f80813744..2630e3565 100644
--- a/Bugzilla/WebService.pm
+++ b/Bugzilla/WebService.pm
@@ -5,7 +5,7 @@
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
-# This is the base class for $self in WebService method calls. For the
+# This is the base class for $self in WebService method calls. For the
# actual RPC server, see Bugzilla::WebService::Server and its subclasses.
package Bugzilla::WebService;
@@ -17,11 +17,12 @@ use Bugzilla::WebService::Server;
# Used by the JSON-RPC server to convert incoming date fields apprpriately.
use constant DATE_FIELDS => {};
+
# Used by the JSON-RPC server to convert incoming base64 fields appropriately.
use constant BASE64_FIELDS => {};
# For some methods, we shouldn't call Bugzilla->login before we call them
-use constant LOGIN_EXEMPT => { };
+use constant LOGIN_EXEMPT => {};
# Used to allow methods to be called in the JSON-RPC WebService via GET.
# Methods that can modify data MUST not be listed here.
@@ -32,8 +33,8 @@ use constant READ_ONLY => ();
use constant PUBLIC_METHODS => ();
sub login_exempt {
- my ($class, $method) = @_;
- return $class->LOGIN_EXEMPT->{$method};
+ my ($class, $method) = @_;
+ return $class->LOGIN_EXEMPT->{$method};
}
1;
diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm
index b07d3cb01..04c4eae2e 100644
--- a/Bugzilla/WebService/Bug.pm
+++ b/Bugzilla/WebService/Bug.pm
@@ -19,7 +19,8 @@ use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Field;
use Bugzilla::WebService::Constants;
-use Bugzilla::WebService::Util qw(extract_flags filter filter_wants validate translate);
+use Bugzilla::WebService::Util
+ qw(extract_flags filter filter_wants validate translate);
use Bugzilla::Bug;
use Bugzilla::BugMail;
use Bugzilla::Util qw(trick_taint trim diff_arrays detaint_natural);
@@ -43,58 +44,54 @@ use Storable qw(dclone);
use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component);
use constant DATE_FIELDS => {
- comments => ['new_since'],
- history => ['new_since'],
- search => ['last_change_time', 'creation_time'],
+ comments => ['new_since'],
+ history => ['new_since'],
+ search => ['last_change_time', 'creation_time'],
};
-use constant BASE64_FIELDS => {
- add_attachment => ['data'],
-};
+use constant BASE64_FIELDS => {add_attachment => ['data'],};
use constant READ_ONLY => qw(
- attachments
- comments
- fields
- get
- history
- legal_values
- search
+ attachments
+ comments
+ fields
+ get
+ history
+ legal_values
+ search
);
use constant PUBLIC_METHODS => qw(
- add_attachment
- add_comment
- attachments
- comments
- create
- fields
- get
- history
- legal_values
- possible_duplicates
- render_comment
- search
- search_comment_tags
- update
- update_attachment
- update_comment_tags
- update_see_also
- update_tags
+ add_attachment
+ add_comment
+ attachments
+ comments
+ create
+ fields
+ get
+ history
+ legal_values
+ possible_duplicates
+ render_comment
+ search
+ search_comment_tags
+ update
+ update_attachment
+ update_comment_tags
+ update_see_also
+ update_tags
);
-use constant ATTACHMENT_MAPPED_SETTERS => {
- file_name => 'filename',
- summary => 'description',
-};
+use constant ATTACHMENT_MAPPED_SETTERS =>
+ {file_name => 'filename', summary => 'description',};
use constant ATTACHMENT_MAPPED_RETURNS => {
- description => 'summary',
- ispatch => 'is_patch',
- isprivate => 'is_private',
- isobsolete => 'is_obsolete',
- filename => 'file_name',
- mimetype => 'content_type',
+ description => 'summary',
+ ispatch => 'is_patch',
+ isprivate => 'is_private',
+ isobsolete => 'is_obsolete',
+ filename => 'file_name',
+ mimetype => 'content_type',
};
###########
@@ -102,1089 +99,1101 @@ use constant ATTACHMENT_MAPPED_RETURNS => {
###########
sub fields {
- my ($self, $params) = validate(@_, 'ids', 'names');
+ my ($self, $params) = validate(@_, 'ids', 'names');
- Bugzilla->switch_to_shadow_db();
+ Bugzilla->switch_to_shadow_db();
- my @fields;
- if (defined $params->{ids}) {
- my $ids = $params->{ids};
- foreach my $id (@$ids) {
- my $loop_field = Bugzilla::Field->check({ id => $id });
- push(@fields, $loop_field);
- }
+ my @fields;
+ if (defined $params->{ids}) {
+ my $ids = $params->{ids};
+ foreach my $id (@$ids) {
+ my $loop_field = Bugzilla::Field->check({id => $id});
+ push(@fields, $loop_field);
}
-
- if (defined $params->{names}) {
- my $names = $params->{names};
- foreach my $field_name (@$names) {
- my $loop_field = Bugzilla::Field->check($field_name);
- # Don't push in duplicate fields if we also asked for this field
- # in "ids".
- if (!grep($_->id == $loop_field->id, @fields)) {
- push(@fields, $loop_field);
- }
- }
+ }
+
+ if (defined $params->{names}) {
+ my $names = $params->{names};
+ foreach my $field_name (@$names) {
+ my $loop_field = Bugzilla::Field->check($field_name);
+
+ # Don't push in duplicate fields if we also asked for this field
+ # in "ids".
+ if (!grep($_->id == $loop_field->id, @fields)) {
+ push(@fields, $loop_field);
+ }
}
-
- if (!defined $params->{ids} and !defined $params->{names}) {
- @fields = @{ Bugzilla->fields({ obsolete => 0 }) };
+ }
+
+ if (!defined $params->{ids} and !defined $params->{names}) {
+ @fields = @{Bugzilla->fields({obsolete => 0})};
+ }
+
+ my @fields_out;
+ foreach my $field (@fields) {
+ my $visibility_field
+ = $field->visibility_field ? $field->visibility_field->name : undef;
+ my $vis_values = $field->visibility_values;
+ my $value_field = $field->value_field ? $field->value_field->name : undef;
+
+ my (@values, $has_values);
+ if ( ($field->is_select and $field->name ne 'product')
+ or grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)
+ or $field->name eq 'keywords')
+ {
+ $has_values = 1;
+ @values = @{$self->_legal_field_values({field => $field})};
}
- my @fields_out;
- foreach my $field (@fields) {
- my $visibility_field = $field->visibility_field
- ? $field->visibility_field->name : undef;
- my $vis_values = $field->visibility_values;
- my $value_field = $field->value_field
- ? $field->value_field->name : undef;
-
- my (@values, $has_values);
- if ( ($field->is_select and $field->name ne 'product')
- or grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)
- or $field->name eq 'keywords')
- {
- $has_values = 1;
- @values = @{ $self->_legal_field_values({ field => $field }) };
- }
-
- if (grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)) {
- $value_field = 'product';
- }
+ if (grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)) {
+ $value_field = 'product';
+ }
- my %field_data = (
- id => $self->type('int', $field->id),
- type => $self->type('int', $field->type),
- is_custom => $self->type('boolean', $field->custom),
- name => $self->type('string', $field->name),
- display_name => $self->type('string', $field->description),
- is_mandatory => $self->type('boolean', $field->is_mandatory),
- is_on_bug_entry => $self->type('boolean', $field->enter_bug),
- visibility_field => $self->type('string', $visibility_field),
- visibility_values =>
- [ map { $self->type('string', $_->name) } @$vis_values ],
- );
- if ($has_values) {
- $field_data{value_field} = $self->type('string', $value_field);
- $field_data{values} = \@values;
- };
- push(@fields_out, filter $params, \%field_data);
+ my %field_data = (
+ id => $self->type('int', $field->id),
+ type => $self->type('int', $field->type),
+ is_custom => $self->type('boolean', $field->custom),
+ name => $self->type('string', $field->name),
+ display_name => $self->type('string', $field->description),
+ is_mandatory => $self->type('boolean', $field->is_mandatory),
+ is_on_bug_entry => $self->type('boolean', $field->enter_bug),
+ visibility_field => $self->type('string', $visibility_field),
+ visibility_values => [map { $self->type('string', $_->name) } @$vis_values],
+ );
+ if ($has_values) {
+ $field_data{value_field} = $self->type('string', $value_field);
+ $field_data{values} = \@values;
}
+ push(@fields_out, filter $params, \%field_data);
+ }
- return { fields => \@fields_out };
+ return {fields => \@fields_out};
}
sub _legal_field_values {
- my ($self, $params) = @_;
- my $field = $params->{field};
- my $field_name = $field->name;
- my $user = Bugzilla->user;
-
- my @result;
- if (grep($_ eq $field_name, PRODUCT_SPECIFIC_FIELDS)) {
- my @list;
- if ($field_name eq 'version') {
- @list = Bugzilla::Version->get_all;
- }
- elsif ($field_name eq 'component') {
- @list = Bugzilla::Component->get_all;
- }
- else {
- @list = Bugzilla::Milestone->get_all;
- }
+ my ($self, $params) = @_;
+ my $field = $params->{field};
+ my $field_name = $field->name;
+ my $user = Bugzilla->user;
+
+ my @result;
+ if (grep($_ eq $field_name, PRODUCT_SPECIFIC_FIELDS)) {
+ my @list;
+ if ($field_name eq 'version') {
+ @list = Bugzilla::Version->get_all;
+ }
+ elsif ($field_name eq 'component') {
+ @list = Bugzilla::Component->get_all;
+ }
+ else {
+ @list = Bugzilla::Milestone->get_all;
+ }
- foreach my $value (@list) {
- my $sortkey = $field_name eq 'target_milestone'
- ? $value->sortkey : 0;
- # XXX This is very slow for large numbers of values.
- my $product_name = $value->product->name;
- if ($user->can_see_product($product_name)) {
- push(@result, {
- name => $self->type('string', $value->name),
- sort_key => $self->type('int', $sortkey),
- sortkey => $self->type('int', $sortkey), # deprecated
- visibility_values => [$self->type('string', $product_name)],
- is_active => $self->type('boolean', $value->is_active),
- });
- }
- }
+ foreach my $value (@list) {
+ my $sortkey = $field_name eq 'target_milestone' ? $value->sortkey : 0;
+
+ # XXX This is very slow for large numbers of values.
+ my $product_name = $value->product->name;
+ if ($user->can_see_product($product_name)) {
+ push(
+ @result,
+ {
+ name => $self->type('string', $value->name),
+ sort_key => $self->type('int', $sortkey),
+ sortkey => $self->type('int', $sortkey), # deprecated
+ visibility_values => [$self->type('string', $product_name)],
+ is_active => $self->type('boolean', $value->is_active),
+ }
+ );
+ }
}
+ }
+
+ elsif ($field_name eq 'bug_status') {
+ my @status_all = Bugzilla::Status->get_all;
+ my $initial_status = bless(
+ {
+ id => 0,
+ name => '',
+ is_open => 1,
+ sortkey => 0,
+ can_change_to => Bugzilla::Status->can_change_to
+ },
+ 'Bugzilla::Status'
+ );
+ unshift(@status_all, $initial_status);
+
+ foreach my $status (@status_all) {
+ my @can_change_to;
+ foreach my $change_to (@{$status->can_change_to}) {
+
+ # There's no need to note that a status can transition
+ # to itself.
+ next if $change_to->id == $status->id;
+ my %change_to_hash = (
+ name => $self->type('string', $change_to->name),
+ comment_required =>
+ $self->type('boolean', $change_to->comment_required_on_change_from($status)),
+ );
+ push(@can_change_to, \%change_to_hash);
+ }
- elsif ($field_name eq 'bug_status') {
- my @status_all = Bugzilla::Status->get_all;
- my $initial_status = bless({ id => 0, name => '', is_open => 1, sortkey => 0,
- can_change_to => Bugzilla::Status->can_change_to },
- 'Bugzilla::Status');
- unshift(@status_all, $initial_status);
-
- foreach my $status (@status_all) {
- my @can_change_to;
- foreach my $change_to (@{ $status->can_change_to }) {
- # There's no need to note that a status can transition
- # to itself.
- next if $change_to->id == $status->id;
- my %change_to_hash = (
- name => $self->type('string', $change_to->name),
- comment_required => $self->type('boolean',
- $change_to->comment_required_on_change_from($status)),
- );
- push(@can_change_to, \%change_to_hash);
- }
-
- push (@result, {
- name => $self->type('string', $status->name),
- is_open => $self->type('boolean', $status->is_open),
- sort_key => $self->type('int', $status->sortkey),
- sortkey => $self->type('int', $status->sortkey), # deprecated
- can_change_to => \@can_change_to,
- visibility_values => [],
- });
+ push(
+ @result,
+ {
+ name => $self->type('string', $status->name),
+ is_open => $self->type('boolean', $status->is_open),
+ sort_key => $self->type('int', $status->sortkey),
+ sortkey => $self->type('int', $status->sortkey), # deprecated
+ can_change_to => \@can_change_to,
+ visibility_values => [],
}
+ );
}
+ }
- elsif ($field_name eq 'keywords') {
- my @legal_keywords = Bugzilla::Keyword->get_all;
- foreach my $value (@legal_keywords) {
- push (@result, {
- name => $self->type('string', $value->name),
- description => $self->type('string', $value->description),
- });
+ elsif ($field_name eq 'keywords') {
+ my @legal_keywords = Bugzilla::Keyword->get_all;
+ foreach my $value (@legal_keywords) {
+ push(
+ @result,
+ {
+ name => $self->type('string', $value->name),
+ description => $self->type('string', $value->description),
}
+ );
}
- else {
- my @values = Bugzilla::Field::Choice->type($field)->get_all();
- foreach my $value (@values) {
- my $vis_val = $value->visibility_value;
- push(@result, {
- name => $self->type('string', $value->name),
- sort_key => $self->type('int' , $value->sortkey),
- sortkey => $self->type('int' , $value->sortkey), # deprecated
- visibility_values => [
- defined $vis_val ? $self->type('string', $vis_val->name)
- : ()
- ],
- });
+ }
+ else {
+ my @values = Bugzilla::Field::Choice->type($field)->get_all();
+ foreach my $value (@values) {
+ my $vis_val = $value->visibility_value;
+ push(
+ @result,
+ {
+ name => $self->type('string', $value->name),
+ sort_key => $self->type('int', $value->sortkey),
+ sortkey => $self->type('int', $value->sortkey), # deprecated
+ visibility_values =>
+ [defined $vis_val ? $self->type('string', $vis_val->name) : ()],
}
+ );
}
+ }
- return \@result;
+ return \@result;
}
sub comments {
- my ($self, $params) = validate(@_, 'ids', 'comment_ids');
+ my ($self, $params) = validate(@_, 'ids', 'comment_ids');
- if (!(defined $params->{ids} || defined $params->{comment_ids})) {
- ThrowCodeError('params_required',
- { function => 'Bug.comments',
- params => ['ids', 'comment_ids'] });
- }
+ if (!(defined $params->{ids} || defined $params->{comment_ids})) {
+ ThrowCodeError('params_required',
+ {function => 'Bug.comments', params => ['ids', 'comment_ids']});
+ }
- my $bug_ids = $params->{ids} || [];
- my $comment_ids = $params->{comment_ids} || [];
-
- my $dbh = Bugzilla->switch_to_shadow_db();
- my $user = Bugzilla->user;
-
- my %bugs;
- foreach my $bug_id (@$bug_ids) {
- my $bug = Bugzilla::Bug->check($bug_id);
- # We want the API to always return comments in the same order.
-
- my $comments = $bug->comments({ order => 'oldest_to_newest',
- after => $params->{new_since} });
- my @result;
- foreach my $comment (@$comments) {
- next if $comment->is_private && !$user->is_insider;
- push(@result, $self->_translate_comment($comment, $params));
- }
- $bugs{$bug->id}{'comments'} = \@result;
+ my $bug_ids = $params->{ids} || [];
+ my $comment_ids = $params->{comment_ids} || [];
+
+ my $dbh = Bugzilla->switch_to_shadow_db();
+ my $user = Bugzilla->user;
+
+ my %bugs;
+ foreach my $bug_id (@$bug_ids) {
+ my $bug = Bugzilla::Bug->check($bug_id);
+
+ # We want the API to always return comments in the same order.
+
+ my $comments = $bug->comments(
+ {order => 'oldest_to_newest', after => $params->{new_since}});
+ my @result;
+ foreach my $comment (@$comments) {
+ next if $comment->is_private && !$user->is_insider;
+ push(@result, $self->_translate_comment($comment, $params));
+ }
+ $bugs{$bug->id}{'comments'} = \@result;
+ }
+
+ my %comments;
+ if (scalar @$comment_ids) {
+ my @ids = map { trim($_) } @$comment_ids;
+ my $comment_data = Bugzilla::Comment->new_from_list(\@ids);
+
+ # See if we were passed any invalid comment ids.
+ my %got_ids = map { $_->id => 1 } @$comment_data;
+ foreach my $comment_id (@ids) {
+ if (!$got_ids{$comment_id}) {
+ ThrowUserError('comment_id_invalid', {id => $comment_id});
+ }
}
- my %comments;
- if (scalar @$comment_ids) {
- my @ids = map { trim($_) } @$comment_ids;
- my $comment_data = Bugzilla::Comment->new_from_list(\@ids);
-
- # See if we were passed any invalid comment ids.
- my %got_ids = map { $_->id => 1 } @$comment_data;
- foreach my $comment_id (@ids) {
- if (!$got_ids{$comment_id}) {
- ThrowUserError('comment_id_invalid', { id => $comment_id });
- }
- }
-
- # Now make sure that we can see all the associated bugs.
- my %got_bug_ids = map { $_->bug_id => 1 } @$comment_data;
- Bugzilla::Bug->check($_) foreach (keys %got_bug_ids);
-
- foreach my $comment (@$comment_data) {
- if ($comment->is_private && !$user->is_insider) {
- ThrowUserError('comment_is_private', { id => $comment->id });
- }
- $comments{$comment->id} =
- $self->_translate_comment($comment, $params);
- }
+ # Now make sure that we can see all the associated bugs.
+ my %got_bug_ids = map { $_->bug_id => 1 } @$comment_data;
+ Bugzilla::Bug->check($_) foreach (keys %got_bug_ids);
+
+ foreach my $comment (@$comment_data) {
+ if ($comment->is_private && !$user->is_insider) {
+ ThrowUserError('comment_is_private', {id => $comment->id});
+ }
+ $comments{$comment->id} = $self->_translate_comment($comment, $params);
}
+ }
- return { bugs => \%bugs, comments => \%comments };
+ return {bugs => \%bugs, comments => \%comments};
}
sub render_comment {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- unless (defined $params->{text}) {
- ThrowCodeError('params_required',
- { function => 'Bug.render_comment',
- params => ['text'] });
- }
+ unless (defined $params->{text}) {
+ ThrowCodeError('params_required',
+ {function => 'Bug.render_comment', params => ['text']});
+ }
- Bugzilla->switch_to_shadow_db();
- my $bug = $params->{id} ? Bugzilla::Bug->check($params->{id}) : undef;
+ Bugzilla->switch_to_shadow_db();
+ my $bug = $params->{id} ? Bugzilla::Bug->check($params->{id}) : undef;
- my $tmpl = '[% text FILTER quoteUrls(bug) %]';
- my $html;
- my $template = Bugzilla->template;
- $template->process(
- \$tmpl,
- { bug => $bug, text => $params->{text}},
- \$html
- );
+ my $tmpl = '[% text FILTER quoteUrls(bug) %]';
+ my $html;
+ my $template = Bugzilla->template;
+ $template->process(\$tmpl, {bug => $bug, text => $params->{text}}, \$html);
- return { html => $html };
+ return {html => $html};
}
# Helper for Bug.comments
sub _translate_comment {
- my ($self, $comment, $filters, $types, $prefix) = @_;
- my $attach_id = $comment->is_about_attachment ? $comment->extra_data
- : undef;
-
- my $comment_hash = {
- id => $self->type('int', $comment->id),
- bug_id => $self->type('int', $comment->bug_id),
- creator => $self->type('email', $comment->author->login),
- time => $self->type('dateTime', $comment->creation_ts),
- creation_time => $self->type('dateTime', $comment->creation_ts),
- is_private => $self->type('boolean', $comment->is_private),
- text => $self->type('string', $comment->body_full),
- attachment_id => $self->type('int', $attach_id),
- count => $self->type('int', $comment->count),
- };
-
- # Don't load comment tags unless enabled
- if (Bugzilla->params->{'comment_taggers_group'}) {
- $comment_hash->{tags} = [
- map { $self->type('string', $_) }
- @{ $comment->tags }
- ];
- }
-
- return filter($filters, $comment_hash, $types, $prefix);
+ my ($self, $comment, $filters, $types, $prefix) = @_;
+ my $attach_id = $comment->is_about_attachment ? $comment->extra_data : undef;
+
+ my $comment_hash = {
+ id => $self->type('int', $comment->id),
+ bug_id => $self->type('int', $comment->bug_id),
+ creator => $self->type('email', $comment->author->login),
+ time => $self->type('dateTime', $comment->creation_ts),
+ creation_time => $self->type('dateTime', $comment->creation_ts),
+ is_private => $self->type('boolean', $comment->is_private),
+ text => $self->type('string', $comment->body_full),
+ attachment_id => $self->type('int', $attach_id),
+ count => $self->type('int', $comment->count),
+ };
+
+ # Don't load comment tags unless enabled
+ if (Bugzilla->params->{'comment_taggers_group'}) {
+ $comment_hash->{tags} = [map { $self->type('string', $_) } @{$comment->tags}];
+ }
+
+ return filter($filters, $comment_hash, $types, $prefix);
}
sub get {
- my ($self, $params) = validate(@_, 'ids');
-
- Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
-
- my $ids = $params->{ids};
- defined $ids || ThrowCodeError('param_required', { param => 'ids' });
-
- my (@bugs, @faults, @hashes);
-
- # Cache permissions for bugs. This highly reduces the number of calls to the DB.
- # visible_bugs() is only able to handle bug IDs, so we have to skip aliases.
- my @int = grep { $_ =~ /^\d+$/ } @$ids;
- Bugzilla->user->visible_bugs(\@int);
-
- foreach my $bug_id (@$ids) {
- my $bug;
- if ($params->{permissive}) {
- eval { $bug = Bugzilla::Bug->check($bug_id); };
- if ($@) {
- push(@faults, {id => $bug_id,
- faultString => $@->faultstring,
- faultCode => $@->faultcode,
- }
- );
- undef $@;
- next;
- }
- }
- else {
- $bug = Bugzilla::Bug->check($bug_id);
- }
- push(@bugs, $bug);
- push(@hashes, $self->_bug_to_hash($bug, $params));
+ my ($self, $params) = validate(@_, 'ids');
+
+ Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
+
+ my $ids = $params->{ids};
+ defined $ids || ThrowCodeError('param_required', {param => 'ids'});
+
+ my (@bugs, @faults, @hashes);
+
+ # Cache permissions for bugs. This highly reduces the number of calls to the DB.
+ # visible_bugs() is only able to handle bug IDs, so we have to skip aliases.
+ my @int = grep { $_ =~ /^\d+$/ } @$ids;
+ Bugzilla->user->visible_bugs(\@int);
+
+ foreach my $bug_id (@$ids) {
+ my $bug;
+ if ($params->{permissive}) {
+ eval { $bug = Bugzilla::Bug->check($bug_id); };
+ if ($@) {
+ push(@faults,
+ {id => $bug_id, faultString => $@->faultstring, faultCode => $@->faultcode,});
+ undef $@;
+ next;
+ }
}
+ else {
+ $bug = Bugzilla::Bug->check($bug_id);
+ }
+ push(@bugs, $bug);
+ push(@hashes, $self->_bug_to_hash($bug, $params));
+ }
- # Set the ETag before inserting the update tokens
- # since the tokens will always be unique even if
- # the data has not changed.
- $self->bz_etag(\@hashes);
+ # Set the ETag before inserting the update tokens
+ # since the tokens will always be unique even if
+ # the data has not changed.
+ $self->bz_etag(\@hashes);
- $self->_add_update_tokens($params, \@bugs, \@hashes);
+ $self->_add_update_tokens($params, \@bugs, \@hashes);
- return { bugs => \@hashes, faults => \@faults };
+ return {bugs => \@hashes, faults => \@faults};
}
-# this is a function that gets bug activity for list of bug ids
+# this is a function that gets bug activity for list of bug ids
# it can be called as the following:
# $call = $rpc->call( 'Bug.history', { ids => [1,2] });
sub history {
- my ($self, $params) = validate(@_, 'ids');
-
- Bugzilla->switch_to_shadow_db();
-
- my $ids = $params->{ids};
- defined $ids || ThrowCodeError('param_required', { param => 'ids' });
-
- my %api_name = reverse %{ Bugzilla::Bug::FIELD_MAP() };
- $api_name{'bug_group'} = 'groups';
-
- my @return;
- foreach my $bug_id (@$ids) {
- my %item;
- my $bug = Bugzilla::Bug->check($bug_id);
- $bug_id = $bug->id;
- $item{id} = $self->type('int', $bug_id);
-
- my ($activity) = $bug->get_activity(undef, $params->{new_since});
-
- my @history;
- foreach my $changeset (@$activity) {
- my %bug_history;
- $bug_history{when} = $self->type('dateTime', $changeset->{when});
- $bug_history{who} = $self->type('string', $changeset->{who});
- $bug_history{changes} = [];
- foreach my $change (@{ $changeset->{changes} }) {
- my $api_field = $api_name{$change->{fieldname}} || $change->{fieldname};
- my $attach_id = delete $change->{attachid};
- if ($attach_id) {
- $change->{attachment_id} = $self->type('int', $attach_id);
- }
- $change->{removed} = $self->type('string', $change->{removed});
- $change->{added} = $self->type('string', $change->{added});
- $change->{field_name} = $self->type('string', $api_field);
- delete $change->{fieldname};
- push (@{$bug_history{changes}}, $change);
- }
-
- push (@history, \%bug_history);
+ my ($self, $params) = validate(@_, 'ids');
+
+ Bugzilla->switch_to_shadow_db();
+
+ my $ids = $params->{ids};
+ defined $ids || ThrowCodeError('param_required', {param => 'ids'});
+
+ my %api_name = reverse %{Bugzilla::Bug::FIELD_MAP()};
+ $api_name{'bug_group'} = 'groups';
+
+ my @return;
+ foreach my $bug_id (@$ids) {
+ my %item;
+ my $bug = Bugzilla::Bug->check($bug_id);
+ $bug_id = $bug->id;
+ $item{id} = $self->type('int', $bug_id);
+
+ my ($activity) = $bug->get_activity(undef, $params->{new_since});
+
+ my @history;
+ foreach my $changeset (@$activity) {
+ my %bug_history;
+ $bug_history{when} = $self->type('dateTime', $changeset->{when});
+ $bug_history{who} = $self->type('string', $changeset->{who});
+ $bug_history{changes} = [];
+ foreach my $change (@{$changeset->{changes}}) {
+ my $api_field = $api_name{$change->{fieldname}} || $change->{fieldname};
+ my $attach_id = delete $change->{attachid};
+ if ($attach_id) {
+ $change->{attachment_id} = $self->type('int', $attach_id);
}
+ $change->{removed} = $self->type('string', $change->{removed});
+ $change->{added} = $self->type('string', $change->{added});
+ $change->{field_name} = $self->type('string', $api_field);
+ delete $change->{fieldname};
+ push(@{$bug_history{changes}}, $change);
+ }
+
+ push(@history, \%bug_history);
+ }
- $item{history} = \@history;
+ $item{history} = \@history;
- # alias is returned in case users passes a mixture of ids and aliases
- # then they get to know which bug activity relates to which value
- # they passed
- $item{alias} = [ map { $self->type('string', $_) } @{ $bug->alias } ];
+ # alias is returned in case users passes a mixture of ids and aliases
+ # then they get to know which bug activity relates to which value
+ # they passed
+ $item{alias} = [map { $self->type('string', $_) } @{$bug->alias}];
- push(@return, \%item);
- }
+ push(@return, \%item);
+ }
- return { bugs => \@return };
+ return {bugs => \@return};
}
sub search {
- my ($self, $params) = @_;
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
-
- Bugzilla->switch_to_shadow_db();
-
- my $match_params = dclone($params);
- delete $match_params->{include_fields};
- delete $match_params->{exclude_fields};
-
- # Determine whether this is a quicksearch query
- if (exists $match_params->{quicksearch}) {
- my $quicksearch = quicksearch($match_params->{'quicksearch'});
- my $cgi = Bugzilla::CGI->new($quicksearch);
- $match_params = $cgi->Vars;
- }
-
- if ( defined($match_params->{offset}) and !defined($match_params->{limit}) ) {
- ThrowCodeError('param_required',
- { param => 'limit', function => 'Bug.search()' });
- }
-
- my $max_results = Bugzilla->params->{max_search_results};
- unless (defined $match_params->{limit} && $match_params->{limit} == 0) {
- if (!defined $match_params->{limit} || $match_params->{limit} > $max_results) {
- $match_params->{limit} = $max_results;
- }
- }
- else {
- delete $match_params->{limit};
- delete $match_params->{offset};
- }
+ my ($self, $params) = @_;
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
- $match_params = Bugzilla::Bug::map_fields($match_params);
+ Bugzilla->switch_to_shadow_db();
- my %options = ( fields => ['bug_id'] );
+ my $match_params = dclone($params);
+ delete $match_params->{include_fields};
+ delete $match_params->{exclude_fields};
- # Find the highest custom field id
- my @field_ids = grep(/^f(\d+)$/, keys %$match_params);
- my $last_field_id = @field_ids ? max @field_ids + 1 : 1;
+ # Determine whether this is a quicksearch query
+ if (exists $match_params->{quicksearch}) {
+ my $quicksearch = quicksearch($match_params->{'quicksearch'});
+ my $cgi = Bugzilla::CGI->new($quicksearch);
+ $match_params = $cgi->Vars;
+ }
- # Do special search types for certain fields.
- if (my $change_when = delete $match_params->{'delta_ts'}) {
- $match_params->{"f${last_field_id}"} = 'delta_ts';
- $match_params->{"o${last_field_id}"} = 'greaterthaneq';
- $match_params->{"v${last_field_id}"} = $change_when;
- $last_field_id++;
- }
- if (my $creation_when = delete $match_params->{'creation_ts'}) {
- $match_params->{"f${last_field_id}"} = 'creation_ts';
- $match_params->{"o${last_field_id}"} = 'greaterthaneq';
- $match_params->{"v${last_field_id}"} = $creation_when;
- $last_field_id++;
- }
-
- # Some fields require a search type such as short desc, keywords, etc.
- foreach my $param (qw(short_desc longdesc status_whiteboard bug_file_loc)) {
- if (defined $match_params->{$param} && !defined $match_params->{$param . '_type'}) {
- $match_params->{$param . '_type'} = 'allwordssubstr';
- }
- }
- if (defined $match_params->{'keywords'} && !defined $match_params->{'keywords_type'}) {
- $match_params->{'keywords_type'} = 'allwords';
- }
+ if (defined($match_params->{offset}) and !defined($match_params->{limit})) {
+ ThrowCodeError('param_required',
+ {param => 'limit', function => 'Bug.search()'});
+ }
- # Backwards compatibility with old method regarding role search
- $match_params->{'reporter'} = delete $match_params->{'creator'} if $match_params->{'creator'};
- foreach my $role (qw(assigned_to reporter qa_contact longdesc cc)) {
- next if !exists $match_params->{$role};
- my $value = delete $match_params->{$role};
- $match_params->{"f${last_field_id}"} = $role;
- $match_params->{"o${last_field_id}"} = "anywordssubstr";
- $match_params->{"v${last_field_id}"} = ref $value ? join(" ", @{$value}) : $value;
- $last_field_id++;
+ my $max_results = Bugzilla->params->{max_search_results};
+ unless (defined $match_params->{limit} && $match_params->{limit} == 0) {
+ if (!defined $match_params->{limit} || $match_params->{limit} > $max_results) {
+ $match_params->{limit} = $max_results;
}
-
- # If no other parameters have been passed other than limit and offset
- # then we throw error if system is configured to do so.
- if (!grep(!/^(limit|offset)$/, keys %$match_params)
- && !Bugzilla->params->{search_allow_no_criteria})
+ }
+ else {
+ delete $match_params->{limit};
+ delete $match_params->{offset};
+ }
+
+ $match_params = Bugzilla::Bug::map_fields($match_params);
+
+ my %options = (fields => ['bug_id']);
+
+ # Find the highest custom field id
+ my @field_ids = grep(/^f(\d+)$/, keys %$match_params);
+ my $last_field_id = @field_ids ? max @field_ids + 1 : 1;
+
+ # Do special search types for certain fields.
+ if (my $change_when = delete $match_params->{'delta_ts'}) {
+ $match_params->{"f${last_field_id}"} = 'delta_ts';
+ $match_params->{"o${last_field_id}"} = 'greaterthaneq';
+ $match_params->{"v${last_field_id}"} = $change_when;
+ $last_field_id++;
+ }
+ if (my $creation_when = delete $match_params->{'creation_ts'}) {
+ $match_params->{"f${last_field_id}"} = 'creation_ts';
+ $match_params->{"o${last_field_id}"} = 'greaterthaneq';
+ $match_params->{"v${last_field_id}"} = $creation_when;
+ $last_field_id++;
+ }
+
+ # Some fields require a search type such as short desc, keywords, etc.
+ foreach my $param (qw(short_desc longdesc status_whiteboard bug_file_loc)) {
+ if (defined $match_params->{$param}
+ && !defined $match_params->{$param . '_type'})
{
- ThrowUserError('buglist_parameters_required');
+ $match_params->{$param . '_type'} = 'allwordssubstr';
}
-
- $options{order} = [ split(/\s*,\s*/, delete $match_params->{order}) ] if $match_params->{order};
- $options{params} = $match_params;
-
- my $search = new Bugzilla::Search(%options);
- my ($data) = $search->data;
-
- if (!scalar @$data) {
- return { bugs => [] };
- }
-
- # Search.pm won't return bugs that the user shouldn't see so no filtering is needed.
- my @bug_ids = map { $_->[0] } @$data;
- my %bug_objects = map { $_->id => $_ } @{ Bugzilla::Bug->new_from_list(\@bug_ids) };
- my @bugs = map { $bug_objects{$_} } @bug_ids;
- @bugs = map { $self->_bug_to_hash($_, $params) } @bugs;
-
- return { bugs => \@bugs };
+ }
+ if (defined $match_params->{'keywords'}
+ && !defined $match_params->{'keywords_type'})
+ {
+ $match_params->{'keywords_type'} = 'allwords';
+ }
+
+ # Backwards compatibility with old method regarding role search
+ $match_params->{'reporter'} = delete $match_params->{'creator'}
+ if $match_params->{'creator'};
+ foreach my $role (qw(assigned_to reporter qa_contact longdesc cc)) {
+ next if !exists $match_params->{$role};
+ my $value = delete $match_params->{$role};
+ $match_params->{"f${last_field_id}"} = $role;
+ $match_params->{"o${last_field_id}"} = "anywordssubstr";
+ $match_params->{"v${last_field_id}"}
+ = ref $value ? join(" ", @{$value}) : $value;
+ $last_field_id++;
+ }
+
+ # If no other parameters have been passed other than limit and offset
+ # then we throw error if system is configured to do so.
+ if ( !grep(!/^(limit|offset)$/, keys %$match_params)
+ && !Bugzilla->params->{search_allow_no_criteria})
+ {
+ ThrowUserError('buglist_parameters_required');
+ }
+
+ $options{order} = [split(/\s*,\s*/, delete $match_params->{order})]
+ if $match_params->{order};
+ $options{params} = $match_params;
+
+ my $search = new Bugzilla::Search(%options);
+ my ($data) = $search->data;
+
+ if (!scalar @$data) {
+ return {bugs => []};
+ }
+
+# Search.pm won't return bugs that the user shouldn't see so no filtering is needed.
+ my @bug_ids = map { $_->[0] } @$data;
+ my %bug_objects
+ = map { $_->id => $_ } @{Bugzilla::Bug->new_from_list(\@bug_ids)};
+ my @bugs = map { $bug_objects{$_} } @bug_ids;
+ @bugs = map { $self->_bug_to_hash($_, $params) } @bugs;
+
+ return {bugs => \@bugs};
}
sub possible_duplicates {
- my ($self, $params) = validate(@_, 'products');
- my $user = Bugzilla->user;
-
- Bugzilla->switch_to_shadow_db();
-
- # Undo the array-ification that validate() does, for "summary".
- $params->{summary} || ThrowCodeError('param_required',
- { function => 'Bug.possible_duplicates', param => 'summary' });
-
- my @products;
- foreach my $name (@{ $params->{'products'} || [] }) {
- my $object = $user->can_enter_product($name, THROW_ERROR);
- push(@products, $object);
- }
-
- my $possible_dupes = Bugzilla::Bug->possible_duplicates(
- { summary => $params->{summary}, products => \@products,
- limit => $params->{limit} });
- my @hashes = map { $self->_bug_to_hash($_, $params) } @$possible_dupes;
- $self->_add_update_tokens($params, $possible_dupes, \@hashes);
- return { bugs => \@hashes };
+ my ($self, $params) = validate(@_, 'products');
+ my $user = Bugzilla->user;
+
+ Bugzilla->switch_to_shadow_db();
+
+ # Undo the array-ification that validate() does, for "summary".
+ $params->{summary}
+ || ThrowCodeError('param_required',
+ {function => 'Bug.possible_duplicates', param => 'summary'});
+
+ my @products;
+ foreach my $name (@{$params->{'products'} || []}) {
+ my $object = $user->can_enter_product($name, THROW_ERROR);
+ push(@products, $object);
+ }
+
+ my $possible_dupes = Bugzilla::Bug->possible_duplicates({
+ summary => $params->{summary},
+ products => \@products,
+ limit => $params->{limit}
+ });
+ my @hashes = map { $self->_bug_to_hash($_, $params) } @$possible_dupes;
+ $self->_add_update_tokens($params, $possible_dupes, \@hashes);
+ return {bugs => \@hashes};
}
sub update {
- my ($self, $params) = validate(@_, 'ids');
+ my ($self, $params) = validate(@_, 'ids');
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
- # We skip certain fields because their set_ methods actually use
- # the external names instead of the internal names.
- $params = Bugzilla::Bug::map_fields($params,
- { summary => 1, platform => 1, severity => 1, url => 1 });
+ # We skip certain fields because their set_ methods actually use
+ # the external names instead of the internal names.
+ $params = Bugzilla::Bug::map_fields($params,
+ {summary => 1, platform => 1, severity => 1, url => 1});
- my $ids = delete $params->{ids};
- defined $ids || ThrowCodeError('param_required', { param => 'ids' });
+ my $ids = delete $params->{ids};
+ defined $ids || ThrowCodeError('param_required', {param => 'ids'});
- my @bugs = map { Bugzilla::Bug->check_for_edit($_) } @$ids;
+ my @bugs = map { Bugzilla::Bug->check_for_edit($_) } @$ids;
- my %values = %$params;
- $values{other_bugs} = \@bugs;
+ my %values = %$params;
+ $values{other_bugs} = \@bugs;
- if (exists $values{comment} and exists $values{comment}{comment}) {
- $values{comment}{body} = delete $values{comment}{comment};
- }
+ if (exists $values{comment} and exists $values{comment}{comment}) {
+ $values{comment}{body} = delete $values{comment}{comment};
+ }
- # Prevent bugs that could be triggered by specifying fields that
- # have valid "set_" functions in Bugzilla::Bug, but shouldn't be
- # called using those field names.
- delete $values{dependencies};
+ # Prevent bugs that could be triggered by specifying fields that
+ # have valid "set_" functions in Bugzilla::Bug, but shouldn't be
+ # called using those field names.
+ delete $values{dependencies};
- # For backwards compatibility, treat alias string or array as a set action
- if (exists $values{alias}) {
- if (not ref $values{alias}) {
- $values{alias} = { set => [ $values{alias} ] };
- }
- elsif (ref $values{alias} eq 'ARRAY') {
- $values{alias} = { set => $values{alias} };
- }
+ # For backwards compatibility, treat alias string or array as a set action
+ if (exists $values{alias}) {
+ if (not ref $values{alias}) {
+ $values{alias} = {set => [$values{alias}]};
}
-
- my $flags = delete $values{flags};
-
- foreach my $bug (@bugs) {
- $bug->set_all(\%values);
- if ($flags) {
- my ($old_flags, $new_flags) = extract_flags($flags, $bug);
- $bug->set_flags($old_flags, $new_flags);
- }
+ elsif (ref $values{alias} eq 'ARRAY') {
+ $values{alias} = {set => $values{alias}};
}
+ }
- my %all_changes;
- $dbh->bz_start_transaction();
- foreach my $bug (@bugs) {
- $all_changes{$bug->id} = $bug->update();
- }
- $dbh->bz_commit_transaction();
+ my $flags = delete $values{flags};
- foreach my $bug (@bugs) {
- $bug->send_changes($all_changes{$bug->id});
+ foreach my $bug (@bugs) {
+ $bug->set_all(\%values);
+ if ($flags) {
+ my ($old_flags, $new_flags) = extract_flags($flags, $bug);
+ $bug->set_flags($old_flags, $new_flags);
}
+ }
+
+ my %all_changes;
+ $dbh->bz_start_transaction();
+ foreach my $bug (@bugs) {
+ $all_changes{$bug->id} = $bug->update();
+ }
+ $dbh->bz_commit_transaction();
+
+ foreach my $bug (@bugs) {
+ $bug->send_changes($all_changes{$bug->id});
+ }
+
+ my %api_name = reverse %{Bugzilla::Bug::FIELD_MAP()};
+
+ # This doesn't normally belong in FIELD_MAP, but we do want to translate
+ # "bug_group" back into "groups".
+ $api_name{'bug_group'} = 'groups';
+
+ my @result;
+ foreach my $bug (@bugs) {
+ my %hash = (
+ id => $self->type('int', $bug->id),
+ last_change_time => $self->type('dateTime', $bug->delta_ts),
+ changes => {},
+ );
- my %api_name = reverse %{ Bugzilla::Bug::FIELD_MAP() };
- # This doesn't normally belong in FIELD_MAP, but we do want to translate
- # "bug_group" back into "groups".
- $api_name{'bug_group'} = 'groups';
-
- my @result;
- foreach my $bug (@bugs) {
- my %hash = (
- id => $self->type('int', $bug->id),
- last_change_time => $self->type('dateTime', $bug->delta_ts),
- changes => {},
- );
-
- # alias is returned in case users pass a mixture of ids and aliases,
- # so that they can know which set of changes relates to which value
- # they passed.
- $hash{alias} = [ map { $self->type('string', $_) } @{ $bug->alias } ];
-
- my %changes = %{ $all_changes{$bug->id} };
- foreach my $field (keys %changes) {
- my $change = $changes{$field};
- my $api_field = $api_name{$field} || $field;
- # We normalize undef to an empty string, so that the API
- # stays consistent for things like Deadline that can become
- # empty.
- $change->[0] = '' if !defined $change->[0];
- $change->[1] = '' if !defined $change->[1];
- $hash{changes}->{$api_field} = {
- removed => $self->type('string', $change->[0]),
- added => $self->type('string', $change->[1])
- };
- }
-
- push(@result, \%hash);
+ # alias is returned in case users pass a mixture of ids and aliases,
+ # so that they can know which set of changes relates to which value
+ # they passed.
+ $hash{alias} = [map { $self->type('string', $_) } @{$bug->alias}];
+
+ my %changes = %{$all_changes{$bug->id}};
+ foreach my $field (keys %changes) {
+ my $change = $changes{$field};
+ my $api_field = $api_name{$field} || $field;
+
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things like Deadline that can become
+ # empty.
+ $change->[0] = '' if !defined $change->[0];
+ $change->[1] = '' if !defined $change->[1];
+ $hash{changes}->{$api_field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
}
- return { bugs => \@result };
+ push(@result, \%hash);
+ }
+
+ return {bugs => \@result};
}
sub create {
- my ($self, $params) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($self, $params) = @_;
+ my $dbh = Bugzilla->dbh;
- Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->login(LOGIN_REQUIRED);
- $params = Bugzilla::Bug::map_fields($params);
+ $params = Bugzilla::Bug::map_fields($params);
- my $flags = delete $params->{flags};
+ my $flags = delete $params->{flags};
- # We start a nested transaction in case flag setting fails
- # we want the bug creation to roll back as well.
- $dbh->bz_start_transaction();
+ # We start a nested transaction in case flag setting fails
+ # we want the bug creation to roll back as well.
+ $dbh->bz_start_transaction();
- my $bug = Bugzilla::Bug->create($params);
+ my $bug = Bugzilla::Bug->create($params);
- # Set bug flags
- if ($flags) {
- my ($flags, $new_flags) = extract_flags($flags, $bug);
- $bug->set_flags($flags, $new_flags);
- $bug->update($bug->creation_ts);
- }
+ # Set bug flags
+ if ($flags) {
+ my ($flags, $new_flags) = extract_flags($flags, $bug);
+ $bug->set_flags($flags, $new_flags);
+ $bug->update($bug->creation_ts);
+ }
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
- $bug->send_changes();
+ $bug->send_changes();
- return { id => $self->type('int', $bug->bug_id) };
+ return {id => $self->type('int', $bug->bug_id)};
}
sub legal_values {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- Bugzilla->switch_to_shadow_db();
+ Bugzilla->switch_to_shadow_db();
- defined $params->{field}
- or ThrowCodeError('param_required', { param => 'field' });
+ defined $params->{field}
+ or ThrowCodeError('param_required', {param => 'field'});
- my $field = Bugzilla::Bug::FIELD_MAP->{$params->{field}}
- || $params->{field};
+ my $field = Bugzilla::Bug::FIELD_MAP->{$params->{field}} || $params->{field};
- my @global_selects =
- @{ Bugzilla->fields({ is_select => 1, is_abnormal => 0 }) };
+ my @global_selects = @{Bugzilla->fields({is_select => 1, is_abnormal => 0})};
- my $values;
- if (grep($_->name eq $field, @global_selects)) {
- # The field is a valid one.
- trick_taint($field);
- $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', { id => $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};
- }
+ my $values;
+ if (grep($_->name eq $field, @global_selects)) {
- $values = [map { $_->name } @objects];
+ # The field is a valid one.
+ trick_taint($field);
+ $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', {id => $id});
+
+ my $product = new Bugzilla::Product($id);
+ my @objects;
+ if ($field eq 'version') {
+ @objects = @{$product->versions};
}
- else {
- ThrowCodeError('invalid_field_name', { field => $params->{field} });
+ elsif ($field eq 'target_milestone') {
+ @objects = @{$product->milestones};
}
-
- my @result;
- foreach my $val (@$values) {
- push(@result, $self->type('string', $val));
+ elsif ($field eq 'component') {
+ @objects = @{$product->components};
}
- return { values => \@result };
+ $values = [map { $_->name } @objects];
+ }
+ else {
+ ThrowCodeError('invalid_field_name', {field => $params->{field}});
+ }
+
+ my @result;
+ foreach my $val (@$values) {
+ push(@result, $self->type('string', $val));
+ }
+
+ return {values => \@result};
}
sub add_attachment {
- my ($self, $params) = validate(@_, 'ids');
- my $dbh = Bugzilla->dbh;
-
- Bugzilla->login(LOGIN_REQUIRED);
- defined $params->{ids}
- || ThrowCodeError('param_required', { param => 'ids' });
- defined $params->{data}
- || ThrowCodeError('param_required', { param => 'data' });
-
- my @bugs = map { Bugzilla::Bug->check_for_edit($_) } @{ $params->{ids} };
-
- my @created;
- $dbh->bz_start_transaction();
- my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
-
- my $flags = delete $params->{flags};
-
- foreach my $bug (@bugs) {
- my $attachment = Bugzilla::Attachment->create({
- bug => $bug,
- creation_ts => $timestamp,
- data => $params->{data},
- description => $params->{summary},
- filename => $params->{file_name},
- mimetype => $params->{content_type},
- ispatch => $params->{is_patch},
- isprivate => $params->{is_private},
- });
-
- if ($flags) {
- my ($old_flags, $new_flags) = extract_flags($flags, $bug, $attachment);
- $attachment->set_flags($old_flags, $new_flags);
- }
+ my ($self, $params) = validate(@_, 'ids');
+ my $dbh = Bugzilla->dbh;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ defined $params->{ids} || ThrowCodeError('param_required', {param => 'ids'});
+ defined $params->{data} || ThrowCodeError('param_required', {param => 'data'});
+
+ my @bugs = map { Bugzilla::Bug->check_for_edit($_) } @{$params->{ids}};
+
+ my @created;
+ $dbh->bz_start_transaction();
+ my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ my $flags = delete $params->{flags};
+
+ foreach my $bug (@bugs) {
+ my $attachment = Bugzilla::Attachment->create({
+ bug => $bug,
+ creation_ts => $timestamp,
+ data => $params->{data},
+ description => $params->{summary},
+ filename => $params->{file_name},
+ mimetype => $params->{content_type},
+ ispatch => $params->{is_patch},
+ isprivate => $params->{is_private},
+ });
- $attachment->update($timestamp);
- my $comment = $params->{comment} || '';
- $attachment->bug->add_comment($comment,
- { isprivate => $attachment->isprivate,
- type => CMT_ATTACHMENT_CREATED,
- extra_data => $attachment->id });
- push(@created, $attachment);
+ if ($flags) {
+ my ($old_flags, $new_flags) = extract_flags($flags, $bug, $attachment);
+ $attachment->set_flags($old_flags, $new_flags);
}
- $_->bug->update($timestamp) foreach @created;
- $dbh->bz_commit_transaction();
- $_->send_changes() foreach @bugs;
+ $attachment->update($timestamp);
+ my $comment = $params->{comment} || '';
+ $attachment->bug->add_comment(
+ $comment,
+ {
+ isprivate => $attachment->isprivate,
+ type => CMT_ATTACHMENT_CREATED,
+ extra_data => $attachment->id
+ }
+ );
+ push(@created, $attachment);
+ }
+ $_->bug->update($timestamp) foreach @created;
+ $dbh->bz_commit_transaction();
+
+ $_->send_changes() foreach @bugs;
- my @created_ids = map { $_->id } @created;
+ my @created_ids = map { $_->id } @created;
- return { ids => \@created_ids };
+ return {ids => \@created_ids};
}
sub update_attachment {
- my ($self, $params) = validate(@_, 'ids');
+ my ($self, $params) = validate(@_, 'ids');
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
+
+ my $ids = delete $params->{ids};
+ defined $ids || ThrowCodeError('param_required', {param => 'ids'});
+
+ # Some fields cannot be sent to set_all
+ foreach my $key (qw(login password token)) {
+ delete $params->{$key};
+ }
+
+ $params = translate($params, ATTACHMENT_MAPPED_SETTERS);
+
+ # Get all the attachments, after verifying that they exist and are editable
+ my @attachments = ();
+ my %bugs = ();
+ foreach my $id (@$ids) {
+ my $attachment = Bugzilla::Attachment->new($id)
+ || ThrowUserError("invalid_attach_id", {attach_id => $id});
+ my $bug = $attachment->bug;
+ $attachment->_check_bug;
+
+ push @attachments, $attachment;
+ $bugs{$bug->id} = $bug;
+ }
+
+ my $flags = delete $params->{flags};
+ my $comment = delete $params->{comment};
+
+ # Update the values
+ foreach my $attachment (@attachments) {
+ my ($update_flags, $new_flags)
+ = $flags ? extract_flags($flags, $attachment->bug, $attachment) : ([], []);
+ if ($attachment->validate_can_edit) {
+ $attachment->set_all($params);
+ $attachment->set_flags($update_flags, $new_flags) if $flags;
+ }
+ elsif (scalar @$update_flags && !scalar(@$new_flags) && !scalar keys %$params) {
+
+ # Requestees can set flags targetted to them, even if they cannot
+ # edit the attachment. Flag setters can edit their own flags too.
+ my %flag_list = map { $_->{id} => $_ } @$update_flags;
+ my $flag_objs = Bugzilla::Flag->new_from_list([keys %flag_list]);
+ my @editable_flags;
+ foreach my $flag_obj (@$flag_objs) {
+ if ($flag_obj->setter_id == $user->id
+ || ($flag_obj->requestee_id && $flag_obj->requestee_id == $user->id))
+ {
+ push(@editable_flags, $flag_list{$flag_obj->id});
+ }
+ }
+ if (!scalar @editable_flags) {
+ ThrowUserError("illegal_attachment_edit", {attach_id => $attachment->id});
+ }
+ $attachment->set_flags(\@editable_flags, []);
+ }
+ else {
+ ThrowUserError("illegal_attachment_edit", {attach_id => $attachment->id});
+ }
+ }
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
- my $ids = delete $params->{ids};
- defined $ids || ThrowCodeError('param_required', { param => 'ids' });
+ # Do the actual update and get information to return to user
+ my @result;
+ foreach my $attachment (@attachments) {
+ my $changes = $attachment->update();
- # Some fields cannot be sent to set_all
- foreach my $key (qw(login password token)) {
- delete $params->{$key};
+ if ($comment = trim($comment)) {
+ $attachment->bug->add_comment(
+ $comment,
+ {
+ isprivate => $attachment->isprivate,
+ type => CMT_ATTACHMENT_UPDATED,
+ extra_data => $attachment->id
+ }
+ );
}
- $params = translate($params, ATTACHMENT_MAPPED_SETTERS);
+ $changes = translate($changes, ATTACHMENT_MAPPED_RETURNS);
- # Get all the attachments, after verifying that they exist and are editable
- my @attachments = ();
- my %bugs = ();
- foreach my $id (@$ids) {
- my $attachment = Bugzilla::Attachment->new($id)
- || ThrowUserError("invalid_attach_id", { attach_id => $id });
- my $bug = $attachment->bug;
- $attachment->_check_bug;
+ my %hash = (
+ id => $self->type('int', $attachment->id),
+ last_change_time => $self->type('dateTime', $attachment->modification_time),
+ changes => {},
+ );
- push @attachments, $attachment;
- $bugs{$bug->id} = $bug;
- }
+ foreach my $field (keys %$changes) {
+ my $change = $changes->{$field};
- my $flags = delete $params->{flags};
- my $comment = delete $params->{comment};
-
- # Update the values
- foreach my $attachment (@attachments) {
- my ($update_flags, $new_flags) = $flags
- ? extract_flags($flags, $attachment->bug, $attachment)
- : ([], []);
- if ($attachment->validate_can_edit) {
- $attachment->set_all($params);
- $attachment->set_flags($update_flags, $new_flags) if $flags;
- }
- elsif (scalar @$update_flags && !scalar(@$new_flags) && !scalar keys %$params) {
- # Requestees can set flags targetted to them, even if they cannot
- # edit the attachment. Flag setters can edit their own flags too.
- my %flag_list = map { $_->{id} => $_ } @$update_flags;
- my $flag_objs = Bugzilla::Flag->new_from_list([ keys %flag_list ]);
- my @editable_flags;
- foreach my $flag_obj (@$flag_objs) {
- if ($flag_obj->setter_id == $user->id
- || ($flag_obj->requestee_id && $flag_obj->requestee_id == $user->id))
- {
- push(@editable_flags, $flag_list{$flag_obj->id});
- }
- }
- if (!scalar @editable_flags) {
- ThrowUserError("illegal_attachment_edit", { attach_id => $attachment->id });
- }
- $attachment->set_flags(\@editable_flags, []);
- }
- else {
- ThrowUserError("illegal_attachment_edit", { attach_id => $attachment->id });
- }
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things like Deadline that can become
+ # empty.
+ $hash{changes}->{$field} = {
+ removed => $self->type('string', $change->[0] // ''),
+ added => $self->type('string', $change->[1] // '')
+ };
}
- $dbh->bz_start_transaction();
-
- # Do the actual update and get information to return to user
- my @result;
- foreach my $attachment (@attachments) {
- my $changes = $attachment->update();
-
- if ($comment = trim($comment)) {
- $attachment->bug->add_comment($comment,
- { isprivate => $attachment->isprivate,
- type => CMT_ATTACHMENT_UPDATED,
- extra_data => $attachment->id });
- }
+ push(@result, \%hash);
+ }
- $changes = translate($changes, ATTACHMENT_MAPPED_RETURNS);
+ $dbh->bz_commit_transaction();
- my %hash = (
- id => $self->type('int', $attachment->id),
- last_change_time => $self->type('dateTime', $attachment->modification_time),
- changes => {},
- );
+ # Email users about the change
+ foreach my $bug (values %bugs) {
+ $bug->update();
+ $bug->send_changes();
+ }
- foreach my $field (keys %$changes) {
- my $change = $changes->{$field};
+ # Return the information to the user
+ return {attachments => \@result};
+}
- # We normalize undef to an empty string, so that the API
- # stays consistent for things like Deadline that can become
- # empty.
- $hash{changes}->{$field} = {
- removed => $self->type('string', $change->[0] // ''),
- added => $self->type('string', $change->[1] // '')
- };
- }
+sub add_comment {
+ my ($self, $params) = @_;
- push(@result, \%hash);
- }
+ # The user must login in order add a comment
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
- $dbh->bz_commit_transaction();
+ # Check parameters
+ defined $params->{id} || ThrowCodeError('param_required', {param => 'id'});
+ my $comment = $params->{comment};
+ (defined $comment && trim($comment) ne '')
+ || ThrowCodeError('param_required', {param => 'comment'});
- # Email users about the change
- foreach my $bug (values %bugs) {
- $bug->update();
- $bug->send_changes();
- }
+ my $bug = Bugzilla::Bug->check_for_edit($params->{id});
- # Return the information to the user
- return { attachments => \@result };
-}
+ # Backwards-compatibility for versions before 3.6
+ if (defined $params->{private}) {
+ $params->{is_private} = delete $params->{private};
+ }
-sub add_comment {
- my ($self, $params) = @_;
-
- # The user must login in order add a comment
- my $user = Bugzilla->login(LOGIN_REQUIRED);
-
- # Check parameters
- defined $params->{id}
- || ThrowCodeError('param_required', { param => 'id' });
- my $comment = $params->{comment};
- (defined $comment && trim($comment) ne '')
- || ThrowCodeError('param_required', { param => 'comment' });
-
- my $bug = Bugzilla::Bug->check_for_edit($params->{id});
-
- # Backwards-compatibility for versions before 3.6
- if (defined $params->{private}) {
- $params->{is_private} = delete $params->{private};
- }
- # Append comment
- $bug->add_comment($comment, { isprivate => $params->{is_private},
- work_time => $params->{work_time} });
- $bug->update();
+ # Append comment
+ $bug->add_comment($comment,
+ {isprivate => $params->{is_private}, work_time => $params->{work_time}});
+ $bug->update();
- my $new_comment_id = $bug->{added_comments}[0]->id;
+ my $new_comment_id = $bug->{added_comments}[0]->id;
- # Send mail.
- Bugzilla::BugMail::Send($bug->bug_id, { changer => $user });
+ # Send mail.
+ Bugzilla::BugMail::Send($bug->bug_id, {changer => $user});
- return { id => $self->type('int', $new_comment_id) };
+ return {id => $self->type('int', $new_comment_id)};
}
sub update_see_also {
- my ($self, $params) = @_;
-
- my $user = Bugzilla->login(LOGIN_REQUIRED);
-
- # Check parameters
- $params->{ids}
- || ThrowCodeError('param_required', { param => 'id' });
- my ($add, $remove) = @$params{qw(add remove)};
- ($add || $remove)
- or ThrowCodeError('params_required', { params => ['add', 'remove'] });
-
- my @bugs;
- foreach my $id (@{ $params->{ids} }) {
- my $bug = Bugzilla::Bug->check_for_edit($id);
- push(@bugs, $bug);
- if ($remove) {
- $bug->remove_see_also($_) foreach @$remove;
- }
- if ($add) {
- $bug->add_see_also($_) foreach @$add;
- }
+ my ($self, $params) = @_;
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ # Check parameters
+ $params->{ids} || ThrowCodeError('param_required', {param => 'id'});
+ my ($add, $remove) = @$params{qw(add remove)};
+ ($add || $remove)
+ or ThrowCodeError('params_required', {params => ['add', 'remove']});
+
+ my @bugs;
+ foreach my $id (@{$params->{ids}}) {
+ my $bug = Bugzilla::Bug->check_for_edit($id);
+ push(@bugs, $bug);
+ if ($remove) {
+ $bug->remove_see_also($_) foreach @$remove;
}
-
- my %changes;
- foreach my $bug (@bugs) {
- my $change = $bug->update();
- if (my $see_also = $change->{see_also}) {
- $changes{$bug->id}->{see_also} = {
- removed => [split(', ', $see_also->[0])],
- added => [split(', ', $see_also->[1])],
- };
- }
- else {
- # We still want a changes entry, for API consistency.
- $changes{$bug->id}->{see_also} = { added => [], removed => [] };
- }
-
- Bugzilla::BugMail::Send($bug->id, { changer => $user });
+ if ($add) {
+ $bug->add_see_also($_) foreach @$add;
+ }
+ }
+
+ my %changes;
+ foreach my $bug (@bugs) {
+ my $change = $bug->update();
+ if (my $see_also = $change->{see_also}) {
+ $changes{$bug->id}->{see_also} = {
+ removed => [split(', ', $see_also->[0])],
+ added => [split(', ', $see_also->[1])],
+ };
}
+ else {
+ # We still want a changes entry, for API consistency.
+ $changes{$bug->id}->{see_also} = {added => [], removed => []};
+ }
+
+ Bugzilla::BugMail::Send($bug->id, {changer => $user});
+ }
- return { changes => \%changes };
+ return {changes => \%changes};
}
sub attachments {
- my ($self, $params) = validate(@_, 'ids', 'attachment_ids');
+ my ($self, $params) = validate(@_, 'ids', 'attachment_ids');
- Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
+ Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
- if (!(defined $params->{ids}
- or defined $params->{attachment_ids}))
- {
- ThrowCodeError('param_required',
- { function => 'Bug.attachments',
- params => ['ids', 'attachment_ids'] });
- }
-
- my $ids = $params->{ids} || [];
- my $attach_ids = $params->{attachment_ids} || [];
-
- my %bugs;
- foreach my $bug_id (@$ids) {
- my $bug = Bugzilla::Bug->check($bug_id);
- $bugs{$bug->id} = [];
- foreach my $attach (@{$bug->attachments}) {
- push @{$bugs{$bug->id}},
- $self->_attachment_to_hash($attach, $params);
- }
+ if (!(defined $params->{ids} or defined $params->{attachment_ids})) {
+ ThrowCodeError('param_required',
+ {function => 'Bug.attachments', params => ['ids', 'attachment_ids']});
+ }
+
+ my $ids = $params->{ids} || [];
+ my $attach_ids = $params->{attachment_ids} || [];
+
+ my %bugs;
+ foreach my $bug_id (@$ids) {
+ my $bug = Bugzilla::Bug->check($bug_id);
+ $bugs{$bug->id} = [];
+ foreach my $attach (@{$bug->attachments}) {
+ push @{$bugs{$bug->id}}, $self->_attachment_to_hash($attach, $params);
}
-
- my %attachments;
- foreach my $attach (@{Bugzilla::Attachment->new_from_list($attach_ids)}) {
- Bugzilla::Bug->check($attach->bug_id);
- if ($attach->isprivate && !Bugzilla->user->is_insider) {
- ThrowUserError('auth_failure', {action => 'access',
- object => 'attachment',
- attach_id => $attach->id});
- }
- $attachments{$attach->id} =
- $self->_attachment_to_hash($attach, $params);
+ }
+
+ my %attachments;
+ foreach my $attach (@{Bugzilla::Attachment->new_from_list($attach_ids)}) {
+ Bugzilla::Bug->check($attach->bug_id);
+ if ($attach->isprivate && !Bugzilla->user->is_insider) {
+ ThrowUserError('auth_failure',
+ {action => 'access', object => 'attachment', attach_id => $attach->id});
}
+ $attachments{$attach->id} = $self->_attachment_to_hash($attach, $params);
+ }
- return { bugs => \%bugs, attachments => \%attachments };
+ return {bugs => \%bugs, attachments => \%attachments};
}
sub update_tags {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->login(LOGIN_REQUIRED);
- my $ids = $params->{ids};
- my $tags = $params->{tags};
+ my $ids = $params->{ids};
+ my $tags = $params->{tags};
- ThrowCodeError('param_required',
- { function => 'Bug.update_tags',
- param => 'ids' }) if !defined $ids;
+ ThrowCodeError('param_required',
+ {function => 'Bug.update_tags', param => 'ids'})
+ if !defined $ids;
- ThrowCodeError('param_required',
- { function => 'Bug.update_tags',
- param => 'tags' }) if !defined $tags;
+ ThrowCodeError('param_required',
+ {function => 'Bug.update_tags', param => 'tags'})
+ if !defined $tags;
- my %changes;
- foreach my $bug_id (@$ids) {
- my $bug = Bugzilla::Bug->check($bug_id);
- my @old_tags = @{ $bug->tags };
+ my %changes;
+ foreach my $bug_id (@$ids) {
+ my $bug = Bugzilla::Bug->check($bug_id);
+ my @old_tags = @{$bug->tags};
- $bug->remove_tag($_) foreach @{ $tags->{remove} || [] };
- $bug->add_tag($_) foreach @{ $tags->{add} || [] };
+ $bug->remove_tag($_) foreach @{$tags->{remove} || []};
+ $bug->add_tag($_) foreach @{$tags->{add} || []};
- my ($removed, $added) = diff_arrays(\@old_tags, $bug->tags);
+ my ($removed, $added) = diff_arrays(\@old_tags, $bug->tags);
- my @removed = map { $self->type('string', $_) } @$removed;
- my @added = map { $self->type('string', $_) } @$added;
+ my @removed = map { $self->type('string', $_) } @$removed;
+ my @added = map { $self->type('string', $_) } @$added;
- $changes{$bug->id}->{tags} = {
- removed => \@removed,
- added => \@added
- };
- }
+ $changes{$bug->id}->{tags} = {removed => \@removed, added => \@added};
+ }
- return { changes => \%changes };
+ return {changes => \%changes};
}
sub update_comment_tags {
- my ($self, $params) = @_;
-
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->params->{'comment_taggers_group'}
- || ThrowUserError("comment_tag_disabled");
- $user->can_tag_comments
- || ThrowUserError("auth_failure",
- { group => Bugzilla->params->{'comment_taggers_group'},
- action => "update",
- object => "comment_tags" });
-
- my $comment_id = $params->{comment_id}
- // ThrowCodeError('param_required',
- { function => 'Bug.update_comment_tags',
- param => 'comment_id' });
-
- ThrowCodeError('param_integer_required', { function => 'Bug.update_comment_tags',
- param => 'comment_id' })
- unless $comment_id =~ /^[0-9]+$/;
-
- my $comment = Bugzilla::Comment->new($comment_id)
- || return [];
- $comment->bug->check_is_visible();
- if ($comment->is_private && !$user->is_insider) {
- ThrowUserError('comment_is_private', { id => $comment_id });
- }
+ my ($self, $params) = @_;
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- foreach my $tag (@{ $params->{add} || [] }) {
- $comment->add_tag($tag) if defined $tag;
- }
- foreach my $tag (@{ $params->{remove} || [] }) {
- $comment->remove_tag($tag) if defined $tag;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->params->{'comment_taggers_group'}
+ || ThrowUserError("comment_tag_disabled");
+ $user->can_tag_comments || ThrowUserError(
+ "auth_failure",
+ {
+ group => Bugzilla->params->{'comment_taggers_group'},
+ action => "update",
+ object => "comment_tags"
}
- $comment->update();
- $dbh->bz_commit_transaction();
-
- return $comment->tags;
+ );
+
+ my $comment_id = $params->{comment_id} // ThrowCodeError('param_required',
+ {function => 'Bug.update_comment_tags', param => 'comment_id'});
+
+ ThrowCodeError('param_integer_required',
+ {function => 'Bug.update_comment_tags', param => 'comment_id'})
+ unless $comment_id =~ /^[0-9]+$/;
+
+ my $comment = Bugzilla::Comment->new($comment_id) || return [];
+ $comment->bug->check_is_visible();
+ if ($comment->is_private && !$user->is_insider) {
+ ThrowUserError('comment_is_private', {id => $comment_id});
+ }
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ foreach my $tag (@{$params->{add} || []}) {
+ $comment->add_tag($tag) if defined $tag;
+ }
+ foreach my $tag (@{$params->{remove} || []}) {
+ $comment->remove_tag($tag) if defined $tag;
+ }
+ $comment->update();
+ $dbh->bz_commit_transaction();
+
+ return $comment->tags;
}
sub search_comment_tags {
- my ($self, $params) = @_;
-
- Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->params->{'comment_taggers_group'}
- || ThrowUserError("comment_tag_disabled");
- Bugzilla->user->can_tag_comments
- || ThrowUserError("auth_failure", { group => Bugzilla->params->{'comment_taggers_group'},
- action => "search",
- object => "comment_tags"});
-
- my $query = $params->{query};
- $query
- // ThrowCodeError('param_required', { param => 'query' });
- my $limit = $params->{limit} || 7;
- detaint_natural($limit)
- || ThrowCodeError('param_must_be_numeric', { param => 'limit',
- function => 'Bug.search_comment_tags' });
-
-
- my $tags = Bugzilla::Comment::TagWeights->match({
- WHERE => {
- 'tag LIKE ?' => "\%$query\%",
- },
- LIMIT => $limit,
- });
- return [ map { $_->tag } @$tags ];
+ my ($self, $params) = @_;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->params->{'comment_taggers_group'}
+ || ThrowUserError("comment_tag_disabled");
+ Bugzilla->user->can_tag_comments || ThrowUserError(
+ "auth_failure",
+ {
+ group => Bugzilla->params->{'comment_taggers_group'},
+ action => "search",
+ object => "comment_tags"
+ }
+ );
+
+ my $query = $params->{query};
+ $query // ThrowCodeError('param_required', {param => 'query'});
+ my $limit = $params->{limit} || 7;
+ detaint_natural($limit)
+ || ThrowCodeError('param_must_be_numeric',
+ {param => 'limit', function => 'Bug.search_comment_tags'});
+
+
+ my $tags = Bugzilla::Comment::TagWeights->match(
+ {WHERE => {'tag LIKE ?' => "\%$query\%",}, LIMIT => $limit,});
+ return [map { $_->tag } @$tags];
}
##############################
@@ -1197,232 +1206,238 @@ sub search_comment_tags {
# return them directly.
sub _bug_to_hash {
- my ($self, $bug, $params) = @_;
-
- # All the basic bug attributes are here, in alphabetical order.
- # A bug attribute is "basic" if it doesn't require an additional
- # database call to get the info.
- my %item = %{ filter $params, {
- # No need to format $bug->deadline specially, because Bugzilla::Bug
- # already does it for us.
- deadline => $self->type('string', $bug->deadline),
- id => $self->type('int', $bug->bug_id),
- is_confirmed => $self->type('boolean', $bug->everconfirmed),
- op_sys => $self->type('string', $bug->op_sys),
- platform => $self->type('string', $bug->rep_platform),
- priority => $self->type('string', $bug->priority),
- resolution => $self->type('string', $bug->resolution),
- severity => $self->type('string', $bug->bug_severity),
- status => $self->type('string', $bug->bug_status),
- summary => $self->type('string', $bug->short_desc),
- target_milestone => $self->type('string', $bug->target_milestone),
- url => $self->type('string', $bug->bug_file_loc),
- version => $self->type('string', $bug->version),
- whiteboard => $self->type('string', $bug->status_whiteboard),
- } };
-
- # First we handle any fields that require extra work (such as date parsing
- # or SQL calls).
- if (filter_wants $params, 'alias') {
- $item{alias} = [ map { $self->type('string', $_) } @{ $bug->alias } ];
- }
- if (filter_wants $params, 'assigned_to') {
- $item{'assigned_to'} = $self->type('email', $bug->assigned_to->login);
- $item{'assigned_to_detail'} = $self->_user_to_hash($bug->assigned_to, $params, undef, 'assigned_to');
- }
- if (filter_wants $params, 'blocks') {
- my @blocks = map { $self->type('int', $_) } @{ $bug->blocked };
- $item{'blocks'} = \@blocks;
- }
- if (filter_wants $params, 'classification') {
- $item{classification} = $self->type('string', $bug->classification);
- }
- if (filter_wants $params, 'component') {
- $item{component} = $self->type('string', $bug->component);
- }
- if (filter_wants $params, 'cc') {
- my @cc = map { $self->type('email', $_) } @{ $bug->cc };
- $item{'cc'} = \@cc;
- $item{'cc_detail'} = [ map { $self->_user_to_hash($_, $params, undef, 'cc') } @{ $bug->cc_users } ];
- }
- if (filter_wants $params, 'creation_time') {
- $item{'creation_time'} = $self->type('dateTime', $bug->creation_ts);
- }
- if (filter_wants $params, 'creator') {
- $item{'creator'} = $self->type('email', $bug->reporter->login);
- $item{'creator_detail'} = $self->_user_to_hash($bug->reporter, $params, undef, 'creator');
- }
- if (filter_wants $params, 'depends_on') {
- my @depends_on = map { $self->type('int', $_) } @{ $bug->dependson };
- $item{'depends_on'} = \@depends_on;
- }
- if (filter_wants $params, 'dupe_of') {
- $item{'dupe_of'} = $self->type('int', $bug->dup_id);
- }
- if (filter_wants $params, 'groups') {
- my @groups = map { $self->type('string', $_->name) }
- @{ $bug->groups_in };
- $item{'groups'} = \@groups;
- }
- if (filter_wants $params, 'is_open') {
- $item{'is_open'} = $self->type('boolean', $bug->status->is_open);
- }
- if (filter_wants $params, 'keywords') {
- my @keywords = map { $self->type('string', $_->name) }
- @{ $bug->keyword_objects };
- $item{'keywords'} = \@keywords;
- }
- if (filter_wants $params, 'last_change_time') {
- $item{'last_change_time'} = $self->type('dateTime', $bug->delta_ts);
- }
- if (filter_wants $params, 'product') {
- $item{product} = $self->type('string', $bug->product);
+ my ($self, $bug, $params) = @_;
+
+ # All the basic bug attributes are here, in alphabetical order.
+ # A bug attribute is "basic" if it doesn't require an additional
+ # database call to get the info.
+ my %item = %{filter $params,
+ {
+ # No need to format $bug->deadline specially, because Bugzilla::Bug
+ # already does it for us.
+ deadline => $self->type('string', $bug->deadline),
+ id => $self->type('int', $bug->bug_id),
+ is_confirmed => $self->type('boolean', $bug->everconfirmed),
+ op_sys => $self->type('string', $bug->op_sys),
+ platform => $self->type('string', $bug->rep_platform),
+ priority => $self->type('string', $bug->priority),
+ resolution => $self->type('string', $bug->resolution),
+ severity => $self->type('string', $bug->bug_severity),
+ status => $self->type('string', $bug->bug_status),
+ summary => $self->type('string', $bug->short_desc),
+ target_milestone => $self->type('string', $bug->target_milestone),
+ url => $self->type('string', $bug->bug_file_loc),
+ version => $self->type('string', $bug->version),
+ whiteboard => $self->type('string', $bug->status_whiteboard),
}
- if (filter_wants $params, 'qa_contact') {
- my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : '';
- $item{'qa_contact'} = $self->type('email', $qa_login);
- if ($bug->qa_contact) {
- $item{'qa_contact_detail'} = $self->_user_to_hash($bug->qa_contact, $params, undef, 'qa_contact');
- }
+ };
+
+ # First we handle any fields that require extra work (such as date parsing
+ # or SQL calls).
+ if (filter_wants $params, 'alias') {
+ $item{alias} = [map { $self->type('string', $_) } @{$bug->alias}];
+ }
+ if (filter_wants $params, 'assigned_to') {
+ $item{'assigned_to'} = $self->type('email', $bug->assigned_to->login);
+ $item{'assigned_to_detail'}
+ = $self->_user_to_hash($bug->assigned_to, $params, undef, 'assigned_to');
+ }
+ if (filter_wants $params, 'blocks') {
+ my @blocks = map { $self->type('int', $_) } @{$bug->blocked};
+ $item{'blocks'} = \@blocks;
+ }
+ if (filter_wants $params, 'classification') {
+ $item{classification} = $self->type('string', $bug->classification);
+ }
+ if (filter_wants $params, 'component') {
+ $item{component} = $self->type('string', $bug->component);
+ }
+ if (filter_wants $params, 'cc') {
+ my @cc = map { $self->type('email', $_) } @{$bug->cc};
+ $item{'cc'} = \@cc;
+ $item{'cc_detail'}
+ = [map { $self->_user_to_hash($_, $params, undef, 'cc') } @{$bug->cc_users}];
+ }
+ if (filter_wants $params, 'creation_time') {
+ $item{'creation_time'} = $self->type('dateTime', $bug->creation_ts);
+ }
+ if (filter_wants $params, 'creator') {
+ $item{'creator'} = $self->type('email', $bug->reporter->login);
+ $item{'creator_detail'}
+ = $self->_user_to_hash($bug->reporter, $params, undef, 'creator');
+ }
+ if (filter_wants $params, 'depends_on') {
+ my @depends_on = map { $self->type('int', $_) } @{$bug->dependson};
+ $item{'depends_on'} = \@depends_on;
+ }
+ if (filter_wants $params, 'dupe_of') {
+ $item{'dupe_of'} = $self->type('int', $bug->dup_id);
+ }
+ if (filter_wants $params, 'groups') {
+ my @groups = map { $self->type('string', $_->name) } @{$bug->groups_in};
+ $item{'groups'} = \@groups;
+ }
+ if (filter_wants $params, 'is_open') {
+ $item{'is_open'} = $self->type('boolean', $bug->status->is_open);
+ }
+ if (filter_wants $params, 'keywords') {
+ my @keywords = map { $self->type('string', $_->name) } @{$bug->keyword_objects};
+ $item{'keywords'} = \@keywords;
+ }
+ if (filter_wants $params, 'last_change_time') {
+ $item{'last_change_time'} = $self->type('dateTime', $bug->delta_ts);
+ }
+ if (filter_wants $params, 'product') {
+ $item{product} = $self->type('string', $bug->product);
+ }
+ if (filter_wants $params, 'qa_contact') {
+ my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : '';
+ $item{'qa_contact'} = $self->type('email', $qa_login);
+ if ($bug->qa_contact) {
+ $item{'qa_contact_detail'}
+ = $self->_user_to_hash($bug->qa_contact, $params, undef, 'qa_contact');
}
- if (filter_wants $params, 'see_also') {
- my @see_also = map { $self->type('string', $_->name) }
- @{ $bug->see_also };
- $item{'see_also'} = \@see_also;
+ }
+ if (filter_wants $params, 'see_also') {
+ my @see_also = map { $self->type('string', $_->name) } @{$bug->see_also};
+ $item{'see_also'} = \@see_also;
+ }
+ if (filter_wants $params, 'flags') {
+ $item{'flags'} = [map { $self->_flag_to_hash($_) } @{$bug->flags}];
+ }
+ if (filter_wants $params, 'tags', 'extra') {
+ $item{'tags'} = $bug->tags;
+ }
+
+ # And now custom fields
+ my @custom_fields = Bugzilla->active_custom_fields;
+ foreach my $field (@custom_fields) {
+ my $name = $field->name;
+ next if !filter_wants($params, $name, ['default', 'custom']);
+ if ($field->type == FIELD_TYPE_BUG_ID) {
+ $item{$name} = $self->type('int', $bug->$name);
}
- if (filter_wants $params, 'flags') {
- $item{'flags'} = [ map { $self->_flag_to_hash($_) } @{$bug->flags} ];
+ elsif ($field->type == FIELD_TYPE_DATETIME || $field->type == FIELD_TYPE_DATE) {
+ $item{$name} = $self->type('dateTime', $bug->$name);
}
- if (filter_wants $params, 'tags', 'extra') {
- $item{'tags'} = $bug->tags;
+ elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ my @values = map { $self->type('string', $_) } @{$bug->$name};
+ $item{$name} = \@values;
}
-
- # And now custom fields
- my @custom_fields = Bugzilla->active_custom_fields;
- foreach my $field (@custom_fields) {
- my $name = $field->name;
- next if !filter_wants($params, $name, ['default', 'custom']);
- if ($field->type == FIELD_TYPE_BUG_ID) {
- $item{$name} = $self->type('int', $bug->$name);
- }
- elsif ($field->type == FIELD_TYPE_DATETIME
- || $field->type == FIELD_TYPE_DATE)
- {
- $item{$name} = $self->type('dateTime', $bug->$name);
- }
- elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
- my @values = map { $self->type('string', $_) } @{ $bug->$name };
- $item{$name} = \@values;
- }
- else {
- $item{$name} = $self->type('string', $bug->$name);
- }
+ else {
+ $item{$name} = $self->type('string', $bug->$name);
}
+ }
- # Timetracking fields are only sent if the user can see them.
- if (Bugzilla->user->is_timetracker) {
- if (filter_wants $params, 'estimated_time') {
- $item{'estimated_time'} = $self->type('double', $bug->estimated_time);
- }
- if (filter_wants $params, 'remaining_time') {
- $item{'remaining_time'} = $self->type('double', $bug->remaining_time);
- }
- if (filter_wants $params, 'actual_time') {
- $item{'actual_time'} = $self->type('double', $bug->actual_time);
- }
+ # Timetracking fields are only sent if the user can see them.
+ if (Bugzilla->user->is_timetracker) {
+ if (filter_wants $params, 'estimated_time') {
+ $item{'estimated_time'} = $self->type('double', $bug->estimated_time);
}
-
- # The "accessible" bits go here because they have long names and it
- # makes the code look nicer to separate them out.
- if (filter_wants $params, 'is_cc_accessible') {
- $item{'is_cc_accessible'} = $self->type('boolean', $bug->cclist_accessible);
+ if (filter_wants $params, 'remaining_time') {
+ $item{'remaining_time'} = $self->type('double', $bug->remaining_time);
}
- if (filter_wants $params, 'is_creator_accessible') {
- $item{'is_creator_accessible'} = $self->type('boolean', $bug->reporter_accessible);
+ if (filter_wants $params, 'actual_time') {
+ $item{'actual_time'} = $self->type('double', $bug->actual_time);
}
-
- return \%item;
+ }
+
+ # The "accessible" bits go here because they have long names and it
+ # makes the code look nicer to separate them out.
+ if (filter_wants $params, 'is_cc_accessible') {
+ $item{'is_cc_accessible'} = $self->type('boolean', $bug->cclist_accessible);
+ }
+ if (filter_wants $params, 'is_creator_accessible') {
+ $item{'is_creator_accessible'}
+ = $self->type('boolean', $bug->reporter_accessible);
+ }
+
+ return \%item;
}
sub _user_to_hash {
- my ($self, $user, $filters, $types, $prefix) = @_;
- my $item = filter $filters, {
- id => $self->type('int', $user->id),
- real_name => $self->type('string', $user->name),
- name => $self->type('email', $user->login),
- email => $self->type('email', $user->email),
- }, $types, $prefix;
- return $item;
+ my ($self, $user, $filters, $types, $prefix) = @_;
+ my $item = filter $filters,
+ {
+ id => $self->type('int', $user->id),
+ real_name => $self->type('string', $user->name),
+ name => $self->type('email', $user->login),
+ email => $self->type('email', $user->email),
+ },
+ $types, $prefix;
+ return $item;
}
sub _attachment_to_hash {
- my ($self, $attach, $filters, $types, $prefix) = @_;
-
- my $item = filter $filters, {
- creation_time => $self->type('dateTime', $attach->attached),
- last_change_time => $self->type('dateTime', $attach->modification_time),
- id => $self->type('int', $attach->id),
- bug_id => $self->type('int', $attach->bug_id),
- file_name => $self->type('string', $attach->filename),
- summary => $self->type('string', $attach->description),
- content_type => $self->type('string', $attach->contenttype),
- is_private => $self->type('int', $attach->isprivate),
- is_obsolete => $self->type('int', $attach->isobsolete),
- is_patch => $self->type('int', $attach->ispatch),
- }, $types, $prefix;
-
- # creator requires an extra lookup, so we only send them if
- # the filter wants them.
- if (filter_wants $filters, 'creator', $types, $prefix) {
- $item->{'creator'} = $self->type('email', $attach->attacher->login);
- }
-
- if (filter_wants $filters, 'data', $types, $prefix) {
- $item->{'data'} = $self->type('base64', $attach->data);
- }
-
- if (filter_wants $filters, 'size', $types, $prefix) {
- $item->{'size'} = $self->type('int', $attach->datasize);
- }
-
- if (filter_wants $filters, 'flags', $types, $prefix) {
- $item->{'flags'} = [ map { $self->_flag_to_hash($_) } @{$attach->flags} ];
- }
+ my ($self, $attach, $filters, $types, $prefix) = @_;
- return $item;
+ my $item = filter $filters,
+ {
+ creation_time => $self->type('dateTime', $attach->attached),
+ last_change_time => $self->type('dateTime', $attach->modification_time),
+ id => $self->type('int', $attach->id),
+ bug_id => $self->type('int', $attach->bug_id),
+ file_name => $self->type('string', $attach->filename),
+ summary => $self->type('string', $attach->description),
+ content_type => $self->type('string', $attach->contenttype),
+ is_private => $self->type('int', $attach->isprivate),
+ is_obsolete => $self->type('int', $attach->isobsolete),
+ is_patch => $self->type('int', $attach->ispatch),
+ },
+ $types, $prefix;
+
+ # creator requires an extra lookup, so we only send them if
+ # the filter wants them.
+ if (filter_wants $filters, 'creator', $types, $prefix) {
+ $item->{'creator'} = $self->type('email', $attach->attacher->login);
+ }
+
+ if (filter_wants $filters, 'data', $types, $prefix) {
+ $item->{'data'} = $self->type('base64', $attach->data);
+ }
+
+ if (filter_wants $filters, 'size', $types, $prefix) {
+ $item->{'size'} = $self->type('int', $attach->datasize);
+ }
+
+ if (filter_wants $filters, 'flags', $types, $prefix) {
+ $item->{'flags'} = [map { $self->_flag_to_hash($_) } @{$attach->flags}];
+ }
+
+ return $item;
}
sub _flag_to_hash {
- my ($self, $flag) = @_;
-
- my $item = {
- id => $self->type('int', $flag->id),
- name => $self->type('string', $flag->name),
- type_id => $self->type('int', $flag->type_id),
- creation_date => $self->type('dateTime', $flag->creation_date),
- modification_date => $self->type('dateTime', $flag->modification_date),
- status => $self->type('string', $flag->status)
- };
-
- foreach my $field (qw(setter requestee)) {
- my $field_id = $field . "_id";
- $item->{$field} = $self->type('email', $flag->$field->login)
- if $flag->$field_id;
- }
-
- return $item;
+ my ($self, $flag) = @_;
+
+ my $item = {
+ id => $self->type('int', $flag->id),
+ name => $self->type('string', $flag->name),
+ type_id => $self->type('int', $flag->type_id),
+ creation_date => $self->type('dateTime', $flag->creation_date),
+ modification_date => $self->type('dateTime', $flag->modification_date),
+ status => $self->type('string', $flag->status)
+ };
+
+ foreach my $field (qw(setter requestee)) {
+ my $field_id = $field . "_id";
+ $item->{$field} = $self->type('email', $flag->$field->login)
+ if $flag->$field_id;
+ }
+
+ return $item;
}
sub _add_update_tokens {
- my ($self, $params, $bugs, $hashes) = @_;
+ my ($self, $params, $bugs, $hashes) = @_;
- return if !Bugzilla->user->id;
- return if !filter_wants($params, 'update_token');
+ return if !Bugzilla->user->id;
+ return if !filter_wants($params, 'update_token');
- for(my $i = 0; $i < @$bugs; $i++) {
- my $token = issue_hash_token([$bugs->[$i]->id, $bugs->[$i]->delta_ts]);
- $hashes->[$i]->{'update_token'} = $self->type('string', $token);
- }
+ for (my $i = 0; $i < @$bugs; $i++) {
+ my $token = issue_hash_token([$bugs->[$i]->id, $bugs->[$i]->delta_ts]);
+ $hashes->[$i]->{'update_token'} = $self->type('string', $token);
+ }
}
1;
diff --git a/Bugzilla/WebService/BugUserLastVisit.pm b/Bugzilla/WebService/BugUserLastVisit.pm
index 56e91ec31..128507376 100644
--- a/Bugzilla/WebService/BugUserLastVisit.pm
+++ b/Bugzilla/WebService/BugUserLastVisit.pm
@@ -19,80 +19,83 @@ use Bugzilla::WebService::Util qw( validate filter );
use Bugzilla::Constants;
use constant PUBLIC_METHODS => qw(
- get
- update
+ get
+ update
);
sub update {
- my ($self, $params) = validate(@_, 'ids');
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
+ my ($self, $params) = validate(@_, 'ids');
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
- $user->login(LOGIN_REQUIRED);
+ $user->login(LOGIN_REQUIRED);
- my $ids = $params->{ids} // [];
- ThrowCodeError('param_required', { param => 'ids' }) unless @$ids;
+ my $ids = $params->{ids} // [];
+ ThrowCodeError('param_required', {param => 'ids'}) unless @$ids;
- # Cache permissions for bugs. This highly reduces the number of calls to the
- # DB. visible_bugs() is only able to handle bug IDs, so we have to skip
- # aliases.
- $user->visible_bugs([grep /^[0-9]+$/, @$ids]);
+ # Cache permissions for bugs. This highly reduces the number of calls to the
+ # DB. visible_bugs() is only able to handle bug IDs, so we have to skip
+ # aliases.
+ $user->visible_bugs([grep /^[0-9]+$/, @$ids]);
- $dbh->bz_start_transaction();
- my @results;
- my $last_visit_ts = $dbh->selectrow_array('SELECT NOW()');
- foreach my $bug_id (@$ids) {
- my $bug = Bugzilla::Bug->check({ id => $bug_id, cache => 1 });
+ $dbh->bz_start_transaction();
+ my @results;
+ my $last_visit_ts = $dbh->selectrow_array('SELECT NOW()');
+ foreach my $bug_id (@$ids) {
+ my $bug = Bugzilla::Bug->check({id => $bug_id, cache => 1});
- ThrowUserError('user_not_involved', { bug_id => $bug->id })
- unless $user->is_involved_in_bug($bug);
+ ThrowUserError('user_not_involved', {bug_id => $bug->id})
+ unless $user->is_involved_in_bug($bug);
- $bug->update_user_last_visit($user, $last_visit_ts);
+ $bug->update_user_last_visit($user, $last_visit_ts);
- push(
- @results,
- $self->_bug_user_last_visit_to_hash(
- $bug->id, $last_visit_ts, $params
- ));
- }
- $dbh->bz_commit_transaction();
+ push(@results,
+ $self->_bug_user_last_visit_to_hash($bug->id, $last_visit_ts, $params));
+ }
+ $dbh->bz_commit_transaction();
- return \@results;
+ return \@results;
}
sub get {
- my ($self, $params) = validate(@_, 'ids');
- my $user = Bugzilla->user;
- my $ids = $params->{ids};
-
- $user->login(LOGIN_REQUIRED);
-
- my @last_visits;
- if ($ids) {
- # Cache permissions for bugs. This highly reduces the number of calls to
- # the DB. visible_bugs() is only able to handle bug IDs, so we have to
- # skip aliases.
- $user->visible_bugs([grep /^[0-9]+$/, @$ids]);
-
- my %last_visit = map { $_->bug_id => $_->last_visit_ts } @{ $user->last_visited($ids) };
- @last_visits = map { $self->_bug_user_last_visit_to_hash($_->id, $last_visit{$_}, $params) } @$ids;
- }
- else {
- @last_visits = map {
- $self->_bug_user_last_visit_to_hash($_->bug_id, $_->last_visit_ts, $params)
- } @{ $user->last_visited };
- }
-
- return \@last_visits;
+ my ($self, $params) = validate(@_, 'ids');
+ my $user = Bugzilla->user;
+ my $ids = $params->{ids};
+
+ $user->login(LOGIN_REQUIRED);
+
+ my @last_visits;
+ if ($ids) {
+
+ # Cache permissions for bugs. This highly reduces the number of calls to
+ # the DB. visible_bugs() is only able to handle bug IDs, so we have to
+ # skip aliases.
+ $user->visible_bugs([grep /^[0-9]+$/, @$ids]);
+
+ my %last_visit
+ = map { $_->bug_id => $_->last_visit_ts } @{$user->last_visited($ids)};
+ @last_visits
+ = map { $self->_bug_user_last_visit_to_hash($_->id, $last_visit{$_}, $params) }
+ @$ids;
+ }
+ else {
+ @last_visits = map {
+ $self->_bug_user_last_visit_to_hash($_->bug_id, $_->last_visit_ts, $params)
+ } @{$user->last_visited};
+ }
+
+ return \@last_visits;
}
sub _bug_user_last_visit_to_hash {
- my ($self, $bug_id, $last_visit_ts, $params) = @_;
+ my ($self, $bug_id, $last_visit_ts, $params) = @_;
- my %result = (id => $self->type('int', $bug_id),
- last_visit_ts => $self->type('dateTime', $last_visit_ts));
+ my %result = (
+ id => $self->type('int', $bug_id),
+ last_visit_ts => $self->type('dateTime', $last_visit_ts)
+ );
- return filter($params, \%result);
+ return filter($params, \%result);
}
1;
diff --git a/Bugzilla/WebService/Bugzilla.pm b/Bugzilla/WebService/Bugzilla.pm
index 848cffd30..f93892068 100644
--- a/Bugzilla/WebService/Bugzilla.pm
+++ b/Bugzilla/WebService/Bugzilla.pm
@@ -20,158 +20,155 @@ use Bugzilla::Util qw(trick_taint);
use DateTime;
# Basic info that is needed before logins
-use constant LOGIN_EXEMPT => {
- parameters => 1,
- timezone => 1,
- version => 1,
-};
+use constant LOGIN_EXEMPT => {parameters => 1, timezone => 1, version => 1,};
use constant READ_ONLY => qw(
- extensions
- parameters
- timezone
- time
- version
+ extensions
+ parameters
+ timezone
+ time
+ version
);
use constant PUBLIC_METHODS => qw(
- extensions
- last_audit_time
- parameters
- time
- timezone
- version
+ extensions
+ last_audit_time
+ parameters
+ time
+ timezone
+ version
);
# Logged-out users do not need to know more than that.
use constant PARAMETERS_LOGGED_OUT => qw(
- maintainer
- requirelogin
+ maintainer
+ requirelogin
);
# These parameters are guessable from the web UI when the user
# is logged in. So it's safe to access them.
use constant PARAMETERS_LOGGED_IN => qw(
- allowemailchange
- attachment_base
- commentonchange_resolution
- commentonduplicate
- cookiepath
- defaultopsys
- defaultplatform
- defaultpriority
- defaultseverity
- duplicate_or_move_bug_status
- emailregexpdesc
- emailsuffix
- letsubmitterchoosemilestone
- letsubmitterchoosepriority
- mailfrom
- maintainer
- maxattachmentsize
- maxlocalattachment
- musthavemilestoneonaccept
- noresolveonopenblockers
- password_complexity
- rememberlogin
- requirelogin
- search_allow_no_criteria
- urlbase
- use_see_also
- useclassification
- usemenuforusers
- useqacontact
- usestatuswhiteboard
- usetargetmilestone
+ allowemailchange
+ attachment_base
+ commentonchange_resolution
+ commentonduplicate
+ cookiepath
+ defaultopsys
+ defaultplatform
+ defaultpriority
+ defaultseverity
+ duplicate_or_move_bug_status
+ emailregexpdesc
+ emailsuffix
+ letsubmitterchoosemilestone
+ letsubmitterchoosepriority
+ mailfrom
+ maintainer
+ maxattachmentsize
+ maxlocalattachment
+ musthavemilestoneonaccept
+ noresolveonopenblockers
+ password_complexity
+ rememberlogin
+ requirelogin
+ search_allow_no_criteria
+ urlbase
+ use_see_also
+ useclassification
+ usemenuforusers
+ useqacontact
+ usestatuswhiteboard
+ usetargetmilestone
);
sub version {
- my $self = shift;
- return { version => $self->type('string', BUGZILLA_VERSION) };
+ my $self = shift;
+ return {version => $self->type('string', BUGZILLA_VERSION)};
}
sub extensions {
- my $self = shift;
-
- my %retval;
- foreach my $extension (@{ Bugzilla->extensions }) {
- my $version = $extension->VERSION || 0;
- my $name = $extension->NAME;
- $retval{$name}->{version} = $self->type('string', $version);
- }
- return { extensions => \%retval };
+ my $self = shift;
+
+ my %retval;
+ foreach my $extension (@{Bugzilla->extensions}) {
+ my $version = $extension->VERSION || 0;
+ my $name = $extension->NAME;
+ $retval{$name}->{version} = $self->type('string', $version);
+ }
+ return {extensions => \%retval};
}
sub timezone {
- my $self = shift;
- # All Webservices return times in UTC; Use UTC here for backwards compat.
- return { timezone => $self->type('string', "+0000") };
+ my $self = shift;
+
+ # All Webservices return times in UTC; Use UTC here for backwards compat.
+ return {timezone => $self->type('string', "+0000")};
}
sub time {
- my ($self) = @_;
- # All Webservices return times in UTC; Use UTC here for backwards compat.
- # Hardcode values where appropriate
- my $dbh = Bugzilla->dbh;
-
- my $db_time = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- $db_time = datetime_from($db_time, 'UTC');
- my $now_utc = DateTime->now();
-
- return {
- db_time => $self->type('dateTime', $db_time),
- web_time => $self->type('dateTime', $now_utc),
- web_time_utc => $self->type('dateTime', $now_utc),
- tz_name => $self->type('string', 'UTC'),
- tz_offset => $self->type('string', '+0000'),
- tz_short_name => $self->type('string', 'UTC'),
- };
+ my ($self) = @_;
+
+ # All Webservices return times in UTC; Use UTC here for backwards compat.
+ # Hardcode values where appropriate
+ my $dbh = Bugzilla->dbh;
+
+ my $db_time = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $db_time = datetime_from($db_time, 'UTC');
+ my $now_utc = DateTime->now();
+
+ return {
+ db_time => $self->type('dateTime', $db_time),
+ web_time => $self->type('dateTime', $now_utc),
+ web_time_utc => $self->type('dateTime', $now_utc),
+ tz_name => $self->type('string', 'UTC'),
+ tz_offset => $self->type('string', '+0000'),
+ tz_short_name => $self->type('string', 'UTC'),
+ };
}
sub last_audit_time {
- my ($self, $params) = validate(@_, 'class');
- my $dbh = Bugzilla->dbh;
-
- my $sql_statement = "SELECT MAX(at_time) FROM audit_log";
- my $class_values = $params->{class};
- my @class_values_quoted;
- foreach my $class_value (@$class_values) {
- push (@class_values_quoted, $dbh->quote($class_value))
- if $class_value =~ /^Bugzilla(::[a-zA-Z0-9_]+)*$/;
- }
-
- if (@class_values_quoted) {
- $sql_statement .= " WHERE " . $dbh->sql_in('class', \@class_values_quoted);
- }
-
- my $last_audit_time = $dbh->selectrow_array("$sql_statement");
-
- # All Webservices return times in UTC; Use UTC here for backwards compat.
- # Hardcode values where appropriate
- $last_audit_time = datetime_from($last_audit_time, 'UTC');
-
- return {
- last_audit_time => $self->type('dateTime', $last_audit_time)
- };
+ my ($self, $params) = validate(@_, 'class');
+ my $dbh = Bugzilla->dbh;
+
+ my $sql_statement = "SELECT MAX(at_time) FROM audit_log";
+ my $class_values = $params->{class};
+ my @class_values_quoted;
+ foreach my $class_value (@$class_values) {
+ push(@class_values_quoted, $dbh->quote($class_value))
+ if $class_value =~ /^Bugzilla(::[a-zA-Z0-9_]+)*$/;
+ }
+
+ if (@class_values_quoted) {
+ $sql_statement .= " WHERE " . $dbh->sql_in('class', \@class_values_quoted);
+ }
+
+ my $last_audit_time = $dbh->selectrow_array("$sql_statement");
+
+ # All Webservices return times in UTC; Use UTC here for backwards compat.
+ # Hardcode values where appropriate
+ $last_audit_time = datetime_from($last_audit_time, 'UTC');
+
+ return {last_audit_time => $self->type('dateTime', $last_audit_time)};
}
sub parameters {
- my ($self, $args) = @_;
- my $user = Bugzilla->login(LOGIN_OPTIONAL);
- my $params = Bugzilla->params;
- $args ||= {};
-
- my @params_list = $user->in_group('tweakparams')
- ? keys(%$params)
- : $user->id ? PARAMETERS_LOGGED_IN : PARAMETERS_LOGGED_OUT;
-
- my %parameters;
- foreach my $param (@params_list) {
- next unless filter_wants($args, $param);
- $parameters{$param} = $self->type('string', $params->{$param});
- }
-
- return { parameters => \%parameters };
+ my ($self, $args) = @_;
+ my $user = Bugzilla->login(LOGIN_OPTIONAL);
+ my $params = Bugzilla->params;
+ $args ||= {};
+
+ my @params_list
+ = $user->in_group('tweakparams') ? keys(%$params)
+ : $user->id ? PARAMETERS_LOGGED_IN
+ : PARAMETERS_LOGGED_OUT;
+
+ my %parameters;
+ foreach my $param (@params_list) {
+ next unless filter_wants($args, $param);
+ $parameters{$param} = $self->type('string', $params->{$param});
+ }
+
+ return {parameters => \%parameters};
}
1;
diff --git a/Bugzilla/WebService/Classification.pm b/Bugzilla/WebService/Classification.pm
index cee597b68..ab539b339 100644
--- a/Bugzilla/WebService/Classification.pm
+++ b/Bugzilla/WebService/Classification.pm
@@ -18,65 +18,76 @@ use Bugzilla::Error;
use Bugzilla::WebService::Util qw(filter validate params_to_objects);
use constant READ_ONLY => qw(
- get
+ get
);
use constant PUBLIC_METHODS => qw(
- get
+ get
);
sub get {
- my ($self, $params) = validate(@_, 'names', 'ids');
+ my ($self, $params) = validate(@_, 'names', 'ids');
- defined $params->{names} || defined $params->{ids}
- || ThrowCodeError('params_required', { function => 'Classification.get',
- params => ['names', 'ids'] });
+ defined $params->{names}
+ || defined $params->{ids}
+ || ThrowCodeError('params_required',
+ {function => 'Classification.get', params => ['names', 'ids']});
- my $user = Bugzilla->user;
+ my $user = Bugzilla->user;
- Bugzilla->params->{'useclassification'}
- || $user->in_group('editclassifications')
- || ThrowUserError('auth_classification_not_enabled');
+ Bugzilla->params->{'useclassification'}
+ || $user->in_group('editclassifications')
+ || ThrowUserError('auth_classification_not_enabled');
- Bugzilla->switch_to_shadow_db;
+ Bugzilla->switch_to_shadow_db;
- my @classification_objs = @{ params_to_objects($params, 'Bugzilla::Classification') };
- unless ($user->in_group('editclassifications')) {
- my %selectable_class = map { $_->id => 1 } @{$user->get_selectable_classifications};
- @classification_objs = grep { $selectable_class{$_->id} } @classification_objs;
- }
+ my @classification_objs
+ = @{params_to_objects($params, 'Bugzilla::Classification')};
+ unless ($user->in_group('editclassifications')) {
+ my %selectable_class
+ = map { $_->id => 1 } @{$user->get_selectable_classifications};
+ @classification_objs = grep { $selectable_class{$_->id} } @classification_objs;
+ }
- my @classifications = map { $self->_classification_to_hash($_, $params) } @classification_objs;
+ my @classifications
+ = map { $self->_classification_to_hash($_, $params) } @classification_objs;
- return { classifications => \@classifications };
+ return {classifications => \@classifications};
}
sub _classification_to_hash {
- my ($self, $classification, $params) = @_;
-
- my $user = Bugzilla->user;
- return unless (Bugzilla->params->{'useclassification'} || $user->in_group('editclassifications'));
-
- my $products = $user->in_group('editclassifications') ?
- $classification->products : $user->get_selectable_products($classification->id);
-
- return filter $params, {
- id => $self->type('int', $classification->id),
- name => $self->type('string', $classification->name),
- description => $self->type('string', $classification->description),
- sort_key => $self->type('int', $classification->sortkey),
- products => [ map { $self->_product_to_hash($_, $params) } @$products ],
+ my ($self, $classification, $params) = @_;
+
+ my $user = Bugzilla->user;
+ return
+ unless (Bugzilla->params->{'useclassification'}
+ || $user->in_group('editclassifications'));
+
+ my $products
+ = $user->in_group('editclassifications')
+ ? $classification->products
+ : $user->get_selectable_products($classification->id);
+
+ return filter $params,
+ {
+ id => $self->type('int', $classification->id),
+ name => $self->type('string', $classification->name),
+ description => $self->type('string', $classification->description),
+ sort_key => $self->type('int', $classification->sortkey),
+ products => [map { $self->_product_to_hash($_, $params) } @$products],
};
}
sub _product_to_hash {
- my ($self, $product, $params) = @_;
-
- return filter $params, {
- id => $self->type('int', $product->id),
- name => $self->type('string', $product->name),
- description => $self->type('string', $product->description),
- }, undef, 'products';
+ my ($self, $product, $params) = @_;
+
+ return filter $params,
+ {
+ id => $self->type('int', $product->id),
+ name => $self->type('string', $product->name),
+ description => $self->type('string', $product->description),
+ },
+ undef, 'products';
}
1;
diff --git a/Bugzilla/WebService/Component.pm b/Bugzilla/WebService/Component.pm
index 4d6723d8b..802f40c73 100644
--- a/Bugzilla/WebService/Component.pm
+++ b/Bugzilla/WebService/Component.pm
@@ -19,37 +19,36 @@ use Bugzilla::Error;
use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Util qw(translate params_to_objects validate);
-use constant PUBLIC_METHODS => qw(
- create
+use constant PUBLIC_METHODS => qw(
+ create
);
use constant MAPPED_FIELDS => {
- default_assignee => 'initialowner',
- default_qa_contact => 'initialqacontact',
- default_cc => 'initial_cc',
- is_open => 'isactive',
+ default_assignee => 'initialowner',
+ default_qa_contact => 'initialqacontact',
+ default_cc => 'initial_cc',
+ is_open => 'isactive',
};
sub create {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
- $user->in_group('editcomponents')
- || scalar @{ $user->get_products_by_permission('editcomponents') }
- || ThrowUserError('auth_failure', { group => 'editcomponents',
- action => 'edit',
- object => 'components' });
+ $user->in_group('editcomponents')
+ || scalar @{$user->get_products_by_permission('editcomponents')}
+ || ThrowUserError('auth_failure',
+ {group => 'editcomponents', action => 'edit', object => 'components'});
- my $product = $user->check_can_admin_product($params->{product});
+ my $product = $user->check_can_admin_product($params->{product});
- # Translate the fields
- my $values = translate($params, MAPPED_FIELDS);
- $values->{product} = $product;
+ # Translate the fields
+ my $values = translate($params, MAPPED_FIELDS);
+ $values->{product} = $product;
- # Create the component and return the newly created id.
- my $component = Bugzilla::Component->create($values);
- return { id => $self->type('int', $component->id) };
+ # Create the component and return the newly created id.
+ my $component = Bugzilla::Component->create($values);
+ return {id => $self->type('int', $component->id)};
}
1;
diff --git a/Bugzilla/WebService/Constants.pm b/Bugzilla/WebService/Constants.pm
index 557a996f8..a2f83f528 100644
--- a/Bugzilla/WebService/Constants.pm
+++ b/Bugzilla/WebService/Constants.pm
@@ -14,25 +14,25 @@ use warnings;
use parent qw(Exporter);
our @EXPORT = qw(
- WS_ERROR_CODE
+ WS_ERROR_CODE
- STATUS_OK
- STATUS_CREATED
- STATUS_ACCEPTED
- STATUS_NO_CONTENT
- STATUS_MULTIPLE_CHOICES
- STATUS_BAD_REQUEST
- STATUS_NOT_FOUND
- STATUS_GONE
- REST_STATUS_CODE_MAP
+ STATUS_OK
+ STATUS_CREATED
+ STATUS_ACCEPTED
+ STATUS_NO_CONTENT
+ STATUS_MULTIPLE_CHOICES
+ STATUS_BAD_REQUEST
+ STATUS_NOT_FOUND
+ STATUS_GONE
+ REST_STATUS_CODE_MAP
- ERROR_UNKNOWN_FATAL
- ERROR_UNKNOWN_TRANSIENT
+ ERROR_UNKNOWN_FATAL
+ ERROR_UNKNOWN_TRANSIENT
- XMLRPC_CONTENT_TYPE_WHITELIST
- REST_CONTENT_TYPE_WHITELIST
+ XMLRPC_CONTENT_TYPE_WHITELIST
+ REST_CONTENT_TYPE_WHITELIST
- WS_DISPATCH
+ WS_DISPATCH
);
# This maps the error names in global/*-error.html.tmpl to numbers.
@@ -54,173 +54,196 @@ our @EXPORT = qw(
# 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 errors (Bugzilla::Object and others) are 50-99.
- object_not_specified => 50,
- reassign_to_empty => 50,
- param_required => 50,
- params_required => 50,
- undefined_field => 50,
- object_does_not_exist => 51,
- param_must_be_numeric => 52,
- number_not_numeric => 52,
- param_invalid => 53,
- number_too_large => 54,
- number_too_small => 55,
- illegal_date => 56,
- param_integer_required => 57,
- param_scalar_array_required => 58,
- # 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,
- multiple_alias_not_allowed => 103,
- # Misc. bug field errors
- illegal_field => 104,
- freetext_too_long => 104,
- # Component errors
- require_component => 105,
- component_name_too_long => 105,
- product_unknown_component => 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,
- # Comment-related errors
- comment_is_private => 110,
- comment_id_invalid => 111,
- comment_too_long => 114,
- comment_invalid_isprivate => 117,
- # Comment tagging
- comment_tag_disabled => 125,
- comment_tag_invalid => 126,
- comment_tag_too_long => 127,
- comment_tag_too_short => 128,
- # See Also errors
- bug_url_invalid => 112,
- bug_url_too_long => 112,
- # Insidergroup Errors
- user_not_insider => 113,
- # Note: 114 is above in the Comment-related section.
- # Bug update errors
- illegal_change => 115,
- # Dependency errors
- dependency_loop_single => 116,
- dependency_loop_multi => 116,
- # Note: 117 is above in the Comment-related section.
- # Dup errors
- dupe_loop_detected => 118,
- dupe_id_required => 119,
- # Bug-related group errors
- group_invalid_removal => 120,
- group_restriction_not_allowed => 120,
- # Status/Resolution errors
- missing_resolution => 121,
- resolution_not_allowed => 122,
- illegal_bug_status_transition => 123,
- # Flag errors
- flag_status_invalid => 129,
- flag_update_denied => 130,
- flag_type_requestee_disabled => 131,
- flag_not_unique => 132,
- flag_type_not_unique => 133,
- flag_type_inactive => 134,
-
- # Authentication errors are usually 300-400.
- invalid_login_or_password => 300,
- account_disabled => 301,
- auth_invalid_email => 302,
- extern_id_conflict => -303,
- auth_failure => 304,
- password_too_short => 305,
- password_not_complex => 305,
- api_key_not_valid => 306,
- api_key_revoked => 306,
- auth_invalid_token => 307,
-
- # Except, historically, AUTH_NODATA, which is 410.
- login_required => 410,
-
- # User errors are 500-600.
- account_exists => 500,
- illegal_email_address => 501,
- auth_cant_create_account => 501,
- account_creation_disabled => 501,
- account_creation_restricted => 501,
- password_too_short => 502,
- # Error 503 password_too_long no longer exists.
- invalid_username => 504,
- # This is from strict_isolation, but it also basically means
- # "invalid user."
- invalid_user_group => 504,
- user_access_by_id_denied => 505,
- user_access_by_match_denied => 505,
-
- # Attachment errors are 600-700.
- file_too_large => 600,
- invalid_content_type => 601,
- # Error 602 attachment_illegal_url no longer exists.
- file_not_specified => 603,
- missing_attachment_description => 604,
- # Error 605 attachment_url_disabled no longer exists.
- zero_length_file => 606,
-
- # Product erros are 700-800
- product_blank_name => 700,
- product_name_too_long => 701,
- product_name_already_in_use => 702,
- product_name_diff_in_case => 702,
- product_must_have_description => 703,
- product_must_have_version => 704,
- product_must_define_defaultmilestone => 705,
-
- # Group errors are 800-900
- empty_group_name => 800,
- group_exists => 801,
- empty_group_description => 802,
- invalid_regexp => 803,
- invalid_group_name => 804,
- group_cannot_view => 805,
-
- # Classification errors are 900-1000
- auth_classification_not_enabled => 900,
-
- # Search errors are 1000-1100
- buglist_parameters_required => 1000,
-
- # Flag type errors are 1100-1200
- flag_type_name_invalid => 1101,
- flag_type_description_invalid => 1102,
- flag_type_cc_list_invalid => 1103,
- flag_type_sortkey_invalid => 1104,
- flag_type_not_editable => 1105,
-
- # Component errors are 1200-1300
- component_already_exists => 1200,
- component_is_last => 1201,
- component_has_bugs => 1202,
-
- # Errors thrown by the WebService itself. The ones that are negative
- # conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
- xmlrpc_invalid_value => -32600,
- unknown_method => -32601,
- json_rpc_post_only => 32610,
- json_rpc_invalid_callback => 32611,
- xmlrpc_illegal_content_type => 32612,
- json_rpc_illegal_content_type => 32613,
- rest_invalid_resource => 32614,
+
+ # Generic errors (Bugzilla::Object and others) are 50-99.
+ object_not_specified => 50,
+ reassign_to_empty => 50,
+ param_required => 50,
+ params_required => 50,
+ undefined_field => 50,
+ object_does_not_exist => 51,
+ param_must_be_numeric => 52,
+ number_not_numeric => 52,
+ param_invalid => 53,
+ number_too_large => 54,
+ number_too_small => 55,
+ illegal_date => 56,
+ param_integer_required => 57,
+ param_scalar_array_required => 58,
+
+ # 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,
+ multiple_alias_not_allowed => 103,
+
+ # Misc. bug field errors
+ illegal_field => 104,
+ freetext_too_long => 104,
+
+ # Component errors
+ require_component => 105,
+ component_name_too_long => 105,
+ product_unknown_component => 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,
+
+ # Comment-related errors
+ comment_is_private => 110,
+ comment_id_invalid => 111,
+ comment_too_long => 114,
+ comment_invalid_isprivate => 117,
+
+ # Comment tagging
+ comment_tag_disabled => 125,
+ comment_tag_invalid => 126,
+ comment_tag_too_long => 127,
+ comment_tag_too_short => 128,
+
+ # See Also errors
+ bug_url_invalid => 112,
+ bug_url_too_long => 112,
+
+ # Insidergroup Errors
+ user_not_insider => 113,
+
+ # Note: 114 is above in the Comment-related section.
+ # Bug update errors
+ illegal_change => 115,
+
+ # Dependency errors
+ dependency_loop_single => 116,
+ dependency_loop_multi => 116,
+
+ # Note: 117 is above in the Comment-related section.
+ # Dup errors
+ dupe_loop_detected => 118,
+ dupe_id_required => 119,
+
+ # Bug-related group errors
+ group_invalid_removal => 120,
+ group_restriction_not_allowed => 120,
+
+ # Status/Resolution errors
+ missing_resolution => 121,
+ resolution_not_allowed => 122,
+ illegal_bug_status_transition => 123,
+
+ # Flag errors
+ flag_status_invalid => 129,
+ flag_update_denied => 130,
+ flag_type_requestee_disabled => 131,
+ flag_not_unique => 132,
+ flag_type_not_unique => 133,
+ flag_type_inactive => 134,
+
+ # Authentication errors are usually 300-400.
+ invalid_login_or_password => 300,
+ account_disabled => 301,
+ auth_invalid_email => 302,
+ extern_id_conflict => -303,
+ auth_failure => 304,
+ password_too_short => 305,
+ password_not_complex => 305,
+ api_key_not_valid => 306,
+ api_key_revoked => 306,
+ auth_invalid_token => 307,
+
+ # Except, historically, AUTH_NODATA, which is 410.
+ login_required => 410,
+
+ # User errors are 500-600.
+ account_exists => 500,
+ illegal_email_address => 501,
+ auth_cant_create_account => 501,
+ account_creation_disabled => 501,
+ account_creation_restricted => 501,
+ password_too_short => 502,
+
+ # Error 503 password_too_long no longer exists.
+ invalid_username => 504,
+
+ # This is from strict_isolation, but it also basically means
+ # "invalid user."
+ invalid_user_group => 504,
+ user_access_by_id_denied => 505,
+ user_access_by_match_denied => 505,
+
+ # Attachment errors are 600-700.
+ file_too_large => 600,
+ invalid_content_type => 601,
+
+ # Error 602 attachment_illegal_url no longer exists.
+ file_not_specified => 603,
+ missing_attachment_description => 604,
+
+ # Error 605 attachment_url_disabled no longer exists.
+ zero_length_file => 606,
+
+ # Product erros are 700-800
+ product_blank_name => 700,
+ product_name_too_long => 701,
+ product_name_already_in_use => 702,
+ product_name_diff_in_case => 702,
+ product_must_have_description => 703,
+ product_must_have_version => 704,
+ product_must_define_defaultmilestone => 705,
+
+ # Group errors are 800-900
+ empty_group_name => 800,
+ group_exists => 801,
+ empty_group_description => 802,
+ invalid_regexp => 803,
+ invalid_group_name => 804,
+ group_cannot_view => 805,
+
+ # Classification errors are 900-1000
+ auth_classification_not_enabled => 900,
+
+ # Search errors are 1000-1100
+ buglist_parameters_required => 1000,
+
+ # Flag type errors are 1100-1200
+ flag_type_name_invalid => 1101,
+ flag_type_description_invalid => 1102,
+ flag_type_cc_list_invalid => 1103,
+ flag_type_sortkey_invalid => 1104,
+ flag_type_not_editable => 1105,
+
+ # Component errors are 1200-1300
+ component_already_exists => 1200,
+ component_is_last => 1201,
+ component_has_bugs => 1202,
+
+ # Errors thrown by the WebService itself. The ones that are negative
+ # conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
+ xmlrpc_invalid_value => -32600,
+ unknown_method => -32601,
+ json_rpc_post_only => 32610,
+ json_rpc_invalid_callback => 32611,
+ xmlrpc_illegal_content_type => 32612,
+ json_rpc_illegal_content_type => 32613,
+ rest_invalid_resource => 32614,
};
# RESTful webservices use the http status code
@@ -241,73 +264,74 @@ use constant STATUS_GONE => 410;
# http status code based on the error code or use the
# default STATUS_BAD_REQUEST.
sub REST_STATUS_CODE_MAP {
- my $status_code_map = {
- 51 => STATUS_NOT_FOUND,
- 101 => STATUS_NOT_FOUND,
- 102 => STATUS_NOT_AUTHORIZED,
- 106 => STATUS_NOT_AUTHORIZED,
- 109 => STATUS_NOT_AUTHORIZED,
- 110 => STATUS_NOT_AUTHORIZED,
- 113 => STATUS_NOT_AUTHORIZED,
- 115 => STATUS_NOT_AUTHORIZED,
- 120 => STATUS_NOT_AUTHORIZED,
- 300 => STATUS_NOT_AUTHORIZED,
- 301 => STATUS_NOT_AUTHORIZED,
- 302 => STATUS_NOT_AUTHORIZED,
- 303 => STATUS_NOT_AUTHORIZED,
- 304 => STATUS_NOT_AUTHORIZED,
- 410 => STATUS_NOT_AUTHORIZED,
- 504 => STATUS_NOT_AUTHORIZED,
- 505 => STATUS_NOT_AUTHORIZED,
- 32614 => STATUS_NOT_FOUND,
- _default => STATUS_BAD_REQUEST
- };
-
- Bugzilla::Hook::process('webservice_status_code_map',
- { status_code_map => $status_code_map });
-
- return $status_code_map;
-};
+ my $status_code_map = {
+ 51 => STATUS_NOT_FOUND,
+ 101 => STATUS_NOT_FOUND,
+ 102 => STATUS_NOT_AUTHORIZED,
+ 106 => STATUS_NOT_AUTHORIZED,
+ 109 => STATUS_NOT_AUTHORIZED,
+ 110 => STATUS_NOT_AUTHORIZED,
+ 113 => STATUS_NOT_AUTHORIZED,
+ 115 => STATUS_NOT_AUTHORIZED,
+ 120 => STATUS_NOT_AUTHORIZED,
+ 300 => STATUS_NOT_AUTHORIZED,
+ 301 => STATUS_NOT_AUTHORIZED,
+ 302 => STATUS_NOT_AUTHORIZED,
+ 303 => STATUS_NOT_AUTHORIZED,
+ 304 => STATUS_NOT_AUTHORIZED,
+ 410 => STATUS_NOT_AUTHORIZED,
+ 504 => STATUS_NOT_AUTHORIZED,
+ 505 => STATUS_NOT_AUTHORIZED,
+ 32614 => STATUS_NOT_FOUND,
+ _default => STATUS_BAD_REQUEST
+ };
+
+ Bugzilla::Hook::process('webservice_status_code_map',
+ {status_code_map => $status_code_map});
+
+ return $status_code_map;
+}
# 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_GENERAL => 999;
+use constant ERROR_GENERAL => 999;
use constant XMLRPC_CONTENT_TYPE_WHITELIST => qw(
- text/xml
- application/xml
+ text/xml
+ application/xml
);
# The first content type specified is used as the default.
use constant REST_CONTENT_TYPE_WHITELIST => qw(
- application/json
- application/javascript
- text/javascript
- text/html
+ application/json
+ application/javascript
+ text/javascript
+ text/html
);
sub WS_DISPATCH {
- # We "require" here instead of "use" above to avoid a dependency loop.
- require Bugzilla::Hook;
- my %hook_dispatch;
- Bugzilla::Hook::process('webservice', { dispatch => \%hook_dispatch });
-
- my $dispatch = {
- 'Bugzilla' => 'Bugzilla::WebService::Bugzilla',
- 'Bug' => 'Bugzilla::WebService::Bug',
- 'Classification' => 'Bugzilla::WebService::Classification',
- 'Component' => 'Bugzilla::WebService::Component',
- 'FlagType' => 'Bugzilla::WebService::FlagType',
- 'Group' => 'Bugzilla::WebService::Group',
- 'Product' => 'Bugzilla::WebService::Product',
- 'User' => 'Bugzilla::WebService::User',
- 'BugUserLastVisit' => 'Bugzilla::WebService::BugUserLastVisit',
- %hook_dispatch
- };
- return $dispatch;
-};
+
+ # We "require" here instead of "use" above to avoid a dependency loop.
+ require Bugzilla::Hook;
+ my %hook_dispatch;
+ Bugzilla::Hook::process('webservice', {dispatch => \%hook_dispatch});
+
+ my $dispatch = {
+ 'Bugzilla' => 'Bugzilla::WebService::Bugzilla',
+ 'Bug' => 'Bugzilla::WebService::Bug',
+ 'Classification' => 'Bugzilla::WebService::Classification',
+ 'Component' => 'Bugzilla::WebService::Component',
+ 'FlagType' => 'Bugzilla::WebService::FlagType',
+ 'Group' => 'Bugzilla::WebService::Group',
+ 'Product' => 'Bugzilla::WebService::Product',
+ 'User' => 'Bugzilla::WebService::User',
+ 'BugUserLastVisit' => 'Bugzilla::WebService::BugUserLastVisit',
+ %hook_dispatch
+ };
+ return $dispatch;
+}
1;
diff --git a/Bugzilla/WebService/FlagType.pm b/Bugzilla/WebService/FlagType.pm
index 9d7cce037..9dc240c7f 100644
--- a/Bugzilla/WebService/FlagType.pm
+++ b/Bugzilla/WebService/FlagType.pm
@@ -22,292 +22,308 @@ use Bugzilla::Util qw(trim);
use List::MoreUtils qw(uniq);
use constant PUBLIC_METHODS => qw(
- create
- get
- update
+ create
+ get
+ update
);
sub get {
- my ($self, $params) = @_;
- my $dbh = Bugzilla->switch_to_shadow_db();
- my $user = Bugzilla->user;
-
- defined $params->{product}
- || ThrowCodeError('param_required',
- { function => 'Bug.flag_types',
- param => 'product' });
-
- my $product = delete $params->{product};
- my $component = delete $params->{component};
-
- $product = Bugzilla::Product->check({ name => $product, cache => 1 });
- $component = Bugzilla::Component->check(
- { name => $component, product => $product, cache => 1 }) if $component;
-
- my $flag_params = { product_id => $product->id };
- $flag_params->{component_id} = $component->id if $component;
- my $matched_flag_types = Bugzilla::FlagType::match($flag_params);
-
- my $flag_types = { bug => [], attachment => [] };
- foreach my $flag_type (@$matched_flag_types) {
- push(@{ $flag_types->{bug} }, $self->_flagtype_to_hash($flag_type, $product))
- if $flag_type->target_type eq 'bug';
- push(@{ $flag_types->{attachment} }, $self->_flagtype_to_hash($flag_type, $product))
- if $flag_type->target_type eq 'attachment';
- }
-
- return $flag_types;
+ my ($self, $params) = @_;
+ my $dbh = Bugzilla->switch_to_shadow_db();
+ my $user = Bugzilla->user;
+
+ defined $params->{product}
+ || ThrowCodeError('param_required',
+ {function => 'Bug.flag_types', param => 'product'});
+
+ my $product = delete $params->{product};
+ my $component = delete $params->{component};
+
+ $product = Bugzilla::Product->check({name => $product, cache => 1});
+ $component
+ = Bugzilla::Component->check(
+ {name => $component, product => $product, cache => 1})
+ if $component;
+
+ my $flag_params = {product_id => $product->id};
+ $flag_params->{component_id} = $component->id if $component;
+ my $matched_flag_types = Bugzilla::FlagType::match($flag_params);
+
+ my $flag_types = {bug => [], attachment => []};
+ foreach my $flag_type (@$matched_flag_types) {
+ push(@{$flag_types->{bug}}, $self->_flagtype_to_hash($flag_type, $product))
+ if $flag_type->target_type eq 'bug';
+ push(
+ @{$flag_types->{attachment}},
+ $self->_flagtype_to_hash($flag_type, $product)
+ ) if $flag_type->target_type eq 'attachment';
+ }
+
+ return $flag_types;
}
sub create {
- my ($self, $params) = @_;
- my $user = Bugzilla->login(LOGIN_REQUIRED);
-
- $user->in_group('editcomponents')
- || scalar(@{$user->get_products_by_permission('editcomponents')})
- || ThrowUserError("auth_failure", { group => "editcomponents",
- action => "add",
- object => "flagtypes" });
-
- $params->{name} || ThrowCodeError('param_required', { param => 'name' });
- $params->{description} || ThrowCodeError('param_required', { param => 'description' });
-
- my %args = (
- sortkey => 1,
- name => undef,
- inclusions => ['0:0'], # Default to __ALL__:__ALL__
- cc_list => '',
- description => undef,
- is_requestable => 'on',
- exclusions => [],
- is_multiplicable => 'on',
- request_group => '',
- is_active => 'on',
- is_specifically_requestable => 'on',
- target_type => 'bug',
- grant_group => '',
- );
-
- foreach my $key (keys %args) {
- $args{$key} = $params->{$key} if defined($params->{$key});
- }
-
- $args{name} = trim($params->{name});
- $args{description} = trim($params->{description});
-
- # Is specifically requestable is actually is_requesteeable
- if (exists $args{is_specifically_requestable}) {
- $args{is_requesteeble} = delete $args{is_specifically_requestable};
- }
-
- # Default is on for the tickbox flags.
- # If the user has set them to 'off' then undefine them so the flags are not ticked
- foreach my $arg_name (qw(is_requestable is_multiplicable is_active is_requesteeble)) {
- if (defined($args{$arg_name}) && ($args{$arg_name} eq '0')) {
- $args{$arg_name} = undef;
- }
+ my ($self, $params) = @_;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ $user->in_group('editcomponents')
+ || scalar(@{$user->get_products_by_permission('editcomponents')})
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "add", object => "flagtypes"});
+
+ $params->{name} || ThrowCodeError('param_required', {param => 'name'});
+ $params->{description}
+ || ThrowCodeError('param_required', {param => 'description'});
+
+ my %args = (
+ sortkey => 1,
+ name => undef,
+ inclusions => ['0:0'], # Default to __ALL__:__ALL__
+ cc_list => '',
+ description => undef,
+ is_requestable => 'on',
+ exclusions => [],
+ is_multiplicable => 'on',
+ request_group => '',
+ is_active => 'on',
+ is_specifically_requestable => 'on',
+ target_type => 'bug',
+ grant_group => '',
+ );
+
+ foreach my $key (keys %args) {
+ $args{$key} = $params->{$key} if defined($params->{$key});
+ }
+
+ $args{name} = trim($params->{name});
+ $args{description} = trim($params->{description});
+
+ # Is specifically requestable is actually is_requesteeable
+ if (exists $args{is_specifically_requestable}) {
+ $args{is_requesteeble} = delete $args{is_specifically_requestable};
+ }
+
+# Default is on for the tickbox flags.
+# If the user has set them to 'off' then undefine them so the flags are not ticked
+ foreach
+ my $arg_name (qw(is_requestable is_multiplicable is_active is_requesteeble))
+ {
+ if (defined($args{$arg_name}) && ($args{$arg_name} eq '0')) {
+ $args{$arg_name} = undef;
}
+ }
- # Process group inclusions and exclusions
- $args{inclusions} = _process_lists($params->{inclusions}) if defined $params->{inclusions};
- $args{exclusions} = _process_lists($params->{exclusions}) if defined $params->{exclusions};
+ # Process group inclusions and exclusions
+ $args{inclusions} = _process_lists($params->{inclusions})
+ if defined $params->{inclusions};
+ $args{exclusions} = _process_lists($params->{exclusions})
+ if defined $params->{exclusions};
- my $flagtype = Bugzilla::FlagType->create(\%args);
+ my $flagtype = Bugzilla::FlagType->create(\%args);
- return { id => $self->type('int', $flagtype->id) };
+ return {id => $self->type('int', $flagtype->id)};
}
sub update {
- my ($self, $params) = @_;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->login(LOGIN_REQUIRED);
-
- $user->in_group('editcomponents')
- || scalar(@{$user->get_products_by_permission('editcomponents')})
- || ThrowUserError("auth_failure", { group => "editcomponents",
- action => "edit",
- object => "flagtypes" });
-
- defined($params->{names}) || defined($params->{ids})
- || ThrowCodeError('params_required',
- { function => 'FlagType.update', params => ['ids', 'names'] });
-
- # Get the list of unique flag type ids we are updating
- my @flag_type_ids = defined($params->{ids}) ? @{$params->{ids}} : ();
- if (defined $params->{names}) {
- push @flag_type_ids, map { $_->id }
- @{ Bugzilla::FlagType::match({ name => $params->{names} }) };
+ my ($self, $params) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ $user->in_group('editcomponents')
+ || scalar(@{$user->get_products_by_permission('editcomponents')})
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "edit", object => "flagtypes"});
+
+ defined($params->{names})
+ || defined($params->{ids})
+ || ThrowCodeError('params_required',
+ {function => 'FlagType.update', params => ['ids', 'names']});
+
+ # Get the list of unique flag type ids we are updating
+ my @flag_type_ids = defined($params->{ids}) ? @{$params->{ids}} : ();
+ if (defined $params->{names}) {
+ push @flag_type_ids,
+ map { $_->id } @{Bugzilla::FlagType::match({name => $params->{names}})};
+ }
+ @flag_type_ids = uniq @flag_type_ids;
+
+ # We delete names and ids to keep only new values to set.
+ delete $params->{names};
+ delete $params->{ids};
+
+ # Process group inclusions and exclusions
+ # We removed them from $params because these are handled differently
+ my $inclusions = _process_lists(delete $params->{inclusions})
+ if defined $params->{inclusions};
+ my $exclusions = _process_lists(delete $params->{exclusions})
+ if defined $params->{exclusions};
+
+ $dbh->bz_start_transaction();
+ my %changes = ();
+
+ foreach my $flag_type_id (@flag_type_ids) {
+ my ($flagtype, $can_fully_edit)
+ = $user->check_can_admin_flagtype($flag_type_id);
+
+ if ($can_fully_edit) {
+ $flagtype->set_all($params);
+ }
+ elsif (scalar keys %$params) {
+ ThrowUserError('flag_type_not_editable', {flagtype => $flagtype});
}
- @flag_type_ids = uniq @flag_type_ids;
-
- # We delete names and ids to keep only new values to set.
- delete $params->{names};
- delete $params->{ids};
-
- # Process group inclusions and exclusions
- # We removed them from $params because these are handled differently
- my $inclusions = _process_lists(delete $params->{inclusions}) if defined $params->{inclusions};
- my $exclusions = _process_lists(delete $params->{exclusions}) if defined $params->{exclusions};
-
- $dbh->bz_start_transaction();
- my %changes = ();
- foreach my $flag_type_id (@flag_type_ids) {
- my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_type_id);
+ # Process the clusions
+ foreach my $type ('inclusions', 'exclusions') {
+ my $clusions = $type eq 'inclusions' ? $inclusions : $exclusions;
+ next if not defined $clusions;
- if ($can_fully_edit) {
- $flagtype->set_all($params);
- }
- elsif (scalar keys %$params) {
- ThrowUserError('flag_type_not_editable', { flagtype => $flagtype });
- }
+ my @extra_clusions = ();
+ if (!$user->in_group('editcomponents')) {
+ my $products = $user->get_products_by_permission('editcomponents');
- # Process the clusions
- foreach my $type ('inclusions', 'exclusions') {
- my $clusions = $type eq 'inclusions' ? $inclusions : $exclusions;
- next if not defined $clusions;
-
- my @extra_clusions = ();
- if (!$user->in_group('editcomponents')) {
- my $products = $user->get_products_by_permission('editcomponents');
- # Bring back the products the user cannot edit.
- foreach my $item (values %{$flagtype->$type}) {
- my ($prod_id, $comp_id) = split(':', $item);
- push(@extra_clusions, $item) unless grep { $_->id == $prod_id } @$products;
- }
- }
-
- $flagtype->set_clusions({
- $type => [@$clusions, @extra_clusions],
- });
+ # Bring back the products the user cannot edit.
+ foreach my $item (values %{$flagtype->$type}) {
+ my ($prod_id, $comp_id) = split(':', $item);
+ push(@extra_clusions, $item) unless grep { $_->id == $prod_id } @$products;
}
+ }
- my $returned_changes = $flagtype->update();
- $changes{$flagtype->id} = {
- name => $flagtype->name,
- changes => $returned_changes,
- };
+ $flagtype->set_clusions({$type => [@$clusions, @extra_clusions],});
}
- $dbh->bz_commit_transaction();
-
- my @result;
- foreach my $flag_type_id (keys %changes) {
- my %hash = (
- id => $self->type('int', $flag_type_id),
- name => $self->type('string', $changes{$flag_type_id}{name}),
- changes => {},
- );
-
- foreach my $field (keys %{ $changes{$flag_type_id}{changes} }) {
- my $change = $changes{$flag_type_id}{changes}{$field};
- $hash{changes}{$field} = {
- removed => $self->type('string', $change->[0]),
- added => $self->type('string', $change->[1])
- };
- }
- push(@result, \%hash);
+ my $returned_changes = $flagtype->update();
+ $changes{$flagtype->id}
+ = {name => $flagtype->name, changes => $returned_changes,};
+ }
+ $dbh->bz_commit_transaction();
+
+ my @result;
+ foreach my $flag_type_id (keys %changes) {
+ my %hash = (
+ id => $self->type('int', $flag_type_id),
+ name => $self->type('string', $changes{$flag_type_id}{name}),
+ changes => {},
+ );
+
+ foreach my $field (keys %{$changes{$flag_type_id}{changes}}) {
+ my $change = $changes{$flag_type_id}{changes}{$field};
+ $hash{changes}{$field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
}
- return { flagtypes => \@result };
+ push(@result, \%hash);
+ }
+
+ return {flagtypes => \@result};
}
sub _flagtype_to_hash {
- my ($self, $flagtype, $product) = @_;
- my $user = Bugzilla->user;
-
- my @values = ('X');
- push(@values, '?') if ($flagtype->is_requestable && $user->can_request_flag($flagtype));
- push(@values, '+', '-') if $user->can_set_flag($flagtype);
-
- my $item = {
- id => $self->type('int' , $flagtype->id),
- name => $self->type('string' , $flagtype->name),
- description => $self->type('string' , $flagtype->description),
- type => $self->type('string' , $flagtype->target_type),
- values => \@values,
- is_active => $self->type('boolean', $flagtype->is_active),
- is_requesteeble => $self->type('boolean', $flagtype->is_requesteeble),
- is_multiplicable => $self->type('boolean', $flagtype->is_multiplicable)
- };
-
- if ($product) {
- my $inclusions = $self->_flagtype_clusions_to_hash($flagtype->inclusions, $product->id);
- my $exclusions = $self->_flagtype_clusions_to_hash($flagtype->exclusions, $product->id);
- # if we have both inclusions and exclusions, the exclusions are redundant
- $exclusions = [] if @$inclusions && @$exclusions;
- # no need to return anything if there's just "any component"
- $item->{inclusions} = $inclusions if @$inclusions && $inclusions->[0] ne '';
- $item->{exclusions} = $exclusions if @$exclusions && $exclusions->[0] ne '';
- }
-
- return $item;
+ my ($self, $flagtype, $product) = @_;
+ my $user = Bugzilla->user;
+
+ my @values = ('X');
+ push(@values, '?')
+ if ($flagtype->is_requestable && $user->can_request_flag($flagtype));
+ push(@values, '+', '-') if $user->can_set_flag($flagtype);
+
+ my $item = {
+ id => $self->type('int', $flagtype->id),
+ name => $self->type('string', $flagtype->name),
+ description => $self->type('string', $flagtype->description),
+ type => $self->type('string', $flagtype->target_type),
+ values => \@values,
+ is_active => $self->type('boolean', $flagtype->is_active),
+ is_requesteeble => $self->type('boolean', $flagtype->is_requesteeble),
+ is_multiplicable => $self->type('boolean', $flagtype->is_multiplicable)
+ };
+
+ if ($product) {
+ my $inclusions
+ = $self->_flagtype_clusions_to_hash($flagtype->inclusions, $product->id);
+ my $exclusions
+ = $self->_flagtype_clusions_to_hash($flagtype->exclusions, $product->id);
+
+ # if we have both inclusions and exclusions, the exclusions are redundant
+ $exclusions = [] if @$inclusions && @$exclusions;
+
+ # no need to return anything if there's just "any component"
+ $item->{inclusions} = $inclusions if @$inclusions && $inclusions->[0] ne '';
+ $item->{exclusions} = $exclusions if @$exclusions && $exclusions->[0] ne '';
+ }
+
+ return $item;
}
sub _flagtype_clusions_to_hash {
- my ($self, $clusions, $product_id) = @_;
- my $result = [];
- foreach my $key (keys %$clusions) {
- my ($prod_id, $comp_id) = split(/:/, $clusions->{$key}, 2);
- if ($prod_id == 0 || $prod_id == $product_id) {
- if ($comp_id) {
- my $component = Bugzilla::Component->new({ id => $comp_id, cache => 1 });
- push @$result, $component->name;
- }
- else {
- return [ '' ];
- }
- }
+ my ($self, $clusions, $product_id) = @_;
+ my $result = [];
+ foreach my $key (keys %$clusions) {
+ my ($prod_id, $comp_id) = split(/:/, $clusions->{$key}, 2);
+ if ($prod_id == 0 || $prod_id == $product_id) {
+ if ($comp_id) {
+ my $component = Bugzilla::Component->new({id => $comp_id, cache => 1});
+ push @$result, $component->name;
+ }
+ else {
+ return [''];
+ }
}
- return $result;
+ }
+ return $result;
}
sub _process_lists {
- my $list = shift;
- my $user = Bugzilla->user;
-
- my @products;
- if ($user->in_group('editcomponents')) {
- @products = Bugzilla::Product->get_all;
- }
- else {
- @products = @{$user->get_products_by_permission('editcomponents')};
- }
-
- my @component_list;
-
- foreach my $item (@$list) {
- # A hash with products as the key and component names as the values
- if(ref($item) eq 'HASH') {
- while (my ($product_name, $component_names) = each %$item) {
- my $product = Bugzilla::Product->check({name => $product_name});
- unless (grep { $product->name eq $_->name } @products) {
- ThrowUserError('product_access_denied', { name => $product_name });
- }
- my @component_ids;
-
- foreach my $comp_name (@$component_names) {
- my $component = Bugzilla::Component->check({product => $product, name => $comp_name});
- ThrowCodeError('param_invalid', { param => $comp_name}) unless defined $component;
- push @component_list, $product->id . ':' . $component->id;
- }
- }
+ my $list = shift;
+ my $user = Bugzilla->user;
+
+ my @products;
+ if ($user->in_group('editcomponents')) {
+ @products = Bugzilla::Product->get_all;
+ }
+ else {
+ @products = @{$user->get_products_by_permission('editcomponents')};
+ }
+
+ my @component_list;
+
+ foreach my $item (@$list) {
+
+ # A hash with products as the key and component names as the values
+ if (ref($item) eq 'HASH') {
+ while (my ($product_name, $component_names) = each %$item) {
+ my $product = Bugzilla::Product->check({name => $product_name});
+ unless (grep { $product->name eq $_->name } @products) {
+ ThrowUserError('product_access_denied', {name => $product_name});
}
- elsif(!ref($item)) {
- # These are whole products
- my $product = Bugzilla::Product->check({name => $item});
- unless (grep { $product->name eq $_->name } @products) {
- ThrowUserError('product_access_denied', { name => $item });
- }
- push @component_list, $product->id . ':0';
- }
- else {
- # The user has passed something invalid
- ThrowCodeError('param_invalid', { param => $item });
+ my @component_ids;
+
+ foreach my $comp_name (@$component_names) {
+ my $component
+ = Bugzilla::Component->check({product => $product, name => $comp_name});
+ ThrowCodeError('param_invalid', {param => $comp_name})
+ unless defined $component;
+ push @component_list, $product->id . ':' . $component->id;
}
+ }
+ }
+ elsif (!ref($item)) {
+
+ # These are whole products
+ my $product = Bugzilla::Product->check({name => $item});
+ unless (grep { $product->name eq $_->name } @products) {
+ ThrowUserError('product_access_denied', {name => $item});
+ }
+ push @component_list, $product->id . ':0';
+ }
+ else {
+ # The user has passed something invalid
+ ThrowCodeError('param_invalid', {param => $item});
}
+ }
- return \@component_list;
+ return \@component_list;
}
1;
diff --git a/Bugzilla/WebService/Group.pm b/Bugzilla/WebService/Group.pm
index 468575a35..c92583d0b 100644
--- a/Bugzilla/WebService/Group.pm
+++ b/Bugzilla/WebService/Group.pm
@@ -17,207 +17,210 @@ use Bugzilla::Error;
use Bugzilla::WebService::Util qw(validate translate params_to_objects);
use constant PUBLIC_METHODS => qw(
- create
- get
- update
+ create
+ get
+ update
);
-use constant MAPPED_RETURNS => {
- userregexp => 'user_regexp',
- isactive => 'is_active'
-};
+use constant MAPPED_RETURNS =>
+ {userregexp => 'user_regexp', isactive => 'is_active'};
sub create {
- my ($self, $params) = @_;
-
- Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->user->in_group('creategroups')
- || ThrowUserError("auth_failure", { group => "creategroups",
- action => "add",
- object => "group"});
- # Create group
- my $group = Bugzilla::Group->create({
- name => $params->{name},
- description => $params->{description},
- userregexp => $params->{user_regexp},
- isactive => $params->{is_active},
- isbuggroup => 1,
- icon_url => $params->{icon_url}
- });
- return { id => $self->type('int', $group->id) };
+ my ($self, $params) = @_;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->user->in_group('creategroups')
+ || ThrowUserError("auth_failure",
+ {group => "creategroups", action => "add", object => "group"});
+
+ # Create group
+ my $group = Bugzilla::Group->create({
+ name => $params->{name},
+ description => $params->{description},
+ userregexp => $params->{user_regexp},
+ isactive => $params->{is_active},
+ isbuggroup => 1,
+ icon_url => $params->{icon_url}
+ });
+ return {id => $self->type('int', $group->id)};
}
sub update {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
+
+ my $dbh = Bugzilla->dbh;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->user->in_group('creategroups')
+ || ThrowUserError("auth_failure",
+ {group => "creategroups", action => "edit", object => "group"});
+
+ defined($params->{names})
+ || defined($params->{ids})
+ || ThrowCodeError('params_required',
+ {function => 'Group.update', params => ['ids', 'names']});
+
+ my $group_objects = params_to_objects($params, 'Bugzilla::Group');
+
+ my %values = %$params;
+
+ # We delete names and ids to keep only new values to set.
+ delete $values{names};
+ delete $values{ids};
+
+ $dbh->bz_start_transaction();
+ foreach my $group (@$group_objects) {
+ $group->set_all(\%values);
+ }
+
+ my %changes;
+ foreach my $group (@$group_objects) {
+ my $returned_changes = $group->update();
+ $changes{$group->id} = translate($returned_changes, MAPPED_RETURNS);
+ }
+ $dbh->bz_commit_transaction();
+
+ my @result;
+ foreach my $group (@$group_objects) {
+ my %hash = (id => $group->id, changes => {},);
+ foreach my $field (keys %{$changes{$group->id}}) {
+ my $change = $changes{$group->id}->{$field};
+ $hash{changes}{$field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
+ }
+ push(@result, \%hash);
+ }
- my $dbh = Bugzilla->dbh;
+ return {groups => \@result};
+}
- Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->user->in_group('creategroups')
- || ThrowUserError("auth_failure", { group => "creategroups",
- action => "edit",
- object => "group" });
+sub get {
+ my ($self, $params) = validate(@_, 'ids', 'names', 'type');
- defined($params->{names}) || defined($params->{ids})
- || ThrowCodeError('params_required',
- { function => 'Group.update', params => ['ids', 'names'] });
+ Bugzilla->login(LOGIN_REQUIRED);
- my $group_objects = params_to_objects($params, 'Bugzilla::Group');
+ # Reject access if there is no sense in continuing.
+ my $user = Bugzilla->user;
+ my $all_groups
+ = $user->in_group('editusers') || $user->in_group('creategroups');
+ if (!$all_groups && !$user->can_bless) {
+ ThrowUserError('group_cannot_view');
+ }
- my %values = %$params;
-
- # We delete names and ids to keep only new values to set.
- delete $values{names};
- delete $values{ids};
+ Bugzilla->switch_to_shadow_db();
- $dbh->bz_start_transaction();
- foreach my $group (@$group_objects) {
- $group->set_all(\%values);
- }
+ my $groups = [];
- my %changes;
- foreach my $group (@$group_objects) {
- my $returned_changes = $group->update();
- $changes{$group->id} = translate($returned_changes, MAPPED_RETURNS);
- }
- $dbh->bz_commit_transaction();
-
- my @result;
- foreach my $group (@$group_objects) {
- my %hash = (
- id => $group->id,
- changes => {},
- );
- foreach my $field (keys %{ $changes{$group->id} }) {
- my $change = $changes{$group->id}->{$field};
- $hash{changes}{$field} = {
- removed => $self->type('string', $change->[0]),
- added => $self->type('string', $change->[1])
- };
- }
- push(@result, \%hash);
- }
+ if (defined $params->{ids}) {
- return { groups => \@result };
-}
-
-sub get {
- my ($self, $params) = validate(@_, 'ids', 'names', 'type');
+ # Get the groups by id
+ $groups = Bugzilla::Group->new_from_list($params->{ids});
+ }
- Bugzilla->login(LOGIN_REQUIRED);
-
- # Reject access if there is no sense in continuing.
- my $user = Bugzilla->user;
- my $all_groups = $user->in_group('editusers') || $user->in_group('creategroups');
- if (!$all_groups && !$user->can_bless) {
- ThrowUserError('group_cannot_view');
- }
+ if (defined $params->{names}) {
- Bugzilla->switch_to_shadow_db();
+ # Get the groups by name. Check will throw an error if a bad name is given
+ foreach my $name (@{$params->{names}}) {
- my $groups = [];
+ # Skip if we got this from params->{id}
+ next if grep { $_->name eq $name } @$groups;
- if (defined $params->{ids}) {
- # Get the groups by id
- $groups = Bugzilla::Group->new_from_list($params->{ids});
+ push @$groups, Bugzilla::Group->check({name => $name});
}
+ }
- if (defined $params->{names}) {
- # Get the groups by name. Check will throw an error if a bad name is given
- foreach my $name (@{$params->{names}}) {
- # Skip if we got this from params->{id}
- next if grep { $_->name eq $name } @$groups;
-
- push @$groups, Bugzilla::Group->check({ name => $name });
- }
+ if (!defined $params->{ids} && !defined $params->{names}) {
+ if ($all_groups) {
+ @$groups = Bugzilla::Group->get_all;
}
-
- if (!defined $params->{ids} && !defined $params->{names}) {
- if ($all_groups) {
- @$groups = Bugzilla::Group->get_all;
- }
- else {
- # Get only groups the user has bless groups too
- $groups = $user->bless_groups;
- }
+ else {
+ # Get only groups the user has bless groups too
+ $groups = $user->bless_groups;
}
+ }
- # Now create a result entry for each.
- my @groups = map { $self->_group_to_hash($params, $_) } @$groups;
- return { groups => \@groups };
+ # Now create a result entry for each.
+ my @groups = map { $self->_group_to_hash($params, $_) } @$groups;
+ return {groups => \@groups};
}
sub _group_to_hash {
- my ($self, $params, $group) = @_;
- my $user = Bugzilla->user;
-
- my $field_data = {
- id => $self->type('int', $group->id),
- name => $self->type('string', $group->name),
- description => $self->type('string', $group->description),
- };
-
- if ($user->in_group('creategroups')) {
- $field_data->{is_active} = $self->type('boolean', $group->is_active);
- $field_data->{is_bug_group} = $self->type('boolean', $group->is_bug_group);
- $field_data->{user_regexp} = $self->type('string', $group->user_regexp);
- }
-
- if ($params->{membership}) {
- $field_data->{membership} = $self->_get_group_membership($group, $params);
- }
- return $field_data;
+ my ($self, $params, $group) = @_;
+ my $user = Bugzilla->user;
+
+ my $field_data = {
+ id => $self->type('int', $group->id),
+ name => $self->type('string', $group->name),
+ description => $self->type('string', $group->description),
+ };
+
+ if ($user->in_group('creategroups')) {
+ $field_data->{is_active} = $self->type('boolean', $group->is_active);
+ $field_data->{is_bug_group} = $self->type('boolean', $group->is_bug_group);
+ $field_data->{user_regexp} = $self->type('string', $group->user_regexp);
+ }
+
+ if ($params->{membership}) {
+ $field_data->{membership} = $self->_get_group_membership($group, $params);
+ }
+ return $field_data;
}
sub _get_group_membership {
- my ($self, $group, $params) = @_;
- my $user = Bugzilla->user;
+ my ($self, $group, $params) = @_;
+ my $user = Bugzilla->user;
- my %users_only;
- my $dbh = Bugzilla->dbh;
- my $editusers = $user->in_group('editusers');
+ my %users_only;
+ my $dbh = Bugzilla->dbh;
+ my $editusers = $user->in_group('editusers');
- my $query = 'SELECT userid FROM profiles';
- my $visibleGroups;
+ my $query = 'SELECT userid FROM profiles';
+ my $visibleGroups;
- if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) {
- # Show only users in visible groups.
- $visibleGroups = $user->visible_groups_inherited;
+ if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) {
- if (scalar @$visibleGroups) {
- $query .= qq{, user_group_map AS ugm
+ # Show only users in visible groups.
+ $visibleGroups = $user->visible_groups_inherited;
+
+ if (scalar @$visibleGroups) {
+ $query .= qq{, user_group_map AS ugm
WHERE ugm.user_id = profiles.userid
AND ugm.isbless = 0
AND } . $dbh->sql_in('ugm.group_id', $visibleGroups);
- }
- } elsif ($editusers || $user->can_bless($group->id) || $user->in_group('creategroups')) {
- $visibleGroups = 1;
- $query .= qq{, user_group_map AS ugm
+ }
+ }
+ elsif ($editusers
+ || $user->can_bless($group->id)
+ || $user->in_group('creategroups'))
+ {
+ $visibleGroups = 1;
+ $query .= qq{, user_group_map AS ugm
WHERE ugm.user_id = profiles.userid
AND ugm.isbless = 0
};
- }
- if (!$visibleGroups) {
- ThrowUserError('group_not_visible', { group => $group });
- }
-
- my $grouplist = Bugzilla::Group->flatten_group_membership($group->id);
- $query .= ' AND ' . $dbh->sql_in('ugm.group_id', $grouplist);
-
- my $userids = $dbh->selectcol_arrayref($query);
- my $user_objects = Bugzilla::User->new_from_list($userids);
- my @users =
- map {{
- id => $self->type('int', $_->id),
- real_name => $self->type('string', $_->name),
- name => $self->type('string', $_->login),
- email => $self->type('string', $_->email),
- can_login => $self->type('boolean', $_->is_enabled),
- email_enabled => $self->type('boolean', $_->email_enabled),
- login_denied_text => $self->type('string', $_->disabledtext),
- }} @$user_objects;
-
- return \@users;
+ }
+ if (!$visibleGroups) {
+ ThrowUserError('group_not_visible', {group => $group});
+ }
+
+ my $grouplist = Bugzilla::Group->flatten_group_membership($group->id);
+ $query .= ' AND ' . $dbh->sql_in('ugm.group_id', $grouplist);
+
+ my $userids = $dbh->selectcol_arrayref($query);
+ my $user_objects = Bugzilla::User->new_from_list($userids);
+ my @users = map { {
+ id => $self->type('int', $_->id),
+ real_name => $self->type('string', $_->name),
+ name => $self->type('string', $_->login),
+ email => $self->type('string', $_->email),
+ can_login => $self->type('boolean', $_->is_enabled),
+ email_enabled => $self->type('boolean', $_->email_enabled),
+ login_denied_text => $self->type('string', $_->disabledtext),
+ } } @$user_objects;
+
+ return \@users;
}
1;
diff --git a/Bugzilla/WebService/Product.pm b/Bugzilla/WebService/Product.pm
index 7d9e7f181..e0f357e61 100644
--- a/Bugzilla/WebService/Product.pm
+++ b/Bugzilla/WebService/Product.pm
@@ -17,40 +17,37 @@ use Bugzilla::User;
use Bugzilla::Error;
use Bugzilla::Constants;
use Bugzilla::WebService::Constants;
-use Bugzilla::WebService::Util qw(validate filter filter_wants translate params_to_objects);
+use Bugzilla::WebService::Util
+ qw(validate filter filter_wants translate params_to_objects);
use constant READ_ONLY => qw(
- get
- get_accessible_products
- get_enterable_products
- get_selectable_products
+ get
+ get_accessible_products
+ get_enterable_products
+ get_selectable_products
);
use constant PUBLIC_METHODS => qw(
- create
- get
- get_accessible_products
- get_enterable_products
- get_products
- get_selectable_products
- update
+ create
+ get
+ get_accessible_products
+ get_enterable_products
+ get_products
+ get_selectable_products
+ update
);
-use constant MAPPED_FIELDS => {
- has_unconfirmed => 'allows_unconfirmed',
- is_open => 'is_active',
-};
+use constant MAPPED_FIELDS =>
+ {has_unconfirmed => 'allows_unconfirmed', is_open => 'is_active',};
use constant MAPPED_RETURNS => {
- allows_unconfirmed => 'has_unconfirmed',
- defaultmilestone => 'default_milestone',
- isactive => 'is_open',
+ allows_unconfirmed => 'has_unconfirmed',
+ defaultmilestone => 'default_milestone',
+ isactive => 'is_open',
};
-use constant FIELD_MAP => {
- has_unconfirmed => 'allows_unconfirmed',
- is_open => 'isactive',
-};
+use constant FIELD_MAP =>
+ {has_unconfirmed => 'allows_unconfirmed', is_open => 'isactive',};
##################################################
# Add aliases here for method name compatibility #
@@ -58,300 +55,277 @@ use constant FIELD_MAP => {
# Get the ids of the products the user can search
sub get_selectable_products {
- Bugzilla->switch_to_shadow_db();
- return {ids => [map {$_->id} @{Bugzilla->user->get_selectable_products}]};
+ Bugzilla->switch_to_shadow_db();
+ 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 {
- Bugzilla->switch_to_shadow_db();
- return {ids => [map {$_->id} @{Bugzilla->user->get_enterable_products}]};
+ Bugzilla->switch_to_shadow_db();
+ 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 {
- Bugzilla->switch_to_shadow_db();
- return {ids => [map {$_->id} @{Bugzilla->user->get_accessible_products}]};
+ Bugzilla->switch_to_shadow_db();
+ return {ids => [map { $_->id } @{Bugzilla->user->get_accessible_products}]};
}
# Get a list of actual products, based on list of ids or names
sub get {
- my ($self, $params) = validate(@_, 'ids', 'names', 'type');
- my $user = Bugzilla->user;
-
- defined $params->{ids} || defined $params->{names} || defined $params->{type}
- || ThrowCodeError("params_required", { function => "Product.get",
- params => ['ids', 'names', 'type'] });
- Bugzilla->switch_to_shadow_db();
-
- my $products = [];
- if (defined $params->{type}) {
- my %product_hash;
- foreach my $type (@{ $params->{type} }) {
- my $result = [];
- if ($type eq 'accessible') {
- $result = $user->get_accessible_products();
- }
- elsif ($type eq 'enterable') {
- $result = $user->get_enterable_products();
- }
- elsif ($type eq 'selectable') {
- $result = $user->get_selectable_products();
- }
- else {
- ThrowUserError('get_products_invalid_type',
- { type => $type });
- }
- map { $product_hash{$_->id} = $_ } @$result;
- }
- $products = [ values %product_hash ];
- }
- else {
- $products = $user->get_accessible_products;
+ my ($self, $params) = validate(@_, 'ids', 'names', 'type');
+ my $user = Bugzilla->user;
+
+ defined $params->{ids}
+ || defined $params->{names}
+ || defined $params->{type}
+ || ThrowCodeError("params_required",
+ {function => "Product.get", params => ['ids', 'names', 'type']});
+ Bugzilla->switch_to_shadow_db();
+
+ my $products = [];
+ if (defined $params->{type}) {
+ my %product_hash;
+ foreach my $type (@{$params->{type}}) {
+ my $result = [];
+ if ($type eq 'accessible') {
+ $result = $user->get_accessible_products();
+ }
+ elsif ($type eq 'enterable') {
+ $result = $user->get_enterable_products();
+ }
+ elsif ($type eq 'selectable') {
+ $result = $user->get_selectable_products();
+ }
+ else {
+ ThrowUserError('get_products_invalid_type', {type => $type});
+ }
+ map { $product_hash{$_->id} = $_ } @$result;
}
+ $products = [values %product_hash];
+ }
+ else {
+ $products = $user->get_accessible_products;
+ }
- my @requested_products;
+ my @requested_products;
- if (defined $params->{ids}) {
- # Create a hash with the ids the user wants
- my %ids = map { $_ => 1 } @{$params->{ids}};
+ if (defined $params->{ids}) {
- # Return the intersection of this, by grepping the ids from $products.
- push(@requested_products,
- grep { $ids{$_->id} } @$products);
- }
+ # Create a hash with the ids the user wants
+ my %ids = map { $_ => 1 } @{$params->{ids}};
- if (defined $params->{names}) {
- # Create a hash with the names the user wants
- my %names = map { lc($_) => 1 } @{$params->{names}};
-
- # Return the intersection of this, by grepping the names
- # from $products, union'ed with products found by ID to
- # avoid duplicates
- foreach my $product (grep { $names{lc $_->name} }
- @$products) {
- next if grep { $_->id == $product->id }
- @requested_products;
- push @requested_products, $product;
- }
- }
+ # Return the intersection of this, by grepping the ids from $products.
+ push(@requested_products, grep { $ids{$_->id} } @$products);
+ }
+
+ if (defined $params->{names}) {
- # If we just requested a specific type of products without
- # specifying ids or names, then return the entire list.
- if (!defined $params->{ids} && !defined $params->{names}) {
- @requested_products = @$products;
+ # Create a hash with the names the user wants
+ my %names = map { lc($_) => 1 } @{$params->{names}};
+
+ # Return the intersection of this, by grepping the names
+ # from $products, union'ed with products found by ID to
+ # avoid duplicates
+ foreach my $product (grep { $names{lc $_->name} } @$products) {
+ next if grep { $_->id == $product->id } @requested_products;
+ push @requested_products, $product;
}
+ }
+
+ # If we just requested a specific type of products without
+ # specifying ids or names, then return the entire list.
+ if (!defined $params->{ids} && !defined $params->{names}) {
+ @requested_products = @$products;
+ }
- # Now create a result entry for each.
- my @products = map { $self->_product_to_hash($params, $_) }
- @requested_products;
- return { products => \@products };
+ # Now create a result entry for each.
+ my @products = map { $self->_product_to_hash($params, $_) } @requested_products;
+ return {products => \@products};
}
sub create {
- my ($self, $params) = @_;
-
- Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->user->in_group('editcomponents')
- || ThrowUserError("auth_failure", { group => "editcomponents",
- action => "add",
- object => "products"});
- # Create product
- my $args = {
- name => $params->{name},
- description => $params->{description},
- version => $params->{version},
- defaultmilestone => $params->{default_milestone},
- # create_series has no default value.
- create_series => defined $params->{create_series} ?
- $params->{create_series} : 1
- };
- foreach my $field (qw(has_unconfirmed is_open classification)) {
- if (defined $params->{$field}) {
- my $name = FIELD_MAP->{$field} || $field;
- $args->{$name} = $params->{$field};
- }
+ my ($self, $params) = @_;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->user->in_group('editcomponents')
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "add", object => "products"});
+
+ # Create product
+ my $args = {
+ name => $params->{name},
+ description => $params->{description},
+ version => $params->{version},
+ defaultmilestone => $params->{default_milestone},
+
+ # create_series has no default value.
+ create_series => defined $params->{create_series}
+ ? $params->{create_series}
+ : 1
+ };
+ foreach my $field (qw(has_unconfirmed is_open classification)) {
+ if (defined $params->{$field}) {
+ my $name = FIELD_MAP->{$field} || $field;
+ $args->{$name} = $params->{$field};
}
- my $product = Bugzilla::Product->create($args);
- return { id => $self->type('int', $product->id) };
+ }
+ my $product = Bugzilla::Product->create($args);
+ return {id => $self->type('int', $product->id)};
}
sub update {
- my ($self, $params) = @_;
-
- my $dbh = Bugzilla->dbh;
-
- Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->user->in_group('editcomponents')
- || ThrowUserError("auth_failure", { group => "editcomponents",
- action => "edit",
- object => "products" });
-
- defined($params->{names}) || defined($params->{ids})
- || ThrowCodeError('params_required',
- { function => 'Product.update', params => ['ids', 'names'] });
-
- my $product_objects = params_to_objects($params, 'Bugzilla::Product');
-
- my $values = translate($params, MAPPED_FIELDS);
-
- # We delete names and ids to keep only new values to set.
- delete $values->{names};
- delete $values->{ids};
-
- $dbh->bz_start_transaction();
- foreach my $product (@$product_objects) {
- $product->set_all($values);
+ my ($self, $params) = @_;
+
+ my $dbh = Bugzilla->dbh;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->user->in_group('editcomponents')
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "edit", object => "products"});
+
+ defined($params->{names})
+ || defined($params->{ids})
+ || ThrowCodeError('params_required',
+ {function => 'Product.update', params => ['ids', 'names']});
+
+ my $product_objects = params_to_objects($params, 'Bugzilla::Product');
+
+ my $values = translate($params, MAPPED_FIELDS);
+
+ # We delete names and ids to keep only new values to set.
+ delete $values->{names};
+ delete $values->{ids};
+
+ $dbh->bz_start_transaction();
+ foreach my $product (@$product_objects) {
+ $product->set_all($values);
+ }
+
+ my %changes;
+ foreach my $product (@$product_objects) {
+ my $returned_changes = $product->update();
+ $changes{$product->id} = translate($returned_changes, MAPPED_RETURNS);
+ }
+ $dbh->bz_commit_transaction();
+
+ my @result;
+ foreach my $product (@$product_objects) {
+ my %hash = (id => $product->id, changes => {},);
+
+ foreach my $field (keys %{$changes{$product->id}}) {
+ my $change = $changes{$product->id}->{$field};
+ $hash{changes}{$field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
}
- my %changes;
- foreach my $product (@$product_objects) {
- my $returned_changes = $product->update();
- $changes{$product->id} = translate($returned_changes, MAPPED_RETURNS);
- }
- $dbh->bz_commit_transaction();
-
- my @result;
- foreach my $product (@$product_objects) {
- my %hash = (
- id => $product->id,
- changes => {},
- );
-
- foreach my $field (keys %{ $changes{$product->id} }) {
- my $change = $changes{$product->id}->{$field};
- $hash{changes}{$field} = {
- removed => $self->type('string', $change->[0]),
- added => $self->type('string', $change->[1])
- };
- }
-
- push(@result, \%hash);
- }
+ push(@result, \%hash);
+ }
- return { products => \@result };
+ return {products => \@result};
}
sub _product_to_hash {
- my ($self, $params, $product) = @_;
-
- my $field_data = {
- id => $self->type('int', $product->id),
- name => $self->type('string', $product->name),
- description => $self->type('string', $product->description),
- is_active => $self->type('boolean', $product->is_active),
- default_milestone => $self->type('string', $product->default_milestone),
- has_unconfirmed => $self->type('boolean', $product->allows_unconfirmed),
- classification => $self->type('string', $product->classification->name),
- };
- if (filter_wants($params, 'components')) {
- $field_data->{components} = [map {
- $self->_component_to_hash($_, $params)
- } @{$product->components}];
- }
- if (filter_wants($params, 'versions')) {
- $field_data->{versions} = [map {
- $self->_version_to_hash($_, $params)
- } @{$product->versions}];
- }
- if (filter_wants($params, 'milestones')) {
- $field_data->{milestones} = [map {
- $self->_milestone_to_hash($_, $params)
- } @{$product->milestones}];
- }
- return filter($params, $field_data);
+ my ($self, $params, $product) = @_;
+
+ my $field_data = {
+ id => $self->type('int', $product->id),
+ name => $self->type('string', $product->name),
+ description => $self->type('string', $product->description),
+ is_active => $self->type('boolean', $product->is_active),
+ default_milestone => $self->type('string', $product->default_milestone),
+ has_unconfirmed => $self->type('boolean', $product->allows_unconfirmed),
+ classification => $self->type('string', $product->classification->name),
+ };
+ if (filter_wants($params, 'components')) {
+ $field_data->{components}
+ = [map { $self->_component_to_hash($_, $params) } @{$product->components}];
+ }
+ if (filter_wants($params, 'versions')) {
+ $field_data->{versions}
+ = [map { $self->_version_to_hash($_, $params) } @{$product->versions}];
+ }
+ if (filter_wants($params, 'milestones')) {
+ $field_data->{milestones}
+ = [map { $self->_milestone_to_hash($_, $params) } @{$product->milestones}];
+ }
+ return filter($params, $field_data);
}
sub _component_to_hash {
- my ($self, $component, $params) = @_;
- my $field_data = filter $params, {
- id =>
- $self->type('int', $component->id),
- name =>
- $self->type('string', $component->name),
- description =>
- $self->type('string' , $component->description),
- default_assigned_to =>
- $self->type('email', $component->default_assignee->login),
- default_qa_contact =>
- $self->type('email', $component->default_qa_contact ?
- $component->default_qa_contact->login : ""),
- sort_key => # sort_key is returned to match Bug.fields
- 0,
- is_active =>
- $self->type('boolean', $component->is_active),
- }, undef, 'components';
-
- if (filter_wants($params, 'flag_types', undef, 'components')) {
- $field_data->{flag_types} = {
- bug =>
- [map {
- $self->_flag_type_to_hash($_)
- } @{$component->flag_types->{'bug'}}],
- attachment =>
- [map {
- $self->_flag_type_to_hash($_)
- } @{$component->flag_types->{'attachment'}}],
- };
- }
+ my ($self, $component, $params) = @_;
+ my $field_data = filter $params, {
+ id => $self->type('int', $component->id),
+ name => $self->type('string', $component->name),
+ description => $self->type('string', $component->description),
+ default_assigned_to =>
+ $self->type('email', $component->default_assignee->login),
+ default_qa_contact => $self->type(
+ 'email',
+ $component->default_qa_contact ? $component->default_qa_contact->login : ""
+ ),
+ sort_key => # sort_key is returned to match Bug.fields
+ 0,
+ is_active => $self->type('boolean', $component->is_active),
+ },
+ undef, 'components';
+
+ if (filter_wants($params, 'flag_types', undef, 'components')) {
+ $field_data->{flag_types} = {
+ bug =>
+ [map { $self->_flag_type_to_hash($_) } @{$component->flag_types->{'bug'}}],
+ attachment => [
+ map { $self->_flag_type_to_hash($_) } @{$component->flag_types->{'attachment'}}
+ ],
+ };
+ }
- return $field_data;
+ return $field_data;
}
sub _flag_type_to_hash {
- my ($self, $flag_type, $params) = @_;
- return filter $params, {
- id =>
- $self->type('int', $flag_type->id),
- name =>
- $self->type('string', $flag_type->name),
- description =>
- $self->type('string', $flag_type->description),
- cc_list =>
- $self->type('string', $flag_type->cc_list),
- sort_key =>
- $self->type('int', $flag_type->sortkey),
- is_active =>
- $self->type('boolean', $flag_type->is_active),
- is_requestable =>
- $self->type('boolean', $flag_type->is_requestable),
- is_requesteeble =>
- $self->type('boolean', $flag_type->is_requesteeble),
- is_multiplicable =>
- $self->type('boolean', $flag_type->is_multiplicable),
- grant_group =>
- $self->type('int', $flag_type->grant_group_id),
- request_group =>
- $self->type('int', $flag_type->request_group_id),
- }, undef, 'flag_types';
+ my ($self, $flag_type, $params) = @_;
+ return filter $params,
+ {
+ id => $self->type('int', $flag_type->id),
+ name => $self->type('string', $flag_type->name),
+ description => $self->type('string', $flag_type->description),
+ cc_list => $self->type('string', $flag_type->cc_list),
+ sort_key => $self->type('int', $flag_type->sortkey),
+ is_active => $self->type('boolean', $flag_type->is_active),
+ is_requestable => $self->type('boolean', $flag_type->is_requestable),
+ is_requesteeble => $self->type('boolean', $flag_type->is_requesteeble),
+ is_multiplicable => $self->type('boolean', $flag_type->is_multiplicable),
+ grant_group => $self->type('int', $flag_type->grant_group_id),
+ request_group => $self->type('int', $flag_type->request_group_id),
+ },
+ undef, 'flag_types';
}
sub _version_to_hash {
- my ($self, $version, $params) = @_;
- return filter $params, {
- id =>
- $self->type('int', $version->id),
- name =>
- $self->type('string', $version->name),
- sort_key => # sort_key is returened to match Bug.fields
- 0,
- is_active =>
- $self->type('boolean', $version->is_active),
- }, undef, 'versions';
+ my ($self, $version, $params) = @_;
+ return filter $params, {
+ id => $self->type('int', $version->id),
+ name => $self->type('string', $version->name),
+ sort_key => # sort_key is returened to match Bug.fields
+ 0,
+ is_active => $self->type('boolean', $version->is_active),
+ },
+ undef, 'versions';
}
sub _milestone_to_hash {
- my ($self, $milestone, $params) = @_;
- return filter $params, {
- id =>
- $self->type('int', $milestone->id),
- name =>
- $self->type('string', $milestone->name),
- sort_key =>
- $self->type('int', $milestone->sortkey),
- is_active =>
- $self->type('boolean', $milestone->is_active),
- }, undef, 'milestones';
+ my ($self, $milestone, $params) = @_;
+ return filter $params,
+ {
+ id => $self->type('int', $milestone->id),
+ name => $self->type('string', $milestone->name),
+ sort_key => $self->type('int', $milestone->sortkey),
+ is_active => $self->type('boolean', $milestone->is_active),
+ },
+ undef, 'milestones';
}
1;
diff --git a/Bugzilla/WebService/Server.pm b/Bugzilla/WebService/Server.pm
index 7950c7a3b..f2844cdcb 100644
--- a/Bugzilla/WebService/Server.pm
+++ b/Bugzilla/WebService/Server.pm
@@ -20,72 +20,77 @@ use Digest::MD5 qw(md5_base64);
use Storable qw(freeze);
sub handle_login {
- my ($self, $class, $method, $full_method) = @_;
- # Throw error if the supplied class does not exist or the method is private
- ThrowCodeError('unknown_method', {method => $full_method}) if (!$class or $method =~ /^_/);
-
- eval "require $class";
- ThrowCodeError('unknown_method', {method => $full_method}) if $@;
- return if ($class->login_exempt($method)
- and !defined Bugzilla->input_params->{Bugzilla_login});
- Bugzilla->login();
-
- Bugzilla::Hook::process(
- 'webservice_before_call',
- { 'method' => $method, full_method => $full_method });
+ my ($self, $class, $method, $full_method) = @_;
+
+ # Throw error if the supplied class does not exist or the method is private
+ ThrowCodeError('unknown_method', {method => $full_method})
+ if (!$class or $method =~ /^_/);
+
+ eval "require $class";
+ ThrowCodeError('unknown_method', {method => $full_method}) if $@;
+ return
+ if ($class->login_exempt($method)
+ and !defined Bugzilla->input_params->{Bugzilla_login});
+ Bugzilla->login();
+
+ Bugzilla::Hook::process('webservice_before_call',
+ {'method' => $method, full_method => $full_method});
}
sub datetime_format_inbound {
- my ($self, $time) = @_;
-
- my $converted = datetime_from($time, Bugzilla->local_timezone);
- if (!defined $converted) {
- ThrowUserError('illegal_date', { date => $time });
- }
- $time = $converted->ymd() . ' ' . $converted->hms();
- return $time
+ my ($self, $time) = @_;
+
+ my $converted = datetime_from($time, Bugzilla->local_timezone);
+ if (!defined $converted) {
+ ThrowUserError('illegal_date', {date => $time});
+ }
+ $time = $converted->ymd() . ' ' . $converted->hms();
+ return $time;
}
sub datetime_format_outbound {
- my ($self, $date) = @_;
-
- return undef if (!defined $date or $date eq '');
-
- my $time = $date;
- if (blessed($date)) {
- # We expect this to mean we were sent a datetime object
- $time->set_time_zone('UTC');
- } else {
- # We always send our time in UTC, for consistency.
- # passed in value is likely a string, create a datetime object
- $time = datetime_from($date, 'UTC');
- }
- return $time->iso8601();
+ my ($self, $date) = @_;
+
+ return undef if (!defined $date or $date eq '');
+
+ my $time = $date;
+ if (blessed($date)) {
+
+ # We expect this to mean we were sent a datetime object
+ $time->set_time_zone('UTC');
+ }
+ else {
+ # We always send our time in UTC, for consistency.
+ # passed in value is likely a string, create a datetime object
+ $time = datetime_from($date, 'UTC');
+ }
+ return $time->iso8601();
}
# ETag support
sub bz_etag {
- my ($self, $data) = @_;
- my $cache = Bugzilla->request_cache;
- if (defined $data) {
- # Serialize the data if passed a reference
- local $Storable::canonical = 1;
- $data = freeze($data) if ref $data;
-
- # Wide characters cause md5_base64() to die.
- utf8::encode($data) if utf8::is_utf8($data);
-
- # Append content_type to the end of the data
- # string as we want the etag to be unique to
- # the content_type. We do not need this for
- # XMLRPC as text/xml is always returned.
- if (blessed($self) && $self->can('content_type')) {
- $data .= $self->content_type if $self->content_type;
- }
-
- $cache->{'bz_etag'} = md5_base64($data);
+ my ($self, $data) = @_;
+ my $cache = Bugzilla->request_cache;
+ if (defined $data) {
+
+ # Serialize the data if passed a reference
+ local $Storable::canonical = 1;
+ $data = freeze($data) if ref $data;
+
+ # Wide characters cause md5_base64() to die.
+ utf8::encode($data) if utf8::is_utf8($data);
+
+ # Append content_type to the end of the data
+ # string as we want the etag to be unique to
+ # the content_type. We do not need this for
+ # XMLRPC as text/xml is always returned.
+ if (blessed($self) && $self->can('content_type')) {
+ $data .= $self->content_type if $self->content_type;
}
- return $cache->{'bz_etag'};
+
+ $cache->{'bz_etag'} = md5_base64($data);
+ }
+ return $cache->{'bz_etag'};
}
1;
diff --git a/Bugzilla/WebService/Server/JSONRPC.pm b/Bugzilla/WebService/Server/JSONRPC.pm
index 70b8fd96c..7847029ba 100644
--- a/Bugzilla/WebService/Server/JSONRPC.pm
+++ b/Bugzilla/WebService/Server/JSONRPC.pm
@@ -12,16 +12,17 @@ use strict;
use warnings;
use Bugzilla::WebService::Server;
-BEGIN {
- our @ISA = qw(Bugzilla::WebService::Server);
- if (eval { require JSON::RPC::Server::CGI }) {
- unshift(@ISA, 'JSON::RPC::Server::CGI');
- }
- else {
- require JSON::RPC::Legacy::Server::CGI;
- unshift(@ISA, 'JSON::RPC::Legacy::Server::CGI');
- }
+BEGIN {
+ our @ISA = qw(Bugzilla::WebService::Server);
+
+ if (eval { require JSON::RPC::Server::CGI }) {
+ unshift(@ISA, 'JSON::RPC::Server::CGI');
+ }
+ else {
+ require JSON::RPC::Legacy::Server::CGI;
+ unshift(@ISA, 'JSON::RPC::Legacy::Server::CGI');
+ }
}
use Bugzilla::Error;
@@ -38,79 +39,83 @@ use List::MoreUtils qw(none);
#####################################
sub new {
- my $class = shift;
- my $self = $class->SUPER::new(@_);
- Bugzilla->_json_server($self);
- $self->dispatch(WS_DISPATCH);
- $self->return_die_message(1);
- return $self;
+ my $class = shift;
+ my $self = $class->SUPER::new(@_);
+ Bugzilla->_json_server($self);
+ $self->dispatch(WS_DISPATCH);
+ $self->return_die_message(1);
+ return $self;
}
sub create_json_coder {
- my $self = shift;
- my $json = $self->SUPER::create_json_coder(@_);
- $json->allow_blessed(1);
- $json->convert_blessed(1);
- # This may seem a little backwards, but what this really means is
- # "don't convert our utf8 into byte strings, just leave it as a
- # utf8 string."
- $json->utf8(0) if Bugzilla->params->{'utf8'};
- return $json;
+ my $self = shift;
+ my $json = $self->SUPER::create_json_coder(@_);
+ $json->allow_blessed(1);
+ $json->convert_blessed(1);
+
+ # This may seem a little backwards, but what this really means is
+ # "don't convert our utf8 into byte strings, just leave it as a
+ # utf8 string."
+ $json->utf8(0) if Bugzilla->params->{'utf8'};
+ return $json;
}
# Override the JSON::RPC method to return our CGI object instead of theirs.
sub cgi { return Bugzilla->cgi; }
sub response_header {
- my $self = shift;
- # The HTTP body needs to be bytes (not a utf8 string) for recent
- # versions of HTTP::Message, but JSON::RPC::Server doesn't handle this
- # properly. $_[1] is the HTTP body content we're going to be sending.
- if (utf8::is_utf8($_[1])) {
- utf8::encode($_[1]);
- # Since we're going to just be sending raw bytes, we need to
- # set STDOUT to not expect utf8.
- disable_utf8();
- }
- return $self->SUPER::response_header(@_);
+ my $self = shift;
+
+ # The HTTP body needs to be bytes (not a utf8 string) for recent
+ # versions of HTTP::Message, but JSON::RPC::Server doesn't handle this
+ # properly. $_[1] is the HTTP body content we're going to be sending.
+ if (utf8::is_utf8($_[1])) {
+ utf8::encode($_[1]);
+
+ # Since we're going to just be sending raw bytes, we need to
+ # set STDOUT to not expect utf8.
+ disable_utf8();
+ }
+ return $self->SUPER::response_header(@_);
}
sub response {
- my ($self, $response) = @_;
- my $cgi = $self->cgi;
-
- # Implement JSONP.
- if (my $callback = $self->_bz_callback) {
- my $content = $response->content;
- # Prepend the JSONP response with /**/ in order to protect
- # against possible encoding attacks (e.g., affecting Flash).
- $response->content("/**/$callback($content)");
- }
-
- # Use $cgi->header properly instead of just printing text directly.
- # This fixes various problems, including sending Bugzilla's cookies
- # properly.
- my $headers = $response->headers;
- my @header_args;
- foreach my $name ($headers->header_field_names) {
- my @values = $headers->header($name);
- $name =~ s/-/_/g;
- foreach my $value (@values) {
- push(@header_args, "-$name", $value);
- }
- }
-
- # ETag support
- my $etag = $self->bz_etag;
- if ($etag && $cgi->check_etag($etag)) {
- push(@header_args, "-ETag", $etag);
- print $cgi->header(-status => '304 Not Modified', @header_args);
- }
- else {
- push(@header_args, "-ETag", $etag) if $etag;
- print $cgi->header(-status => $response->code, @header_args);
- print $response->content;
- }
+ my ($self, $response) = @_;
+ my $cgi = $self->cgi;
+
+ # Implement JSONP.
+ if (my $callback = $self->_bz_callback) {
+ my $content = $response->content;
+
+ # Prepend the JSONP response with /**/ in order to protect
+ # against possible encoding attacks (e.g., affecting Flash).
+ $response->content("/**/$callback($content)");
+ }
+
+ # Use $cgi->header properly instead of just printing text directly.
+ # This fixes various problems, including sending Bugzilla's cookies
+ # properly.
+ my $headers = $response->headers;
+ my @header_args;
+ foreach my $name ($headers->header_field_names) {
+ my @values = $headers->header($name);
+ $name =~ s/-/_/g;
+ foreach my $value (@values) {
+ push(@header_args, "-$name", $value);
+ }
+ }
+
+ # ETag support
+ my $etag = $self->bz_etag;
+ if ($etag && $cgi->check_etag($etag)) {
+ push(@header_args, "-ETag", $etag);
+ print $cgi->header(-status => '304 Not Modified', @header_args);
+ }
+ else {
+ push(@header_args, "-ETag", $etag) if $etag;
+ print $cgi->header(-status => $response->code, @header_args);
+ print $response->content;
+ }
}
# The JSON-RPC 1.1 GET specification is not so great--you can't specify
@@ -122,70 +127,69 @@ sub response {
# Base64 encoded, because that is ridiculous and obnoxious for JavaScript
# clients.
sub retrieve_json_from_get {
- my $self = shift;
- my $cgi = $self->cgi;
-
- my %input;
-
- # Both version and id must be set before any errors are thrown.
- if ($cgi->param('version')) {
- $self->version(scalar $cgi->param('version'));
- $input{version} = $cgi->param('version');
- }
- else {
- $self->version('1.0');
- }
-
- # The JSON-RPC 2.0 spec says that any request that omits an id doesn't
- # want a response. However, in an HTTP GET situation, it's stupid to
- # expect all clients to specify some id parameter just to get a response,
- # so we don't require it.
- my $id;
- if (defined $cgi->param('id')) {
- $id = $cgi->param('id');
- }
- # However, JSON::RPC does require that an id exist in most cases, in
- # order to throw proper errors. We use the installation's urlbase as
- # the id, in this case.
- else {
- $id = correct_urlbase();
- }
- # Setting _bz_request_id here is required in case we throw errors early,
- # before _handle.
- $self->{_bz_request_id} = $input{id} = $id;
-
- # _bz_callback can throw an error, so we have to set it here, after we're
- # ready to throw errors.
- $self->_bz_callback(scalar $cgi->param('callback'));
-
- if (!$cgi->param('method')) {
- ThrowUserError('json_rpc_get_method_required');
- }
- $input{method} = $cgi->param('method');
-
- my $params;
- if (defined $cgi->param('params')) {
- local $@;
- $params = eval {
- $self->json->decode(scalar $cgi->param('params'))
- };
- if ($@) {
- ThrowUserError('json_rpc_invalid_params',
- { params => scalar $cgi->param('params'),
- err_msg => $@ });
- }
- }
- elsif (!$self->version or $self->version ne '1.1') {
- $params = [];
- }
- else {
- $params = {};
- }
-
- $input{params} = $params;
-
- my $json = $self->json->encode(\%input);
- return $json;
+ my $self = shift;
+ my $cgi = $self->cgi;
+
+ my %input;
+
+ # Both version and id must be set before any errors are thrown.
+ if ($cgi->param('version')) {
+ $self->version(scalar $cgi->param('version'));
+ $input{version} = $cgi->param('version');
+ }
+ else {
+ $self->version('1.0');
+ }
+
+ # The JSON-RPC 2.0 spec says that any request that omits an id doesn't
+ # want a response. However, in an HTTP GET situation, it's stupid to
+ # expect all clients to specify some id parameter just to get a response,
+ # so we don't require it.
+ my $id;
+ if (defined $cgi->param('id')) {
+ $id = $cgi->param('id');
+ }
+
+ # However, JSON::RPC does require that an id exist in most cases, in
+ # order to throw proper errors. We use the installation's urlbase as
+ # the id, in this case.
+ else {
+ $id = correct_urlbase();
+ }
+
+ # Setting _bz_request_id here is required in case we throw errors early,
+ # before _handle.
+ $self->{_bz_request_id} = $input{id} = $id;
+
+ # _bz_callback can throw an error, so we have to set it here, after we're
+ # ready to throw errors.
+ $self->_bz_callback(scalar $cgi->param('callback'));
+
+ if (!$cgi->param('method')) {
+ ThrowUserError('json_rpc_get_method_required');
+ }
+ $input{method} = $cgi->param('method');
+
+ my $params;
+ if (defined $cgi->param('params')) {
+ local $@;
+ $params = eval { $self->json->decode(scalar $cgi->param('params')) };
+ if ($@) {
+ ThrowUserError('json_rpc_invalid_params',
+ {params => scalar $cgi->param('params'), err_msg => $@});
+ }
+ }
+ elsif (!$self->version or $self->version ne '1.1') {
+ $params = [];
+ }
+ else {
+ $params = {};
+ }
+
+ $input{params} = $params;
+
+ my $json = $self->json->encode(\%input);
+ return $json;
}
#######################################
@@ -193,72 +197,76 @@ sub retrieve_json_from_get {
#######################################
sub type {
- my ($self, $type, $value) = @_;
-
- # This is the only type that does something special with undef.
- if ($type eq 'boolean') {
- return $value ? JSON::true : JSON::false;
- }
-
- return JSON::null if !defined $value;
-
- my $retval = $value;
-
- if ($type eq 'int') {
- $retval = int($value);
- }
- if ($type eq 'double') {
- $retval = 0.0 + $value;
- }
- elsif ($type eq 'string') {
- # Forces string context, so that JSON will make it a string.
- $retval = "$value";
- }
- elsif ($type eq 'dateTime') {
- # ISO-8601 "YYYYMMDDTHH:MM:SS" with a literal T
- $retval = $self->datetime_format_outbound($value);
- }
- elsif ($type eq 'base64') {
- utf8::encode($value) if utf8::is_utf8($value);
- $retval = encode_base64($value, '');
- }
- elsif ($type eq 'email' && Bugzilla->params->{'webservice_email_filter'}) {
- $retval = email_filter($value);
- }
-
- return $retval;
+ my ($self, $type, $value) = @_;
+
+ # This is the only type that does something special with undef.
+ if ($type eq 'boolean') {
+ return $value ? JSON::true : JSON::false;
+ }
+
+ return JSON::null if !defined $value;
+
+ my $retval = $value;
+
+ if ($type eq 'int') {
+ $retval = int($value);
+ }
+ if ($type eq 'double') {
+ $retval = 0.0 + $value;
+ }
+ elsif ($type eq 'string') {
+
+ # Forces string context, so that JSON will make it a string.
+ $retval = "$value";
+ }
+ elsif ($type eq 'dateTime') {
+
+ # ISO-8601 "YYYYMMDDTHH:MM:SS" with a literal T
+ $retval = $self->datetime_format_outbound($value);
+ }
+ elsif ($type eq 'base64') {
+ utf8::encode($value) if utf8::is_utf8($value);
+ $retval = encode_base64($value, '');
+ }
+ elsif ($type eq 'email' && Bugzilla->params->{'webservice_email_filter'}) {
+ $retval = email_filter($value);
+ }
+
+ return $retval;
}
sub datetime_format_outbound {
- my $self = shift;
- # YUI expects ISO8601 in UTC time; including TZ specifier
- return $self->SUPER::datetime_format_outbound(@_) . 'Z';
+ my $self = shift;
+
+ # YUI expects ISO8601 in UTC time; including TZ specifier
+ return $self->SUPER::datetime_format_outbound(@_) . 'Z';
}
sub handle_login {
- my $self = shift;
-
- # If we're being called using GET, we don't allow cookie-based or Env
- # login, because GET requests can be done cross-domain, and we don't
- # want private data showing up on another site unless the user
- # explicitly gives that site their username and password. (This is
- # particularly important for JSONP, which would allow a remote site
- # to use private data without the user's knowledge, unless we had this
- # protection in place.)
- if ($self->request->method ne 'POST') {
- # XXX There's no particularly good way for us to get a parameter
- # to Bugzilla->login at this point, so we pass this information
- # around using request_cache, which is a bit of a hack. The
- # implementation of it is in Bugzilla::Auth::Login::Stack.
- Bugzilla->request_cache->{auth_no_automatic_login} = 1;
- }
-
- my $path = $self->path_info;
- my $class = $self->{dispatch_path}->{$path};
- my $full_method = $self->_bz_method_name;
- $full_method =~ /^\S+\.(\S+)/;
- my $method = $1;
- $self->SUPER::handle_login($class, $method, $full_method);
+ my $self = shift;
+
+ # If we're being called using GET, we don't allow cookie-based or Env
+ # login, because GET requests can be done cross-domain, and we don't
+ # want private data showing up on another site unless the user
+ # explicitly gives that site their username and password. (This is
+ # particularly important for JSONP, which would allow a remote site
+ # to use private data without the user's knowledge, unless we had this
+ # protection in place.)
+ if ($self->request->method ne 'POST') {
+
+ # XXX There's no particularly good way for us to get a parameter
+ # to Bugzilla->login at this point, so we pass this information
+ # around using request_cache, which is a bit of a hack. The
+ # implementation of it is in Bugzilla::Auth::Login::Stack.
+ Bugzilla->request_cache->{auth_no_automatic_login} = 1;
+ }
+
+ my $path = $self->path_info;
+ my $class = $self->{dispatch_path}->{$path};
+ my $full_method = $self->_bz_method_name;
+ $full_method =~ /^\S+\.(\S+)/;
+ my $method = $1;
+ $self->SUPER::handle_login($class, $method, $full_method);
}
######################################
@@ -267,165 +275,165 @@ sub handle_login {
# Store the ID of the current call, because Bugzilla::Error will need it.
sub _handle {
- my $self = shift;
- my ($obj) = @_;
- $self->{_bz_request_id} = $obj->{id};
+ my $self = shift;
+ my ($obj) = @_;
+ $self->{_bz_request_id} = $obj->{id};
- my $result = $self->SUPER::_handle(@_);
+ my $result = $self->SUPER::_handle(@_);
- # Set the ETag if not already set in the webservice methods.
- my $etag = $self->bz_etag;
- if (!$etag && ref $result) {
- my $data = $self->json->decode($result)->{'result'};
- $self->bz_etag($data);
- }
+ # Set the ETag if not already set in the webservice methods.
+ my $etag = $self->bz_etag;
+ if (!$etag && ref $result) {
+ my $data = $self->json->decode($result)->{'result'};
+ $self->bz_etag($data);
+ }
- return $result;
+ return $result;
}
# Make all error messages returned by JSON::RPC go into the 100000
# range, and bring down all our errors into the normal range.
sub _error {
- my ($self, $id, $code) = (shift, shift, shift);
- # All JSON::RPC errors are less than 1000.
- if ($code < 1000) {
- $code += 100000;
- }
- # Bugzilla::Error adds 100,000 to all *our* errors, so
- # we know they came from us.
- elsif ($code > 100000) {
- $code -= 100000;
- }
-
- # We can't just set $_[1] because it's not always settable,
- # in JSON::RPC::Server.
- unshift(@_, $id, $code);
- my $json = $self->SUPER::_error(@_);
-
- # We want to always send the JSON-RPC 1.1 error format, although
- # If we're not in JSON-RPC 1.1, we don't need the silly "name" parameter.
- if (!$self->version or $self->version ne '1.1') {
- my $object = $self->json->decode($json);
- my $message = $object->{error};
- # Just assure that future versions of JSON::RPC don't change the
- # JSON-RPC 1.0 error format.
- if (!ref $message) {
- $object->{error} = {
- code => $code,
- message => $message,
- };
- $json = $self->json->encode($object);
- }
- }
- return $json;
+ my ($self, $id, $code) = (shift, shift, shift);
+
+ # All JSON::RPC errors are less than 1000.
+ if ($code < 1000) {
+ $code += 100000;
+ }
+
+ # Bugzilla::Error adds 100,000 to all *our* errors, so
+ # we know they came from us.
+ elsif ($code > 100000) {
+ $code -= 100000;
+ }
+
+ # We can't just set $_[1] because it's not always settable,
+ # in JSON::RPC::Server.
+ unshift(@_, $id, $code);
+ my $json = $self->SUPER::_error(@_);
+
+ # We want to always send the JSON-RPC 1.1 error format, although
+ # If we're not in JSON-RPC 1.1, we don't need the silly "name" parameter.
+ if (!$self->version or $self->version ne '1.1') {
+ my $object = $self->json->decode($json);
+ my $message = $object->{error};
+
+ # Just assure that future versions of JSON::RPC don't change the
+ # JSON-RPC 1.0 error format.
+ if (!ref $message) {
+ $object->{error} = {code => $code, message => $message,};
+ $json = $self->json->encode($object);
+ }
+ }
+ return $json;
}
# This handles dispatching our calls to the appropriate class based on
# the name of the method.
sub _find_procedure {
- my $self = shift;
+ my $self = shift;
- my $method = shift;
- $self->{_bz_method_name} = $method;
+ my $method = shift;
+ $self->{_bz_method_name} = $method;
- # This tricks SUPER::_find_procedure into finding the right class.
- $method =~ /^(\S+)\.(\S+)$/;
- $self->path_info($1);
- unshift(@_, $2);
+ # This tricks SUPER::_find_procedure into finding the right class.
+ $method =~ /^(\S+)\.(\S+)$/;
+ $self->path_info($1);
+ unshift(@_, $2);
- return $self->SUPER::_find_procedure(@_);
+ return $self->SUPER::_find_procedure(@_);
}
# This is a hacky way to do something right before methods are called.
# This is the last thing that JSON::RPC::Server::_handle calls right before
# the method is actually called.
sub _argument_type_check {
- my $self = shift;
- my $params = $self->SUPER::_argument_type_check(@_);
-
- # JSON-RPC 1.0 requires all parameters to be passed as an array, so
- # we just pull out the first item and assume it's an object.
- my $params_is_array;
- if (ref $params eq 'ARRAY') {
- $params = $params->[0];
- $params_is_array = 1;
- }
-
- taint_data($params);
-
- # Now, convert dateTime fields on input.
- $self->_bz_method_name =~ /^(\S+)\.(\S+)$/;
- my ($class, $method) = ($1, $2);
- my $pkg = $self->{dispatch_path}->{$class};
- my @date_fields = @{ $pkg->DATE_FIELDS->{$method} || [] };
- foreach my $field (@date_fields) {
- if (defined $params->{$field}) {
- my $value = $params->{$field};
- if (ref $value eq 'ARRAY') {
- $params->{$field} =
- [ map { $self->datetime_format_inbound($_) } @$value ];
- }
- else {
- $params->{$field} = $self->datetime_format_inbound($value);
- }
- }
- }
- my @base64_fields = @{ $pkg->BASE64_FIELDS->{$method} || [] };
- foreach my $field (@base64_fields) {
- if (defined $params->{$field}) {
- $params->{$field} = decode_base64($params->{$field});
- }
- }
-
- # Update the params to allow for several convenience key/values
- # use for authentication
- fix_credentials($params);
-
- Bugzilla->input_params($params);
-
- if ($self->request->method eq 'POST') {
- # CSRF is possible via XMLHttpRequest when the Content-Type header
- # is not application/json (for example: text/plain or
- # application/x-www-form-urlencoded).
- # application/json is the single official MIME type, per RFC 4627.
- my $content_type = $self->cgi->content_type;
- # The charset can be appended to the content type, so we use a regexp.
- if ($content_type !~ m{^application/json(-rpc)?(;.*)?$}i) {
- ThrowUserError('json_rpc_illegal_content_type',
- { content_type => $content_type });
- }
- }
- else {
- # When being called using GET, we don't allow calling
- # methods that can change data. This protects us against cross-site
- # request forgeries.
- if (!grep($_ eq $method, $pkg->READ_ONLY)) {
- ThrowUserError('json_rpc_post_only',
- { method => $self->_bz_method_name });
- }
- }
-
- # Only allowed methods to be used from our whitelist
- if (none { $_ eq $method} $pkg->PUBLIC_METHODS) {
- ThrowCodeError('unknown_method', { method => $self->_bz_method_name });
- }
-
- # This is the best time to do login checks.
- $self->handle_login();
-
- # Bugzilla::WebService packages call internal methods like
- # $self->_some_private_method. So we have to inherit from
- # that class as well as this Server class.
- my $new_class = ref($self) . '::' . $pkg;
- my $isa_string = 'our @ISA = qw(' . ref($self) . " $pkg)";
- eval "package $new_class;$isa_string;";
- bless $self, $new_class;
-
- if ($params_is_array) {
- $params = [$params];
- }
-
- return $params;
+ my $self = shift;
+ my $params = $self->SUPER::_argument_type_check(@_);
+
+ # JSON-RPC 1.0 requires all parameters to be passed as an array, so
+ # we just pull out the first item and assume it's an object.
+ my $params_is_array;
+ if (ref $params eq 'ARRAY') {
+ $params = $params->[0];
+ $params_is_array = 1;
+ }
+
+ taint_data($params);
+
+ # Now, convert dateTime fields on input.
+ $self->_bz_method_name =~ /^(\S+)\.(\S+)$/;
+ my ($class, $method) = ($1, $2);
+ my $pkg = $self->{dispatch_path}->{$class};
+ my @date_fields = @{$pkg->DATE_FIELDS->{$method} || []};
+ foreach my $field (@date_fields) {
+ if (defined $params->{$field}) {
+ my $value = $params->{$field};
+ if (ref $value eq 'ARRAY') {
+ $params->{$field} = [map { $self->datetime_format_inbound($_) } @$value];
+ }
+ else {
+ $params->{$field} = $self->datetime_format_inbound($value);
+ }
+ }
+ }
+ my @base64_fields = @{$pkg->BASE64_FIELDS->{$method} || []};
+ foreach my $field (@base64_fields) {
+ if (defined $params->{$field}) {
+ $params->{$field} = decode_base64($params->{$field});
+ }
+ }
+
+ # Update the params to allow for several convenience key/values
+ # use for authentication
+ fix_credentials($params);
+
+ Bugzilla->input_params($params);
+
+ if ($self->request->method eq 'POST') {
+
+ # CSRF is possible via XMLHttpRequest when the Content-Type header
+ # is not application/json (for example: text/plain or
+ # application/x-www-form-urlencoded).
+ # application/json is the single official MIME type, per RFC 4627.
+ my $content_type = $self->cgi->content_type;
+
+ # The charset can be appended to the content type, so we use a regexp.
+ if ($content_type !~ m{^application/json(-rpc)?(;.*)?$}i) {
+ ThrowUserError('json_rpc_illegal_content_type',
+ {content_type => $content_type});
+ }
+ }
+ else {
+ # When being called using GET, we don't allow calling
+ # methods that can change data. This protects us against cross-site
+ # request forgeries.
+ if (!grep($_ eq $method, $pkg->READ_ONLY)) {
+ ThrowUserError('json_rpc_post_only', {method => $self->_bz_method_name});
+ }
+ }
+
+ # Only allowed methods to be used from our whitelist
+ if (none { $_ eq $method } $pkg->PUBLIC_METHODS) {
+ ThrowCodeError('unknown_method', {method => $self->_bz_method_name});
+ }
+
+ # This is the best time to do login checks.
+ $self->handle_login();
+
+ # Bugzilla::WebService packages call internal methods like
+ # $self->_some_private_method. So we have to inherit from
+ # that class as well as this Server class.
+ my $new_class = ref($self) . '::' . $pkg;
+ my $isa_string = 'our @ISA = qw(' . ref($self) . " $pkg)";
+ eval "package $new_class;$isa_string;";
+ bless $self, $new_class;
+
+ if ($params_is_array) {
+ $params = [$params];
+ }
+
+ return $params;
}
##########################
@@ -434,22 +442,24 @@ sub _argument_type_check {
# _bz_method_name is stored by _find_procedure for later use.
sub _bz_method_name {
- return $_[0]->{_bz_method_name};
+ return $_[0]->{_bz_method_name};
}
sub _bz_callback {
- my ($self, $value) = @_;
- if (defined $value) {
- $value = trim($value);
- # We don't use \w because we don't want to allow Unicode here.
- if ($value !~ /^[A-Za-z0-9_\.\[\]]+$/) {
- ThrowUserError('json_rpc_invalid_callback', { callback => $value });
- }
- $self->{_bz_callback} = $value;
- # JSONP needs to be parsed by a JS parser, not by a JSON parser.
- $self->content_type('text/javascript');
+ my ($self, $value) = @_;
+ if (defined $value) {
+ $value = trim($value);
+
+ # We don't use \w because we don't want to allow Unicode here.
+ if ($value !~ /^[A-Za-z0-9_\.\[\]]+$/) {
+ ThrowUserError('json_rpc_invalid_callback', {callback => $value});
}
- return $self->{_bz_callback};
+ $self->{_bz_callback} = $value;
+
+ # JSONP needs to be parsed by a JS parser, not by a JSON parser.
+ $self->content_type('text/javascript');
+ }
+ return $self->{_bz_callback};
}
1;
diff --git a/Bugzilla/WebService/Server/REST.pm b/Bugzilla/WebService/Server/REST.pm
index 8450a7a28..143b938b6 100644
--- a/Bugzilla/WebService/Server/REST.pm
+++ b/Bugzilla/WebService/Server/REST.pm
@@ -40,134 +40,134 @@ use MIME::Base64 qw(decode_base64);
###########################
sub handle {
- my ($self) = @_;
-
- # Determine how the data should be represented. We do this early so
- # errors will also be returned with the proper content type.
- # If no accept header was sent or the content types specified were not
- # matched, we default to the first type in the whitelist.
- $self->content_type($self->_best_content_type(REST_CONTENT_TYPE_WHITELIST()));
-
- # Using current path information, decide which class/method to
- # use to serve the request. Throw error if no resource was found
- # unless we were looking for OPTIONS
- if (!$self->_find_resource($self->cgi->path_info)) {
- if ($self->request->method eq 'OPTIONS'
- && $self->bz_rest_options)
- {
- my $response = $self->response_header(STATUS_OK, "");
- my $options_string = join(', ', @{ $self->bz_rest_options });
- $response->header('Allow' => $options_string,
- 'Access-Control-Allow-Methods' => $options_string);
- return $self->response($response);
- }
-
- ThrowUserError("rest_invalid_resource",
- { path => $self->cgi->path_info,
- method => $self->request->method });
+ my ($self) = @_;
+
+ # Determine how the data should be represented. We do this early so
+ # errors will also be returned with the proper content type.
+ # If no accept header was sent or the content types specified were not
+ # matched, we default to the first type in the whitelist.
+ $self->content_type($self->_best_content_type(REST_CONTENT_TYPE_WHITELIST()));
+
+ # Using current path information, decide which class/method to
+ # use to serve the request. Throw error if no resource was found
+ # unless we were looking for OPTIONS
+ if (!$self->_find_resource($self->cgi->path_info)) {
+ if ($self->request->method eq 'OPTIONS' && $self->bz_rest_options) {
+ my $response = $self->response_header(STATUS_OK, "");
+ my $options_string = join(', ', @{$self->bz_rest_options});
+ $response->header(
+ 'Allow' => $options_string,
+ 'Access-Control-Allow-Methods' => $options_string
+ );
+ return $self->response($response);
}
- # Dispatch to the proper module
- my $class = $self->bz_class_name;
- my ($path) = $class =~ /::([^:]+)$/;
- $self->path_info($path);
- delete $self->{dispatch_path};
- $self->dispatch({ $path => $class });
+ ThrowUserError("rest_invalid_resource",
+ {path => $self->cgi->path_info, method => $self->request->method});
+ }
- my $params = $self->_retrieve_json_params;
+ # Dispatch to the proper module
+ my $class = $self->bz_class_name;
+ my ($path) = $class =~ /::([^:]+)$/;
+ $self->path_info($path);
+ delete $self->{dispatch_path};
+ $self->dispatch({$path => $class});
- fix_credentials($params);
+ my $params = $self->_retrieve_json_params;
- # Fix includes/excludes for each call
- rest_include_exclude($params);
+ fix_credentials($params);
- # Set callback name if exists
- $self->_bz_callback($params->{'callback'}) if $params->{'callback'};
+ # Fix includes/excludes for each call
+ rest_include_exclude($params);
- Bugzilla->input_params($params);
+ # Set callback name if exists
+ $self->_bz_callback($params->{'callback'}) if $params->{'callback'};
- # Set the JSON version to 1.1 and the id to the current urlbase
- # also set up the correct handler method
- my $obj = {
- version => '1.1',
- id => correct_urlbase(),
- method => $self->bz_method_name,
- params => $params
- };
+ Bugzilla->input_params($params);
- # Execute the handler
- my $result = $self->_handle($obj);
+ # Set the JSON version to 1.1 and the id to the current urlbase
+ # also set up the correct handler method
+ my $obj = {
+ version => '1.1',
+ id => correct_urlbase(),
+ method => $self->bz_method_name,
+ params => $params
+ };
- if (!$self->error_response_header) {
- return $self->response(
- $self->response_header($self->bz_success_code || STATUS_OK, $result));
- }
+ # Execute the handler
+ my $result = $self->_handle($obj);
+
+ if (!$self->error_response_header) {
+ return $self->response(
+ $self->response_header($self->bz_success_code || STATUS_OK, $result));
+ }
- $self->response($self->error_response_header);
+ $self->response($self->error_response_header);
}
sub response {
- my ($self, $response) = @_;
-
- # If we have thrown an error, the 'error' key will exist
- # otherwise we use 'result'. JSONRPC returns other data
- # along with the result/error such as version and id which
- # we will strip off for REST calls.
- my $content = $response->content;
- my $json_data = {};
- if ($content) {
- $json_data = $self->json->decode($content);
- }
-
- my $result = {};
- if (exists $json_data->{error}) {
- $result = $json_data->{error};
- $result->{error} = $self->type('boolean', 1);
- $result->{documentation} = REST_DOC;
- delete $result->{'name'}; # Remove JSONRPCError
- }
- elsif (exists $json_data->{result}) {
- $result = $json_data->{result};
- }
-
- # The result needs to be a valid JSON data structure
- # and not a undefined or scalar value.
- if (!ref $result
- || blessed($result)
- || (ref $result ne 'HASH' && ref $result ne 'ARRAY'))
- {
- $result = { result => $result };
- }
-
- Bugzilla::Hook::process('webservice_rest_response',
- { rpc => $self, result => \$result, response => $response });
-
- # Access Control
- $response->header("Access-Control-Allow-Origin", "*");
- $response->header("Access-Control-Allow-Headers", "origin, content-type, accept, x-requested-with");
-
- # ETag support
- my $etag = $self->bz_etag;
- $self->bz_etag($result) if !$etag;
-
- # If accessing through web browser, then display in readable format
- if ($self->content_type eq 'text/html') {
- $result = $self->json->pretty->canonical->allow_nonref->encode($result);
-
- my $template = Bugzilla->template;
- $content = "";
- $template->process("rest.html.tmpl", { result => $result }, \$content)
- || ThrowTemplateError($template->error());
-
- $response->content_type('text/html');
- }
- else {
- $content = $self->json->encode($result);
- }
-
- $response->content($content);
-
- $self->SUPER::response($response);
+ my ($self, $response) = @_;
+
+ # If we have thrown an error, the 'error' key will exist
+ # otherwise we use 'result'. JSONRPC returns other data
+ # along with the result/error such as version and id which
+ # we will strip off for REST calls.
+ my $content = $response->content;
+ my $json_data = {};
+ if ($content) {
+ $json_data = $self->json->decode($content);
+ }
+
+ my $result = {};
+ if (exists $json_data->{error}) {
+ $result = $json_data->{error};
+ $result->{error} = $self->type('boolean', 1);
+ $result->{documentation} = REST_DOC;
+ delete $result->{'name'}; # Remove JSONRPCError
+ }
+ elsif (exists $json_data->{result}) {
+ $result = $json_data->{result};
+ }
+
+ # The result needs to be a valid JSON data structure
+ # and not a undefined or scalar value.
+ if ( !ref $result
+ || blessed($result)
+ || (ref $result ne 'HASH' && ref $result ne 'ARRAY'))
+ {
+ $result = {result => $result};
+ }
+
+ Bugzilla::Hook::process('webservice_rest_response',
+ {rpc => $self, result => \$result, response => $response});
+
+ # Access Control
+ $response->header("Access-Control-Allow-Origin", "*");
+ $response->header("Access-Control-Allow-Headers",
+ "origin, content-type, accept, x-requested-with");
+
+ # ETag support
+ my $etag = $self->bz_etag;
+ $self->bz_etag($result) if !$etag;
+
+ # If accessing through web browser, then display in readable format
+ if ($self->content_type eq 'text/html') {
+ $result = $self->json->pretty->canonical->allow_nonref->encode($result);
+
+ my $template = Bugzilla->template;
+ $content = "";
+ $template->process("rest.html.tmpl", {result => $result}, \$content)
+ || ThrowTemplateError($template->error());
+
+ $response->content_type('text/html');
+ }
+ else {
+ $content = $self->json->encode($result);
+ }
+
+ $response->content($content);
+
+ $self->SUPER::response($response);
}
#######################################
@@ -175,36 +175,40 @@ sub response {
#######################################
sub handle_login {
- my $self = shift;
-
- # If we're being called using GET, we don't allow cookie-based or Env
- # login, because GET requests can be done cross-domain, and we don't
- # want private data showing up on another site unless the user
- # explicitly gives that site their username and password. (This is
- # particularly important for JSONP, which would allow a remote site
- # to use private data without the user's knowledge, unless we had this
- # protection in place.) We do allow this for GET /login as we need to
- # for Bugzilla::Auth::Persist::Cookie to create a login cookie that we
- # can also use for Bugzilla_token support. This is OK as it requires
- # a login and password to be supplied and will fail if they are not
- # valid for the user.
- if (!grep($_ eq $self->request->method, ('POST', 'PUT'))
- && !($self->bz_class_name eq 'Bugzilla::WebService::User'
- && $self->bz_method_name eq 'login'))
- {
- # XXX There's no particularly good way for us to get a parameter
- # to Bugzilla->login at this point, so we pass this information
- # around using request_cache, which is a bit of a hack. The
- # implementation of it is in Bugzilla::Auth::Login::Stack.
- Bugzilla->request_cache->{'auth_no_automatic_login'} = 1;
- }
-
- my $class = $self->bz_class_name;
- my $method = $self->bz_method_name;
- my $full_method = $class . "." . $method;
-
- # Bypass JSONRPC::handle_login
- Bugzilla::WebService::Server->handle_login($class, $method, $full_method);
+ my $self = shift;
+
+ # If we're being called using GET, we don't allow cookie-based or Env
+ # login, because GET requests can be done cross-domain, and we don't
+ # want private data showing up on another site unless the user
+ # explicitly gives that site their username and password. (This is
+ # particularly important for JSONP, which would allow a remote site
+ # to use private data without the user's knowledge, unless we had this
+ # protection in place.) We do allow this for GET /login as we need to
+ # for Bugzilla::Auth::Persist::Cookie to create a login cookie that we
+ # can also use for Bugzilla_token support. This is OK as it requires
+ # a login and password to be supplied and will fail if they are not
+ # valid for the user.
+ if (
+ !grep($_ eq $self->request->method, ('POST', 'PUT'))
+ && !(
+ $self->bz_class_name eq 'Bugzilla::WebService::User'
+ && $self->bz_method_name eq 'login'
+ )
+ )
+ {
+ # XXX There's no particularly good way for us to get a parameter
+ # to Bugzilla->login at this point, so we pass this information
+ # around using request_cache, which is a bit of a hack. The
+ # implementation of it is in Bugzilla::Auth::Login::Stack.
+ Bugzilla->request_cache->{'auth_no_automatic_login'} = 1;
+ }
+
+ my $class = $self->bz_class_name;
+ my $method = $self->bz_method_name;
+ my $full_method = $class . "." . $method;
+
+ # Bypass JSONRPC::handle_login
+ Bugzilla::WebService::Server->handle_login($class, $method, $full_method);
}
############################
@@ -214,79 +218,78 @@ sub handle_login {
# We do not want to run Bugzilla::WebService::Server::JSONRPC->_find_prodedure
# as it determines the method name differently.
sub _find_procedure {
- my $self = shift;
- if ($self->isa('JSON::RPC::Server::CGI')) {
- return JSON::RPC::Server::_find_procedure($self, @_);
- }
- else {
- return JSON::RPC::Legacy::Server::_find_procedure($self, @_);
- }
+ my $self = shift;
+ if ($self->isa('JSON::RPC::Server::CGI')) {
+ return JSON::RPC::Server::_find_procedure($self, @_);
+ }
+ else {
+ return JSON::RPC::Legacy::Server::_find_procedure($self, @_);
+ }
}
sub _argument_type_check {
- my $self = shift;
- my $params;
-
- if ($self->isa('JSON::RPC::Server::CGI')) {
- $params = JSON::RPC::Server::_argument_type_check($self, @_);
+ my $self = shift;
+ my $params;
+
+ if ($self->isa('JSON::RPC::Server::CGI')) {
+ $params = JSON::RPC::Server::_argument_type_check($self, @_);
+ }
+ else {
+ $params = JSON::RPC::Legacy::Server::_argument_type_check($self, @_);
+ }
+
+ # JSON-RPC 1.0 requires all parameters to be passed as an array, so
+ # we just pull out the first item and assume it's an object.
+ my $params_is_array;
+ if (ref $params eq 'ARRAY') {
+ $params = $params->[0];
+ $params_is_array = 1;
+ }
+
+ taint_data($params);
+
+ # Now, convert dateTime fields on input.
+ my $method = $self->bz_method_name;
+ my $pkg = $self->{dispatch_path}->{$self->path_info};
+ my @date_fields = @{$pkg->DATE_FIELDS->{$method} || []};
+ foreach my $field (@date_fields) {
+ if (defined $params->{$field}) {
+ my $value = $params->{$field};
+ if (ref $value eq 'ARRAY') {
+ $params->{$field} = [map { $self->datetime_format_inbound($_) } @$value];
+ }
+ else {
+ $params->{$field} = $self->datetime_format_inbound($value);
+ }
}
- else {
- $params = JSON::RPC::Legacy::Server::_argument_type_check($self, @_);
+ }
+ my @base64_fields = @{$pkg->BASE64_FIELDS->{$method} || []};
+ foreach my $field (@base64_fields) {
+ if (defined $params->{$field}) {
+ $params->{$field} = decode_base64($params->{$field});
}
+ }
- # JSON-RPC 1.0 requires all parameters to be passed as an array, so
- # we just pull out the first item and assume it's an object.
- my $params_is_array;
- if (ref $params eq 'ARRAY') {
- $params = $params->[0];
- $params_is_array = 1;
- }
+ # This is the best time to do login checks.
+ $self->handle_login();
- taint_data($params);
-
- # Now, convert dateTime fields on input.
- my $method = $self->bz_method_name;
- my $pkg = $self->{dispatch_path}->{$self->path_info};
- my @date_fields = @{ $pkg->DATE_FIELDS->{$method} || [] };
- foreach my $field (@date_fields) {
- if (defined $params->{$field}) {
- my $value = $params->{$field};
- if (ref $value eq 'ARRAY') {
- $params->{$field} =
- [ map { $self->datetime_format_inbound($_) } @$value ];
- }
- else {
- $params->{$field} = $self->datetime_format_inbound($value);
- }
- }
- }
- my @base64_fields = @{ $pkg->BASE64_FIELDS->{$method} || [] };
- foreach my $field (@base64_fields) {
- if (defined $params->{$field}) {
- $params->{$field} = decode_base64($params->{$field});
- }
- }
+ # Bugzilla::WebService packages call internal methods like
+ # $self->_some_private_method. So we have to inherit from
+ # that class as well as this Server class.
+ my $new_class = ref($self) . '::' . $pkg;
+ my $isa_string = 'our @ISA = qw(' . ref($self) . " $pkg)";
+ eval "package $new_class;$isa_string;";
+ bless $self, $new_class;
- # This is the best time to do login checks.
- $self->handle_login();
+ # Allow extensions to modify the params post login
+ Bugzilla::Hook::process('webservice_rest_request',
+ {rpc => $self, params => $params});
- # Bugzilla::WebService packages call internal methods like
- # $self->_some_private_method. So we have to inherit from
- # that class as well as this Server class.
- my $new_class = ref($self) . '::' . $pkg;
- my $isa_string = 'our @ISA = qw(' . ref($self) . " $pkg)";
- eval "package $new_class;$isa_string;";
- bless $self, $new_class;
+ if ($params_is_array) {
+ $params = [$params];
+ }
- # Allow extensions to modify the params post login
- Bugzilla::Hook::process('webservice_rest_request',
- { rpc => $self, params => $params });
-
- if ($params_is_array) {
- $params = [$params];
- }
-
- return $params;
+ return $params;
}
###################
@@ -294,46 +297,46 @@ sub _argument_type_check {
###################
sub bz_method_name {
- my ($self, $method) = @_;
- $self->{_bz_method_name} = $method if $method;
- return $self->{_bz_method_name};
+ my ($self, $method) = @_;
+ $self->{_bz_method_name} = $method if $method;
+ return $self->{_bz_method_name};
}
sub bz_class_name {
- my ($self, $class) = @_;
- $self->{_bz_class_name} = $class if $class;
- return $self->{_bz_class_name};
+ my ($self, $class) = @_;
+ $self->{_bz_class_name} = $class if $class;
+ return $self->{_bz_class_name};
}
sub bz_success_code {
- my ($self, $value) = @_;
- $self->{_bz_success_code} = $value if $value;
- return $self->{_bz_success_code};
+ my ($self, $value) = @_;
+ $self->{_bz_success_code} = $value if $value;
+ return $self->{_bz_success_code};
}
sub bz_rest_params {
- my ($self, $params) = @_;
- $self->{_bz_rest_params} = $params if $params;
- return $self->{_bz_rest_params};
+ my ($self, $params) = @_;
+ $self->{_bz_rest_params} = $params if $params;
+ return $self->{_bz_rest_params};
}
sub bz_rest_options {
- my ($self, $options) = @_;
- $self->{_bz_rest_options} = $options if $options;
- return $self->{_bz_rest_options};
+ my ($self, $options) = @_;
+ $self->{_bz_rest_options} = $options if $options;
+ return $self->{_bz_rest_options};
}
sub rest_include_exclude {
- my ($params) = @_;
+ my ($params) = @_;
- if ($params->{'include_fields'} && !ref $params->{'include_fields'}) {
- $params->{'include_fields'} = [ split(/[\s+,]/, $params->{'include_fields'}) ];
- }
- if ($params->{'exclude_fields'} && !ref $params->{'exclude_fields'}) {
- $params->{'exclude_fields'} = [ split(/[\s+,]/, $params->{'exclude_fields'}) ];
- }
+ if ($params->{'include_fields'} && !ref $params->{'include_fields'}) {
+ $params->{'include_fields'} = [split(/[\s+,]/, $params->{'include_fields'})];
+ }
+ if ($params->{'exclude_fields'} && !ref $params->{'exclude_fields'}) {
+ $params->{'exclude_fields'} = [split(/[\s+,]/, $params->{'exclude_fields'})];
+ }
- return $params;
+ return $params;
}
##########################
@@ -341,184 +344,191 @@ sub rest_include_exclude {
##########################
sub _retrieve_json_params {
- my $self = shift;
-
- # Make a copy of the current input_params rather than edit directly
- my $params = {};
- %{$params} = %{ Bugzilla->input_params };
-
- # First add any parameters we were able to pull out of the path
- # based on the resource regexp and combine with the normal URL
- # parameters.
- if (my $rest_params = $self->bz_rest_params) {
- foreach my $param (keys %$rest_params) {
- # If the param does not already exist or if the
- # rest param is a single value, add it to the
- # global params.
- if (!exists $params->{$param} || !ref $rest_params->{$param}) {
- $params->{$param} = $rest_params->{$param};
- }
- # If rest_param is a list then add any extra values to the list
- elsif (ref $rest_params->{$param}) {
- my @extra_values = ref $params->{$param}
- ? @{ $params->{$param} }
- : ($params->{$param});
- $params->{$param}
- = [ uniq (@{ $rest_params->{$param} }, @extra_values) ];
- }
- }
+ my $self = shift;
+
+ # Make a copy of the current input_params rather than edit directly
+ my $params = {};
+ %{$params} = %{Bugzilla->input_params};
+
+ # First add any parameters we were able to pull out of the path
+ # based on the resource regexp and combine with the normal URL
+ # parameters.
+ if (my $rest_params = $self->bz_rest_params) {
+ foreach my $param (keys %$rest_params) {
+
+ # If the param does not already exist or if the
+ # rest param is a single value, add it to the
+ # global params.
+ if (!exists $params->{$param} || !ref $rest_params->{$param}) {
+ $params->{$param} = $rest_params->{$param};
+ }
+
+ # If rest_param is a list then add any extra values to the list
+ elsif (ref $rest_params->{$param}) {
+ my @extra_values
+ = ref $params->{$param} ? @{$params->{$param}} : ($params->{$param});
+ $params->{$param} = [uniq(@{$rest_params->{$param}}, @extra_values)];
+ }
+ }
+ }
+
+ # Any parameters passed in in the body of a non-GET request will override
+ # any parameters pull from the url path. Otherwise non-unique keys are
+ # combined.
+ if ($self->request->method ne 'GET') {
+ my $extra_params = {};
+
+ # We do this manually because CGI.pm doesn't understand JSON strings.
+ my $json = delete $params->{'POSTDATA'} || delete $params->{'PUTDATA'};
+ if ($json) {
+ eval { $extra_params = $self->json->decode($json); };
+ if ($@) {
+ ThrowUserError('json_rpc_invalid_params', {err_msg => $@});
+ }
}
- # Any parameters passed in in the body of a non-GET request will override
- # any parameters pull from the url path. Otherwise non-unique keys are
- # combined.
- if ($self->request->method ne 'GET') {
- my $extra_params = {};
- # We do this manually because CGI.pm doesn't understand JSON strings.
- my $json = delete $params->{'POSTDATA'} || delete $params->{'PUTDATA'};
- if ($json) {
- eval { $extra_params = $self->json->decode($json); };
- if ($@) {
- ThrowUserError('json_rpc_invalid_params', { err_msg => $@ });
- }
- }
-
- # Allow parameters in the query string if request was non-GET.
- # Note: parameters in query string body override any matching
- # parameters in the request body.
- foreach my $param ($self->cgi->url_param()) {
- $extra_params->{$param} = $self->cgi->url_param($param);
- }
-
- %{$params} = (%{$params}, %{$extra_params}) if %{$extra_params};
+ # Allow parameters in the query string if request was non-GET.
+ # Note: parameters in query string body override any matching
+ # parameters in the request body.
+ foreach my $param ($self->cgi->url_param()) {
+ $extra_params->{$param} = $self->cgi->url_param($param);
}
- return $params;
+ %{$params} = (%{$params}, %{$extra_params}) if %{$extra_params};
+ }
+
+ return $params;
}
sub _find_resource {
- my ($self, $path) = @_;
-
- # Load in the WebService module from the dispatch map and then call
- # $module->rest_resources to get the resources array ref.
- my $resources = {};
- foreach my $module (values %{ $self->{dispatch_path} }) {
- eval("require $module") || die $@;
- next if !$module->can('rest_resources');
- $resources->{$module} = $module->rest_resources;
- }
-
- Bugzilla::Hook::process('webservice_rest_resources',
- { rpc => $self, resources => $resources });
-
- # Use the resources hash from each module loaded earlier to determine
- # which handler to use based on a regex match of the CGI path.
- # Also any matches found in the regex will be passed in later to the
- # handler for possible use.
- my $request_method = $self->request->method;
-
- my (@matches, $handler_found, $handler_method, $handler_class);
- foreach my $class (keys %{ $resources }) {
- # The resource data for each module needs to be
- # an array ref with an even number of elements
- # to work correctly.
- next if (ref $resources->{$class} ne 'ARRAY'
- || scalar @{ $resources->{$class} } % 2 != 0);
-
- while (my $regex = shift @{ $resources->{$class} }) {
- my $options_data = shift @{ $resources->{$class} };
- next if ref $options_data ne 'HASH';
-
- if (@matches = ($path =~ $regex)) {
- # If a specific path is accompanied by a OPTIONS request
- # method, the user is asking for a list of possible request
- # methods for a specific path.
- $self->bz_rest_options([ keys %{ $options_data } ]);
-
- if ($options_data->{$request_method}) {
- my $resource_data = $options_data->{$request_method};
- $self->bz_class_name($class);
-
- # The method key/value can be a simple scalar method name
- # or a anonymous subroutine so we execute it here.
- my $method = ref $resource_data->{method} eq 'CODE'
- ? $resource_data->{method}->($self)
- : $resource_data->{method};
- $self->bz_method_name($method);
-
- # Pull out any parameters parsed from the URL path
- # and store them for use by the method.
- if ($resource_data->{params}) {
- $self->bz_rest_params($resource_data->{params}->(@matches));
- }
-
- # If a special success code is needed for this particular
- # method, then store it for later when generating response.
- if ($resource_data->{success_code}) {
- $self->bz_success_code($resource_data->{success_code});
- }
- $handler_found = 1;
- }
- }
- last if $handler_found;
+ my ($self, $path) = @_;
+
+ # Load in the WebService module from the dispatch map and then call
+ # $module->rest_resources to get the resources array ref.
+ my $resources = {};
+ foreach my $module (values %{$self->{dispatch_path}}) {
+ eval("require $module") || die $@;
+ next if !$module->can('rest_resources');
+ $resources->{$module} = $module->rest_resources;
+ }
+
+ Bugzilla::Hook::process('webservice_rest_resources',
+ {rpc => $self, resources => $resources});
+
+ # Use the resources hash from each module loaded earlier to determine
+ # which handler to use based on a regex match of the CGI path.
+ # Also any matches found in the regex will be passed in later to the
+ # handler for possible use.
+ my $request_method = $self->request->method;
+
+ my (@matches, $handler_found, $handler_method, $handler_class);
+ foreach my $class (keys %{$resources}) {
+
+ # The resource data for each module needs to be
+ # an array ref with an even number of elements
+ # to work correctly.
+ next
+ if (ref $resources->{$class} ne 'ARRAY'
+ || scalar @{$resources->{$class}} % 2 != 0);
+
+ while (my $regex = shift @{$resources->{$class}}) {
+ my $options_data = shift @{$resources->{$class}};
+ next if ref $options_data ne 'HASH';
+
+ if (@matches = ($path =~ $regex)) {
+
+ # If a specific path is accompanied by a OPTIONS request
+ # method, the user is asking for a list of possible request
+ # methods for a specific path.
+ $self->bz_rest_options([keys %{$options_data}]);
+
+ if ($options_data->{$request_method}) {
+ my $resource_data = $options_data->{$request_method};
+ $self->bz_class_name($class);
+
+ # The method key/value can be a simple scalar method name
+ # or a anonymous subroutine so we execute it here.
+ my $method
+ = ref $resource_data->{method} eq 'CODE'
+ ? $resource_data->{method}->($self)
+ : $resource_data->{method};
+ $self->bz_method_name($method);
+
+ # Pull out any parameters parsed from the URL path
+ # and store them for use by the method.
+ if ($resource_data->{params}) {
+ $self->bz_rest_params($resource_data->{params}->(@matches));
+ }
+
+ # If a special success code is needed for this particular
+ # method, then store it for later when generating response.
+ if ($resource_data->{success_code}) {
+ $self->bz_success_code($resource_data->{success_code});
+ }
+ $handler_found = 1;
}
- last if $handler_found;
+ }
+ last if $handler_found;
}
+ last if $handler_found;
+ }
- return $handler_found;
+ return $handler_found;
}
sub _best_content_type {
- my ($self, @types) = @_;
- return ($self->_simple_content_negotiation(@types))[0] || '*/*';
+ my ($self, @types) = @_;
+ return ($self->_simple_content_negotiation(@types))[0] || '*/*';
}
sub _simple_content_negotiation {
- my ($self, @types) = @_;
- my @accept_types = $self->_get_content_prefs();
- # Return the types as-is if no accept header sent, since sorting will be a no-op.
- if (!@accept_types) {
- return @types;
- }
- my $score = sub { $self->_score_type(shift, @accept_types) };
- return sort {$score->($b) <=> $score->($a)} @types;
+ my ($self, @types) = @_;
+ my @accept_types = $self->_get_content_prefs();
+
+ # Return the types as-is if no accept header sent, since sorting will be a no-op.
+ if (!@accept_types) {
+ return @types;
+ }
+ my $score = sub { $self->_score_type(shift, @accept_types) };
+ return sort { $score->($b) <=> $score->($a) } @types;
}
sub _score_type {
- my ($self, $type, @accept_types) = @_;
- my $score = scalar(@accept_types);
- for my $accept_type (@accept_types) {
- return $score if $type eq $accept_type;
- $score--;
- }
- return 0;
+ my ($self, $type, @accept_types) = @_;
+ my $score = scalar(@accept_types);
+ for my $accept_type (@accept_types) {
+ return $score if $type eq $accept_type;
+ $score--;
+ }
+ return 0;
}
sub _get_content_prefs {
- my $self = shift;
- my $default_weight = 1;
- my @prefs;
-
- # Parse the Accept header, and save type name, score, and position.
- my @accept_types = split /,/, $self->cgi->http('accept') || '';
- my $order = 0;
- for my $accept_type (@accept_types) {
- my ($weight) = ($accept_type =~ /q=(\d\.\d+|\d+)/);
- my ($name) = ($accept_type =~ m#(\S+/[^;]+)#);
- next unless $name;
- push @prefs, { name => $name, order => $order++};
- if (defined $weight) {
- $prefs[-1]->{score} = $weight;
- } else {
- $prefs[-1]->{score} = $default_weight;
- $default_weight -= 0.001;
- }
+ my $self = shift;
+ my $default_weight = 1;
+ my @prefs;
+
+ # Parse the Accept header, and save type name, score, and position.
+ my @accept_types = split /,/, $self->cgi->http('accept') || '';
+ my $order = 0;
+ for my $accept_type (@accept_types) {
+ my ($weight) = ($accept_type =~ /q=(\d\.\d+|\d+)/);
+ my ($name) = ($accept_type =~ m#(\S+/[^;]+)#);
+ next unless $name;
+ push @prefs, {name => $name, order => $order++};
+ if (defined $weight) {
+ $prefs[-1]->{score} = $weight;
+ }
+ else {
+ $prefs[-1]->{score} = $default_weight;
+ $default_weight -= 0.001;
}
+ }
- # Sort the types by score, subscore by order, and pull out just the name
- @prefs = map {$_->{name}} sort {$b->{score} <=> $a->{score} ||
- $a->{order} <=> $b->{order}} @prefs;
- return @prefs;
+ # Sort the types by score, subscore by order, and pull out just the name
+ @prefs = map { $_->{name} }
+ sort { $b->{score} <=> $a->{score} || $a->{order} <=> $b->{order} } @prefs;
+ return @prefs;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Bug.pm b/Bugzilla/WebService/Server/REST/Resources/Bug.pm
index 3fa8b65cf..5cc25f432 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Bug.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Bug.pm
@@ -15,150 +15,150 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Bug;
BEGIN {
- *Bugzilla::WebService::Bug::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Bug::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/bug$}, {
- GET => {
- method => 'search',
- },
- POST => {
- method => 'create',
- status_code => STATUS_CREATED
- }
- },
- qr{^/bug/$}, {
- GET => {
- method => 'get'
- }
- },
- qr{^/bug/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- return { ids => [ $_[0] ] };
- }
- },
- PUT => {
- method => 'update',
- params => sub {
- return { ids => [ $_[0] ] };
- }
- }
- },
- qr{^/bug/([^/]+)/comment$}, {
- GET => {
- method => 'comments',
- params => sub {
- return { ids => [ $_[0] ] };
- }
- },
- POST => {
- method => 'add_comment',
- params => sub {
- return { id => $_[0] };
- },
- success_code => STATUS_CREATED
- }
- },
- qr{^/bug/comment/([^/]+)$}, {
- GET => {
- method => 'comments',
- params => sub {
- return { comment_ids => [ $_[0] ] };
- }
- }
- },
- qr{^/bug/comment/tags/([^/]+)$}, {
- GET => {
- method => 'search_comment_tags',
- params => sub {
- return { query => $_[0] };
- },
- },
- },
- qr{^/bug/comment/([^/]+)/tags$}, {
- PUT => {
- method => 'update_comment_tags',
- params => sub {
- return { comment_id => $_[0] };
- },
- },
- },
- qr{^/bug/([^/]+)/history$}, {
- GET => {
- method => 'history',
- params => sub {
- return { ids => [ $_[0] ] };
- },
- }
- },
- qr{^/bug/([^/]+)/attachment$}, {
- GET => {
- method => 'attachments',
- params => sub {
- return { ids => [ $_[0] ] };
- }
- },
- POST => {
- method => 'add_attachment',
- params => sub {
- return { ids => [ $_[0] ] };
- },
- success_code => STATUS_CREATED
- }
- },
- qr{^/bug/attachment/([^/]+)$}, {
- GET => {
- method => 'attachments',
- params => sub {
- return { attachment_ids => [ $_[0] ] };
- }
- },
- PUT => {
- method => 'update_attachment',
- params => sub {
- return { ids => [ $_[0] ] };
- }
- }
+ my $rest_resources = [
+ qr{^/bug$},
+ {
+ GET => {method => 'search',},
+ POST => {method => 'create', status_code => STATUS_CREATED}
+ },
+ qr{^/bug/$},
+ {GET => {method => 'get'}},
+ qr{^/bug/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ return {ids => [$_[0]]};
+ }
+ },
+ PUT => {
+ method => 'update',
+ params => sub {
+ return {ids => [$_[0]]};
+ }
+ }
+ },
+ qr{^/bug/([^/]+)/comment$},
+ {
+ GET => {
+ method => 'comments',
+ params => sub {
+ return {ids => [$_[0]]};
+ }
+ },
+ POST => {
+ method => 'add_comment',
+ params => sub {
+ return {id => $_[0]};
},
- qr{^/field/bug$}, {
- GET => {
- method => 'fields',
- }
+ success_code => STATUS_CREATED
+ }
+ },
+ qr{^/bug/comment/([^/]+)$},
+ {
+ GET => {
+ method => 'comments',
+ params => sub {
+ return {comment_ids => [$_[0]]};
+ }
+ }
+ },
+ qr{^/bug/comment/tags/([^/]+)$},
+ {
+ GET => {
+ method => 'search_comment_tags',
+ params => sub {
+ return {query => $_[0]};
},
- qr{^/field/bug/([^/]+)$}, {
- GET => {
- method => 'fields',
- params => sub {
- my $value = $_[0];
- my $param = 'names';
- $param = 'ids' if $value =~ /^\d+$/;
- return { $param => [ $_[0] ] };
- }
- }
+ },
+ },
+ qr{^/bug/comment/([^/]+)/tags$},
+ {
+ PUT => {
+ method => 'update_comment_tags',
+ params => sub {
+ return {comment_id => $_[0]};
},
- qr{^/field/bug/([^/]+)/values$}, {
- GET => {
- method => 'legal_values',
- params => sub {
- return { field => $_[0] };
- }
- }
+ },
+ },
+ qr{^/bug/([^/]+)/history$},
+ {
+ GET => {
+ method => 'history',
+ params => sub {
+ return {ids => [$_[0]]};
},
- qr{^/field/bug/([^/]+)/([^/]+)/values$}, {
- GET => {
- method => 'legal_values',
- params => sub {
- return { field => $_[0],
- product_id => $_[1] };
- }
- }
+ }
+ },
+ qr{^/bug/([^/]+)/attachment$},
+ {
+ GET => {
+ method => 'attachments',
+ params => sub {
+ return {ids => [$_[0]]};
+ }
+ },
+ POST => {
+ method => 'add_attachment',
+ params => sub {
+ return {ids => [$_[0]]};
},
- ];
- return $rest_resources;
+ success_code => STATUS_CREATED
+ }
+ },
+ qr{^/bug/attachment/([^/]+)$},
+ {
+ GET => {
+ method => 'attachments',
+ params => sub {
+ return {attachment_ids => [$_[0]]};
+ }
+ },
+ PUT => {
+ method => 'update_attachment',
+ params => sub {
+ return {ids => [$_[0]]};
+ }
+ }
+ },
+ qr{^/field/bug$},
+ {GET => {method => 'fields',}},
+ qr{^/field/bug/([^/]+)$},
+ {
+ GET => {
+ method => 'fields',
+ params => sub {
+ my $value = $_[0];
+ my $param = 'names';
+ $param = 'ids' if $value =~ /^\d+$/;
+ return {$param => [$_[0]]};
+ }
+ }
+ },
+ qr{^/field/bug/([^/]+)/values$},
+ {
+ GET => {
+ method => 'legal_values',
+ params => sub {
+ return {field => $_[0]};
+ }
+ }
+ },
+ qr{^/field/bug/([^/]+)/([^/]+)/values$},
+ {
+ GET => {
+ method => 'legal_values',
+ params => sub {
+ return {field => $_[0], product_id => $_[1]};
+ }
+ }
+ },
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
index 8502d6b3b..806c3f9c7 100644
--- a/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
@@ -12,27 +12,28 @@ use strict;
use warnings;
BEGIN {
- *Bugzilla::WebService::BugUserLastVisit::rest_resources = \&_rest_resources;
+ *Bugzilla::WebService::BugUserLastVisit::rest_resources = \&_rest_resources;
}
sub _rest_resources {
- return [
- # bug-id
- qr{^/bug_user_last_visit/(\d+)$}, {
- GET => {
- method => 'get',
- params => sub {
- return { ids => [$_[0]] };
- },
- },
- POST => {
- method => 'update',
- params => sub {
- return { ids => [$_[0]] };
- },
- },
+ return [
+ # bug-id
+ qr{^/bug_user_last_visit/(\d+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ return {ids => [$_[0]]};
},
- ];
+ },
+ POST => {
+ method => 'update',
+ params => sub {
+ return {ids => [$_[0]]};
+ },
+ },
+ },
+ ];
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm b/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm
index a8f3f9330..072cfe2f6 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm
@@ -15,43 +15,19 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Bugzilla;
BEGIN {
- *Bugzilla::WebService::Bugzilla::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Bugzilla::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/version$}, {
- GET => {
- method => 'version'
- }
- },
- qr{^/extensions$}, {
- GET => {
- method => 'extensions'
- }
- },
- qr{^/timezone$}, {
- GET => {
- method => 'timezone'
- }
- },
- qr{^/time$}, {
- GET => {
- method => 'time'
- }
- },
- qr{^/last_audit_time$}, {
- GET => {
- method => 'last_audit_time'
- }
- },
- qr{^/parameters$}, {
- GET => {
- method => 'parameters'
- }
- }
- ];
- return $rest_resources;
+ my $rest_resources = [
+ qr{^/version$}, {GET => {method => 'version'}},
+ qr{^/extensions$}, {GET => {method => 'extensions'}},
+ qr{^/timezone$}, {GET => {method => 'timezone'}},
+ qr{^/time$}, {GET => {method => 'time'}},
+ qr{^/last_audit_time$}, {GET => {method => 'last_audit_time'}},
+ qr{^/parameters$}, {GET => {method => 'parameters'}}
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Classification.pm b/Bugzilla/WebService/Server/REST/Resources/Classification.pm
index 3f8d32a03..ed65aea5c 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Classification.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Classification.pm
@@ -15,22 +15,23 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Classification;
BEGIN {
- *Bugzilla::WebService::Classification::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Classification::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/classification/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- }
+ my $rest_resources = [
+ qr{^/classification/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
}
- ];
- return $rest_resources;
+ }
+ }
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Component.pm b/Bugzilla/WebService/Server/REST/Resources/Component.pm
index 198c09332..8870a0f04 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Component.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Component.pm
@@ -17,19 +17,15 @@ use Bugzilla::WebService::Component;
use Bugzilla::Error;
BEGIN {
- *Bugzilla::WebService::Component::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Component::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/component$}, {
- POST => {
- method => 'create',
- success_code => STATUS_CREATED
- }
- },
- ];
- return $rest_resources;
+ my $rest_resources = [
+ qr{^/component$},
+ {POST => {method => 'create', success_code => STATUS_CREATED}},
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/FlagType.pm b/Bugzilla/WebService/Server/REST/Resources/FlagType.pm
index 21dad0f73..438c8fb30 100644
--- a/Bugzilla/WebService/Server/REST/Resources/FlagType.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/FlagType.pm
@@ -17,43 +17,40 @@ use Bugzilla::WebService::FlagType;
use Bugzilla::Error;
BEGIN {
- *Bugzilla::WebService::FlagType::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::FlagType::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/flag_type$}, {
- POST => {
- method => 'create',
- success_code => STATUS_CREATED
- }
- },
- qr{^/flag_type/([^/]+)/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- return { product => $_[0],
- component => $_[1] };
- }
- }
- },
- qr{^/flag_type/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- return { product => $_[0] };
- }
- },
- PUT => {
- method => 'update',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- }
- },
- ];
- return $rest_resources;
+ my $rest_resources = [
+ qr{^/flag_type$},
+ {POST => {method => 'create', success_code => STATUS_CREATED}},
+ qr{^/flag_type/([^/]+)/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ return {product => $_[0], component => $_[1]};
+ }
+ }
+ },
+ qr{^/flag_type/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ return {product => $_[0]};
+ }
+ },
+ PUT => {
+ method => 'update',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
+ }
+ }
+ },
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Group.pm b/Bugzilla/WebService/Server/REST/Resources/Group.pm
index b052e384b..7f607b7d1 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Group.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Group.pm
@@ -15,31 +15,28 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Group;
BEGIN {
- *Bugzilla::WebService::Group::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Group::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/group$}, {
- GET => {
- method => 'get'
- },
- POST => {
- method => 'create',
- success_code => STATUS_CREATED
- }
- },
- qr{^/group/([^/]+)$}, {
- PUT => {
- method => 'update',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- }
+ my $rest_resources = [
+ qr{^/group$},
+ {
+ GET => {method => 'get'},
+ POST => {method => 'create', success_code => STATUS_CREATED}
+ },
+ qr{^/group/([^/]+)$},
+ {
+ PUT => {
+ method => 'update',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
}
- ];
- return $rest_resources;
+ }
+ }
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Product.pm b/Bugzilla/WebService/Server/REST/Resources/Product.pm
index 607b94b53..eabe19681 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Product.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Product.pm
@@ -17,53 +17,41 @@ use Bugzilla::WebService::Product;
use Bugzilla::Error;
BEGIN {
- *Bugzilla::WebService::Product::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Product::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/product_accessible$}, {
- GET => {
- method => 'get_accessible_products'
- }
- },
- qr{^/product_enterable$}, {
- GET => {
- method => 'get_enterable_products'
- }
- },
- qr{^/product_selectable$}, {
- GET => {
- method => 'get_selectable_products'
- }
- },
- qr{^/product$}, {
- GET => {
- method => 'get'
- },
- POST => {
- method => 'create',
- success_code => STATUS_CREATED
- }
- },
- qr{^/product/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- },
- PUT => {
- method => 'update',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- }
- },
- ];
- return $rest_resources;
+ my $rest_resources = [
+ qr{^/product_accessible$},
+ {GET => {method => 'get_accessible_products'}},
+ qr{^/product_enterable$},
+ {GET => {method => 'get_enterable_products'}},
+ qr{^/product_selectable$},
+ {GET => {method => 'get_selectable_products'}},
+ qr{^/product$},
+ {
+ GET => {method => 'get'},
+ POST => {method => 'create', success_code => STATUS_CREATED}
+ },
+ qr{^/product/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
+ }
+ },
+ PUT => {
+ method => 'update',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
+ }
+ }
+ },
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/User.pm b/Bugzilla/WebService/Server/REST/Resources/User.pm
index a83109e73..4555b4dbc 100644
--- a/Bugzilla/WebService/Server/REST/Resources/User.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/User.pm
@@ -15,53 +15,41 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::User;
BEGIN {
- *Bugzilla::WebService::User::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::User::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/login$}, {
- GET => {
- method => 'login'
- }
- },
- qr{^/logout$}, {
- GET => {
- method => 'logout'
- }
- },
- qr{^/valid_login$}, {
- GET => {
- method => 'valid_login'
- }
- },
- qr{^/user$}, {
- GET => {
- method => 'get'
- },
- POST => {
- method => 'create',
- success_code => STATUS_CREATED
- }
- },
- qr{^/user/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- },
- PUT => {
- method => 'update',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- }
+ my $rest_resources = [
+ qr{^/login$},
+ {GET => {method => 'login'}},
+ qr{^/logout$},
+ {GET => {method => 'logout'}},
+ qr{^/valid_login$},
+ {GET => {method => 'valid_login'}},
+ qr{^/user$},
+ {
+ GET => {method => 'get'},
+ POST => {method => 'create', success_code => STATUS_CREATED}
+ },
+ qr{^/user/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
+ }
+ },
+ PUT => {
+ method => 'update',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
}
- ];
- return $rest_resources;
+ }
+ }
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/XMLRPC.pm b/Bugzilla/WebService/Server/XMLRPC.pm
index 8deb253ad..f96f960da 100644
--- a/Bugzilla/WebService/Server/XMLRPC.pm
+++ b/Bugzilla/WebService/Server/XMLRPC.pm
@@ -14,9 +14,10 @@ use warnings;
use XMLRPC::Transport::HTTP;
use Bugzilla::WebService::Server;
if ($ENV{MOD_PERL}) {
- our @ISA = qw(XMLRPC::Transport::HTTP::Apache Bugzilla::WebService::Server);
-} else {
- our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
+ our @ISA = qw(XMLRPC::Transport::HTTP::Apache Bugzilla::WebService::Server);
+}
+else {
+ our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
}
use Bugzilla::WebService::Constants;
@@ -26,97 +27,99 @@ use Bugzilla::Util;
use List::MoreUtils qw(none);
BEGIN {
- # Allow WebService methods to call XMLRPC::Lite's type method directly
- *Bugzilla::WebService::type = sub {
- my ($self, $type, $value) = @_;
- if ($type eq 'dateTime') {
- # This is the XML-RPC implementation, see the README in Bugzilla/WebService/.
- # Our "base" implementation is in Bugzilla::WebService::Server.
- $value = Bugzilla::WebService::Server->datetime_format_outbound($value);
- $value =~ s/-//g;
- }
- elsif ($type eq 'email') {
- $type = 'string';
- if (Bugzilla->params->{'webservice_email_filter'}) {
- $value = email_filter($value);
- }
- }
- return XMLRPC::Data->type($type)->value($value);
- };
-
- # Add support for ETags into XMLRPC WebServices
- *Bugzilla::WebService::bz_etag = sub {
- return Bugzilla::WebService::Server->bz_etag($_[1]);
- };
+ # Allow WebService methods to call XMLRPC::Lite's type method directly
+ *Bugzilla::WebService::type = sub {
+ my ($self, $type, $value) = @_;
+ if ($type eq 'dateTime') {
+
+ # This is the XML-RPC implementation, see the README in Bugzilla/WebService/.
+ # Our "base" implementation is in Bugzilla::WebService::Server.
+ $value = Bugzilla::WebService::Server->datetime_format_outbound($value);
+ $value =~ s/-//g;
+ }
+ elsif ($type eq 'email') {
+ $type = 'string';
+ if (Bugzilla->params->{'webservice_email_filter'}) {
+ $value = email_filter($value);
+ }
+ }
+ return XMLRPC::Data->type($type)->value($value);
+ };
+
+ # Add support for ETags into XMLRPC WebServices
+ *Bugzilla::WebService::bz_etag = sub {
+ return Bugzilla::WebService::Server->bz_etag($_[1]);
+ };
}
sub initialize {
- my $self = shift;
- my %retval = $self->SUPER::initialize(@_);
- $retval{'serializer'} = Bugzilla::XMLRPC::Serializer->new;
- $retval{'deserializer'} = Bugzilla::XMLRPC::Deserializer->new;
- $retval{'dispatch_with'} = WS_DISPATCH;
- return %retval;
+ my $self = shift;
+ my %retval = $self->SUPER::initialize(@_);
+ $retval{'serializer'} = Bugzilla::XMLRPC::Serializer->new;
+ $retval{'deserializer'} = Bugzilla::XMLRPC::Deserializer->new;
+ $retval{'dispatch_with'} = WS_DISPATCH;
+ return %retval;
}
sub make_response {
- my $self = shift;
- my $cgi = Bugzilla->cgi;
-
- # Fix various problems with IIS.
- if ($ENV{'SERVER_SOFTWARE'} =~ /IIS/) {
- $ENV{CONTENT_LENGTH} = 0;
- binmode(STDOUT, ':bytes');
- }
-
- $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 my $cookie (@{$cgi->{'Bugzilla_cookie_list'}}) {
- $self->response->headers->push_header('Set-Cookie', $cookie);
- }
-
- # Copy across security related headers from Bugzilla::CGI
- foreach my $header (split(/[\r\n]+/, $cgi->header)) {
- my ($name, $value) = $header =~ /^([^:]+): (.*)/;
- if (!$self->response->headers->header($name)) {
- $self->response->headers->header($name => $value);
- }
- }
-
- # ETag support
- my $etag = $self->bz_etag;
- if (!$etag) {
- my $data = $self->response->as_string;
- $etag = $self->bz_etag($data);
- }
-
- if ($etag && $cgi->check_etag($etag)) {
- $self->response->headers->push_header('ETag', $etag);
- $self->response->headers->push_header('status', '304 Not Modified');
- }
- elsif ($etag) {
- $self->response->headers->push_header('ETag', $etag);
+ my $self = shift;
+ my $cgi = Bugzilla->cgi;
+
+ # Fix various problems with IIS.
+ if ($ENV{'SERVER_SOFTWARE'} =~ /IIS/) {
+ $ENV{CONTENT_LENGTH} = 0;
+ binmode(STDOUT, ':bytes');
+ }
+
+ $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 my $cookie (@{$cgi->{'Bugzilla_cookie_list'}}) {
+ $self->response->headers->push_header('Set-Cookie', $cookie);
+ }
+
+ # Copy across security related headers from Bugzilla::CGI
+ foreach my $header (split(/[\r\n]+/, $cgi->header)) {
+ my ($name, $value) = $header =~ /^([^:]+): (.*)/;
+ if (!$self->response->headers->header($name)) {
+ $self->response->headers->header($name => $value);
}
+ }
+
+ # ETag support
+ my $etag = $self->bz_etag;
+ if (!$etag) {
+ my $data = $self->response->as_string;
+ $etag = $self->bz_etag($data);
+ }
+
+ if ($etag && $cgi->check_etag($etag)) {
+ $self->response->headers->push_header('ETag', $etag);
+ $self->response->headers->push_header('status', '304 Not Modified');
+ }
+ elsif ($etag) {
+ $self->response->headers->push_header('ETag', $etag);
+ }
}
sub handle_login {
- my ($self, $classes, $action, $uri, $method) = @_;
- my $class = $classes->{$uri};
- my $full_method = $uri . "." . $method;
- # Only allowed methods to be used from the module's whitelist
- my $file = $class;
- $file =~ s{::}{/}g;
- $file .= ".pm";
- require $file;
- if (none { $_ eq $method } $class->PUBLIC_METHODS) {
- ThrowCodeError('unknown_method', { method => $full_method });
- }
-
- $ENV{CONTENT_LENGTH} = 0 if $ENV{'SERVER_SOFTWARE'} =~ /IIS/;
- $self->SUPER::handle_login($class, $method, $full_method);
- return;
+ my ($self, $classes, $action, $uri, $method) = @_;
+ my $class = $classes->{$uri};
+ my $full_method = $uri . "." . $method;
+
+ # Only allowed methods to be used from the module's whitelist
+ my $file = $class;
+ $file =~ s{::}{/}g;
+ $file .= ".pm";
+ require $file;
+ if (none { $_ eq $method } $class->PUBLIC_METHODS) {
+ ThrowCodeError('unknown_method', {method => $full_method});
+ }
+
+ $ENV{CONTENT_LENGTH} = 0 if $ENV{'SERVER_SOFTWARE'} =~ /IIS/;
+ $self->SUPER::handle_login($class, $method, $full_method);
+ return;
}
1;
@@ -140,100 +143,111 @@ use Bugzilla::WebService::Util qw(fix_credentials);
use Scalar::Util qw(tainted);
sub new {
- my $self = shift->SUPER::new(@_);
- # Initialise XML::Parser to not expand references to entities, to prevent DoS
- require XML::Parser;
- my $parser = XML::Parser->new( NoExpand => 1, Handlers => { Default => sub {} } );
- $self->{_parser}->parser($parser, $parser);
- return $self;
+ my $self = shift->SUPER::new(@_);
+
+ # Initialise XML::Parser to not expand references to entities, to prevent DoS
+ require XML::Parser;
+ my $parser = XML::Parser->new(
+ NoExpand => 1,
+ Handlers => {
+ Default => sub { }
+ }
+ );
+ $self->{_parser}->parser($parser, $parser);
+ return $self;
}
sub deserialize {
- my $self = shift;
-
- # Only allow certain content types to protect against CSRF attacks
- my $content_type = lc($ENV{'CONTENT_TYPE'});
- # Remove charset, etc, if provided
- $content_type =~ s/^([^;]+);.*/$1/;
- if (!grep($_ eq $content_type, XMLRPC_CONTENT_TYPE_WHITELIST)) {
- ThrowUserError('xmlrpc_illegal_content_type',
- { content_type => $ENV{'CONTENT_TYPE'} });
- }
+ my $self = shift;
- my ($xml) = @_;
- my $som = $self->SUPER::deserialize(@_);
- if (tainted($xml)) {
- $som->{_bz_do_taint} = 1;
- }
- bless $som, 'Bugzilla::XMLRPC::SOM';
- my $params = $som->paramsin;
- # This allows positional parameters for Testopia.
- $params = {} if ref $params ne 'HASH';
+ # Only allow certain content types to protect against CSRF attacks
+ my $content_type = lc($ENV{'CONTENT_TYPE'});
- # Update the params to allow for several convenience key/values
- # use for authentication
- fix_credentials($params);
+ # Remove charset, etc, if provided
+ $content_type =~ s/^([^;]+);.*/$1/;
+ if (!grep($_ eq $content_type, XMLRPC_CONTENT_TYPE_WHITELIST)) {
+ ThrowUserError('xmlrpc_illegal_content_type',
+ {content_type => $ENV{'CONTENT_TYPE'}});
+ }
- Bugzilla->input_params($params);
+ my ($xml) = @_;
+ my $som = $self->SUPER::deserialize(@_);
+ if (tainted($xml)) {
+ $som->{_bz_do_taint} = 1;
+ }
+ bless $som, 'Bugzilla::XMLRPC::SOM';
+ my $params = $som->paramsin;
- return $som;
+ # This allows positional parameters for Testopia.
+ $params = {} if ref $params ne 'HASH';
+
+ # Update the params to allow for several convenience key/values
+ # use for authentication
+ fix_credentials($params);
+
+ Bugzilla->input_params($params);
+
+ return $som;
}
# Some method arguments need to be converted in some way, when they are input.
sub decode_value {
- my $self = shift;
- my ($type) = @{ $_[0] };
- my $value = $self->SUPER::decode_value(@_);
-
- # We only validate/convert certain types here.
- return $value if $type !~ /^(?:int|i4|boolean|double|dateTime\.iso8601)$/;
-
- # Though the XML-RPC standard doesn't allow an empty <int>,
- # <double>,or <dateTime.iso8601>, we do, and we just say
- # "that's undef".
- if (grep($type eq $_, qw(int double dateTime))) {
- return undef if $value eq '';
- }
-
- my $validator = $self->_validation_subs->{$type};
- if (!$validator->($value)) {
- ThrowUserError('xmlrpc_invalid_value',
- { type => $type, value => $value });
- }
-
- # We convert dateTimes to a DB-friendly date format.
- if ($type eq 'dateTime.iso8601') {
- if ($value !~ /T.*[\-+Z]/i) {
- # The caller did not specify a timezone, so we assume UTC.
- # pass 'Z' specifier to datetime_from to force it
- $value = $value . 'Z';
- }
- $value = Bugzilla::WebService::Server::XMLRPC->datetime_format_inbound($value);
+ my $self = shift;
+ my ($type) = @{$_[0]};
+ my $value = $self->SUPER::decode_value(@_);
+
+ # We only validate/convert certain types here.
+ return $value if $type !~ /^(?:int|i4|boolean|double|dateTime\.iso8601)$/;
+
+ # Though the XML-RPC standard doesn't allow an empty <int>,
+ # <double>,or <dateTime.iso8601>, we do, and we just say
+ # "that's undef".
+ if (grep($type eq $_, qw(int double dateTime))) {
+ return undef if $value eq '';
+ }
+
+ my $validator = $self->_validation_subs->{$type};
+ if (!$validator->($value)) {
+ ThrowUserError('xmlrpc_invalid_value', {type => $type, value => $value});
+ }
+
+ # We convert dateTimes to a DB-friendly date format.
+ if ($type eq 'dateTime.iso8601') {
+ if ($value !~ /T.*[\-+Z]/i) {
+
+ # The caller did not specify a timezone, so we assume UTC.
+ # pass 'Z' specifier to datetime_from to force it
+ $value = $value . 'Z';
}
+ $value = Bugzilla::WebService::Server::XMLRPC->datetime_format_inbound($value);
+ }
- return $value;
+ return $value;
}
sub _validation_subs {
- my $self = shift;
- return $self->{_validation_subs} if $self->{_validation_subs};
- # The only place that XMLRPC::Lite stores any sort of validation
- # regex is in XMLRPC::Serializer. We want to re-use those regexes here.
- my $lookup = Bugzilla::XMLRPC::Serializer->new->typelookup;
-
- # $lookup is a hash whose values are arrayrefs, and whose keys are the
- # names of types. The second item of each arrayref is a subroutine
- # that will do our validation for us.
- my %validators = map { $_ => $lookup->{$_}->[1] } (keys %$lookup);
- # Add a boolean validator
- $validators{'boolean'} = sub {$_[0] =~ /^[01]$/};
- # Some types have multiple names, or have a different name in
- # XMLRPC::Serializer than their standard XML-RPC name.
- $validators{'dateTime.iso8601'} = $validators{'dateTime'};
- $validators{'i4'} = $validators{'int'};
-
- $self->{_validation_subs} = \%validators;
- return \%validators;
+ my $self = shift;
+ return $self->{_validation_subs} if $self->{_validation_subs};
+
+ # The only place that XMLRPC::Lite stores any sort of validation
+ # regex is in XMLRPC::Serializer. We want to re-use those regexes here.
+ my $lookup = Bugzilla::XMLRPC::Serializer->new->typelookup;
+
+ # $lookup is a hash whose values are arrayrefs, and whose keys are the
+ # names of types. The second item of each arrayref is a subroutine
+ # that will do our validation for us.
+ my %validators = map { $_ => $lookup->{$_}->[1] } (keys %$lookup);
+
+ # Add a boolean validator
+ $validators{'boolean'} = sub { $_[0] =~ /^[01]$/ };
+
+ # Some types have multiple names, or have a different name in
+ # XMLRPC::Serializer than their standard XML-RPC name.
+ $validators{'dateTime.iso8601'} = $validators{'dateTime'};
+ $validators{'i4'} = $validators{'int'};
+
+ $self->{_validation_subs} = \%validators;
+ return \%validators;
}
1;
@@ -249,16 +263,16 @@ our @ISA = qw(XMLRPC::SOM);
use Bugzilla::WebService::Util qw(taint_data);
sub paramsin {
- my $self = shift;
- if (!$self->{bz_params_in}) {
- my @params = $self->SUPER::paramsin(@_);
- if ($self->{_bz_do_taint}) {
- taint_data(@params);
- }
- $self->{bz_params_in} = \@params;
+ my $self = shift;
+ if (!$self->{bz_params_in}) {
+ my @params = $self->SUPER::paramsin(@_);
+ if ($self->{_bz_do_taint}) {
+ taint_data(@params);
}
- my $params = $self->{bz_params_in};
- return wantarray ? @$params : $params->[0];
+ $self->{bz_params_in} = \@params;
+ }
+ my $params = $self->{bz_params_in};
+ return wantarray ? @$params : $params->[0];
}
1;
@@ -272,43 +286,46 @@ use strict;
use warnings;
use Scalar::Util qw(blessed reftype);
+
# We can't use "use parent" because XMLRPC::Serializer doesn't return
# a true value.
use 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;
+ 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;
}
# Here the XMLRPC::Serializer is extended to use the XMLRPC nil extension.
sub encode_object {
- my $self = shift;
- my @encoded = $self->SUPER::encode_object(@_);
+ my $self = shift;
+ my @encoded = $self->SUPER::encode_object(@_);
- return $encoded[0]->[0] eq 'nil'
- ? ['value', {}, [@encoded]]
- : @encoded;
+ return $encoded[0]->[0] eq 'nil' ? ['value', {}, [@encoded]] : @encoded;
}
# Removes undefined values so they do not produce invalid XMLRPC.
sub envelope {
- my $self = shift;
- my ($type, $method, $data) = @_;
- # If the type isn't a successful response we don't want to change the values.
- if ($type eq 'response') {
- _strip_undefs($data);
- }
- return $self->SUPER::envelope($type, $method, $data);
+ my $self = shift;
+ my ($type, $method, $data) = @_;
+
+ # If the type isn't a successful response we don't want to change the values.
+ if ($type eq 'response') {
+ _strip_undefs($data);
+ }
+ return $self->SUPER::envelope($type, $method, $data);
}
# In an XMLRPC response we have to handle hashes of arrays, hashes, scalars,
@@ -316,58 +333,58 @@ sub envelope {
# The whole XMLRPC::Data object must be removed if its value key is undefined
# so it cannot be recursed like the other hash type objects.
sub _strip_undefs {
- my ($initial) = @_;
- my $type = reftype($initial) or return;
-
- if ($type eq "HASH") {
- while (my ($key, $value) = each(%$initial)) {
- if ( !defined $value
- || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value) )
- {
- # If the value is undefined remove it from the hash.
- delete $initial->{$key};
- }
- else {
- _strip_undefs($value);
- }
- }
+ my ($initial) = @_;
+ my $type = reftype($initial) or return;
+
+ if ($type eq "HASH") {
+ while (my ($key, $value) = each(%$initial)) {
+ if (!defined $value
+ || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value))
+ {
+ # If the value is undefined remove it from the hash.
+ delete $initial->{$key};
+ }
+ else {
+ _strip_undefs($value);
+ }
}
- elsif ($type eq "ARRAY") {
- for (my $count = 0; $count < scalar @{$initial}; $count++) {
- my $value = $initial->[$count];
- if ( !defined $value
- || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value) )
- {
- # If the value is undefined remove it from the array.
- splice(@$initial, $count, 1);
- $count--;
- }
- else {
- _strip_undefs($value);
- }
- }
+ }
+ elsif ($type eq "ARRAY") {
+ for (my $count = 0; $count < scalar @{$initial}; $count++) {
+ my $value = $initial->[$count];
+ if (!defined $value
+ || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value))
+ {
+ # If the value is undefined remove it from the array.
+ splice(@$initial, $count, 1);
+ $count--;
+ }
+ else {
+ _strip_undefs($value);
+ }
}
+ }
}
sub BEGIN {
- no strict 'refs';
- for my $type (qw(double i4 int dateTime)) {
- my $method = 'as_' . $type;
- *$method = sub {
- my ($self, $value) = @_;
- if (!defined($value)) {
- return as_nil();
- }
- else {
- my $super_method = "SUPER::$method";
- return $self->$super_method($value);
- }
- }
+ no strict 'refs';
+ for my $type (qw(double i4 int dateTime)) {
+ my $method = 'as_' . $type;
+ *$method = sub {
+ my ($self, $value) = @_;
+ if (!defined($value)) {
+ return as_nil();
+ }
+ else {
+ my $super_method = "SUPER::$method";
+ return $self->$super_method($value);
+ }
}
+ }
}
sub as_nil {
- return ['nil', {}];
+ return ['nil', {}];
}
1;
diff --git a/Bugzilla/WebService/User.pm b/Bugzilla/WebService/User.pm
index 0ae76d70f..3fcc929ce 100644
--- a/Bugzilla/WebService/User.pm
+++ b/Bugzilla/WebService/User.pm
@@ -18,40 +18,35 @@ use Bugzilla::Error;
use Bugzilla::Group;
use Bugzilla::User;
use Bugzilla::Util qw(trim detaint_natural);
-use Bugzilla::WebService::Util qw(filter filter_wants validate translate params_to_objects);
+use Bugzilla::WebService::Util
+ qw(filter filter_wants validate translate params_to_objects);
use List::Util qw(first min);
# Don't need auth to login
-use constant LOGIN_EXEMPT => {
- login => 1,
- offer_account_by_email => 1,
-};
+use constant LOGIN_EXEMPT => {login => 1, offer_account_by_email => 1,};
use constant READ_ONLY => qw(
- get
+ get
);
use constant PUBLIC_METHODS => qw(
- create
- get
- login
- logout
- offer_account_by_email
- update
- valid_login
+ create
+ get
+ login
+ logout
+ offer_account_by_email
+ update
+ valid_login
);
-use constant MAPPED_FIELDS => {
- email => 'login',
- full_name => 'name',
- login_denied_text => 'disabledtext',
-};
+use constant MAPPED_FIELDS =>
+ {email => 'login', full_name => 'name', login_denied_text => 'disabledtext',};
use constant MAPPED_RETURNS => {
- login_name => 'email',
- realname => 'full_name',
- disabledtext => 'login_denied_text',
+ login_name => 'email',
+ realname => 'full_name',
+ disabledtext => 'login_denied_text',
};
##############
@@ -59,38 +54,38 @@ use constant MAPPED_RETURNS => {
##############
sub login {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- # Check to see if we are already logged in
- my $user = Bugzilla->user;
- if ($user->id) {
- return $self->_login_to_hash($user);
- }
+ # Check to see if we are already logged in
+ my $user = Bugzilla->user;
+ if ($user->id) {
+ return $self->_login_to_hash($user);
+ }
- # Username and password params are required
- foreach my $param ("login", "password") {
- (defined $params->{$param} || defined $params->{'Bugzilla_' . $param})
- || ThrowCodeError('param_required', { param => $param });
- }
+ # Username and password params are required
+ foreach my $param ("login", "password") {
+ (defined $params->{$param} || defined $params->{'Bugzilla_' . $param})
+ || ThrowCodeError('param_required', {param => $param});
+ }
- $user = Bugzilla->login();
- return $self->_login_to_hash($user);
+ $user = Bugzilla->login();
+ return $self->_login_to_hash($user);
}
sub logout {
- my $self = shift;
- Bugzilla->logout;
+ my $self = shift;
+ Bugzilla->logout;
}
sub valid_login {
- my ($self, $params) = @_;
- defined $params->{login}
- || ThrowCodeError('param_required', { param => 'login' });
- Bugzilla->login();
- if (Bugzilla->user->id && Bugzilla->user->login eq $params->{login}) {
- return $self->type('boolean', 1);
- }
- return $self->type('boolean', 0);
+ my ($self, $params) = @_;
+ defined $params->{login}
+ || ThrowCodeError('param_required', {param => 'login'});
+ Bugzilla->login();
+ if (Bugzilla->user->id && Bugzilla->user->login eq $params->{login}) {
+ return $self->type('boolean', 1);
+ }
+ return $self->type('boolean', 0);
}
#################
@@ -98,168 +93,169 @@ sub valid_login {
#################
sub offer_account_by_email {
- my $self = shift;
- my ($params) = @_;
- my $email = trim($params->{email})
- || ThrowCodeError('param_required', { param => 'email' });
-
- Bugzilla->user->check_account_creation_enabled;
- Bugzilla->user->check_and_send_account_creation_confirmation($email);
- return undef;
+ my $self = shift;
+ my ($params) = @_;
+ my $email = trim($params->{email})
+ || ThrowCodeError('param_required', {param => 'email'});
+
+ Bugzilla->user->check_account_creation_enabled;
+ Bugzilla->user->check_and_send_account_creation_confirmation($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 => $self->type('int', $user->id) };
+ 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 => $self->type('int', $user->id)};
}
-# function to return user information by passing either user ids or
+# function to return user information by passing either user ids or
# login names or both together:
-# $call = $rpc->call( 'User.get', { ids => [1,2,3],
+# $call = $rpc->call( 'User.get', { ids => [1,2,3],
# names => ['testusera@redhat.com', 'testuserb@redhat.com'] });
sub get {
- my ($self, $params) = validate(@_, 'names', 'ids', 'match', 'group_ids', 'groups');
-
- Bugzilla->switch_to_shadow_db();
-
- defined($params->{names}) || defined($params->{ids})
- || defined($params->{match})
- || ThrowCodeError('params_required',
- { function => 'User.get', params => ['ids', 'names', 'match'] });
-
- my @user_objects;
- @user_objects = map { Bugzilla::User->check($_) } @{ $params->{names} }
- if $params->{names};
-
- # start filtering to remove duplicate user ids
- my %unique_users = map { $_->id => $_ } @user_objects;
- @user_objects = values %unique_users;
-
- my @users;
-
- # If the user is not logged in: Return an error if they passed any user ids.
- # Otherwise, return a limited amount of information based on login names.
- if (!Bugzilla->user->id){
- if ($params->{ids}){
- ThrowUserError("user_access_by_id_denied");
- }
- if ($params->{match}) {
- ThrowUserError('user_access_by_match_denied');
- }
- my $in_group = $self->_filter_users_by_group(
- \@user_objects, $params);
- @users = map { filter $params, {
- id => $self->type('int', $_->id),
- real_name => $self->type('string', $_->name),
- name => $self->type('email', $_->login),
- } } @$in_group;
-
- return { users => \@users };
- }
+ my ($self, $params)
+ = validate(@_, 'names', 'ids', 'match', 'group_ids', 'groups');
- my $obj_by_ids;
- $obj_by_ids = Bugzilla::User->new_from_list($params->{ids}) if $params->{ids};
-
- # obj_by_ids are only visible to the user if they can see
- # the otheruser, for non visible otheruser throw an error
- foreach my $obj (@$obj_by_ids) {
- if (Bugzilla->user->can_see_user($obj)){
- if (!$unique_users{$obj->id}) {
- push (@user_objects, $obj);
- $unique_users{$obj->id} = $obj;
- }
- }
- else {
- ThrowUserError('auth_failure', {reason => "not_visible",
- action => "access",
- object => "user",
- userid => $obj->id});
- }
- }
+ Bugzilla->switch_to_shadow_db();
- # User Matching
- my $limit = Bugzilla->params->{maxusermatches};
- if ($params->{limit}) {
- detaint_natural($params->{limit})
- || ThrowCodeError('param_must_be_numeric',
- { function => 'Bugzilla::WebService::User::match',
- param => 'limit' });
- $limit = $limit ? min($params->{limit}, $limit) : $params->{limit};
- }
+ defined($params->{names})
+ || defined($params->{ids})
+ || defined($params->{match})
+ || ThrowCodeError('params_required',
+ {function => 'User.get', params => ['ids', 'names', 'match']});
- my $exclude_disabled = $params->{'include_disabled'} ? 0 : 1;
- foreach my $match_string (@{ $params->{'match'} || [] }) {
- my $matched = Bugzilla::User::match($match_string, $limit, $exclude_disabled);
- foreach my $user (@$matched) {
- if (!$unique_users{$user->id}) {
- push(@user_objects, $user);
- $unique_users{$user->id} = $user;
- }
- }
- }
+ my @user_objects;
+ @user_objects = map { Bugzilla::User->check($_) } @{$params->{names}}
+ if $params->{names};
+
+ # start filtering to remove duplicate user ids
+ my %unique_users = map { $_->id => $_ } @user_objects;
+ @user_objects = values %unique_users;
+ my @users;
+
+ # If the user is not logged in: Return an error if they passed any user ids.
+ # Otherwise, return a limited amount of information based on login names.
+ if (!Bugzilla->user->id) {
+ if ($params->{ids}) {
+ ThrowUserError("user_access_by_id_denied");
+ }
+ if ($params->{match}) {
+ ThrowUserError('user_access_by_match_denied');
+ }
my $in_group = $self->_filter_users_by_group(\@user_objects, $params);
- foreach my $user (@$in_group) {
- my $user_info = filter $params, {
- id => $self->type('int', $user->id),
- real_name => $self->type('string', $user->name),
- name => $self->type('email', $user->login),
- email => $self->type('email', $user->email),
- can_login => $self->type('boolean', $user->is_enabled ? 1 : 0),
- };
-
- if (Bugzilla->user->in_group('editusers')) {
- $user_info->{email_enabled} = $self->type('boolean', $user->email_enabled);
- $user_info->{login_denied_text} = $self->type('string', $user->disabledtext);
+ @users = map {
+ filter $params,
+ {
+ id => $self->type('int', $_->id),
+ real_name => $self->type('string', $_->name),
+ name => $self->type('email', $_->login),
}
-
- if (Bugzilla->user->id == $user->id) {
- if (filter_wants($params, 'saved_searches')) {
- $user_info->{saved_searches} = [
- map { $self->_query_to_hash($_) } @{ $user->queries }
- ];
- }
- if (filter_wants($params, 'saved_reports')) {
- $user_info->{saved_reports} = [
- map { $self->_report_to_hash($_) } @{ $user->reports }
- ];
- }
+ } @$in_group;
+
+ return {users => \@users};
+ }
+
+ my $obj_by_ids;
+ $obj_by_ids = Bugzilla::User->new_from_list($params->{ids}) if $params->{ids};
+
+ # obj_by_ids are only visible to the user if they can see
+ # the otheruser, for non visible otheruser throw an error
+ foreach my $obj (@$obj_by_ids) {
+ if (Bugzilla->user->can_see_user($obj)) {
+ if (!$unique_users{$obj->id}) {
+ push(@user_objects, $obj);
+ $unique_users{$obj->id} = $obj;
+ }
+ }
+ else {
+ ThrowUserError(
+ 'auth_failure',
+ {
+ reason => "not_visible",
+ action => "access",
+ object => "user",
+ userid => $obj->id
}
+ );
+ }
+ }
+
+ # User Matching
+ my $limit = Bugzilla->params->{maxusermatches};
+ if ($params->{limit}) {
+ detaint_natural($params->{limit})
+ || ThrowCodeError('param_must_be_numeric',
+ {function => 'Bugzilla::WebService::User::match', param => 'limit'});
+ $limit = $limit ? min($params->{limit}, $limit) : $params->{limit};
+ }
+
+ my $exclude_disabled = $params->{'include_disabled'} ? 0 : 1;
+ foreach my $match_string (@{$params->{'match'} || []}) {
+ my $matched = Bugzilla::User::match($match_string, $limit, $exclude_disabled);
+ foreach my $user (@$matched) {
+ if (!$unique_users{$user->id}) {
+ push(@user_objects, $user);
+ $unique_users{$user->id} = $user;
+ }
+ }
+ }
+
+ my $in_group = $self->_filter_users_by_group(\@user_objects, $params);
+ foreach my $user (@$in_group) {
+ my $user_info = filter $params,
+ {
+ id => $self->type('int', $user->id),
+ real_name => $self->type('string', $user->name),
+ name => $self->type('email', $user->login),
+ email => $self->type('email', $user->email),
+ can_login => $self->type('boolean', $user->is_enabled ? 1 : 0),
+ };
+
+ if (Bugzilla->user->in_group('editusers')) {
+ $user_info->{email_enabled} = $self->type('boolean', $user->email_enabled);
+ $user_info->{login_denied_text} = $self->type('string', $user->disabledtext);
+ }
- if (filter_wants($params, 'groups')) {
- if (Bugzilla->user->id == $user->id || Bugzilla->user->in_group('editusers')) {
- $user_info->{groups} = [
- map { $self->_group_to_hash($_) } @{ $user->groups }
- ];
- }
- else {
- $user_info->{groups} = $self->_filter_bless_groups($user->groups);
- }
- }
+ if (Bugzilla->user->id == $user->id) {
+ if (filter_wants($params, 'saved_searches')) {
+ $user_info->{saved_searches}
+ = [map { $self->_query_to_hash($_) } @{$user->queries}];
+ }
+ if (filter_wants($params, 'saved_reports')) {
+ $user_info->{saved_reports}
+ = [map { $self->_report_to_hash($_) } @{$user->reports}];
+ }
+ }
- push(@users, $user_info);
+ if (filter_wants($params, 'groups')) {
+ if (Bugzilla->user->id == $user->id || Bugzilla->user->in_group('editusers')) {
+ $user_info->{groups} = [map { $self->_group_to_hash($_) } @{$user->groups}];
+ }
+ else {
+ $user_info->{groups} = $self->_filter_bless_groups($user->groups);
+ }
}
- return { users => \@users };
+ push(@users, $user_info);
+ }
+
+ return {users => \@users};
}
###############
@@ -267,156 +263,157 @@ sub get {
###############
sub update {
- my ($self, $params) = @_;
-
- my $dbh = Bugzilla->dbh;
+ my ($self, $params) = @_;
- my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
- # Reject access if there is no sense in continuing.
- $user->in_group('editusers')
- || ThrowUserError("auth_failure", {group => "editusers",
- action => "edit",
- object => "users"});
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
- defined($params->{names}) || defined($params->{ids})
- || ThrowCodeError('params_required',
- { function => 'User.update', params => ['ids', 'names'] });
+ # Reject access if there is no sense in continuing.
+ $user->in_group('editusers')
+ || ThrowUserError("auth_failure",
+ {group => "editusers", action => "edit", object => "users"});
- my $user_objects = params_to_objects($params, 'Bugzilla::User');
+ defined($params->{names})
+ || defined($params->{ids})
+ || ThrowCodeError('params_required',
+ {function => 'User.update', params => ['ids', 'names']});
- my $values = translate($params, MAPPED_FIELDS);
+ my $user_objects = params_to_objects($params, 'Bugzilla::User');
- # We delete names and ids to keep only new values to set.
- delete $values->{names};
- delete $values->{ids};
+ my $values = translate($params, MAPPED_FIELDS);
- $dbh->bz_start_transaction();
- foreach my $user (@$user_objects){
- $user->set_all($values);
- }
+ # We delete names and ids to keep only new values to set.
+ delete $values->{names};
+ delete $values->{ids};
- my %changes;
- foreach my $user (@$user_objects){
- my $returned_changes = $user->update();
- $changes{$user->id} = translate($returned_changes, MAPPED_RETURNS);
- }
- $dbh->bz_commit_transaction();
-
- my @result;
- foreach my $user (@$user_objects) {
- my %hash = (
- id => $user->id,
- changes => {},
- );
-
- foreach my $field (keys %{ $changes{$user->id} }) {
- my $change = $changes{$user->id}->{$field};
- # We normalize undef to an empty string, so that the API
- # stays consistent for things that can become empty.
- $change->[0] = '' if !defined $change->[0];
- $change->[1] = '' if !defined $change->[1];
- # We also flatten arrays (used by groups and blessed_groups)
- $change->[0] = join(',', @{$change->[0]}) if ref $change->[0];
- $change->[1] = join(',', @{$change->[1]}) if ref $change->[1];
-
- $hash{changes}{$field} = {
- removed => $self->type('string', $change->[0]),
- added => $self->type('string', $change->[1])
- };
- }
+ $dbh->bz_start_transaction();
+ foreach my $user (@$user_objects) {
+ $user->set_all($values);
+ }
- push(@result, \%hash);
- }
+ my %changes;
+ foreach my $user (@$user_objects) {
+ my $returned_changes = $user->update();
+ $changes{$user->id} = translate($returned_changes, MAPPED_RETURNS);
+ }
+ $dbh->bz_commit_transaction();
- return { users => \@result };
-}
+ my @result;
+ foreach my $user (@$user_objects) {
+ my %hash = (id => $user->id, changes => {},);
-sub _filter_users_by_group {
- my ($self, $users, $params) = @_;
- my ($group_ids, $group_names) = @$params{qw(group_ids groups)};
+ foreach my $field (keys %{$changes{$user->id}}) {
+ my $change = $changes{$user->id}->{$field};
- # If no groups are specified, we return all users.
- return $users if (!$group_ids and !$group_names);
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things that can become empty.
+ $change->[0] = '' if !defined $change->[0];
+ $change->[1] = '' if !defined $change->[1];
- my $user = Bugzilla->user;
- my (@groups, %groups);
+ # We also flatten arrays (used by groups and blessed_groups)
+ $change->[0] = join(',', @{$change->[0]}) if ref $change->[0];
+ $change->[1] = join(',', @{$change->[1]}) if ref $change->[1];
- if ($group_ids) {
- @groups = map { Bugzilla::Group->check({ id => $_ }) } @$group_ids;
- $groups{$_->id} = $_ foreach @groups;
+ $hash{changes}{$field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
}
- if ($group_names) {
- foreach my $name (@$group_names) {
- my $group = Bugzilla::Group->check({ name => $name, _error => 'invalid_group_name' });
- $user->in_group($group) || ThrowUserError('invalid_group_name', { name => $name });
- $groups{$group->id} = $group;
- }
+
+ push(@result, \%hash);
+ }
+
+ return {users => \@result};
+}
+
+sub _filter_users_by_group {
+ my ($self, $users, $params) = @_;
+ my ($group_ids, $group_names) = @$params{qw(group_ids groups)};
+
+ # If no groups are specified, we return all users.
+ return $users if (!$group_ids and !$group_names);
+
+ my $user = Bugzilla->user;
+ my (@groups, %groups);
+
+ if ($group_ids) {
+ @groups = map { Bugzilla::Group->check({id => $_}) } @$group_ids;
+ $groups{$_->id} = $_ foreach @groups;
+ }
+ if ($group_names) {
+ foreach my $name (@$group_names) {
+ my $group
+ = Bugzilla::Group->check({name => $name, _error => 'invalid_group_name'});
+ $user->in_group($group)
+ || ThrowUserError('invalid_group_name', {name => $name});
+ $groups{$group->id} = $group;
}
- @groups = values %groups;
+ }
+ @groups = values %groups;
- my @in_group = grep { $self->_user_in_any_group($_, \@groups) } @$users;
- return \@in_group;
+ my @in_group = grep { $self->_user_in_any_group($_, \@groups) } @$users;
+ return \@in_group;
}
sub _user_in_any_group {
- my ($self, $user, $groups) = @_;
- foreach my $group (@$groups) {
- return 1 if $user->in_group($group);
- }
- return 0;
+ my ($self, $user, $groups) = @_;
+ foreach my $group (@$groups) {
+ return 1 if $user->in_group($group);
+ }
+ return 0;
}
sub _filter_bless_groups {
- my ($self, $groups) = @_;
- my $user = Bugzilla->user;
+ my ($self, $groups) = @_;
+ my $user = Bugzilla->user;
- my @filtered_groups;
- foreach my $group (@$groups) {
- next unless $user->can_bless($group->id);
- push(@filtered_groups, $self->_group_to_hash($group));
- }
+ my @filtered_groups;
+ foreach my $group (@$groups) {
+ next unless $user->can_bless($group->id);
+ push(@filtered_groups, $self->_group_to_hash($group));
+ }
- return \@filtered_groups;
+ return \@filtered_groups;
}
sub _group_to_hash {
- my ($self, $group) = @_;
- my $item = {
- id => $self->type('int', $group->id),
- name => $self->type('string', $group->name),
- description => $self->type('string', $group->description),
- };
- return $item;
+ my ($self, $group) = @_;
+ my $item = {
+ id => $self->type('int', $group->id),
+ name => $self->type('string', $group->name),
+ description => $self->type('string', $group->description),
+ };
+ return $item;
}
sub _query_to_hash {
- my ($self, $query) = @_;
- my $item = {
- id => $self->type('int', $query->id),
- name => $self->type('string', $query->name),
- query => $self->type('string', $query->url),
- };
- return $item;
+ my ($self, $query) = @_;
+ my $item = {
+ id => $self->type('int', $query->id),
+ name => $self->type('string', $query->name),
+ query => $self->type('string', $query->url),
+ };
+ return $item;
}
sub _report_to_hash {
- my ($self, $report) = @_;
- my $item = {
- id => $self->type('int', $report->id),
- name => $self->type('string', $report->name),
- query => $self->type('string', $report->query),
- };
- return $item;
+ my ($self, $report) = @_;
+ my $item = {
+ id => $self->type('int', $report->id),
+ name => $self->type('string', $report->name),
+ query => $self->type('string', $report->query),
+ };
+ return $item;
}
sub _login_to_hash {
- my ($self, $user) = @_;
- my $item = { id => $self->type('int', $user->id) };
- if ($user->{_login_token}) {
- $item->{'token'} = $user->id . "-" . $user->{_login_token};
- }
- return $item;
+ my ($self, $user) = @_;
+ my $item = {id => $self->type('int', $user->id)};
+ if ($user->{_login_token}) {
+ $item->{'token'} = $user->id . "-" . $user->{_login_token};
+ }
+ return $item;
}
1;
diff --git a/Bugzilla/WebService/Util.pm b/Bugzilla/WebService/Util.pm
index a879c0e0d..3e70921b3 100644
--- a/Bugzilla/WebService/Util.pm
+++ b/Bugzilla/WebService/Util.pm
@@ -25,269 +25,277 @@ use parent qw(Exporter);
require Test::Taint;
our @EXPORT_OK = qw(
- extract_flags
- filter
- filter_wants
- taint_data
- validate
- translate
- params_to_objects
- fix_credentials
+ extract_flags
+ filter
+ filter_wants
+ taint_data
+ validate
+ translate
+ params_to_objects
+ fix_credentials
);
sub extract_flags {
- my ($flags, $bug, $attachment) = @_;
- my (@new_flags, @old_flags);
+ my ($flags, $bug, $attachment) = @_;
+ my (@new_flags, @old_flags);
- my $flag_types = $attachment ? $attachment->flag_types : $bug->flag_types;
- my $current_flags = $attachment ? $attachment->flags : $bug->flags;
+ my $flag_types = $attachment ? $attachment->flag_types : $bug->flag_types;
+ my $current_flags = $attachment ? $attachment->flags : $bug->flags;
- # Copy the user provided $flags as we may call extract_flags more than
- # once when editing multiple bugs or attachments.
- my $flags_copy = dclone($flags);
+ # Copy the user provided $flags as we may call extract_flags more than
+ # once when editing multiple bugs or attachments.
+ my $flags_copy = dclone($flags);
- foreach my $flag (@$flags_copy) {
- my $id = $flag->{id};
- my $type_id = $flag->{type_id};
+ foreach my $flag (@$flags_copy) {
+ my $id = $flag->{id};
+ my $type_id = $flag->{type_id};
- my $new = delete $flag->{new};
- my $name = delete $flag->{name};
+ my $new = delete $flag->{new};
+ my $name = delete $flag->{name};
- if ($id) {
- my $flag_obj = grep($id == $_->id, @$current_flags);
- $flag_obj || ThrowUserError('object_does_not_exist',
- { class => 'Bugzilla::Flag', id => $id });
- }
- elsif ($type_id) {
- my $type_obj = grep($type_id == $_->id, @$flag_types);
- $type_obj || ThrowUserError('object_does_not_exist',
- { class => 'Bugzilla::FlagType', id => $type_id });
- if (!$new) {
- my @flag_matches = grep($type_id == $_->type->id, @$current_flags);
- @flag_matches > 1 && ThrowUserError('flag_not_unique',
- { value => $type_id });
- if (!@flag_matches) {
- delete $flag->{id};
- }
- else {
- delete $flag->{type_id};
- $flag->{id} = $flag_matches[0]->id;
- }
- }
+ if ($id) {
+ my $flag_obj = grep($id == $_->id, @$current_flags);
+ $flag_obj
+ || ThrowUserError('object_does_not_exist',
+ {class => 'Bugzilla::Flag', id => $id});
+ }
+ elsif ($type_id) {
+ my $type_obj = grep($type_id == $_->id, @$flag_types);
+ $type_obj
+ || ThrowUserError('object_does_not_exist',
+ {class => 'Bugzilla::FlagType', id => $type_id});
+ if (!$new) {
+ my @flag_matches = grep($type_id == $_->type->id, @$current_flags);
+ @flag_matches > 1 && ThrowUserError('flag_not_unique', {value => $type_id});
+ if (!@flag_matches) {
+ delete $flag->{id};
}
- elsif ($name) {
- my @type_matches = grep($name eq $_->name, @$flag_types);
- @type_matches > 1 && ThrowUserError('flag_type_not_unique',
- { value => $name });
- @type_matches || ThrowUserError('object_does_not_exist',
- { class => 'Bugzilla::FlagType', name => $name });
- if ($new) {
- delete $flag->{id};
- $flag->{type_id} = $type_matches[0]->id;
- }
- else {
- my @flag_matches = grep($name eq $_->type->name, @$current_flags);
- @flag_matches > 1 && ThrowUserError('flag_not_unique', { value => $name });
- if (@flag_matches) {
- $flag->{id} = $flag_matches[0]->id;
- }
- else {
- delete $flag->{id};
- $flag->{type_id} = $type_matches[0]->id;
- }
- }
+ else {
+ delete $flag->{type_id};
+ $flag->{id} = $flag_matches[0]->id;
}
-
- if ($flag->{id}) {
- push(@old_flags, $flag);
+ }
+ }
+ elsif ($name) {
+ my @type_matches = grep($name eq $_->name, @$flag_types);
+ @type_matches > 1 && ThrowUserError('flag_type_not_unique', {value => $name});
+ @type_matches
+ || ThrowUserError('object_does_not_exist',
+ {class => 'Bugzilla::FlagType', name => $name});
+ if ($new) {
+ delete $flag->{id};
+ $flag->{type_id} = $type_matches[0]->id;
+ }
+ else {
+ my @flag_matches = grep($name eq $_->type->name, @$current_flags);
+ @flag_matches > 1 && ThrowUserError('flag_not_unique', {value => $name});
+ if (@flag_matches) {
+ $flag->{id} = $flag_matches[0]->id;
}
else {
- push(@new_flags, $flag);
+ delete $flag->{id};
+ $flag->{type_id} = $type_matches[0]->id;
}
+ }
}
- return (\@old_flags, \@new_flags);
+ if ($flag->{id}) {
+ push(@old_flags, $flag);
+ }
+ else {
+ push(@new_flags, $flag);
+ }
+ }
+
+ return (\@old_flags, \@new_flags);
}
sub filter($$;$$) {
- my ($params, $hash, $types, $prefix) = @_;
- my %newhash = %$hash;
+ my ($params, $hash, $types, $prefix) = @_;
+ my %newhash = %$hash;
- foreach my $key (keys %$hash) {
- delete $newhash{$key} if !filter_wants($params, $key, $types, $prefix);
- }
+ foreach my $key (keys %$hash) {
+ delete $newhash{$key} if !filter_wants($params, $key, $types, $prefix);
+ }
- return \%newhash;
+ return \%newhash;
}
sub filter_wants($$;$$) {
- my ($params, $field, $types, $prefix) = @_;
-
- # Since this is operation is resource intensive, we will cache the results
- # This assumes that $params->{*_fields} doesn't change between calls
- my $cache = Bugzilla->request_cache->{filter_wants} ||= {};
- $field = "${prefix}.${field}" if $prefix;
-
- if (exists $cache->{$field}) {
- return $cache->{$field};
- }
-
- # Mimic old behavior if no types provided
- my %field_types = map { $_ => 1 } (ref $types ? @$types : ($types || 'default'));
-
- my %include = map { $_ => 1 } @{ $params->{'include_fields'} || [] };
- my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] };
-
- my %include_types;
- my %exclude_types;
-
- # Only return default fields if nothing is specified
- $include_types{default} = 1 if !%include;
-
- # Look for any field types requested
- foreach my $key (keys %include) {
- next if $key !~ /^_(.*)$/;
- $include_types{$1} = 1;
- delete $include{$key};
- }
- foreach my $key (keys %exclude) {
- next if $key !~ /^_(.*)$/;
- $exclude_types{$1} = 1;
- delete $exclude{$key};
- }
-
- # Explicit inclusion/exclusion
- return $cache->{$field} = 0 if $exclude{$field};
- return $cache->{$field} = 1 if $include{$field};
-
- # If the user has asked to include all or exclude all
- return $cache->{$field} = 0 if $exclude_types{'all'};
- return $cache->{$field} = 1 if $include_types{'all'};
-
- # If the user has not asked for any fields specifically or if the user has asked
- # for one or more of the field's types (and not excluded them)
- foreach my $type (keys %field_types) {
- return $cache->{$field} = 0 if $exclude_types{$type};
- return $cache->{$field} = 1 if $include_types{$type};
- }
-
- my $wants = 0;
- if ($prefix) {
- # Include the field if the parent is include (and this one is not excluded)
- $wants = 1 if $include{$prefix};
- }
- else {
- # We want to include this if one of the sub keys is included
- my $key = $field . '.';
- my $len = length($key);
- $wants = 1 if grep { substr($_, 0, $len) eq $key } keys %include;
- }
-
- return $cache->{$field} = $wants;
+ my ($params, $field, $types, $prefix) = @_;
+
+ # Since this is operation is resource intensive, we will cache the results
+ # This assumes that $params->{*_fields} doesn't change between calls
+ my $cache = Bugzilla->request_cache->{filter_wants} ||= {};
+ $field = "${prefix}.${field}" if $prefix;
+
+ if (exists $cache->{$field}) {
+ return $cache->{$field};
+ }
+
+ # Mimic old behavior if no types provided
+ my %field_types
+ = map { $_ => 1 } (ref $types ? @$types : ($types || 'default'));
+
+ my %include = map { $_ => 1 } @{$params->{'include_fields'} || []};
+ my %exclude = map { $_ => 1 } @{$params->{'exclude_fields'} || []};
+
+ my %include_types;
+ my %exclude_types;
+
+ # Only return default fields if nothing is specified
+ $include_types{default} = 1 if !%include;
+
+ # Look for any field types requested
+ foreach my $key (keys %include) {
+ next if $key !~ /^_(.*)$/;
+ $include_types{$1} = 1;
+ delete $include{$key};
+ }
+ foreach my $key (keys %exclude) {
+ next if $key !~ /^_(.*)$/;
+ $exclude_types{$1} = 1;
+ delete $exclude{$key};
+ }
+
+ # Explicit inclusion/exclusion
+ return $cache->{$field} = 0 if $exclude{$field};
+ return $cache->{$field} = 1 if $include{$field};
+
+ # If the user has asked to include all or exclude all
+ return $cache->{$field} = 0 if $exclude_types{'all'};
+ return $cache->{$field} = 1 if $include_types{'all'};
+
+ # If the user has not asked for any fields specifically or if the user has asked
+ # for one or more of the field's types (and not excluded them)
+ foreach my $type (keys %field_types) {
+ return $cache->{$field} = 0 if $exclude_types{$type};
+ return $cache->{$field} = 1 if $include_types{$type};
+ }
+
+ my $wants = 0;
+ if ($prefix) {
+
+ # Include the field if the parent is include (and this one is not excluded)
+ $wants = 1 if $include{$prefix};
+ }
+ else {
+ # We want to include this if one of the sub keys is included
+ my $key = $field . '.';
+ my $len = length($key);
+ $wants = 1 if grep { substr($_, 0, $len) eq $key } keys %include;
+ }
+
+ return $cache->{$field} = $wants;
}
sub taint_data {
- my @params = @_;
- return if !@params;
- # Though this is a private function, it hasn't changed since 2004 and
- # should be safe to use, and prevents us from having to write it ourselves
- # or require another module to do it.
- Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params);
- Test::Taint::taint_deeply(\@params);
+ my @params = @_;
+ return if !@params;
+
+ # Though this is a private function, it hasn't changed since 2004 and
+ # should be safe to use, and prevents us from having to write it ourselves
+ # or require another module to do it.
+ Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params);
+ Test::Taint::taint_deeply(\@params);
}
sub _delete_bad_keys {
- foreach my $item (@_) {
- next if ref $item ne 'HASH';
- foreach my $key (keys %$item) {
- # Making something a hash key always untaints it, in Perl.
- # However, we need to validate our argument names in some way.
- # We know that all hash keys passed in to the WebService will
- # match \w+, contain '.' or '-', so we delete any key that
- # doesn't match that.
- if ($key !~ /^[\w\.\-]+$/) {
- delete $item->{$key};
- }
- }
+ foreach my $item (@_) {
+ next if ref $item ne 'HASH';
+ foreach my $key (keys %$item) {
+
+ # Making something a hash key always untaints it, in Perl.
+ # However, we need to validate our argument names in some way.
+ # We know that all hash keys passed in to the WebService will
+ # match \w+, contain '.' or '-', so we delete any key that
+ # doesn't match that.
+ if ($key !~ /^[\w\.\-]+$/) {
+ delete $item->{$key};
+ }
}
- return @_;
+ }
+ return @_;
}
-sub validate {
- my ($self, $params, @keys) = @_;
-
- # If $params is defined but not a reference, then we weren't
- # sent any parameters at all, and we're getting @keys where
- # $params should be.
- return ($self, undef) if (defined $params and !ref $params);
-
- my @id_params = qw(ids comment_ids);
- # If @keys is not empty then we convert any named
- # parameters that have scalar values to arrayrefs
- # that match.
- foreach my $key (@keys) {
- if (exists $params->{$key}) {
- $params->{$key} = [ $params->{$key} ] unless ref $params->{$key};
-
- if (any { $key eq $_ } @id_params) {
- my $ids = $params->{$key};
- ThrowCodeError('param_scalar_array_required', { param => $key })
- unless ref($ids) eq 'ARRAY' && none { ref $_ } @$ids;
- }
- }
+sub validate {
+ my ($self, $params, @keys) = @_;
+
+ # If $params is defined but not a reference, then we weren't
+ # sent any parameters at all, and we're getting @keys where
+ # $params should be.
+ return ($self, undef) if (defined $params and !ref $params);
+
+ my @id_params = qw(ids comment_ids);
+
+ # If @keys is not empty then we convert any named
+ # parameters that have scalar values to arrayrefs
+ # that match.
+ foreach my $key (@keys) {
+ if (exists $params->{$key}) {
+ $params->{$key} = [$params->{$key}] unless ref $params->{$key};
+
+ if (any { $key eq $_ } @id_params) {
+ my $ids = $params->{$key};
+ ThrowCodeError('param_scalar_array_required', {param => $key})
+ unless ref($ids) eq 'ARRAY' && none { ref $_ } @$ids;
+ }
}
+ }
- return ($self, $params);
+ return ($self, $params);
}
sub translate {
- my ($params, $mapped) = @_;
- my %changes;
- while (my ($key,$value) = each (%$params)) {
- my $new_field = $mapped->{$key} || $key;
- $changes{$new_field} = $value;
- }
- return \%changes;
+ my ($params, $mapped) = @_;
+ my %changes;
+ while (my ($key, $value) = each(%$params)) {
+ my $new_field = $mapped->{$key} || $key;
+ $changes{$new_field} = $value;
+ }
+ return \%changes;
}
sub params_to_objects {
- my ($params, $class) = @_;
- my (@objects, @objects_by_ids);
+ my ($params, $class) = @_;
+ my (@objects, @objects_by_ids);
- @objects = map { $class->check($_) }
- @{ $params->{names} } if $params->{names};
+ @objects = map { $class->check($_) } @{$params->{names}} if $params->{names};
- @objects_by_ids = map { $class->check({ id => $_ }) }
- @{ $params->{ids} } if $params->{ids};
+ @objects_by_ids = map { $class->check({id => $_}) } @{$params->{ids}}
+ if $params->{ids};
- push(@objects, @objects_by_ids);
- my %seen;
- @objects = grep { !$seen{$_->id}++ } @objects;
- return \@objects;
+ push(@objects, @objects_by_ids);
+ my %seen;
+ @objects = grep { !$seen{$_->id}++ } @objects;
+ return \@objects;
}
sub fix_credentials {
- my ($params) = @_;
- # Allow user to pass in login=foo&password=bar as a convenience
- # even if not calling GET /login. We also do not delete them as
- # GET /login requires "login" and "password".
- if (exists $params->{'login'} && exists $params->{'password'}) {
- $params->{'Bugzilla_login'} = delete $params->{'login'};
- $params->{'Bugzilla_password'} = delete $params->{'password'};
- }
- # Allow user to pass api_key=12345678 as a convenience which becomes
- # "Bugzilla_api_key" which is what the auth code looks for.
- if (exists $params->{api_key}) {
- $params->{Bugzilla_api_key} = delete $params->{api_key};
- }
- # Allow user to pass token=12345678 as a convenience which becomes
- # "Bugzilla_token" which is what the auth code looks for.
- if (exists $params->{'token'}) {
- $params->{'Bugzilla_token'} = delete $params->{'token'};
- }
-
- # Allow extensions to modify the credential data before login
- Bugzilla::Hook::process('webservice_fix_credentials', { params => $params });
+ my ($params) = @_;
+
+ # Allow user to pass in login=foo&password=bar as a convenience
+ # even if not calling GET /login. We also do not delete them as
+ # GET /login requires "login" and "password".
+ if (exists $params->{'login'} && exists $params->{'password'}) {
+ $params->{'Bugzilla_login'} = delete $params->{'login'};
+ $params->{'Bugzilla_password'} = delete $params->{'password'};
+ }
+
+ # Allow user to pass api_key=12345678 as a convenience which becomes
+ # "Bugzilla_api_key" which is what the auth code looks for.
+ if (exists $params->{api_key}) {
+ $params->{Bugzilla_api_key} = delete $params->{api_key};
+ }
+
+ # Allow user to pass token=12345678 as a convenience which becomes
+ # "Bugzilla_token" which is what the auth code looks for.
+ if (exists $params->{'token'}) {
+ $params->{'Bugzilla_token'} = delete $params->{'token'};
+ }
+
+ # Allow extensions to modify the credential data before login
+ Bugzilla::Hook::process('webservice_fix_credentials', {params => $params});
}
__END__
diff --git a/Bugzilla/Whine.pm b/Bugzilla/Whine.pm
index eeaea6da4..081933cba 100644
--- a/Bugzilla/Whine.pm
+++ b/Bugzilla/Whine.pm
@@ -27,11 +27,11 @@ use Bugzilla::Whine::Query;
use constant DB_TABLE => 'whine_events';
use constant DB_COLUMNS => qw(
- id
- owner_userid
- subject
- body
- mailifnobugs
+ id
+ owner_userid
+ subject
+ body
+ mailifnobugs
);
use constant LIST_ORDER => 'id';
@@ -39,15 +39,15 @@ use constant LIST_ORDER => 'id';
####################
# Simple Accessors #
####################
-sub subject { return $_[0]->{'subject'}; }
-sub body { return $_[0]->{'body'}; }
+sub subject { return $_[0]->{'subject'}; }
+sub body { return $_[0]->{'body'}; }
sub mail_if_no_bugs { return $_[0]->{'mailifnobugs'}; }
sub user {
- my ($self) = @_;
- return $self->{user} if defined $self->{user};
- $self->{user} = new Bugzilla::User($self->{'owner_userid'});
- return $self->{user};
+ my ($self) = @_;
+ return $self->{user} if defined $self->{user};
+ $self->{user} = new Bugzilla::User($self->{'owner_userid'});
+ return $self->{user};
}
1;
diff --git a/Bugzilla/Whine/Query.pm b/Bugzilla/Whine/Query.pm
index b2a2c9e07..6648eab66 100644
--- a/Bugzilla/Whine/Query.pm
+++ b/Bugzilla/Whine/Query.pm
@@ -23,12 +23,12 @@ use Bugzilla::Search::Saved;
use constant DB_TABLE => 'whine_queries';
use constant DB_COLUMNS => qw(
- id
- eventid
- query_name
- sortkey
- onemailperbug
- title
+ id
+ eventid
+ query_name
+ sortkey
+ onemailperbug
+ title
);
use constant NAME_FIELD => 'id';
@@ -37,11 +37,11 @@ use constant LIST_ORDER => 'sortkey';
####################
# Simple Accessors #
####################
-sub eventid { return $_[0]->{'eventid'}; }
-sub sortkey { return $_[0]->{'sortkey'}; }
+sub eventid { return $_[0]->{'eventid'}; }
+sub sortkey { return $_[0]->{'sortkey'}; }
sub one_email_per_bug { return $_[0]->{'onemailperbug'}; }
-sub title { return $_[0]->{'title'}; }
-sub name { return $_[0]->{'query_name'}; }
+sub title { return $_[0]->{'title'}; }
+sub name { return $_[0]->{'query_name'}; }
1;
diff --git a/Bugzilla/Whine/Schedule.pm b/Bugzilla/Whine/Schedule.pm
index 11f0bf16f..7517a3f26 100644
--- a/Bugzilla/Whine/Schedule.pm
+++ b/Bugzilla/Whine/Schedule.pm
@@ -22,22 +22,22 @@ use Bugzilla::Constants;
use constant DB_TABLE => 'whine_schedules';
use constant DB_COLUMNS => qw(
- id
- eventid
- run_day
- run_time
- run_next
- mailto
- mailto_type
+ id
+ eventid
+ run_day
+ run_time
+ run_next
+ mailto
+ mailto_type
);
use constant UPDATE_COLUMNS => qw(
- eventid
- run_day
- run_time
- run_next
- mailto
- mailto_type
+ eventid
+ run_day
+ run_time
+ run_next
+ mailto
+ mailto_type
);
use constant NAME_FIELD => 'id';
use constant LIST_ORDER => 'id';
@@ -45,36 +45,38 @@ use constant LIST_ORDER => 'id';
####################
# Simple Accessors #
####################
-sub eventid { return $_[0]->{'eventid'}; }
-sub run_day { return $_[0]->{'run_day'}; }
-sub run_time { return $_[0]->{'run_time'}; }
+sub eventid { return $_[0]->{'eventid'}; }
+sub run_day { return $_[0]->{'run_day'}; }
+sub run_time { return $_[0]->{'run_time'}; }
sub mailto_is_group { return $_[0]->{'mailto_type'}; }
sub mailto {
- my $self = shift;
-
- return $self->{mailto_object} if exists $self->{mailto_object};
- my $id = $self->{'mailto'};
-
- if ($self->mailto_is_group) {
- $self->{mailto_object} = Bugzilla::Group->new($id);
- } else {
- $self->{mailto_object} = Bugzilla::User->new($id);
- }
- return $self->{mailto_object};
+ my $self = shift;
+
+ return $self->{mailto_object} if exists $self->{mailto_object};
+ my $id = $self->{'mailto'};
+
+ if ($self->mailto_is_group) {
+ $self->{mailto_object} = Bugzilla::Group->new($id);
+ }
+ else {
+ $self->{mailto_object} = Bugzilla::User->new($id);
+ }
+ return $self->{mailto_object};
}
-sub mailto_users {
- my $self = shift;
- return $self->{mailto_users} if exists $self->{mailto_users};
- my $object = $self->mailto;
-
- if ($self->mailto_is_group) {
- $self->{mailto_users} = $object->members_non_inherited if $object->is_active;
- } else {
- $self->{mailto_users} = $object;
- }
- return $self->{mailto_users};
+sub mailto_users {
+ my $self = shift;
+ return $self->{mailto_users} if exists $self->{mailto_users};
+ my $object = $self->mailto;
+
+ if ($self->mailto_is_group) {
+ $self->{mailto_users} = $object->members_non_inherited if $object->is_active;
+ }
+ else {
+ $self->{mailto_users} = $object;
+ }
+ return $self->{mailto_users};
}
1;
diff --git a/Build.PL b/Build.PL
index 024a56024..37072fc18 100644
--- a/Build.PL
+++ b/Build.PL
@@ -19,43 +19,43 @@ use Bugzilla::Install::Requirements qw(REQUIRED_MODULES OPTIONAL_MODULES);
use Bugzilla::Constants qw(BUGZILLA_VERSION);
sub requires {
- my $requirements = REQUIRED_MODULES();
- my $hrequires = {};
- foreach my $module (@$requirements) {
- $hrequires->{$module->{module}} = $module->{version};
- }
- return $hrequires;
-};
+ my $requirements = REQUIRED_MODULES();
+ my $hrequires = {};
+ foreach my $module (@$requirements) {
+ $hrequires->{$module->{module}} = $module->{version};
+ }
+ return $hrequires;
+}
sub build_requires {
- return requires();
+ return requires();
}
sub recommends {
- my $recommends = OPTIONAL_MODULES();
- my @blacklist = ('Apache-SizeLimit', 'mod_perl'); # Does not compile properly on Travis
- my $hrecommends = {};
- foreach my $module (@$recommends) {
- next if grep($_ eq $module->{package}, @blacklist);
- $hrecommends->{$module->{module}} = $module->{version};
- }
- return $hrecommends;
+ my $recommends = OPTIONAL_MODULES();
+ my @blacklist = ('Apache-SizeLimit', 'mod_perl'); # Does not compile properly on Travis
+ my $hrecommends = {};
+ foreach my $module (@$recommends) {
+ next if grep($_ eq $module->{package}, @blacklist);
+ $hrecommends->{$module->{module}} = $module->{version};
+ }
+ return $hrecommends;
}
my $build = Module::Build->new(
- module_name => 'Bugzilla',
- dist_abstract => <<END,
+ module_name => 'Bugzilla',
+ dist_abstract => <<END,
Bugzilla is a free bug-tracking system that is developed by an active
community of volunteers. You can install and use it without having to
pay any license fee.
END
- dist_version_from => 'Bugzilla/Constants.pm',
- dist_version => BUGZILLA_VERSION,
- requires => requires(),
- recommends => recommends(),
- license => 'Mozilla_2_0',
- create_readme => 0,
- create_makefile_pl => 0
+ dist_version_from => 'Bugzilla/Constants.pm',
+ dist_version => BUGZILLA_VERSION,
+ requires => requires(),
+ recommends => recommends(),
+ license => 'Mozilla_2_0',
+ create_readme => 0,
+ create_makefile_pl => 0
);
$build->create_build_script;
diff --git a/admin.cgi b/admin.cgi
index 1dc9b2c1b..2ba0cdc7f 100755
--- a/admin.cgi
+++ b/admin.cgi
@@ -16,14 +16,15 @@ use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $user = Bugzilla->login(LOGIN_REQUIRED);
+my $user = Bugzilla->login(LOGIN_REQUIRED);
print $cgi->header();
$user->can_administer
- || ThrowUserError('auth_failure', {action => 'access', object => 'administrative_pages'});
+ || ThrowUserError('auth_failure',
+ {action => 'access', object => 'administrative_pages'});
$template->process('admin/admin.html.tmpl')
|| ThrowTemplateError($template->error());
diff --git a/attachment.cgi b/attachment.cgi
index 3f0ff22ba..0d87c7fcd 100755
--- a/attachment.cgi
+++ b/attachment.cgi
@@ -16,8 +16,8 @@ use Bugzilla;
use Bugzilla::BugMail;
use Bugzilla::Constants;
use Bugzilla::Error;
-use Bugzilla::Flag;
-use Bugzilla::FlagType;
+use Bugzilla::Flag;
+use Bugzilla::FlagType;
use Bugzilla::User;
use Bugzilla::Util;
use Bugzilla::Bug;
@@ -26,15 +26,15 @@ use Bugzilla::Attachment::PatchReader;
use Bugzilla::Token;
use Encode qw(encode find_encoding);
-use Encode::MIME::Header; # Required to alter Encode::Encoding{'MIME-Q'}.
+use Encode::MIME::Header; # Required to alter Encode::Encoding{'MIME-Q'}.
# For most scripts we don't make $cgi and $template global variables. But
# when preparing Bugzilla for mod_perl, this script used these
# variables in so many subroutines that it was easier to just
# make them globals.
-local our $cgi = Bugzilla->cgi;
-local our $template = Bugzilla->template;
-local our $vars = {};
+local our $cgi = Bugzilla->cgi;
+local our $template = Bugzilla->template;
+local our $vars = {};
local $Bugzilla::CGI::ALLOW_UNSAFE_RESPONSE = 1;
# All calls to this script should contain an "action" variable whose
@@ -49,57 +49,48 @@ my $format = $cgi->param('format') || '';
# You must use the appropriate urlbase/sslbase param when doing anything
# but viewing an attachment, or a raw diff.
if ($action ne 'view'
- && (($action !~ /^(?:interdiff|diff)$/) || $format ne 'raw'))
+ && (($action !~ /^(?:interdiff|diff)$/) || $format ne 'raw'))
{
- do_ssl_redirect_if_required();
- if ($cgi->url_is_attachment_base) {
- $cgi->redirect_to_urlbase;
- }
- Bugzilla->login();
+ do_ssl_redirect_if_required();
+ if ($cgi->url_is_attachment_base) {
+ $cgi->redirect_to_urlbase;
+ }
+ Bugzilla->login();
}
# When viewing an attachment, do not request credentials if we are on
# the alternate host. Let view() decide when to call Bugzilla->login.
-if ($action eq "view")
-{
- view();
+if ($action eq "view") {
+ view();
}
-elsif ($action eq "interdiff")
-{
- interdiff();
+elsif ($action eq "interdiff") {
+ interdiff();
}
-elsif ($action eq "diff")
-{
- diff();
+elsif ($action eq "diff") {
+ diff();
}
-elsif ($action eq "viewall")
-{
- viewall();
+elsif ($action eq "viewall") {
+ viewall();
}
-elsif ($action eq "enter")
-{
- Bugzilla->login(LOGIN_REQUIRED);
- enter();
+elsif ($action eq "enter") {
+ Bugzilla->login(LOGIN_REQUIRED);
+ enter();
}
-elsif ($action eq "insert")
-{
- Bugzilla->login(LOGIN_REQUIRED);
- insert();
+elsif ($action eq "insert") {
+ Bugzilla->login(LOGIN_REQUIRED);
+ insert();
}
-elsif ($action eq "edit")
-{
- edit();
+elsif ($action eq "edit") {
+ edit();
}
-elsif ($action eq "update")
-{
- Bugzilla->login(LOGIN_REQUIRED);
- update();
+elsif ($action eq "update") {
+ Bugzilla->login(LOGIN_REQUIRED);
+ update();
}
elsif ($action eq "delete") {
- delete_attachment();
+ delete_attachment();
}
-else
-{
+else {
ThrowUserError('unknown_action', {action => $action});
}
@@ -121,72 +112,73 @@ exit;
# Returns an attachment object.
sub validateID {
- my($param, $dont_validate_access) = @_;
- $param ||= 'id';
+ my ($param, $dont_validate_access) = @_;
+ $param ||= 'id';
- # If we're not doing interdiffs, check if id wasn't specified and
- # prompt them with a page that allows them to choose an attachment.
- # Happens when calling plain attachment.cgi from the urlbar directly
- if ($param eq 'id' && !$cgi->param('id')) {
- print $cgi->header();
- $template->process("attachment/choose.html.tmpl", $vars) ||
- ThrowTemplateError($template->error());
- exit;
- }
-
- my $attach_id = $cgi->param($param);
-
- # Validate the specified attachment id. detaint kills $attach_id if
- # non-natural, so use the original value from $cgi in our exception
- # message here.
- detaint_natural($attach_id)
- || ThrowUserError("invalid_attach_id",
- { attach_id => scalar $cgi->param($param) });
-
- # Make sure the attachment exists in the database.
- my $attachment = new Bugzilla::Attachment({ id => $attach_id, cache => 1 })
- || ThrowUserError("invalid_attach_id", { attach_id => $attach_id });
-
- return $attachment if ($dont_validate_access || check_can_access($attachment));
+ # If we're not doing interdiffs, check if id wasn't specified and
+ # prompt them with a page that allows them to choose an attachment.
+ # Happens when calling plain attachment.cgi from the urlbar directly
+ if ($param eq 'id' && !$cgi->param('id')) {
+ print $cgi->header();
+ $template->process("attachment/choose.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+
+ my $attach_id = $cgi->param($param);
+
+ # Validate the specified attachment id. detaint kills $attach_id if
+ # non-natural, so use the original value from $cgi in our exception
+ # message here.
+ detaint_natural($attach_id)
+ || ThrowUserError("invalid_attach_id",
+ {attach_id => scalar $cgi->param($param)});
+
+ # Make sure the attachment exists in the database.
+ my $attachment = new Bugzilla::Attachment({id => $attach_id, cache => 1})
+ || ThrowUserError("invalid_attach_id", {attach_id => $attach_id});
+
+ return $attachment if ($dont_validate_access || check_can_access($attachment));
}
# Make sure the current user has access to the specified attachment.
sub check_can_access {
- my $attachment = shift;
- my $user = Bugzilla->user;
-
- # Make sure the user is authorized to access this attachment's bug.
- Bugzilla::Bug->check({ id => $attachment->bug_id, cache => 1 });
- if ($attachment->isprivate && $user->id != $attachment->attacher->id
- && !$user->is_insider)
- {
- ThrowUserError('auth_failure', {action => 'access',
- object => 'attachment',
- attach_id => $attachment->id});
- }
- return 1;
+ my $attachment = shift;
+ my $user = Bugzilla->user;
+
+ # Make sure the user is authorized to access this attachment's bug.
+ Bugzilla::Bug->check({id => $attachment->bug_id, cache => 1});
+ if ( $attachment->isprivate
+ && $user->id != $attachment->attacher->id
+ && !$user->is_insider)
+ {
+ ThrowUserError('auth_failure',
+ {action => 'access', object => 'attachment', attach_id => $attachment->id});
+ }
+ return 1;
}
# Determines if the attachment is public -- that is, if users who are
# not logged in have access to the attachment
sub attachmentIsPublic {
- my $attachment = shift;
+ my $attachment = shift;
- return 0 if Bugzilla->params->{'requirelogin'};
- return 0 if $attachment->isprivate;
+ return 0 if Bugzilla->params->{'requirelogin'};
+ return 0 if $attachment->isprivate;
- my $anon_user = new Bugzilla::User;
- return $anon_user->can_see_bug($attachment->bug_id);
+ my $anon_user = new Bugzilla::User;
+ return $anon_user->can_see_bug($attachment->bug_id);
}
# Validates format of a diff/interdiff. Takes a list as an parameter, which
# defines the valid format values. Will throw an error if the format is not
# in the list. Returns either the user selected or default format.
sub validateFormat {
+
# receives a list of legal formats; first item is a default
my $format = $cgi->param('format') || $_[0];
if (not grep($_ eq $format, @_)) {
- ThrowUserError("invalid_format", { format => $format, formats => \@_ });
+ ThrowUserError("invalid_format", {format => $format, formats => \@_});
}
return $format;
@@ -195,125 +187,139 @@ sub validateFormat {
# Gets the attachment object(s) generated by validateID, while ensuring
# attachbase and token authentication is used when required.
sub get_attachment {
- my @field_names = @_ ? @_ : qw(id);
-
- my %attachments;
-
- if (use_attachbase()) {
- # Load each attachment, and ensure they are all from the same bug
- my $bug_id = 0;
+ my @field_names = @_ ? @_ : qw(id);
+
+ my %attachments;
+
+ if (use_attachbase()) {
+
+ # Load each attachment, and ensure they are all from the same bug
+ my $bug_id = 0;
+ foreach my $field_name (@field_names) {
+ my $attachment = validateID($field_name, 1);
+ if (!$bug_id) {
+ $bug_id = $attachment->bug_id;
+ }
+ elsif ($attachment->bug_id != $bug_id) {
+ ThrowUserError('attachment_bug_id_mismatch');
+ }
+ $attachments{$field_name} = $attachment;
+ }
+ my @args = map { $_ . '=' . $attachments{$_}->id } @field_names;
+ my $cgi_params = $cgi->canonicalise_query(@field_names, 't', 'Bugzilla_login',
+ 'Bugzilla_password');
+ push(@args, $cgi_params) if $cgi_params;
+ my $path = 'attachment.cgi?' . join('&', @args);
+
+ # Make sure the attachment is served from the correct server.
+ if ($cgi->url_is_attachment_base($bug_id)) {
+
+ # No need to validate the token for public attachments. We cannot request
+ # credentials as we are on the alternate host.
+ if (!all_attachments_are_public(\%attachments)) {
+ my $token = $cgi->param('t');
+ my ($userid, undef, $token_data) = Bugzilla::Token::GetTokenData($token);
+ my %token_data = unpack_token_data($token_data);
+ my $valid_token = 1;
foreach my $field_name (@field_names) {
- my $attachment = validateID($field_name, 1);
- if (!$bug_id) {
- $bug_id = $attachment->bug_id;
- } elsif ($attachment->bug_id != $bug_id) {
- ThrowUserError('attachment_bug_id_mismatch');
- }
- $attachments{$field_name} = $attachment;
- }
- my @args = map { $_ . '=' . $attachments{$_}->id } @field_names;
- my $cgi_params = $cgi->canonicalise_query(@field_names, 't',
- 'Bugzilla_login', 'Bugzilla_password');
- push(@args, $cgi_params) if $cgi_params;
- my $path = 'attachment.cgi?' . join('&', @args);
-
- # Make sure the attachment is served from the correct server.
- if ($cgi->url_is_attachment_base($bug_id)) {
- # No need to validate the token for public attachments. We cannot request
- # credentials as we are on the alternate host.
- if (!all_attachments_are_public(\%attachments)) {
- my $token = $cgi->param('t');
- my ($userid, undef, $token_data) = Bugzilla::Token::GetTokenData($token);
- my %token_data = unpack_token_data($token_data);
- my $valid_token = 1;
- foreach my $field_name (@field_names) {
- my $token_id = $token_data{$field_name};
- if (!$token_id
- || !detaint_natural($token_id)
- || $attachments{$field_name}->id != $token_id)
- {
- $valid_token = 0;
- last;
- }
- }
- unless ($userid && $valid_token) {
- # Not a valid token.
- print $cgi->redirect('-location' => correct_urlbase() . $path);
- exit;
- }
- # Change current user without creating cookies.
- Bugzilla->set_user(new Bugzilla::User($userid));
- # Tokens are single use only, delete it.
- delete_token($token);
- }
- }
- elsif ($cgi->url_is_attachment_base) {
- # If we come here, this means that each bug has its own host
- # for attachments, and that we are trying to view one attachment
- # using another bug's host. That's not desired.
- $cgi->redirect_to_urlbase;
+ my $token_id = $token_data{$field_name};
+ if ( !$token_id
+ || !detaint_natural($token_id)
+ || $attachments{$field_name}->id != $token_id)
+ {
+ $valid_token = 0;
+ last;
+ }
}
- else {
- # We couldn't call Bugzilla->login earlier as we first had to
- # make sure we were not going to request credentials on the
- # alternate host.
- Bugzilla->login();
- my $attachbase = Bugzilla->params->{'attachment_base'};
- # Replace %bugid% by the ID of the bug the attachment
- # belongs to, if present.
- $attachbase =~ s/\%bugid\%/$bug_id/;
- if (all_attachments_are_public(\%attachments)) {
- # No need for a token; redirect to attachment base.
- print $cgi->redirect(-location => $attachbase . $path);
- exit;
- } else {
- # Make sure the user can view the attachment.
- foreach my $field_name (@field_names) {
- check_can_access($attachments{$field_name});
- }
- # Create a token and redirect.
- my $token = url_quote(issue_session_token(pack_token_data(\%attachments)));
- print $cgi->redirect(-location => $attachbase . "$path&t=$token");
- exit;
- }
+ unless ($userid && $valid_token) {
+
+ # Not a valid token.
+ print $cgi->redirect('-location' => correct_urlbase() . $path);
+ exit;
}
- } else {
- do_ssl_redirect_if_required();
- # No alternate host is used. Request credentials if required.
- Bugzilla->login();
+
+ # Change current user without creating cookies.
+ Bugzilla->set_user(new Bugzilla::User($userid));
+
+ # Tokens are single use only, delete it.
+ delete_token($token);
+ }
+ }
+ elsif ($cgi->url_is_attachment_base) {
+
+ # If we come here, this means that each bug has its own host
+ # for attachments, and that we are trying to view one attachment
+ # using another bug's host. That's not desired.
+ $cgi->redirect_to_urlbase;
+ }
+ else {
+ # We couldn't call Bugzilla->login earlier as we first had to
+ # make sure we were not going to request credentials on the
+ # alternate host.
+ Bugzilla->login();
+ my $attachbase = Bugzilla->params->{'attachment_base'};
+
+ # Replace %bugid% by the ID of the bug the attachment
+ # belongs to, if present.
+ $attachbase =~ s/\%bugid\%/$bug_id/;
+ if (all_attachments_are_public(\%attachments)) {
+
+ # No need for a token; redirect to attachment base.
+ print $cgi->redirect(-location => $attachbase . $path);
+ exit;
+ }
+ else {
+ # Make sure the user can view the attachment.
foreach my $field_name (@field_names) {
- $attachments{$field_name} = validateID($field_name);
+ check_can_access($attachments{$field_name});
}
+
+ # Create a token and redirect.
+ my $token = url_quote(issue_session_token(pack_token_data(\%attachments)));
+ print $cgi->redirect(-location => $attachbase . "$path&t=$token");
+ exit;
+ }
+ }
+ }
+ else {
+ do_ssl_redirect_if_required();
+
+ # No alternate host is used. Request credentials if required.
+ Bugzilla->login();
+ foreach my $field_name (@field_names) {
+ $attachments{$field_name} = validateID($field_name);
}
+ }
- return wantarray
- ? map { $attachments{$_} } @field_names
- : $attachments{$field_names[0]};
+ return
+ wantarray
+ ? map { $attachments{$_} } @field_names
+ : $attachments{$field_names[0]};
}
sub all_attachments_are_public {
- my $attachments = shift;
- foreach my $field_name (keys %$attachments) {
- if (!attachmentIsPublic($attachments->{$field_name})) {
- return 0;
- }
+ my $attachments = shift;
+ foreach my $field_name (keys %$attachments) {
+ if (!attachmentIsPublic($attachments->{$field_name})) {
+ return 0;
}
- return 1;
+ }
+ return 1;
}
sub pack_token_data {
- my $attachments = shift;
- return join(' ', map { $_ . '=' . $attachments->{$_}->id } keys %$attachments);
+ my $attachments = shift;
+ return join(' ', map { $_ . '=' . $attachments->{$_}->id } keys %$attachments);
}
sub unpack_token_data {
- my @token_data = split(/ /, shift || '');
- my %data;
- foreach my $token (@token_data) {
- my ($field_name, $attach_id) = split('=', $token);
- $data{$field_name} = $attach_id;
- }
- return %data;
+ my @token_data = split(/ /, shift || '');
+ my %data;
+ foreach my $token (@token_data) {
+ my ($field_name, $attach_id) = split('=', $token);
+ $data{$field_name} = $attach_id;
+ }
+ return %data;
}
################################################################################
@@ -322,256 +328,278 @@ sub unpack_token_data {
# Display an attachment.
sub view {
- my $attachment = get_attachment();
+ my $attachment = get_attachment();
- # At this point, Bugzilla->login has been called if it had to.
- my $contenttype = $attachment->contenttype;
- my $filename = $attachment->filename;
+ # At this point, Bugzilla->login has been called if it had to.
+ my $contenttype = $attachment->contenttype;
+ my $filename = $attachment->filename;
- # Bug 111522: allow overriding content-type manually in the posted form
- # params.
- if (defined $cgi->param('content_type')) {
- $contenttype = $attachment->_check_content_type($cgi->param('content_type'));
- }
+ # Bug 111522: allow overriding content-type manually in the posted form
+ # params.
+ if (defined $cgi->param('content_type')) {
+ $contenttype = $attachment->_check_content_type($cgi->param('content_type'));
+ }
- # Return the appropriate HTTP response headers.
- $attachment->datasize || ThrowUserError("attachment_removed");
+ # Return the appropriate HTTP response headers.
+ $attachment->datasize || ThrowUserError("attachment_removed");
- $filename =~ s/^.*[\/\\]//;
- # escape quotes and backslashes in the filename, per RFCs 2045/822
- $filename =~ s/\\/\\\\/g; # escape backslashes
- $filename =~ s/"/\\"/g; # escape quotes
+ $filename =~ s/^.*[\/\\]//;
- # Avoid line wrapping done by Encode, which we don't need for HTTP
- # headers. See discussion in bug 328628 for details.
- local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 10000;
- $filename = encode('MIME-Q', $filename);
+ # escape quotes and backslashes in the filename, per RFCs 2045/822
+ $filename =~ s/\\/\\\\/g; # escape backslashes
+ $filename =~ s/"/\\"/g; # escape quotes
- my $disposition = (Bugzilla->params->{'allow_attachment_display'} || $contenttype eq "text/plain") ? 'inline' : 'attachment';
+ # Avoid line wrapping done by Encode, which we don't need for HTTP
+ # headers. See discussion in bug 328628 for details.
+ local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 10000;
+ $filename = encode('MIME-Q', $filename);
- # Don't send a charset header with attachments--they might not be UTF-8.
- # However, we do allow people to explicitly specify a charset if they
- # want.
- if ($contenttype !~ /\bcharset=/i) {
- # In order to prevent Apache from adding a charset, we have to send a
- # charset that's a single space.
- $cgi->charset('UTF-8');
- }
- print $cgi->header(-type=>"$contenttype; name=\"$filename\"",
- -content_disposition=> "$disposition; filename=\"$filename\"",
- -content_length => $attachment->datasize);
- disable_utf8();
- print $attachment->data;
+ my $disposition = (Bugzilla->params->{'allow_attachment_display'}
+ || $contenttype eq "text/plain") ? 'inline' : 'attachment';
+
+ # Don't send a charset header with attachments--they might not be UTF-8.
+ # However, we do allow people to explicitly specify a charset if they
+ # want.
+ if ($contenttype !~ /\bcharset=/i) {
+
+ # In order to prevent Apache from adding a charset, we have to send a
+ # charset that's a single space.
+ $cgi->charset('UTF-8');
+ }
+ print $cgi->header(
+ -type => "$contenttype; name=\"$filename\"",
+ -content_disposition => "$disposition; filename=\"$filename\"",
+ -content_length => $attachment->datasize
+ );
+ disable_utf8();
+ print $attachment->data;
}
sub interdiff {
- # Retrieve and validate parameters
- my $format = validateFormat('html', 'raw');
- my($old_attachment, $new_attachment);
- if ($format eq 'raw') {
- ($old_attachment, $new_attachment) = get_attachment('oldid', 'newid');
- } else {
- $old_attachment = validateID('oldid');
- $new_attachment = validateID('newid');
- }
- Bugzilla::Attachment::PatchReader::process_interdiff(
- $old_attachment, $new_attachment, $format);
+ # Retrieve and validate parameters
+ my $format = validateFormat('html', 'raw');
+ my ($old_attachment, $new_attachment);
+ if ($format eq 'raw') {
+ ($old_attachment, $new_attachment) = get_attachment('oldid', 'newid');
+ }
+ else {
+ $old_attachment = validateID('oldid');
+ $new_attachment = validateID('newid');
+ }
+
+ Bugzilla::Attachment::PatchReader::process_interdiff($old_attachment,
+ $new_attachment, $format);
}
sub diff {
- # Retrieve and validate parameters
- my $format = validateFormat('html', 'raw');
- my $attachment = $format eq 'raw' ? get_attachment() : validateID();
-
- # If it is not a patch, view normally.
- if (!$attachment->ispatch) {
- view();
- return;
- }
- Bugzilla::Attachment::PatchReader::process_diff($attachment, $format);
+ # Retrieve and validate parameters
+ my $format = validateFormat('html', 'raw');
+ my $attachment = $format eq 'raw' ? get_attachment() : validateID();
+
+ # If it is not a patch, view normally.
+ if (!$attachment->ispatch) {
+ view();
+ return;
+ }
+
+ Bugzilla::Attachment::PatchReader::process_diff($attachment, $format);
}
# Display all attachments for a given bug in a series of IFRAMEs within one
# HTML page.
sub viewall {
- # Retrieve and validate parameters
- my $bug = Bugzilla::Bug->check({ id => scalar $cgi->param('bugid'), cache => 1 });
- my $attachments = Bugzilla::Attachment->get_attachments_by_bug($bug);
- # Ignore deleted attachments.
- @$attachments = grep { $_->datasize } @$attachments;
+ # Retrieve and validate parameters
+ my $bug = Bugzilla::Bug->check({id => scalar $cgi->param('bugid'), cache => 1});
- if ($cgi->param('hide_obsolete')) {
- @$attachments = grep { !$_->isobsolete } @$attachments;
- $vars->{'hide_obsolete'} = 1;
- }
+ my $attachments = Bugzilla::Attachment->get_attachments_by_bug($bug);
- # Define the variables and functions that will be passed to the UI template.
- $vars->{'bug'} = $bug;
- $vars->{'attachments'} = $attachments;
+ # Ignore deleted attachments.
+ @$attachments = grep { $_->datasize } @$attachments;
- print $cgi->header();
+ if ($cgi->param('hide_obsolete')) {
+ @$attachments = grep { !$_->isobsolete } @$attachments;
+ $vars->{'hide_obsolete'} = 1;
+ }
- # Generate and return the UI (HTML page) from the appropriate template.
- $template->process("attachment/show-multiple.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ # Define the variables and functions that will be passed to the UI template.
+ $vars->{'bug'} = $bug;
+ $vars->{'attachments'} = $attachments;
+
+ print $cgi->header();
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("attachment/show-multiple.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
# Display a form for entering a new attachment.
sub enter {
- # Retrieve and validate parameters
- my $bug = Bugzilla::Bug->check(scalar $cgi->param('bugid'));
- my $bugid = $bug->id;
- Bugzilla::Attachment->_check_bug($bug);
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
-
- # Retrieve the attachments the user can edit from the database and write
- # them into an array of hashes where each hash represents one attachment.
-
- my ($can_edit, $not_private) = ('', '');
- if (!$user->in_group('editbugs', $bug->product_id)) {
- $can_edit = "AND submitter_id = " . $user->id;
- }
- if (!$user->is_insider) {
- $not_private = "AND isprivate = 0";
- }
- my $attach_ids = $dbh->selectcol_arrayref(
- "SELECT attach_id
+
+ # Retrieve and validate parameters
+ my $bug = Bugzilla::Bug->check(scalar $cgi->param('bugid'));
+ my $bugid = $bug->id;
+ Bugzilla::Attachment->_check_bug($bug);
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ # Retrieve the attachments the user can edit from the database and write
+ # them into an array of hashes where each hash represents one attachment.
+
+ my ($can_edit, $not_private) = ('', '');
+ if (!$user->in_group('editbugs', $bug->product_id)) {
+ $can_edit = "AND submitter_id = " . $user->id;
+ }
+ if (!$user->is_insider) {
+ $not_private = "AND isprivate = 0";
+ }
+ my $attach_ids = $dbh->selectcol_arrayref(
+ "SELECT attach_id
FROM attachments
WHERE bug_id = ?
AND isobsolete = 0
$can_edit $not_private
- ORDER BY attach_id",
- undef, $bugid);
-
- # Define the variables and functions that will be passed to the UI template.
- $vars->{'bug'} = $bug;
- $vars->{'attachments'} = Bugzilla::Attachment->new_from_list($attach_ids);
-
- my $flag_types = Bugzilla::FlagType::match({
- 'target_type' => 'attachment',
- 'product_id' => $bug->product_id,
- 'component_id' => $bug->component_id
- });
- $vars->{'flag_types'} = $flag_types;
- $vars->{'any_flags_requesteeble'} =
- grep { $_->is_requestable && $_->is_requesteeble } @$flag_types;
- $vars->{'token'} = issue_session_token('create_attachment');
-
- print $cgi->header();
-
- # Generate and return the UI (HTML page) from the appropriate template.
- $template->process("attachment/create.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ ORDER BY attach_id", undef, $bugid
+ );
+
+ # Define the variables and functions that will be passed to the UI template.
+ $vars->{'bug'} = $bug;
+ $vars->{'attachments'} = Bugzilla::Attachment->new_from_list($attach_ids);
+
+ my $flag_types = Bugzilla::FlagType::match({
+ 'target_type' => 'attachment',
+ 'product_id' => $bug->product_id,
+ 'component_id' => $bug->component_id
+ });
+ $vars->{'flag_types'} = $flag_types;
+ $vars->{'any_flags_requesteeble'}
+ = grep { $_->is_requestable && $_->is_requesteeble } @$flag_types;
+ $vars->{'token'} = issue_session_token('create_attachment');
+
+ print $cgi->header();
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("attachment/create.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
# Insert a new attachment into the database.
sub insert {
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
-
- $dbh->bz_start_transaction;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ $dbh->bz_start_transaction;
+
+ # Retrieve and validate parameters
+ my $bug = Bugzilla::Bug->check(scalar $cgi->param('bugid'));
+ my $bugid = $bug->id;
+ my ($timestamp) = $dbh->selectrow_array("SELECT NOW()");
+
+ # Detect if the user already used the same form to submit an attachment
+ my $token = trim($cgi->param('token'));
+ check_token_data($token, 'create_attachment', 'index.cgi');
+
+ # Check attachments the user tries to mark as obsolete.
+ my @obsolete_attachments;
+ if ($cgi->param('obsolete')) {
+ my @obsolete = $cgi->param('obsolete');
+ @obsolete_attachments
+ = Bugzilla::Attachment->validate_obsolete($bug, \@obsolete);
+ }
- # Retrieve and validate parameters
- my $bug = Bugzilla::Bug->check(scalar $cgi->param('bugid'));
- my $bugid = $bug->id;
- my ($timestamp) = $dbh->selectrow_array("SELECT NOW()");
+ # Must be called before create() as it may alter $cgi->param('ispatch').
+ my $content_type = Bugzilla::Attachment::get_content_type();
+
+ # Get the filehandle of the attachment.
+ my $data_fh = $cgi->upload('data');
+ my $attach_text = $cgi->param('attach_text');
+
+ my $attachment = Bugzilla::Attachment->create({
+ bug => $bug,
+ creation_ts => $timestamp,
+ data => $attach_text || $data_fh,
+ description => scalar $cgi->param('description'),
+ filename => $attach_text ? "file_$bugid.txt" : $data_fh,
+ ispatch => scalar $cgi->param('ispatch'),
+ isprivate => scalar $cgi->param('isprivate'),
+ mimetype => $content_type,
+ });
+
+ # Delete the token used to create this attachment.
+ delete_token($token);
+
+ foreach my $obsolete_attachment (@obsolete_attachments) {
+ $obsolete_attachment->set_is_obsolete(1);
+ $obsolete_attachment->update($timestamp);
+ }
- # Detect if the user already used the same form to submit an attachment
- my $token = trim($cgi->param('token'));
- check_token_data($token, 'create_attachment', 'index.cgi');
+ my ($flags, $new_flags)
+ = Bugzilla::Flag->extract_flags_from_cgi($bug, $attachment, $vars,
+ SKIP_REQUESTEE_ON_ERROR);
+ $attachment->set_flags($flags, $new_flags);
- # Check attachments the user tries to mark as obsolete.
- my @obsolete_attachments;
- if ($cgi->param('obsolete')) {
- my @obsolete = $cgi->param('obsolete');
- @obsolete_attachments = Bugzilla::Attachment->validate_obsolete($bug, \@obsolete);
+ # Insert a comment about the new attachment into the database.
+ my $comment = $cgi->param('comment');
+ $comment = '' unless defined $comment;
+ $bug->add_comment(
+ $comment,
+ {
+ isprivate => $attachment->isprivate,
+ type => CMT_ATTACHMENT_CREATED,
+ extra_data => $attachment->id
}
+ );
- # Must be called before create() as it may alter $cgi->param('ispatch').
- my $content_type = Bugzilla::Attachment::get_content_type();
-
- # Get the filehandle of the attachment.
- my $data_fh = $cgi->upload('data');
- my $attach_text = $cgi->param('attach_text');
-
- my $attachment = Bugzilla::Attachment->create(
- {bug => $bug,
- creation_ts => $timestamp,
- data => $attach_text || $data_fh,
- description => scalar $cgi->param('description'),
- filename => $attach_text ? "file_$bugid.txt" : $data_fh,
- ispatch => scalar $cgi->param('ispatch'),
- isprivate => scalar $cgi->param('isprivate'),
- mimetype => $content_type,
- });
-
- # Delete the token used to create this attachment.
- delete_token($token);
+ # Assign the bug to the user, if they are allowed to take it
+ my $owner = "";
+ if ($cgi->param('takebug') && $user->in_group('editbugs', $bug->product_id)) {
+
+ # When taking a bug, we have to follow the workflow.
+ my $bug_status = $cgi->param('bug_status') || '';
+ ($bug_status) = grep { $_->name eq $bug_status } @{$bug->status->can_change_to};
- foreach my $obsolete_attachment (@obsolete_attachments) {
- $obsolete_attachment->set_is_obsolete(1);
- $obsolete_attachment->update($timestamp);
+ if ( $bug_status
+ && $bug_status->is_open
+ && ($bug_status->name ne 'UNCONFIRMED' || $bug->product_obj->allows_unconfirmed)
+ )
+ {
+ $bug->set_bug_status($bug_status->name);
+ $bug->clear_resolution();
}
- my ($flags, $new_flags) = Bugzilla::Flag->extract_flags_from_cgi(
- $bug, $attachment, $vars, SKIP_REQUESTEE_ON_ERROR);
- $attachment->set_flags($flags, $new_flags);
+ # Make sure the person we are taking the bug from gets mail.
+ $owner = $bug->assigned_to->login;
+ $bug->set_assigned_to($user);
+ }
- # Insert a comment about the new attachment into the database.
- my $comment = $cgi->param('comment');
- $comment = '' unless defined $comment;
- $bug->add_comment($comment, { isprivate => $attachment->isprivate,
- type => CMT_ATTACHMENT_CREATED,
- extra_data => $attachment->id });
-
- # Assign the bug to the user, if they are allowed to take it
- my $owner = "";
- if ($cgi->param('takebug') && $user->in_group('editbugs', $bug->product_id)) {
- # When taking a bug, we have to follow the workflow.
- my $bug_status = $cgi->param('bug_status') || '';
- ($bug_status) = grep { $_->name eq $bug_status }
- @{ $bug->status->can_change_to };
-
- if ($bug_status && $bug_status->is_open
- && ($bug_status->name ne 'UNCONFIRMED'
- || $bug->product_obj->allows_unconfirmed))
- {
- $bug->set_bug_status($bug_status->name);
- $bug->clear_resolution();
- }
- # Make sure the person we are taking the bug from gets mail.
- $owner = $bug->assigned_to->login;
- $bug->set_assigned_to($user);
- }
+ $bug->add_cc($user) if $cgi->param('addselfcc');
+ $bug->update($timestamp);
- $bug->add_cc($user) if $cgi->param('addselfcc');
- $bug->update($timestamp);
+ # We have to update the attachment after updating the bug, to ensure new
+ # comments are available.
+ $attachment->update($timestamp);
- # We have to update the attachment after updating the bug, to ensure new
- # comments are available.
- $attachment->update($timestamp);
+ $dbh->bz_commit_transaction;
- $dbh->bz_commit_transaction;
+ # Define the variables and functions that will be passed to the UI template.
+ $vars->{'attachment'} = $attachment;
- # Define the variables and functions that will be passed to the UI template.
- $vars->{'attachment'} = $attachment;
- # We cannot reuse the $bug object as delta_ts has eventually been updated
- # since the object was created.
- $vars->{'bugs'} = [new Bugzilla::Bug($bugid)];
- $vars->{'header_done'} = 1;
- $vars->{'contenttypemethod'} = $cgi->param('contenttypemethod');
+ # We cannot reuse the $bug object as delta_ts has eventually been updated
+ # since the object was created.
+ $vars->{'bugs'} = [new Bugzilla::Bug($bugid)];
+ $vars->{'header_done'} = 1;
+ $vars->{'contenttypemethod'} = $cgi->param('contenttypemethod');
- my $recipients = { 'changer' => $user, 'owner' => $owner };
- $vars->{'sent_bugmail'} = Bugzilla::BugMail::Send($bugid, $recipients);
+ my $recipients = {'changer' => $user, 'owner' => $owner};
+ $vars->{'sent_bugmail'} = Bugzilla::BugMail::Send($bugid, $recipients);
- print $cgi->header();
- # Generate and return the UI (HTML page) from the appropriate template.
- $template->process("attachment/created.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ print $cgi->header();
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("attachment/created.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
# Displays a form for editing attachment properties.
@@ -579,227 +607,237 @@ sub insert {
# is private and the user does not belong to the insider group.
# Validations are done later when the user submits changes.
sub edit {
- my $attachment = validateID();
+ my $attachment = validateID();
- my $bugattachments =
- Bugzilla::Attachment->get_attachments_by_bug($attachment->bug);
+ my $bugattachments
+ = Bugzilla::Attachment->get_attachments_by_bug($attachment->bug);
- my $any_flags_requesteeble = grep { $_->is_requestable && $_->is_requesteeble }
- @{ $attachment->flag_types };
- # Useful in case a flagtype is no longer requestable but a requestee
- # has been set before we turned off that bit.
- $any_flags_requesteeble ||= grep { $_->requestee_id } @{ $attachment->flags };
- $vars->{'any_flags_requesteeble'} = $any_flags_requesteeble;
- $vars->{'attachment'} = $attachment;
- $vars->{'attachments'} = $bugattachments;
+ my $any_flags_requesteeble = grep { $_->is_requestable && $_->is_requesteeble }
+ @{$attachment->flag_types};
- print $cgi->header();
+ # Useful in case a flagtype is no longer requestable but a requestee
+ # has been set before we turned off that bit.
+ $any_flags_requesteeble ||= grep { $_->requestee_id } @{$attachment->flags};
+ $vars->{'any_flags_requesteeble'} = $any_flags_requesteeble;
+ $vars->{'attachment'} = $attachment;
+ $vars->{'attachments'} = $bugattachments;
+
+ print $cgi->header();
- # Generate and return the UI (HTML page) from the appropriate template.
- $template->process("attachment/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("attachment/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
# Updates an attachment record. Only users with "editbugs" privileges,
# (or the original attachment's submitter) can edit the attachment.
# Users cannot edit the content of the attachment itself.
sub update {
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
-
- # Start a transaction in preparation for updating the attachment.
- $dbh->bz_start_transaction();
-
- # Retrieve and validate parameters
- my $attachment = validateID();
- my $bug = $attachment->bug;
- $attachment->_check_bug;
- my $can_edit = $attachment->validate_can_edit;
-
- if ($can_edit) {
- $attachment->set_description(scalar $cgi->param('description'));
- $attachment->set_is_patch(scalar $cgi->param('ispatch'));
- $attachment->set_content_type(scalar $cgi->param('contenttypeentry'));
- $attachment->set_is_obsolete(scalar $cgi->param('isobsolete'));
- $attachment->set_is_private(scalar $cgi->param('isprivate'));
- $attachment->set_filename(scalar $cgi->param('filename'));
-
- # Now make sure the attachment has not been edited since we loaded the page.
- my $delta_ts = $cgi->param('delta_ts');
- my $modification_time = $attachment->modification_time;
-
- if ($delta_ts && $delta_ts ne $modification_time) {
- datetime_from($delta_ts)
- or ThrowCodeError('invalid_timestamp', { timestamp => $delta_ts });
- ($vars->{'operations'}) = $bug->get_activity($attachment->id, $delta_ts);
-
- # If the modification date changed but there is no entry in
- # the activity table, this means someone commented only.
- # In this case, there is no reason to midair.
- if (scalar(@{$vars->{'operations'}})) {
- $cgi->param('delta_ts', $modification_time);
- # The token contains the old modification_time. We need a new one.
- $cgi->param('token', issue_hash_token([$attachment->id, $modification_time]));
-
- $vars->{'attachment'} = $attachment;
-
- print $cgi->header();
- # Warn the user about the mid-air collision and ask them what to do.
- $template->process("attachment/midair.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
- }
- }
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+
+ # Start a transaction in preparation for updating the attachment.
+ $dbh->bz_start_transaction();
+
+ # Retrieve and validate parameters
+ my $attachment = validateID();
+ my $bug = $attachment->bug;
+ $attachment->_check_bug;
+ my $can_edit = $attachment->validate_can_edit;
+
+ if ($can_edit) {
+ $attachment->set_description(scalar $cgi->param('description'));
+ $attachment->set_is_patch(scalar $cgi->param('ispatch'));
+ $attachment->set_content_type(scalar $cgi->param('contenttypeentry'));
+ $attachment->set_is_obsolete(scalar $cgi->param('isobsolete'));
+ $attachment->set_is_private(scalar $cgi->param('isprivate'));
+ $attachment->set_filename(scalar $cgi->param('filename'));
+
+ # Now make sure the attachment has not been edited since we loaded the page.
+ my $delta_ts = $cgi->param('delta_ts');
+ my $modification_time = $attachment->modification_time;
+
+ if ($delta_ts && $delta_ts ne $modification_time) {
+ datetime_from($delta_ts)
+ or ThrowCodeError('invalid_timestamp', {timestamp => $delta_ts});
+ ($vars->{'operations'}) = $bug->get_activity($attachment->id, $delta_ts);
+
+ # If the modification date changed but there is no entry in
+ # the activity table, this means someone commented only.
+ # In this case, there is no reason to midair.
+ if (scalar(@{$vars->{'operations'}})) {
+ $cgi->param('delta_ts', $modification_time);
+
+ # The token contains the old modification_time. We need a new one.
+ $cgi->param('token', issue_hash_token([$attachment->id, $modification_time]));
- # We couldn't do this check earlier as we first had to validate attachment ID
- # and display the mid-air collision page if modification_time changed.
- my $token = $cgi->param('token');
- check_hash_token($token, [$attachment->id, $attachment->modification_time]);
-
- # If the user submitted a comment while editing the attachment,
- # add the comment to the bug. Do this after having validated isprivate!
- my $comment = $cgi->param('comment');
- if (defined $comment && trim($comment) ne '') {
- $bug->add_comment($comment, { isprivate => $attachment->isprivate,
- type => CMT_ATTACHMENT_UPDATED,
- extra_data => $attachment->id });
+ $vars->{'attachment'} = $attachment;
+
+ print $cgi->header();
+
+ # Warn the user about the mid-air collision and ask them what to do.
+ $template->process("attachment/midair.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
}
+ }
- $bug->add_cc($user) if $cgi->param('addselfcc');
+ # We couldn't do this check earlier as we first had to validate attachment ID
+ # and display the mid-air collision page if modification_time changed.
+ my $token = $cgi->param('token');
+ check_hash_token($token, [$attachment->id, $attachment->modification_time]);
+
+ # If the user submitted a comment while editing the attachment,
+ # add the comment to the bug. Do this after having validated isprivate!
+ my $comment = $cgi->param('comment');
+ if (defined $comment && trim($comment) ne '') {
+ $bug->add_comment(
+ $comment,
+ {
+ isprivate => $attachment->isprivate,
+ type => CMT_ATTACHMENT_UPDATED,
+ extra_data => $attachment->id
+ }
+ );
+ }
- my ($flags, $new_flags) =
- Bugzilla::Flag->extract_flags_from_cgi($bug, $attachment, $vars);
+ $bug->add_cc($user) if $cgi->param('addselfcc');
+
+ my ($flags, $new_flags)
+ = Bugzilla::Flag->extract_flags_from_cgi($bug, $attachment, $vars);
+
+ if ($can_edit) {
+ $attachment->set_flags($flags, $new_flags);
+ }
- if ($can_edit) {
- $attachment->set_flags($flags, $new_flags);
+ # Requestees can set flags targetted to them, even if they cannot
+ # edit the attachment. Flag setters can edit their own flags too.
+ elsif (scalar @$flags) {
+ my %flag_list = map { $_->{id} => $_ } @$flags;
+ my $flag_objs = Bugzilla::Flag->new_from_list([keys %flag_list]);
+
+ my @editable_flags;
+ foreach my $flag_obj (@$flag_objs) {
+ if ($flag_obj->setter_id == $user->id
+ || ($flag_obj->requestee_id && $flag_obj->requestee_id == $user->id))
+ {
+ push(@editable_flags, $flag_list{$flag_obj->id});
+ }
}
- # Requestees can set flags targetted to them, even if they cannot
- # edit the attachment. Flag setters can edit their own flags too.
- elsif (scalar @$flags) {
- my %flag_list = map { $_->{id} => $_ } @$flags;
- my $flag_objs = Bugzilla::Flag->new_from_list([keys %flag_list]);
-
- my @editable_flags;
- foreach my $flag_obj (@$flag_objs) {
- if ($flag_obj->setter_id == $user->id
- || ($flag_obj->requestee_id && $flag_obj->requestee_id == $user->id))
- {
- push(@editable_flags, $flag_list{$flag_obj->id});
- }
- }
- if (scalar @editable_flags) {
- $attachment->set_flags(\@editable_flags, []);
- # Flag changes must be committed.
- $can_edit = 1;
- }
+ if (scalar @editable_flags) {
+ $attachment->set_flags(\@editable_flags, []);
+
+ # Flag changes must be committed.
+ $can_edit = 1;
}
+ }
- # Figure out when the changes were made.
- my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ # Figure out when the changes were made.
+ my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- # Commit the comment, if any.
- # This has to happen before updating the attachment, to ensure new comments
- # are available to $attachment->update.
- $bug->update($timestamp);
+ # Commit the comment, if any.
+ # This has to happen before updating the attachment, to ensure new comments
+ # are available to $attachment->update.
+ $bug->update($timestamp);
- if ($can_edit) {
- my $changes = $attachment->update($timestamp);
- # If there are changes, we updated delta_ts in the DB. We have to
- # reflect this change in the bug object.
- $bug->{delta_ts} = $timestamp if scalar(keys %$changes);
- }
+ if ($can_edit) {
+ my $changes = $attachment->update($timestamp);
- # Commit the transaction now that we are finished updating the database.
- $dbh->bz_commit_transaction();
+ # If there are changes, we updated delta_ts in the DB. We have to
+ # reflect this change in the bug object.
+ $bug->{delta_ts} = $timestamp if scalar(keys %$changes);
+ }
- # Define the variables and functions that will be passed to the UI template.
- $vars->{'attachment'} = $attachment;
- $vars->{'bugs'} = [$bug];
- $vars->{'header_done'} = 1;
- $vars->{'sent_bugmail'} =
- Bugzilla::BugMail::Send($bug->id, { 'changer' => $user });
+ # Commit the transaction now that we are finished updating the database.
+ $dbh->bz_commit_transaction();
- print $cgi->header();
+ # Define the variables and functions that will be passed to the UI template.
+ $vars->{'attachment'} = $attachment;
+ $vars->{'bugs'} = [$bug];
+ $vars->{'header_done'} = 1;
+ $vars->{'sent_bugmail'}
+ = Bugzilla::BugMail::Send($bug->id, {'changer' => $user});
- # Generate and return the UI (HTML page) from the appropriate template.
- $template->process("attachment/updated.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ print $cgi->header();
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("attachment/updated.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
# Only administrators can delete attachments.
sub delete_attachment {
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
- print $cgi->header();
+ print $cgi->header();
- $user->in_group('admin')
- || ThrowUserError('auth_failure', {group => 'admin',
- action => 'delete',
- object => 'attachment'});
-
- Bugzilla->params->{'allow_attachment_deletion'}
- || ThrowUserError('attachment_deletion_disabled');
-
- # Make sure the administrator is allowed to edit this attachment.
- my $attachment = validateID();
- Bugzilla::Attachment->_check_bug($attachment->bug);
-
- $attachment->datasize || ThrowUserError('attachment_removed');
-
- # We don't want to let a malicious URL accidentally delete an attachment.
- my $token = trim($cgi->param('token'));
- if ($token) {
- my ($creator_id, $date, $event) = Bugzilla::Token::GetTokenData($token);
- unless ($creator_id
- && ($creator_id == $user->id)
- && ($event eq 'delete_attachment' . $attachment->id))
- {
- # The token is invalid.
- ThrowUserError('token_does_not_exist');
- }
+ $user->in_group('admin')
+ || ThrowUserError('auth_failure',
+ {group => 'admin', action => 'delete', object => 'attachment'});
- my $bug = new Bugzilla::Bug($attachment->bug_id);
+ Bugzilla->params->{'allow_attachment_deletion'}
+ || ThrowUserError('attachment_deletion_disabled');
- # The token is valid. Delete the content of the attachment.
- my $msg;
- $vars->{'attachment'} = $attachment;
- $vars->{'reason'} = clean_text($cgi->param('reason') || '');
+ # Make sure the administrator is allowed to edit this attachment.
+ my $attachment = validateID();
+ Bugzilla::Attachment->_check_bug($attachment->bug);
- $template->process("attachment/delete_reason.txt.tmpl", $vars, \$msg)
- || ThrowTemplateError($template->error());
+ $attachment->datasize || ThrowUserError('attachment_removed');
- # Paste the reason provided by the admin into a comment.
- $bug->add_comment($msg);
+ # We don't want to let a malicious URL accidentally delete an attachment.
+ my $token = trim($cgi->param('token'));
+ if ($token) {
+ my ($creator_id, $date, $event) = Bugzilla::Token::GetTokenData($token);
+ unless ($creator_id
+ && ($creator_id == $user->id)
+ && ($event eq 'delete_attachment' . $attachment->id))
+ {
+ # The token is invalid.
+ ThrowUserError('token_does_not_exist');
+ }
- $attachment->remove_from_db();
+ my $bug = new Bugzilla::Bug($attachment->bug_id);
- # Now delete the token.
- delete_token($token);
+ # The token is valid. Delete the content of the attachment.
+ my $msg;
+ $vars->{'attachment'} = $attachment;
+ $vars->{'reason'} = clean_text($cgi->param('reason') || '');
- # Insert the comment.
- $bug->update();
+ $template->process("attachment/delete_reason.txt.tmpl", $vars, \$msg)
+ || ThrowTemplateError($template->error());
- # Required to display the bug the deleted attachment belongs to.
- $vars->{'bugs'} = [$bug];
- $vars->{'header_done'} = 1;
+ # Paste the reason provided by the admin into a comment.
+ $bug->add_comment($msg);
- $vars->{'sent_bugmail'} =
- Bugzilla::BugMail::Send($bug->id, { 'changer' => $user });
+ $attachment->remove_from_db();
- $template->process("attachment/updated.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- }
- else {
- # Create a token.
- $token = issue_session_token('delete_attachment' . $attachment->id);
+ # Now delete the token.
+ delete_token($token);
- $vars->{'a'} = $attachment;
- $vars->{'token'} = $token;
+ # Insert the comment.
+ $bug->update();
- $template->process("attachment/confirm-delete.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- }
+ # Required to display the bug the deleted attachment belongs to.
+ $vars->{'bugs'} = [$bug];
+ $vars->{'header_done'} = 1;
+
+ $vars->{'sent_bugmail'}
+ = Bugzilla::BugMail::Send($bug->id, {'changer' => $user});
+
+ $template->process("attachment/updated.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ }
+ else {
+ # Create a token.
+ $token = issue_session_token('delete_attachment' . $attachment->id);
+
+ $vars->{'a'} = $attachment;
+ $vars->{'token'} = $token;
+
+ $template->process("attachment/confirm-delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ }
}
diff --git a/buglist.cgi b/buglist.cgi
index 719bb9639..df40bd6c3 100755
--- a/buglist.cgi
+++ b/buglist.cgi
@@ -28,10 +28,10 @@ use Bugzilla::Token;
use Date::Parse;
-my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
# We have to check the login here to get the correct footer if an error is
# thrown and to prevent a logged out user to use QuickSearch if 'requirelogin'
@@ -42,26 +42,28 @@ $cgi->redirect_search_url();
my $buffer = $cgi->query_string();
if (length($buffer) == 0) {
- ThrowUserError("buglist_parameters_required");
+ ThrowUserError("buglist_parameters_required");
}
# Determine whether this is a quicksearch query.
my $searchstring = $cgi->param('quicksearch');
if (defined($searchstring)) {
- $buffer = quicksearch($searchstring);
- # Quicksearch may do a redirect, in which case it does not return.
- # If it does return, it has modified $cgi->params so we can use them here
- # as if this had been a normal query from the beginning.
+ $buffer = quicksearch($searchstring);
+
+ # Quicksearch may do a redirect, in which case it does not return.
+ # If it does return, it has modified $cgi->params so we can use them here
+ # as if this had been a normal query from the beginning.
}
# If configured to not allow empty words, reject empty searches from the
-# Find a Specific Bug search form, including words being a single or
+# Find a Specific Bug search form, including words being a single or
# several consecutive whitespaces only.
-if (!Bugzilla->params->{'search_allow_no_criteria'}
- && defined($cgi->param('content')) && $cgi->param('content') =~ /^\s*$/)
+if ( !Bugzilla->params->{'search_allow_no_criteria'}
+ && defined($cgi->param('content'))
+ && $cgi->param('content') =~ /^\s*$/)
{
- ThrowUserError("buglist_parameters_required");
+ ThrowUserError("buglist_parameters_required");
}
################################################################################
@@ -73,26 +75,31 @@ my $dotweak = $cgi->param('tweak') ? 1 : 0;
# Log the user in
if ($dotweak) {
- Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->login(LOGIN_REQUIRED);
}
# Hack to support legacy applications that think the RDF ctype is at format=rdf.
-if (defined $cgi->param('format') && $cgi->param('format') eq "rdf"
- && !defined $cgi->param('ctype')) {
- $cgi->param('ctype', "rdf");
- $cgi->delete('format');
+if ( defined $cgi->param('format')
+ && $cgi->param('format') eq "rdf"
+ && !defined $cgi->param('ctype'))
+{
+ $cgi->param('ctype', "rdf");
+ $cgi->delete('format');
}
# Treat requests for ctype=rss as requests for ctype=atom
if (defined $cgi->param('ctype') && $cgi->param('ctype') eq "rss") {
- $cgi->param('ctype', "atom");
+ $cgi->param('ctype', "atom");
}
# Determine the format in which the user would like to receive the output.
# Uses the default format if the user did not specify an output format;
# otherwise validates the user's choice against the list of available formats.
-my $format = $template->get_format("list/list", scalar $cgi->param('format'),
- scalar $cgi->param('ctype'));
+my $format = $template->get_format(
+ "list/list",
+ scalar $cgi->param('format'),
+ scalar $cgi->param('ctype')
+);
# Use server push to display a "Please wait..." message for the user while
# executing their query if their browser supports it and they are viewing
@@ -102,14 +109,13 @@ my $format = $template->get_format("list/list", scalar $cgi->param('format'),
# Server push is compatible with Gecko-based browsers and Opera, but not with
# MSIE, Lynx or Safari (bug 441496).
-my $serverpush =
- $format->{'extension'} eq "html"
- && exists $ENV{'HTTP_USER_AGENT'}
- && $ENV{'HTTP_USER_AGENT'} =~ /(Mozilla.[3-9]|Opera)/
- && $ENV{'HTTP_USER_AGENT'} !~ /compatible/i
- && $ENV{'HTTP_USER_AGENT'} !~ /(?:WebKit|Trident|KHTML)/
- && !defined($cgi->param('serverpush'))
- || $cgi->param('serverpush');
+my $serverpush
+ = $format->{'extension'} eq "html"
+ && exists $ENV{'HTTP_USER_AGENT'}
+ && $ENV{'HTTP_USER_AGENT'} =~ /(Mozilla.[3-9]|Opera)/
+ && $ENV{'HTTP_USER_AGENT'} !~ /compatible/i
+ && $ENV{'HTTP_USER_AGENT'} !~ /(?:WebKit|Trident|KHTML)/
+ && !defined($cgi->param('serverpush')) || $cgi->param('serverpush');
my $order = $cgi->param('order') || "";
@@ -119,25 +125,26 @@ my $params;
# If the user is retrieving the last bug list they looked at, hack the buffer
# storing the query string so that it looks like a query retrieving those bugs.
if (my $last_list = $cgi->param('regetlastlist')) {
- my $bug_ids;
-
- # Logged-out users use the old cookie method for storing the last search.
- if (!$user->id or $last_list eq 'cookie') {
- $bug_ids = $cgi->cookie('BUGLIST') or ThrowUserError("missing_cookie");
- $bug_ids =~ s/[:-]/,/g;
- $order ||= "reuse last sort";
- }
- # But logged in users store the last X searches in the DB so they can
- # have multiple bug lists available.
- else {
- my $last_search = Bugzilla::Search::Recent->check(
- { id => $last_list });
- $bug_ids = join(',', @{ $last_search->bug_list });
- $order ||= $last_search->list_order;
- }
- # set up the params for this new query
- $params = new Bugzilla::CGI({ bug_id => $bug_ids, order => $order });
- $params->param('list_id', $last_list);
+ my $bug_ids;
+
+ # Logged-out users use the old cookie method for storing the last search.
+ if (!$user->id or $last_list eq 'cookie') {
+ $bug_ids = $cgi->cookie('BUGLIST') or ThrowUserError("missing_cookie");
+ $bug_ids =~ s/[:-]/,/g;
+ $order ||= "reuse last sort";
+ }
+
+ # But logged in users store the last X searches in the DB so they can
+ # have multiple bug lists available.
+ else {
+ my $last_search = Bugzilla::Search::Recent->check({id => $last_list});
+ $bug_ids = join(',', @{$last_search->bug_list});
+ $order ||= $last_search->list_order;
+ }
+
+ # set up the params for this new query
+ $params = new Bugzilla::CGI({bug_id => $bug_ids, order => $order});
+ $params->param('list_id', $last_list);
}
# Figure out whether or not the user is doing a fulltext search. If not,
@@ -147,10 +154,10 @@ my $fulltext = 0;
if ($cgi->param('content')) { $fulltext = 1 }
my @charts = map(/^field(\d-\d-\d)$/ ? $1 : (), $cgi->param());
foreach my $chart (@charts) {
- if ($cgi->param("field$chart") eq 'content' && $cgi->param("value$chart")) {
- $fulltext = 1;
- last;
- }
+ if ($cgi->param("field$chart") eq 'content' && $cgi->param("value$chart")) {
+ $fulltext = 1;
+ last;
+ }
}
################################################################################
@@ -158,34 +165,35 @@ foreach my $chart (@charts) {
################################################################################
sub DiffDate {
- my ($datestr) = @_;
- my $date = str2time($datestr);
- my $age = time() - $date;
-
- if( $age < 18*60*60 ) {
- $date = format_time($datestr, '%H:%M:%S');
- } elsif( $age < 6*24*60*60 ) {
- $date = format_time($datestr, '%a %H:%M');
- } else {
- $date = format_time($datestr, '%Y-%m-%d');
- }
- return $date;
+ my ($datestr) = @_;
+ my $date = str2time($datestr);
+ my $age = time() - $date;
+
+ if ($age < 18 * 60 * 60) {
+ $date = format_time($datestr, '%H:%M:%S');
+ }
+ elsif ($age < 6 * 24 * 60 * 60) {
+ $date = format_time($datestr, '%a %H:%M');
+ }
+ else {
+ $date = format_time($datestr, '%Y-%m-%d');
+ }
+ return $date;
}
sub LookupNamedQuery {
- my ($name, $sharer_id) = @_;
+ my ($name, $sharer_id) = @_;
- Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->login(LOGIN_REQUIRED);
- my $query = Bugzilla::Search::Saved->check(
- { user => $sharer_id, name => $name, _error => 'missing_query' });
+ my $query = Bugzilla::Search::Saved->check(
+ {user => $sharer_id, name => $name, _error => 'missing_query'});
- $query->url
- || ThrowUserError("buglist_parameters_required");
+ $query->url || ThrowUserError("buglist_parameters_required");
- # Detaint $sharer_id.
- $sharer_id = $query->user->id if $sharer_id;
- return wantarray ? ($query->url, $query->id, $sharer_id) : $query->url;
+ # Detaint $sharer_id.
+ $sharer_id = $query->user->id if $sharer_id;
+ return wantarray ? ($query->url, $query->id, $sharer_id) : $query->url;
}
# Inserts a Named Query (a "Saved Search") into the database, or
@@ -197,119 +205,120 @@ sub LookupNamedQuery {
# will throw a UserError. Leading and trailing whitespace
# will be stripped from this value before it is inserted
# into the DB.
-# query - The query part of the buglist.cgi URL, unencoded. Must not be
+# query - The query part of the buglist.cgi URL, unencoded. Must not be
# empty, or we will throw a UserError.
-# link_in_footer (optional) - 1 if the Named Query should be
+# link_in_footer (optional) - 1 if the Named Query should be
# displayed in the user's footer, 0 otherwise.
#
# All parameters are validated before passing them into the database.
#
-# Returns: A boolean true value if the query existed in the database
+# Returns: A boolean true value if the query existed in the database
# before, and we updated it. A boolean false value otherwise.
sub InsertNamedQuery {
- my ($query_name, $query, $link_in_footer) = @_;
- my $dbh = Bugzilla->dbh;
-
- $query_name = trim($query_name);
- my ($query_obj) = grep {lc($_->name) eq lc($query_name)} @{Bugzilla->user->queries};
-
- if ($query_obj) {
- $query_obj->set_name($query_name);
- $query_obj->set_url($query);
- $query_obj->update();
- } else {
- Bugzilla::Search::Saved->create({
- name => $query_name,
- query => $query,
- link_in_footer => $link_in_footer
- });
- }
-
- return $query_obj ? 1 : 0;
+ my ($query_name, $query, $link_in_footer) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $query_name = trim($query_name);
+ my ($query_obj)
+ = grep { lc($_->name) eq lc($query_name) } @{Bugzilla->user->queries};
+
+ if ($query_obj) {
+ $query_obj->set_name($query_name);
+ $query_obj->set_url($query);
+ $query_obj->update();
+ }
+ else {
+ Bugzilla::Search::Saved->create(
+ {name => $query_name, query => $query, link_in_footer => $link_in_footer});
+ }
+
+ return $query_obj ? 1 : 0;
}
sub LookupSeries {
- my ($series_id) = @_;
- detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
-
- my $dbh = Bugzilla->dbh;
- my $result = $dbh->selectrow_array("SELECT query FROM series " .
- "WHERE series_id = ?"
- , undef, ($series_id));
- $result
- || ThrowCodeError("invalid_series_id", {'series_id' => $series_id});
- return $result;
+ my ($series_id) = @_;
+ detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
+
+ my $dbh = Bugzilla->dbh;
+ my $result
+ = $dbh->selectrow_array("SELECT query FROM series " . "WHERE series_id = ?",
+ undef, ($series_id));
+ $result || ThrowCodeError("invalid_series_id", {'series_id' => $series_id});
+ return $result;
}
sub GetQuip {
- my $dbh = Bugzilla->dbh;
- # COUNT is quick because it is cached for MySQL. We may want to revisit
- # this when we support other databases.
- my $count = $dbh->selectrow_array("SELECT COUNT(quip)"
- . " FROM quips WHERE approved = 1");
- my $random = int(rand($count));
- my $quip =
- $dbh->selectrow_array("SELECT quip FROM quips WHERE approved = 1 " .
- $dbh->sql_limit(1, $random));
- return $quip;
+ my $dbh = Bugzilla->dbh;
+
+ # COUNT is quick because it is cached for MySQL. We may want to revisit
+ # this when we support other databases.
+ my $count = $dbh->selectrow_array(
+ "SELECT COUNT(quip)" . " FROM quips WHERE approved = 1");
+ my $random = int(rand($count));
+ my $quip = $dbh->selectrow_array(
+ "SELECT quip FROM quips WHERE approved = 1 " . $dbh->sql_limit(1, $random));
+ return $quip;
}
# Return groups available for at least one product of the buglist.
sub GetGroups {
- my $product_names = shift;
- my $user = Bugzilla->user;
- my %legal_groups;
+ my $product_names = shift;
+ my $user = Bugzilla->user;
+ my %legal_groups;
- foreach my $product_name (@$product_names) {
- my $product = Bugzilla::Product->new({name => $product_name, cache => 1});
+ foreach my $product_name (@$product_names) {
+ my $product = Bugzilla::Product->new({name => $product_name, cache => 1});
- foreach my $gid (keys %{$product->group_controls}) {
- # The user can only edit groups they belong to.
- next unless $user->in_group_id($gid);
+ foreach my $gid (keys %{$product->group_controls}) {
- # The user has no control on groups marked as NA or MANDATORY.
- my $group = $product->group_controls->{$gid};
- next if ($group->{membercontrol} == CONTROLMAPMANDATORY
- || $group->{membercontrol} == CONTROLMAPNA);
+ # The user can only edit groups they belong to.
+ next unless $user->in_group_id($gid);
- # It's fine to include inactive groups. Those will be marked
- # as "remove only" when editing several bugs at once.
- $legal_groups{$gid} ||= $group->{group};
- }
+ # The user has no control on groups marked as NA or MANDATORY.
+ my $group = $product->group_controls->{$gid};
+ next
+ if ($group->{membercontrol} == CONTROLMAPMANDATORY
+ || $group->{membercontrol} == CONTROLMAPNA);
+
+ # It's fine to include inactive groups. Those will be marked
+ # as "remove only" when editing several bugs at once.
+ $legal_groups{$gid} ||= $group->{group};
}
- # Return a list of group objects.
- return [values %legal_groups];
+ }
+
+ # Return a list of group objects.
+ return [values %legal_groups];
}
sub _get_common_flag_types {
- my $component_ids = shift;
- my $user = Bugzilla->user;
-
- # Get all the different components in the bug list
- my $components = Bugzilla::Component->new_from_list($component_ids);
- my %flag_types;
- my @flag_types_ids;
- foreach my $component (@$components) {
- foreach my $flag_type (@{$component->flag_types->{'bug'}}) {
- push @flag_types_ids, $flag_type->id;
- $flag_types{$flag_type->id} = $flag_type;
- }
- }
-
- # We only want flags that appear in all components
- my %common_flag_types;
- foreach my $id (keys %flag_types) {
- my $flag_type_count = scalar grep { $_ == $id } @flag_types_ids;
- $common_flag_types{$id} = $flag_types{$id}
- if $flag_type_count == scalar @$components;
+ my $component_ids = shift;
+ my $user = Bugzilla->user;
+
+ # Get all the different components in the bug list
+ my $components = Bugzilla::Component->new_from_list($component_ids);
+ my %flag_types;
+ my @flag_types_ids;
+ foreach my $component (@$components) {
+ foreach my $flag_type (@{$component->flag_types->{'bug'}}) {
+ push @flag_types_ids, $flag_type->id;
+ $flag_types{$flag_type->id} = $flag_type;
}
-
- # We only show flags that a user can request.
- my @show_flag_types
- = grep { $user->can_request_flag($_) } values %common_flag_types;
- my $any_flags_requesteeble = grep { $_->is_requesteeble } @show_flag_types;
-
- return(\@show_flag_types, $any_flags_requesteeble);
+ }
+
+ # We only want flags that appear in all components
+ my %common_flag_types;
+ foreach my $id (keys %flag_types) {
+ my $flag_type_count = scalar grep { $_ == $id } @flag_types_ids;
+ $common_flag_types{$id} = $flag_types{$id}
+ if $flag_type_count == scalar @$components;
+ }
+
+ # We only show flags that a user can request.
+ my @show_flag_types
+ = grep { $user->can_request_flag($_) } values %common_flag_types;
+ my $any_flags_requesteeble = grep { $_->is_requesteeble } @show_flag_types;
+
+ return (\@show_flag_types, $any_flags_requesteeble);
}
################################################################################
@@ -322,13 +331,13 @@ my $sharer_id;
# Backwards-compatibility - the old interface had cmdtype="runnamed" to run
# a named command, and we can't break this because it's in bookmarks.
-if ($cmdtype eq "runnamed") {
- $cmdtype = "dorem";
- $remaction = "run";
+if ($cmdtype eq "runnamed") {
+ $cmdtype = "dorem";
+ $remaction = "run";
}
# Now we're going to be running, so ensure that the params object is set up,
-# using ||= so that we only do so if someone hasn't overridden this
+# using ||= so that we only do so if someone hasn't overridden this
# earlier, for example by setting up a named query search.
# This will be modified, so make a copy.
@@ -336,51 +345,52 @@ $params ||= new Bugzilla::CGI($cgi);
# Generate a reasonable filename for the user agent to suggest to the user
# when the user saves the bug list. Uses the name of the remembered query
-# if available. We have to do this now, even though we return HTTP headers
-# at the end, because the fact that there is a remembered query gets
+# if available. We have to do this now, even though we return HTTP headers
+# at the end, because the fact that there is a remembered query gets
# forgotten in the process of retrieving it.
my $disp_prefix = "bugs";
if ($cmdtype eq "dorem" && $remaction =~ /^run/) {
- $disp_prefix = $cgi->param('namedcmd');
+ $disp_prefix = $cgi->param('namedcmd');
}
# Take appropriate action based on user's request.
-if ($cmdtype eq "dorem") {
- if ($remaction eq "run") {
- my $query_id;
- ($buffer, $query_id, $sharer_id) =
- LookupNamedQuery(scalar $cgi->param("namedcmd"),
- scalar $cgi->param('sharer_id'));
- # If this is the user's own query, remember information about it
- # so that it can be modified easily.
- $vars->{'searchname'} = $cgi->param('namedcmd');
- if (!$cgi->param('sharer_id') ||
- $cgi->param('sharer_id') == $user->id) {
- $vars->{'searchtype'} = "saved";
- $vars->{'search_id'} = $query_id;
- }
- $params = new Bugzilla::CGI($buffer);
- $order = $params->param('order') || $order;
-
- }
- elsif ($remaction eq "runseries") {
- $buffer = LookupSeries(scalar $cgi->param("series_id"));
- $vars->{'searchname'} = $cgi->param('namedcmd');
- $vars->{'searchtype'} = "series";
- $params = new Bugzilla::CGI($buffer);
- $order = $params->param('order') || $order;
+if ($cmdtype eq "dorem") {
+ if ($remaction eq "run") {
+ my $query_id;
+ ($buffer, $query_id, $sharer_id)
+ = LookupNamedQuery(scalar $cgi->param("namedcmd"),
+ scalar $cgi->param('sharer_id'));
+
+ # If this is the user's own query, remember information about it
+ # so that it can be modified easily.
+ $vars->{'searchname'} = $cgi->param('namedcmd');
+ if (!$cgi->param('sharer_id') || $cgi->param('sharer_id') == $user->id) {
+ $vars->{'searchtype'} = "saved";
+ $vars->{'search_id'} = $query_id;
}
- elsif ($remaction eq "forget") {
- $user = Bugzilla->login(LOGIN_REQUIRED);
- # Copy the name into a variable, so that we can trick_taint it for
- # the DB. We know it's safe, because we're using placeholders in
- # the SQL, and the SQL is only a DELETE.
- my $qname = $cgi->param('namedcmd');
- trick_taint($qname);
-
- # Do not forget the saved search if it is being used in a whine
- my $whines_in_use =
- $dbh->selectcol_arrayref('SELECT DISTINCT whine_events.subject
+ $params = new Bugzilla::CGI($buffer);
+ $order = $params->param('order') || $order;
+
+ }
+ elsif ($remaction eq "runseries") {
+ $buffer = LookupSeries(scalar $cgi->param("series_id"));
+ $vars->{'searchname'} = $cgi->param('namedcmd');
+ $vars->{'searchtype'} = "series";
+ $params = new Bugzilla::CGI($buffer);
+ $order = $params->param('order') || $order;
+ }
+ elsif ($remaction eq "forget") {
+ $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ # Copy the name into a variable, so that we can trick_taint it for
+ # the DB. We know it's safe, because we're using placeholders in
+ # the SQL, and the SQL is only a DELETE.
+ my $qname = $cgi->param('namedcmd');
+ trick_taint($qname);
+
+ # Do not forget the saved search if it is being used in a whine
+ my $whines_in_use = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT whine_events.subject
FROM whine_events
INNER JOIN whine_queries
ON whine_queries.eventid
@@ -389,92 +399,100 @@ if ($cmdtype eq "dorem") {
= ?
AND whine_queries.query_name
= ?
- ', undef, $user->id, $qname);
- if (scalar(@$whines_in_use)) {
- ThrowUserError('saved_search_used_by_whines',
- { subjects => join(',', @$whines_in_use),
- search_name => $qname }
- );
- }
-
- # If we are here, then we can safely remove the saved search
- my $query_id;
- ($buffer, $query_id) = LookupNamedQuery(scalar $cgi->param("namedcmd"),
- $user->id);
- if ($query_id) {
- # Make sure the user really wants to delete their saved search.
- my $token = $cgi->param('token');
- check_hash_token($token, [$query_id, $qname]);
-
- $dbh->do('DELETE FROM namedqueries
- WHERE id = ?',
- undef, $query_id);
- $dbh->do('DELETE FROM namedqueries_link_in_footer
- WHERE namedquery_id = ?',
- undef, $query_id);
- $dbh->do('DELETE FROM namedquery_group_map
- WHERE namedquery_id = ?',
- undef, $query_id);
- Bugzilla->memcached->clear({ table => 'namedqueries', id => $query_id });
- }
+ ', undef, $user->id, $qname
+ );
+ if (scalar(@$whines_in_use)) {
+ ThrowUserError('saved_search_used_by_whines',
+ {subjects => join(',', @$whines_in_use), search_name => $qname});
+ }
- # Now reset the cached queries
- $user->flush_queries_cache();
-
- print $cgi->header();
- # Generate and return the UI (HTML page) from the appropriate template.
- $vars->{'message'} = "buglist_query_gone";
- $vars->{'namedcmd'} = $qname;
- $vars->{'url'} = "buglist.cgi?newquery=" . url_quote($buffer)
- . "&cmdtype=doit&remtype=asnamed&newqueryname=" . url_quote($qname)
- . "&token=" . url_quote(issue_hash_token(['savedsearch']));
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ # If we are here, then we can safely remove the saved search
+ my $query_id;
+ ($buffer, $query_id)
+ = LookupNamedQuery(scalar $cgi->param("namedcmd"), $user->id);
+ if ($query_id) {
+
+ # Make sure the user really wants to delete their saved search.
+ my $token = $cgi->param('token');
+ check_hash_token($token, [$query_id, $qname]);
+
+ $dbh->do(
+ 'DELETE FROM namedqueries
+ WHERE id = ?', undef, $query_id
+ );
+ $dbh->do(
+ 'DELETE FROM namedqueries_link_in_footer
+ WHERE namedquery_id = ?', undef, $query_id
+ );
+ $dbh->do(
+ 'DELETE FROM namedquery_group_map
+ WHERE namedquery_id = ?', undef, $query_id
+ );
+ Bugzilla->memcached->clear({table => 'namedqueries', id => $query_id});
}
+
+ # Now reset the cached queries
+ $user->flush_queries_cache();
+
+ print $cgi->header();
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $vars->{'message'} = "buglist_query_gone";
+ $vars->{'namedcmd'} = $qname;
+ $vars->{'url'}
+ = "buglist.cgi?newquery="
+ . url_quote($buffer)
+ . "&cmdtype=doit&remtype=asnamed&newqueryname="
+ . url_quote($qname)
+ . "&token="
+ . url_quote(issue_hash_token(['savedsearch']));
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
}
elsif (($cmdtype eq "doit") && defined $cgi->param('remtype')) {
- if ($cgi->param('remtype') eq "asdefault") {
- $user = Bugzilla->login(LOGIN_REQUIRED);
- my $token = $cgi->param('token');
- check_hash_token($token, ['searchknob']);
- $buffer = $params->canonicalise_query('cmdtype', 'remtype',
- 'query_based_on', 'token');
- InsertNamedQuery(DEFAULT_QUERY_NAME, $buffer);
- $vars->{'message'} = "buglist_new_default_query";
+ if ($cgi->param('remtype') eq "asdefault") {
+ $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['searchknob']);
+ $buffer = $params->canonicalise_query('cmdtype', 'remtype', 'query_based_on',
+ 'token');
+ InsertNamedQuery(DEFAULT_QUERY_NAME, $buffer);
+ $vars->{'message'} = "buglist_new_default_query";
+ }
+ elsif ($cgi->param('remtype') eq "asnamed") {
+ $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $query_name = $cgi->param('newqueryname');
+ my $new_query = $cgi->param('newquery');
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['savedsearch']);
+ my $existed_before = InsertNamedQuery($query_name, $new_query, 1);
+ if ($existed_before) {
+ $vars->{'message'} = "buglist_updated_named_query";
}
- elsif ($cgi->param('remtype') eq "asnamed") {
- $user = Bugzilla->login(LOGIN_REQUIRED);
- my $query_name = $cgi->param('newqueryname');
- my $new_query = $cgi->param('newquery');
- my $token = $cgi->param('token');
- check_hash_token($token, ['savedsearch']);
- my $existed_before = InsertNamedQuery($query_name, $new_query, 1);
- if ($existed_before) {
- $vars->{'message'} = "buglist_updated_named_query";
- }
- else {
- $vars->{'message'} = "buglist_new_named_query";
- }
- $vars->{'queryname'} = $query_name;
+ else {
+ $vars->{'message'} = "buglist_new_named_query";
+ }
+ $vars->{'queryname'} = $query_name;
- # Make sure to invalidate any cached query data, so that the footer is
- # correctly displayed
- $user->flush_queries_cache();
+ # Make sure to invalidate any cached query data, so that the footer is
+ # correctly displayed
+ $user->flush_queries_cache();
- print $cgi->header();
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
+ print $cgi->header();
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
}
# backward compatibility hack: if the saved query doesn't say which
# form was used to create it, assume it was on the advanced query
# form - see bug 252295
if (!$params->param('query_format')) {
- $params->param('query_format', 'advanced');
- $buffer = $params->query_string;
+ $params->param('query_format', 'advanced');
+ $buffer = $params->query_string;
}
################################################################################
@@ -487,40 +505,42 @@ my $columns = Bugzilla::Search::COLUMNS;
# Display Column Determination
################################################################################
-# Determine the columns that will be displayed in the bug list via the
+# Determine the columns that will be displayed in the bug list via the
# columnlist CGI parameter, the user's preferences, or the default.
my @displaycolumns = ();
if (defined $params->param('columnlist')) {
- if ($params->param('columnlist') eq "all") {
- # If the value of the CGI parameter is "all", display all columns,
- # but remove the redundant "short_desc" column.
- @displaycolumns = grep($_ ne 'short_desc', keys(%$columns));
- }
- else {
- @displaycolumns = split(/[ ,]+/, $params->param('columnlist'));
- }
+ if ($params->param('columnlist') eq "all") {
+
+ # If the value of the CGI parameter is "all", display all columns,
+ # but remove the redundant "short_desc" column.
+ @displaycolumns = grep($_ ne 'short_desc', keys(%$columns));
+ }
+ else {
+ @displaycolumns = split(/[ ,]+/, $params->param('columnlist'));
+ }
}
elsif (defined $cgi->cookie('COLUMNLIST')) {
- # 2002-10-31 Rename column names (see bug 176461)
- my $columnlist = $cgi->cookie('COLUMNLIST');
- $columnlist =~ s/\bowner\b/assigned_to/;
- $columnlist =~ s/\bowner_realname\b/assigned_to_realname/;
- $columnlist =~ s/\bplatform\b/rep_platform/;
- $columnlist =~ s/\bseverity\b/bug_severity/;
- $columnlist =~ s/\bstatus\b/bug_status/;
- $columnlist =~ s/\bsummaryfull\b/short_desc/;
- $columnlist =~ s/\bsummary\b/short_short_desc/;
-
- # Use the columns listed in the user's preferences.
- @displaycolumns = split(/ /, $columnlist);
+
+ # 2002-10-31 Rename column names (see bug 176461)
+ my $columnlist = $cgi->cookie('COLUMNLIST');
+ $columnlist =~ s/\bowner\b/assigned_to/;
+ $columnlist =~ s/\bowner_realname\b/assigned_to_realname/;
+ $columnlist =~ s/\bplatform\b/rep_platform/;
+ $columnlist =~ s/\bseverity\b/bug_severity/;
+ $columnlist =~ s/\bstatus\b/bug_status/;
+ $columnlist =~ s/\bsummaryfull\b/short_desc/;
+ $columnlist =~ s/\bsummary\b/short_short_desc/;
+
+ # Use the columns listed in the user's preferences.
+ @displaycolumns = split(/ /, $columnlist);
}
else {
- # Use the default list of columns.
- @displaycolumns = DEFAULT_COLUMN_LIST;
+ # Use the default list of columns.
+ @displaycolumns = DEFAULT_COLUMN_LIST;
}
-# Weed out columns that don't actually exist to prevent the user
-# from hacking their column list cookie to grab data to which they
+# Weed out columns that don't actually exist to prevent the user
+# from hacking their column list cookie to grab data to which they
# should not have access. Detaint the data along the way.
@displaycolumns = grep($columns->{$_} && trick_taint($_), @displaycolumns);
@@ -531,14 +551,14 @@ else {
# Remove the timetracking columns if they are not a part of the group
# (happens if a user had access to time tracking and it was revoked/disabled)
if (!$user->is_timetracker) {
- foreach my $tt_field (TIMETRACKING_FIELDS) {
- @displaycolumns = grep($_ ne $tt_field, @displaycolumns);
- }
+ foreach my $tt_field (TIMETRACKING_FIELDS) {
+ @displaycolumns = grep($_ ne $tt_field, @displaycolumns);
+ }
}
# Remove the relevance column if the user is not doing a fulltext search.
if (grep('relevance', @displaycolumns) && !$fulltext) {
- @displaycolumns = grep($_ ne 'relevance', @displaycolumns);
+ @displaycolumns = grep($_ ne 'relevance', @displaycolumns);
}
################################################################################
@@ -550,13 +570,14 @@ if (grep('relevance', @displaycolumns) && !$fulltext) {
# The bug ID is always selected because bug IDs are always displayed.
# Severity, priority, resolution and status are required for buglist
# CSS classes.
-my @selectcolumns = ("bug_id", "bug_severity", "priority", "bug_status",
- "resolution", "product");
+my @selectcolumns
+ = ("bug_id", "bug_severity", "priority", "bug_status", "resolution",
+ "product");
# remaining and actual_time are required for percentage_complete calculation:
if (grep { $_ eq "percentage_complete" } @displaycolumns) {
- push (@selectcolumns, "remaining_time");
- push (@selectcolumns, "actual_time");
+ push(@selectcolumns, "remaining_time");
+ push(@selectcolumns, "actual_time");
}
# Make sure that the login_name version of a field is always also
@@ -564,58 +585,51 @@ if (grep { $_ eq "percentage_complete" } @displaycolumns) {
# display the login name when the realname is empty.
my @realname_fields = grep(/_realname$/, @displaycolumns);
foreach my $item (@realname_fields) {
- my $login_field = $item;
- $login_field =~ s/_realname$//;
- if (!grep($_ eq $login_field, @selectcolumns)) {
- push(@selectcolumns, $login_field);
- }
+ my $login_field = $item;
+ $login_field =~ s/_realname$//;
+ if (!grep($_ eq $login_field, @selectcolumns)) {
+ push(@selectcolumns, $login_field);
+ }
}
# Display columns are selected because otherwise we could not display them.
foreach my $col (@displaycolumns) {
- push (@selectcolumns, $col) if !grep($_ eq $col, @selectcolumns);
+ push(@selectcolumns, $col) if !grep($_ eq $col, @selectcolumns);
}
-# If the user is editing multiple bugs, we also make sure to select the
+# If the user is editing multiple bugs, we also make sure to select the
# status, because the values of that field determines what options the user
# has for modifying the bugs.
if ($dotweak) {
- push(@selectcolumns, "bug_status") if !grep($_ eq 'bug_status', @selectcolumns);
- push(@selectcolumns, "bugs.component_id");
+ push(@selectcolumns, "bug_status") if !grep($_ eq 'bug_status', @selectcolumns);
+ push(@selectcolumns, "bugs.component_id");
}
if ($format->{'extension'} eq 'ics') {
- push(@selectcolumns, "opendate") if !grep($_ eq 'opendate', @selectcolumns);
- if (Bugzilla->params->{'timetrackinggroup'}) {
- push(@selectcolumns, "deadline") if !grep($_ eq 'deadline', @selectcolumns);
- }
+ push(@selectcolumns, "opendate") if !grep($_ eq 'opendate', @selectcolumns);
+ if (Bugzilla->params->{'timetrackinggroup'}) {
+ push(@selectcolumns, "deadline") if !grep($_ eq 'deadline', @selectcolumns);
+ }
}
if ($format->{'extension'} eq 'atom') {
- # The title of the Atom feed will be the same one as for the bug list.
- $vars->{'title'} = $cgi->param('title');
-
- # This is the list of fields that are needed by the Atom filter.
- my @required_atom_columns = (
- 'short_desc',
- 'opendate',
- 'changeddate',
- 'reporter',
- 'reporter_realname',
- 'priority',
- 'bug_severity',
- 'assigned_to',
- 'assigned_to_realname',
- 'bug_status',
- 'product',
- 'component',
- 'resolution'
- );
- push(@required_atom_columns, 'target_milestone') if Bugzilla->params->{'usetargetmilestone'};
- foreach my $required (@required_atom_columns) {
- push(@selectcolumns, $required) if !grep($_ eq $required,@selectcolumns);
- }
+ # The title of the Atom feed will be the same one as for the bug list.
+ $vars->{'title'} = $cgi->param('title');
+
+ # This is the list of fields that are needed by the Atom filter.
+ my @required_atom_columns = (
+ 'short_desc', 'opendate', 'changeddate', 'reporter',
+ 'reporter_realname', 'priority', 'bug_severity', 'assigned_to',
+ 'assigned_to_realname', 'bug_status', 'product', 'component',
+ 'resolution'
+ );
+ push(@required_atom_columns, 'target_milestone')
+ if Bugzilla->params->{'usetargetmilestone'};
+
+ foreach my $required (@required_atom_columns) {
+ push(@selectcolumns, $required) if !grep($_ eq $required, @selectcolumns);
+ }
}
################################################################################
@@ -627,76 +641,79 @@ if ($format->{'extension'} eq 'atom') {
# First check if we'll want to reuse the last sorting order; that happens if
# the order is not defined or its value is "reuse last sort"
if (!$order || $order =~ /^reuse/i) {
- if ($cgi->cookie('LASTORDER')) {
- $order = $cgi->cookie('LASTORDER');
-
- # Cookies from early versions of Specific Search included this text,
- # which is now invalid.
- $order =~ s/ LIMIT 200//;
- }
- else {
- $order = ''; # Remove possible "reuse" identifier as unnecessary
- }
+ if ($cgi->cookie('LASTORDER')) {
+ $order = $cgi->cookie('LASTORDER');
+
+ # Cookies from early versions of Specific Search included this text,
+ # which is now invalid.
+ $order =~ s/ LIMIT 200//;
+ }
+ else {
+ $order = ''; # Remove possible "reuse" identifier as unnecessary
+ }
}
my @order_columns;
if ($order) {
- # Convert the value of the "order" form field into a list of columns
- # by which to sort the results.
- ORDER: for ($order) {
- /^Bug Number$/ && do {
- @order_columns = ("bug_id");
- last ORDER;
- };
- /^Importance$/ && do {
- @order_columns = ("priority", "bug_severity");
- last ORDER;
- };
- /^Assignee$/ && do {
- @order_columns = ("assigned_to", "bug_status", "priority",
- "bug_id");
- last ORDER;
- };
- /^Last Changed$/ && do {
- @order_columns = ("changeddate", "bug_status", "priority",
- "assigned_to", "bug_id");
- last ORDER;
- };
- do {
- # A custom list of columns. Bugzilla::Search will validate items.
- @order_columns = split(/\s*,\s*/, $order);
- };
- }
+
+ # Convert the value of the "order" form field into a list of columns
+ # by which to sort the results.
+ORDER: for ($order) {
+ /^Bug Number$/ && do {
+ @order_columns = ("bug_id");
+ last ORDER;
+ };
+ /^Importance$/ && do {
+ @order_columns = ("priority", "bug_severity");
+ last ORDER;
+ };
+ /^Assignee$/ && do {
+ @order_columns = ("assigned_to", "bug_status", "priority", "bug_id");
+ last ORDER;
+ };
+ /^Last Changed$/ && do {
+ @order_columns
+ = ("changeddate", "bug_status", "priority", "assigned_to", "bug_id");
+ last ORDER;
+ };
+ do {
+ # A custom list of columns. Bugzilla::Search will validate items.
+ @order_columns = split(/\s*,\s*/, $order);
+ };
+ }
}
if (!scalar @order_columns) {
- # DEFAULT
- @order_columns = ("bug_status", "priority", "assigned_to", "bug_id");
+
+ # DEFAULT
+ @order_columns = ("bug_status", "priority", "assigned_to", "bug_id");
}
# In the HTML interface, by default, we limit the returned results,
# which speeds up quite a few searches where people are really only looking
# for the top results.
if ($format->{'extension'} eq 'html' && !defined $params->param('limit')) {
- $params->param('limit', Bugzilla->params->{'default_search_limit'});
- $vars->{'default_limited'} = 1;
+ $params->param('limit', Bugzilla->params->{'default_search_limit'});
+ $vars->{'default_limited'} = 1;
}
# Generate the basic SQL query that will be used to generate the bug list.
-my $search = new Bugzilla::Search('fields' => \@selectcolumns,
- 'params' => scalar $params->Vars,
- 'order' => \@order_columns,
- 'sharer' => $sharer_id);
+my $search = new Bugzilla::Search(
+ 'fields' => \@selectcolumns,
+ 'params' => scalar $params->Vars,
+ 'order' => \@order_columns,
+ 'sharer' => $sharer_id
+);
$order = join(',', $search->order);
if (scalar @{$search->invalid_order_columns}) {
- $vars->{'message'} = 'invalid_column_name';
- $vars->{'invalid_fragments'} = $search->invalid_order_columns;
+ $vars->{'message'} = 'invalid_column_name';
+ $vars->{'invalid_fragments'} = $search->invalid_order_columns;
}
-if ($fulltext and grep { /^relevance/ } $search->order) {
- $vars->{'message'} = 'buglist_sorted_by_relevance'
+if ($fulltext and grep {/^relevance/} $search->order) {
+ $vars->{'message'} = 'buglist_sorted_by_relevance';
}
# We don't want saved searches and other buglist things to save
@@ -710,22 +727,22 @@ $params->delete('limit') if $vars->{'default_limited'};
# Time to use server push to display an interim message to the user until
# the query completes and we can display the bug list.
if ($serverpush) {
- print $cgi->multipart_init();
- print $cgi->multipart_start(-type => 'text/html');
-
- # Generate and return the UI (HTML page) from the appropriate template.
- $template->process("list/server-push.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
-
- # Under mod_perl, flush stdout so that the page actually shows up.
- if ($ENV{MOD_PERL}) {
- require Apache2::RequestUtil;
- Apache2::RequestUtil->request->rflush();
- }
-
- # Don't do multipart_end() until we're ready to display the replacement
- # page, otherwise any errors that happen before then (like SQL errors)
- # will result in a blank page being shown to the user instead of the error.
+ print $cgi->multipart_init();
+ print $cgi->multipart_start(-type => 'text/html');
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("list/server-push.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ # Under mod_perl, flush stdout so that the page actually shows up.
+ if ($ENV{MOD_PERL}) {
+ require Apache2::RequestUtil;
+ Apache2::RequestUtil->request->rflush();
+ }
+
+ # Don't do multipart_end() until we're ready to display the replacement
+ # page, otherwise any errors that happen before then (like SQL errors)
+ # will result in a blank page being shown to the user instead of the error.
}
# Connect to the shadow database if this installation is using one to improve
@@ -742,24 +759,25 @@ $::SIG{PIPE} = 'DEFAULT';
my ($data, $extra_data) = $search->data;
$vars->{'search_description'} = $search->search_description;
-if ($cgi->param('debug')
- && Bugzilla->params->{debug_group}
- && $user->in_group(Bugzilla->params->{debug_group})
-) {
- $vars->{'debug'} = 1;
- $vars->{'queries'} = $extra_data;
- my $query_time = 0;
- $query_time += $_->{'time'} foreach @$extra_data;
- $vars->{'query_time'} = $query_time;
- # Explains are limited to admins because you could use them to figure
- # out how many hidden bugs are in a particular product (by doing
- # searches and looking at the number of rows the explain says it's
- # examining).
- if ($user->in_group('admin')) {
- foreach my $query (@$extra_data) {
- $query->{explain} = $dbh->bz_explain($query->{sql});
- }
+if ( $cgi->param('debug')
+ && Bugzilla->params->{debug_group}
+ && $user->in_group(Bugzilla->params->{debug_group}))
+{
+ $vars->{'debug'} = 1;
+ $vars->{'queries'} = $extra_data;
+ my $query_time = 0;
+ $query_time += $_->{'time'} foreach @$extra_data;
+ $vars->{'query_time'} = $query_time;
+
+ # Explains are limited to admins because you could use them to figure
+ # out how many hidden bugs are in a particular product (by doing
+ # searches and looking at the number of rows the explain says it's
+ # examining).
+ if ($user->in_group('admin')) {
+ foreach my $query (@$extra_data) {
+ $query->{explain} = $dbh->bz_explain($query->{sql});
}
+ }
}
################################################################################
@@ -771,73 +789,75 @@ if ($cgi->param('debug')
# If we're doing time tracking, then keep totals for all bugs.
my $percentage_complete = grep($_ eq 'percentage_complete', @displaycolumns);
-my $estimated_time = grep($_ eq 'estimated_time', @displaycolumns);
-my $remaining_time = grep($_ eq 'remaining_time', @displaycolumns)
- || $percentage_complete;
-my $actual_time = grep($_ eq 'actual_time', @displaycolumns)
- || $percentage_complete;
-
-my $time_info = { 'estimated_time' => 0,
- 'remaining_time' => 0,
- 'actual_time' => 0,
- 'percentage_complete' => 0,
- 'time_present' => ($estimated_time || $remaining_time ||
- $actual_time || $percentage_complete),
- };
-
-my $bugowners = {};
-my $bugproducts = {};
+my $estimated_time = grep($_ eq 'estimated_time', @displaycolumns);
+my $remaining_time
+ = grep($_ eq 'remaining_time', @displaycolumns) || $percentage_complete;
+my $actual_time
+ = grep($_ eq 'actual_time', @displaycolumns) || $percentage_complete;
+
+my $time_info = {
+ 'estimated_time' => 0,
+ 'remaining_time' => 0,
+ 'actual_time' => 0,
+ 'percentage_complete' => 0,
+ 'time_present' =>
+ ($estimated_time || $remaining_time || $actual_time || $percentage_complete),
+};
+
+my $bugowners = {};
+my $bugproducts = {};
my $bugcomponentids = {};
-my $bugcomponents = {};
-my $bugstatuses = {};
+my $bugcomponents = {};
+my $bugstatuses = {};
my @bugidlist;
-my @bugs; # the list of records
+my @bugs; # the list of records
foreach my $row (@$data) {
- my $bug = {}; # a record
-
- # Slurp the row of data into the record.
- # The second from last column in the record is the number of groups
- # to which the bug is restricted.
- foreach my $column (@selectcolumns) {
- $bug->{$column} = shift @$row;
- }
-
- # Process certain values further (i.e. date format conversion).
- if ($bug->{'changeddate'}) {
- $bug->{'changeddate'} =~
- s/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/$1-$2-$3 $4:$5:$6/;
-
- $bug->{'changedtime'} = $bug->{'changeddate'}; # for iCalendar and Atom
- $bug->{'changeddate'} = DiffDate($bug->{'changeddate'});
- }
-
- if ($bug->{'opendate'}) {
- $bug->{'opentime'} = $bug->{'opendate'}; # for iCalendar
- $bug->{'opendate'} = DiffDate($bug->{'opendate'});
- }
-
- # Record the assignee, product, and status in the big hashes of those things.
- $bugowners->{$bug->{'assigned_to'}} = 1 if $bug->{'assigned_to'};
- $bugproducts->{$bug->{'product'}} = 1 if $bug->{'product'};
- $bugcomponentids->{$bug->{'bugs.component_id'}} = 1 if $bug->{'bugs.component_id'};
- $bugcomponents->{$bug->{'component'}} = 1 if $bug->{'component'};
- $bugstatuses->{$bug->{'bug_status'}} = 1 if $bug->{'bug_status'};
-
- $bug->{'secure_mode'} = undef;
-
- # Add the record to the list.
- push(@bugs, $bug);
-
- # Add id to list for checking for bug privacy later
- detaint_natural($bug->{'bug_id'});
- push(@bugidlist, $bug->{'bug_id'});
-
- # Compute time tracking info.
- $time_info->{'estimated_time'} += $bug->{'estimated_time'} if ($estimated_time);
- $time_info->{'remaining_time'} += $bug->{'remaining_time'} if ($remaining_time);
- $time_info->{'actual_time'} += $bug->{'actual_time'} if ($actual_time);
+ my $bug = {}; # a record
+
+ # Slurp the row of data into the record.
+ # The second from last column in the record is the number of groups
+ # to which the bug is restricted.
+ foreach my $column (@selectcolumns) {
+ $bug->{$column} = shift @$row;
+ }
+
+ # Process certain values further (i.e. date format conversion).
+ if ($bug->{'changeddate'}) {
+ $bug->{'changeddate'}
+ =~ s/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/$1-$2-$3 $4:$5:$6/;
+
+ $bug->{'changedtime'} = $bug->{'changeddate'}; # for iCalendar and Atom
+ $bug->{'changeddate'} = DiffDate($bug->{'changeddate'});
+ }
+
+ if ($bug->{'opendate'}) {
+ $bug->{'opentime'} = $bug->{'opendate'}; # for iCalendar
+ $bug->{'opendate'} = DiffDate($bug->{'opendate'});
+ }
+
+ # Record the assignee, product, and status in the big hashes of those things.
+ $bugowners->{$bug->{'assigned_to'}} = 1 if $bug->{'assigned_to'};
+ $bugproducts->{$bug->{'product'}} = 1 if $bug->{'product'};
+ $bugcomponentids->{$bug->{'bugs.component_id'}} = 1
+ if $bug->{'bugs.component_id'};
+ $bugcomponents->{$bug->{'component'}} = 1 if $bug->{'component'};
+ $bugstatuses->{$bug->{'bug_status'}} = 1 if $bug->{'bug_status'};
+
+ $bug->{'secure_mode'} = undef;
+
+ # Add the record to the list.
+ push(@bugs, $bug);
+
+ # Add id to list for checking for bug privacy later
+ detaint_natural($bug->{'bug_id'});
+ push(@bugidlist, $bug->{'bug_id'});
+
+ # Compute time tracking info.
+ $time_info->{'estimated_time'} += $bug->{'estimated_time'} if ($estimated_time);
+ $time_info->{'remaining_time'} += $bug->{'remaining_time'} if ($remaining_time);
+ $time_info->{'actual_time'} += $bug->{'actual_time'} if ($actual_time);
}
# Check for bug privacy and set $bug->{'secure_mode'} to 'implied' or 'manual'
@@ -845,39 +865,41 @@ foreach my $row (@$data) {
# or because of human choice
my %min_membercontrol;
if (@bugidlist) {
- my $sth = $dbh->prepare(
- "SELECT DISTINCT bugs.bug_id, MIN(group_control_map.membercontrol) " .
- "FROM bugs " .
- "INNER JOIN bug_group_map " .
- "ON bugs.bug_id = bug_group_map.bug_id " .
- "LEFT JOIN group_control_map " .
- "ON group_control_map.product_id = bugs.product_id " .
- "AND group_control_map.group_id = bug_group_map.group_id " .
- "WHERE " . $dbh->sql_in('bugs.bug_id', \@bugidlist) .
- $dbh->sql_group_by('bugs.bug_id'));
- $sth->execute();
- while (my ($bug_id, $min_membercontrol) = $sth->fetchrow_array()) {
- $min_membercontrol{$bug_id} = $min_membercontrol || CONTROLMAPNA;
+ my $sth
+ = $dbh->prepare(
+ "SELECT DISTINCT bugs.bug_id, MIN(group_control_map.membercontrol) "
+ . "FROM bugs "
+ . "INNER JOIN bug_group_map "
+ . "ON bugs.bug_id = bug_group_map.bug_id "
+ . "LEFT JOIN group_control_map "
+ . "ON group_control_map.product_id = bugs.product_id "
+ . "AND group_control_map.group_id = bug_group_map.group_id "
+ . "WHERE "
+ . $dbh->sql_in('bugs.bug_id', \@bugidlist)
+ . $dbh->sql_group_by('bugs.bug_id'));
+ $sth->execute();
+ while (my ($bug_id, $min_membercontrol) = $sth->fetchrow_array()) {
+ $min_membercontrol{$bug_id} = $min_membercontrol || CONTROLMAPNA;
+ }
+ foreach my $bug (@bugs) {
+ next unless defined($min_membercontrol{$bug->{'bug_id'}});
+ if ($min_membercontrol{$bug->{'bug_id'}} == CONTROLMAPMANDATORY) {
+ $bug->{'secure_mode'} = 'implied';
}
- foreach my $bug (@bugs) {
- next unless defined($min_membercontrol{$bug->{'bug_id'}});
- if ($min_membercontrol{$bug->{'bug_id'}} == CONTROLMAPMANDATORY) {
- $bug->{'secure_mode'} = 'implied';
- }
- else {
- $bug->{'secure_mode'} = 'manual';
- }
+ else {
+ $bug->{'secure_mode'} = 'manual';
}
+ }
}
# Compute percentage complete without rounding.
-my $sum = $time_info->{'actual_time'}+$time_info->{'remaining_time'};
+my $sum = $time_info->{'actual_time'} + $time_info->{'remaining_time'};
if ($sum > 0) {
- $time_info->{'percentage_complete'} = 100*$time_info->{'actual_time'}/$sum;
+ $time_info->{'percentage_complete'} = 100 * $time_info->{'actual_time'} / $sum;
+}
+else { # remaining_time <= 0
+ $time_info->{'percentage_complete'} = 0;
}
-else { # remaining_time <= 0
- $time_info->{'percentage_complete'} = 0
-}
################################################################################
# Template Variable Definition
@@ -885,45 +907,45 @@ else { # remaining_time <= 0
# Define the variables and functions that will be passed to the UI template.
-$vars->{'bugs'} = \@bugs;
-$vars->{'buglist'} = \@bugidlist;
-$vars->{'columns'} = $columns;
+$vars->{'bugs'} = \@bugs;
+$vars->{'buglist'} = \@bugidlist;
+$vars->{'columns'} = $columns;
$vars->{'displaycolumns'} = \@displaycolumns;
-$vars->{'openstates'} = [BUG_STATE_OPEN];
-$vars->{'closedstates'} = [map {$_->name} closed_bug_statuses()];
+$vars->{'openstates'} = [BUG_STATE_OPEN];
+$vars->{'closedstates'} = [map { $_->name } closed_bug_statuses()];
# The iCal file needs priorities ordered from 1 to 9 (highest to lowest)
# If there are more than 9 values, just make all the lower ones 9
if ($format->{'extension'} eq 'ics') {
- my $n = 1;
- $vars->{'ics_priorities'} = {};
- my $priorities = get_legal_field_values('priority');
- foreach my $p (@$priorities) {
- $vars->{'ics_priorities'}->{$p} = ($n > 9) ? 9 : $n++;
- }
+ my $n = 1;
+ $vars->{'ics_priorities'} = {};
+ my $priorities = get_legal_field_values('priority');
+ foreach my $p (@$priorities) {
+ $vars->{'ics_priorities'}->{$p} = ($n > 9) ? 9 : $n++;
+ }
}
-$vars->{'order'} = $order;
+$vars->{'order'} = $order;
$vars->{'caneditbugs'} = 1;
-$vars->{'time_info'} = $time_info;
+$vars->{'time_info'} = $time_info;
if (!$user->in_group('editbugs')) {
- foreach my $product (keys %$bugproducts) {
- my $prod = Bugzilla::Product->new({name => $product, cache => 1});
- if (!$user->in_group('editbugs', $prod->id)) {
- $vars->{'caneditbugs'} = 0;
- last;
- }
+ foreach my $product (keys %$bugproducts) {
+ my $prod = Bugzilla::Product->new({name => $product, cache => 1});
+ if (!$user->in_group('editbugs', $prod->id)) {
+ $vars->{'caneditbugs'} = 0;
+ last;
}
+ }
}
my @bugowners = keys %$bugowners;
if (scalar(@bugowners) > 1 && $user->in_group('editbugs')) {
- my $suffix = Bugzilla->params->{'emailsuffix'};
- map(s/$/$suffix/, @bugowners) if $suffix;
- my $bugowners = join(",", @bugowners);
- $vars->{'bugowners'} = $bugowners;
+ my $suffix = Bugzilla->params->{'emailsuffix'};
+ map(s/$/$suffix/, @bugowners) if $suffix;
+ my $bugowners = join(",", @bugowners);
+ $vars->{'bugowners'} = $bugowners;
}
# Whether or not to split the column titles across two rows to make
@@ -931,7 +953,7 @@ if (scalar(@bugowners) > 1 && $user->in_group('editbugs')) {
$vars->{'splitheader'} = $cgi->cookie('SPLITHEADER') ? 1 : 0;
if ($user->settings->{'display_quips'}->{'value'} eq 'on') {
- $vars->{'quip'} = GetQuip();
+ $vars->{'quip'} = GetQuip();
}
$vars->{'currenttime'} = localtime(time());
@@ -941,18 +963,20 @@ $vars->{'currenttime'} = localtime(time());
my @products = keys %$bugproducts;
my $one_product;
if (scalar(@products) == 1) {
- $one_product = Bugzilla::Product->new({ name => $products[0], cache => 1 });
+ $one_product = Bugzilla::Product->new({name => $products[0], cache => 1});
}
+
# This is used in the "Zarroo Boogs" case.
elsif (my @product_input = $cgi->param('product')) {
- if (scalar(@product_input) == 1 and $product_input[0] ne '') {
- $one_product = Bugzilla::Product->new({ name => $product_input[0], cache => 1 });
- }
+ if (scalar(@product_input) == 1 and $product_input[0] ne '') {
+ $one_product = Bugzilla::Product->new({name => $product_input[0], cache => 1});
+ }
}
-# We only want the template to use it if the user can actually
+
+# We only want the template to use it if the user can actually
# enter bugs against it.
if ($one_product && $user->can_enter_product($one_product)) {
- $vars->{'one_product'} = $one_product;
+ $vars->{'one_product'} = $one_product;
}
# See if there's only one component in all the results (or only one component
@@ -960,50 +984,50 @@ if ($one_product && $user->can_enter_product($one_product)) {
my @components = keys %$bugcomponents;
my $one_component;
if (scalar(@components) == 1) {
- $vars->{one_component} = $components[0];
+ $vars->{one_component} = $components[0];
}
+
# This is used in the "Zarroo Boogs" case.
elsif (my @component_input = $cgi->param('component')) {
- if (scalar(@component_input) == 1 and $component_input[0] ne '') {
- $vars->{one_component}= $cgi->param('component');
- }
+ if (scalar(@component_input) == 1 and $component_input[0] ne '') {
+ $vars->{one_component} = $cgi->param('component');
+ }
}
# The following variables are used when the user is making changes to multiple bugs.
if ($dotweak && scalar @bugs) {
- if (!$vars->{'caneditbugs'}) {
- ThrowUserError('auth_failure', {group => 'editbugs',
- action => 'modify',
- object => 'multiple_bugs'});
- }
- $vars->{'dotweak'} = 1;
-
- # issue_session_token needs to write to the master DB.
- Bugzilla->switch_to_main_db();
- $vars->{'token'} = issue_session_token('buglist_mass_change');
- Bugzilla->switch_to_shadow_db();
-
- $vars->{'products'} = $user->get_enterable_products;
- $vars->{'platforms'} = get_legal_field_values('rep_platform');
- $vars->{'op_sys'} = get_legal_field_values('op_sys');
- $vars->{'priorities'} = get_legal_field_values('priority');
- $vars->{'severities'} = get_legal_field_values('bug_severity');
- $vars->{'resolutions'} = get_legal_field_values('resolution');
-
- ($vars->{'flag_types'}, $vars->{any_flags_requesteeble})
- = _get_common_flag_types([keys %$bugcomponentids]);
-
- # Convert bug statuses to their ID.
- my @bug_statuses = map {$dbh->quote($_)} keys %$bugstatuses;
- my $bug_status_ids =
- $dbh->selectcol_arrayref('SELECT id FROM bug_status
- WHERE ' . $dbh->sql_in('value', \@bug_statuses));
-
- # This query collects new statuses which are common to all current bug statuses.
- # It also accepts transitions where the bug status doesn't change.
- $bug_status_ids =
- $dbh->selectcol_arrayref(
- 'SELECT DISTINCT sw1.new_status
+ if (!$vars->{'caneditbugs'}) {
+ ThrowUserError('auth_failure',
+ {group => 'editbugs', action => 'modify', object => 'multiple_bugs'});
+ }
+ $vars->{'dotweak'} = 1;
+
+ # issue_session_token needs to write to the master DB.
+ Bugzilla->switch_to_main_db();
+ $vars->{'token'} = issue_session_token('buglist_mass_change');
+ Bugzilla->switch_to_shadow_db();
+
+ $vars->{'products'} = $user->get_enterable_products;
+ $vars->{'platforms'} = get_legal_field_values('rep_platform');
+ $vars->{'op_sys'} = get_legal_field_values('op_sys');
+ $vars->{'priorities'} = get_legal_field_values('priority');
+ $vars->{'severities'} = get_legal_field_values('bug_severity');
+ $vars->{'resolutions'} = get_legal_field_values('resolution');
+
+ ($vars->{'flag_types'}, $vars->{any_flags_requesteeble})
+ = _get_common_flag_types([keys %$bugcomponentids]);
+
+ # Convert bug statuses to their ID.
+ my @bug_statuses = map { $dbh->quote($_) } keys %$bugstatuses;
+ my $bug_status_ids = $dbh->selectcol_arrayref(
+ 'SELECT id FROM bug_status
+ WHERE ' . $dbh->sql_in('value', \@bug_statuses)
+ );
+
+ # This query collects new statuses which are common to all current bug statuses.
+ # It also accepts transitions where the bug status doesn't change.
+ $bug_status_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT sw1.new_status
FROM status_workflow sw1
INNER JOIN bug_status
ON bug_status.id = sw1.new_status
@@ -1012,74 +1036,78 @@ if ($dotweak && scalar @bugs) {
(SELECT * FROM status_workflow sw2
WHERE sw2.old_status != sw1.new_status
AND '
- . $dbh->sql_in('sw2.old_status', $bug_status_ids)
- . ' AND NOT EXISTS
+ . $dbh->sql_in('sw2.old_status', $bug_status_ids) . ' AND NOT EXISTS
(SELECT * FROM status_workflow sw3
WHERE sw3.new_status = sw1.new_status
- AND sw3.old_status = sw2.old_status))');
-
- $vars->{'current_bug_statuses'} = [keys %$bugstatuses];
- $vars->{'new_bug_statuses'} = Bugzilla::Status->new_from_list($bug_status_ids);
-
- # The groups the user belongs to and which are editable for the given buglist.
- $vars->{'groups'} = GetGroups(\@products);
-
- # If all bugs being changed are in the same product, the user can change
- # their version and component, so generate a list of products, a list of
- # versions for the product (if there is only one product on the list of
- # products), and a list of components for the product.
- if ($one_product) {
- $vars->{'versions'} = [map($_->name, grep($_->is_active, @{ $one_product->versions }))];
- $vars->{'components'} = [map($_->name, grep($_->is_active, @{ $one_product->components }))];
- if (Bugzilla->params->{'usetargetmilestone'}) {
- $vars->{'milestones'} = [map($_->name, grep($_->is_active,
- @{ $one_product->milestones }))];
- }
+ AND sw3.old_status = sw2.old_status))'
+ );
+
+ $vars->{'current_bug_statuses'} = [keys %$bugstatuses];
+ $vars->{'new_bug_statuses'} = Bugzilla::Status->new_from_list($bug_status_ids);
+
+ # The groups the user belongs to and which are editable for the given buglist.
+ $vars->{'groups'} = GetGroups(\@products);
+
+ # If all bugs being changed are in the same product, the user can change
+ # their version and component, so generate a list of products, a list of
+ # versions for the product (if there is only one product on the list of
+ # products), and a list of components for the product.
+ if ($one_product) {
+ $vars->{'versions'}
+ = [map($_->name, grep($_->is_active, @{$one_product->versions}))];
+ $vars->{'components'}
+ = [map($_->name, grep($_->is_active, @{$one_product->components}))];
+ if (Bugzilla->params->{'usetargetmilestone'}) {
+ $vars->{'milestones'}
+ = [map($_->name, grep($_->is_active, @{$one_product->milestones}))];
+ }
+ }
+ else {
+ # We will only show the values at are active in all products.
+ my %values = ();
+ my @fields = ('components', 'versions');
+ if (Bugzilla->params->{'usetargetmilestone'}) {
+ push @fields, 'milestones';
}
- else {
- # We will only show the values at are active in all products.
- my %values = ();
- my @fields = ('components', 'versions');
- if (Bugzilla->params->{'usetargetmilestone'}) {
- push @fields, 'milestones';
- }
- # Go through each product and count the number of times each field
- # is used
- foreach my $product_name (@products) {
- my $product = Bugzilla::Product->new({name => $product_name, cache => 1});
- foreach my $field (@fields) {
- my $list = $product->$field;
- foreach my $item (@$list) {
- ++$values{$field}{$item->name} if $item->is_active;
- }
- }
+ # Go through each product and count the number of times each field
+ # is used
+ foreach my $product_name (@products) {
+ my $product = Bugzilla::Product->new({name => $product_name, cache => 1});
+ foreach my $field (@fields) {
+ my $list = $product->$field;
+ foreach my $item (@$list) {
+ ++$values{$field}{$item->name} if $item->is_active;
}
+ }
+ }
- # Now we get the list of each field and see which values have
- # $product_count (i.e. appears in every product)
- my $product_count = scalar(@products);
- foreach my $field (@fields) {
- my @values = grep { $values{$field}{$_} == $product_count } keys %{$values{$field}};
- if (scalar @values) {
- @{$vars->{$field}} = $field eq 'version'
- ? sort { vers_cmp(lc($a), lc($b)) } @values
- : sort { lc($a) cmp lc($b) } @values
- }
-
- # Do we need to show a warning about limited visiblity?
- if (@values != scalar keys %{$values{$field}}) {
- $vars->{excluded_values} = 1;
- }
- }
+ # Now we get the list of each field and see which values have
+ # $product_count (i.e. appears in every product)
+ my $product_count = scalar(@products);
+ foreach my $field (@fields) {
+ my @values
+ = grep { $values{$field}{$_} == $product_count } keys %{$values{$field}};
+ if (scalar @values) {
+ @{$vars->{$field}}
+ = $field eq 'version'
+ ? sort { vers_cmp(lc($a), lc($b)) } @values
+ : sort { lc($a) cmp lc($b) } @values;
+ }
+
+ # Do we need to show a warning about limited visiblity?
+ if (@values != scalar keys %{$values{$field}}) {
+ $vars->{excluded_values} = 1;
+ }
}
+ }
}
# If we're editing a stored query, use the existing query name as default for
# the "Remember search as" field.
$vars->{'defaultsavename'} = $cgi->param('query_based_on');
-# If we did a quick search then redisplay the previously entered search
+# If we did a quick search then redisplay the previously entered search
# string in the text field.
$vars->{'quicksearch'} = $searchstring;
@@ -1093,32 +1121,33 @@ my $contenttype;
my $disposition = "inline";
if ($format->{'extension'} eq "html") {
- my $list_id = $cgi->param('list_id') || $cgi->param('regetlastlist');
- my $search = $user->save_last_search(
- { bugs => \@bugidlist, order => $order, vars => $vars, list_id => $list_id });
- $cgi->param('list_id', $search->id) if $search;
- $contenttype = "text/html";
+ my $list_id = $cgi->param('list_id') || $cgi->param('regetlastlist');
+ my $search = $user->save_last_search(
+ {bugs => \@bugidlist, order => $order, vars => $vars, list_id => $list_id});
+ $cgi->param('list_id', $search->id) if $search;
+ $contenttype = "text/html";
}
else {
- $contenttype = $format->{'ctype'};
+ $contenttype = $format->{'ctype'};
}
# Set 'urlquerypart' once the buglist ID is known.
-$vars->{'urlquerypart'} = $params->canonicalise_query('order', 'cmdtype',
- 'query_based_on',
- 'token');
+$vars->{'urlquerypart'}
+ = $params->canonicalise_query('order', 'cmdtype', 'query_based_on', 'token');
if ($format->{'extension'} eq "csv") {
- # We set CSV files to be downloaded, as they are designed for importing
- # into other programs.
- $disposition = "attachment";
- # If the user clicked the CSV link in the search results,
- # They should get the Field Description, not the column name in the db
- $vars->{'human'} = $cgi->param('human');
+ # We set CSV files to be downloaded, as they are designed for importing
+ # into other programs.
+ $disposition = "attachment";
+
+ # If the user clicked the CSV link in the search results,
+ # They should get the Field Description, not the column name in the db
+ $vars->{'human'} = $cgi->param('human');
}
-$cgi->close_standby_message($contenttype, $disposition, $disp_prefix, $format->{'extension'});
+$cgi->close_standby_message($contenttype, $disposition, $disp_prefix,
+ $format->{'extension'});
################################################################################
# Content Generation
diff --git a/chart.cgi b/chart.cgi
index c1bafa117..bfbbf300e 100755
--- a/chart.cgi
+++ b/chart.cgi
@@ -46,26 +46,27 @@ use Bugzilla::Token;
# when preparing Bugzilla for mod_perl, this script used these
# variables in so many subroutines that it was easier to just
# make them globals.
-local our $cgi = Bugzilla->cgi;
+local our $cgi = Bugzilla->cgi;
local our $template = Bugzilla->template;
-local our $vars = {};
+local our $vars = {};
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->login(LOGIN_REQUIRED);
if (!Bugzilla->feature('new_charts')) {
- ThrowUserError('feature_disabled', { feature => 'new_charts' });
+ ThrowUserError('feature_disabled', {feature => 'new_charts'});
}
# Go back to query.cgi if we are adding a boolean chart parameter.
if (grep(/^cmd-/, $cgi->param())) {
- my $params = $cgi->canonicalise_query("format", "ctype", "action");
- print $cgi->redirect("query.cgi?format=" . $cgi->param('query_format') .
- ($params ? "&$params" : ""));
- exit;
+ my $params = $cgi->canonicalise_query("format", "ctype", "action");
+ print $cgi->redirect("query.cgi?format="
+ . $cgi->param('query_format')
+ . ($params ? "&$params" : ""));
+ exit;
}
-my $action = $cgi->param('action');
+my $action = $cgi->param('action');
my $series_id = $cgi->param('series_id');
$vars->{'doc_section'} = 'using/reports-and-charts.html#charts';
@@ -75,283 +76,296 @@ $vars->{'doc_section'} = 'using/reports-and-charts.html#charts';
# series_id they apply to (e.g. subscribe, unsubscribe).
my @actions = grep(/^action-/, $cgi->param());
if ($actions[0] && $actions[0] =~ /^action-([^\d]+)(\d*)$/) {
- $action = $1;
- $series_id = $2 if $2;
+ $action = $1;
+ $series_id = $2 if $2;
}
$action ||= "assemble";
# Go to buglist.cgi if we are doing a search.
if ($action eq "search") {
- my $params = $cgi->canonicalise_query("format", "ctype", "action");
- print $cgi->redirect("buglist.cgi" . ($params ? "?$params" : ""));
- exit;
+ my $params = $cgi->canonicalise_query("format", "ctype", "action");
+ print $cgi->redirect("buglist.cgi" . ($params ? "?$params" : ""));
+ exit;
}
-$user->in_group(Bugzilla->params->{"chartgroup"})
- || ThrowUserError("auth_failure", {group => Bugzilla->params->{"chartgroup"},
- action => "use",
- object => "charts"});
+$user->in_group(Bugzilla->params->{"chartgroup"}) || ThrowUserError(
+ "auth_failure",
+ {
+ group => Bugzilla->params->{"chartgroup"},
+ action => "use",
+ object => "charts"
+ }
+);
# Only admins may create public queries
$user->in_group('admin') || $cgi->delete('public');
# All these actions relate to chart construction.
if ($action =~ /^(assemble|add|remove|sum|subscribe|unsubscribe)$/) {
- # These two need to be done before the creation of the Chart object, so
- # that the changes they make will be reflected in it.
- if ($action =~ /^subscribe|unsubscribe$/) {
- detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
- my $series = new Bugzilla::Series($series_id);
- $series->$action($user->id);
- }
-
- my $chart = new Bugzilla::Chart($cgi);
- if ($action =~ /^remove|sum$/) {
- $chart->$action(getSelectedLines());
- }
- elsif ($action eq "add") {
- my @series_ids = getAndValidateSeriesIDs();
- $chart->add(@series_ids);
- }
-
- view($chart);
+ # These two need to be done before the creation of the Chart object, so
+ # that the changes they make will be reflected in it.
+ if ($action =~ /^subscribe|unsubscribe$/) {
+ detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
+ my $series = new Bugzilla::Series($series_id);
+ $series->$action($user->id);
+ }
+
+ my $chart = new Bugzilla::Chart($cgi);
+
+ if ($action =~ /^remove|sum$/) {
+ $chart->$action(getSelectedLines());
+ }
+ elsif ($action eq "add") {
+ my @series_ids = getAndValidateSeriesIDs();
+ $chart->add(@series_ids);
+ }
+
+ view($chart);
}
elsif ($action eq "plot") {
- plot();
+ plot();
}
elsif ($action eq "wrap") {
- # For CSV "wrap", we go straight to "plot".
- if ($cgi->param('ctype') && $cgi->param('ctype') eq "csv") {
- plot();
- }
- else {
- wrap();
- }
+
+ # For CSV "wrap", we go straight to "plot".
+ if ($cgi->param('ctype') && $cgi->param('ctype') eq "csv") {
+ plot();
+ }
+ else {
+ wrap();
+ }
}
elsif ($action eq "create") {
- assertCanCreate($cgi);
- my $token = $cgi->param('token');
- check_hash_token($token, ['create-series']);
-
- my $series = new Bugzilla::Series($cgi);
+ assertCanCreate($cgi);
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['create-series']);
- ThrowUserError("series_already_exists", {'series' => $series})
- if $series->existsInDatabase;
+ my $series = new Bugzilla::Series($cgi);
- $series->writeToDatabase();
- $vars->{'message'} = "series_created";
- $vars->{'series'} = $series;
+ ThrowUserError("series_already_exists", {'series' => $series})
+ if $series->existsInDatabase;
- my $chart = new Bugzilla::Chart($cgi);
- view($chart);
+ $series->writeToDatabase();
+ $vars->{'message'} = "series_created";
+ $vars->{'series'} = $series;
+
+ my $chart = new Bugzilla::Chart($cgi);
+ view($chart);
}
elsif ($action eq "edit") {
- my $series = assertCanEdit($series_id);
- edit($series);
+ my $series = assertCanEdit($series_id);
+ edit($series);
}
elsif ($action eq "alter") {
- my $series = assertCanEdit($series_id);
- my $token = $cgi->param('token');
- check_hash_token($token, [$series->id, $series->name]);
- # XXX - This should be replaced by $series->set_foo() methods.
- $series = new Bugzilla::Series($cgi);
-
- # We need to check if there is _another_ series in the database with
- # our (potentially new) name. So we call existsInDatabase() to see if
- # the return value is us or some other series we need to avoid stomping
- # on.
- my $id_of_series_in_db = $series->existsInDatabase();
- if (defined($id_of_series_in_db) &&
- $id_of_series_in_db != $series->{'series_id'})
- {
- ThrowUserError("series_already_exists", {'series' => $series});
- }
-
- $series->writeToDatabase();
- $vars->{'changes_saved'} = 1;
-
- edit($series);
+ my $series = assertCanEdit($series_id);
+ my $token = $cgi->param('token');
+ check_hash_token($token, [$series->id, $series->name]);
+
+ # XXX - This should be replaced by $series->set_foo() methods.
+ $series = new Bugzilla::Series($cgi);
+
+ # We need to check if there is _another_ series in the database with
+ # our (potentially new) name. So we call existsInDatabase() to see if
+ # the return value is us or some other series we need to avoid stomping
+ # on.
+ my $id_of_series_in_db = $series->existsInDatabase();
+ if (defined($id_of_series_in_db)
+ && $id_of_series_in_db != $series->{'series_id'})
+ {
+ ThrowUserError("series_already_exists", {'series' => $series});
+ }
+
+ $series->writeToDatabase();
+ $vars->{'changes_saved'} = 1;
+
+ edit($series);
}
elsif ($action eq "confirm-delete") {
- $vars->{'series'} = assertCanEdit($series_id);
+ $vars->{'series'} = assertCanEdit($series_id);
- print $cgi->header();
- $template->process("reports/delete-series.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ print $cgi->header();
+ $template->process("reports/delete-series.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
elsif ($action eq "delete") {
- my $series = assertCanEdit($series_id);
- my $token = $cgi->param('token');
- check_hash_token($token, [$series->id, $series->name]);
-
- $dbh->bz_start_transaction();
-
- $series->remove_from_db();
- # Remove (sub)categories which no longer have any series.
- foreach my $cat (qw(category subcategory)) {
- my $is_used = $dbh->selectrow_array("SELECT COUNT(*) FROM series WHERE $cat = ?",
- undef, $series->{"${cat}_id"});
- if (!$is_used) {
- $dbh->do('DELETE FROM series_categories WHERE id = ?',
- undef, $series->{"${cat}_id"});
- }
+ my $series = assertCanEdit($series_id);
+ my $token = $cgi->param('token');
+ check_hash_token($token, [$series->id, $series->name]);
+
+ $dbh->bz_start_transaction();
+
+ $series->remove_from_db();
+
+ # Remove (sub)categories which no longer have any series.
+ foreach my $cat (qw(category subcategory)) {
+ my $is_used
+ = $dbh->selectrow_array("SELECT COUNT(*) FROM series WHERE $cat = ?",
+ undef, $series->{"${cat}_id"});
+ if (!$is_used) {
+ $dbh->do('DELETE FROM series_categories WHERE id = ?',
+ undef, $series->{"${cat}_id"});
}
- $dbh->bz_commit_transaction();
+ }
+ $dbh->bz_commit_transaction();
- $vars->{'message'} = "series_deleted";
- $vars->{'series'} = $series;
- view();
+ $vars->{'message'} = "series_deleted";
+ $vars->{'series'} = $series;
+ view();
}
elsif ($action eq "convert_search") {
- my $saved_search = $cgi->param('series_from_search') || '';
- my ($query) = grep { $_->name eq $saved_search } @{ $user->queries };
- my $url = '';
- if ($query) {
- my $params = new Bugzilla::CGI($query->edit_link);
- # These two parameters conflict with the one below.
- $url = $params->canonicalise_query('format', 'query_format');
- $url = '&amp;' . html_quote($url);
- }
- print $cgi->redirect(-location => correct_urlbase() . "query.cgi?format=create-series$url");
+ my $saved_search = $cgi->param('series_from_search') || '';
+ my ($query) = grep { $_->name eq $saved_search } @{$user->queries};
+ my $url = '';
+ if ($query) {
+ my $params = new Bugzilla::CGI($query->edit_link);
+
+ # These two parameters conflict with the one below.
+ $url = $params->canonicalise_query('format', 'query_format');
+ $url = '&amp;' . html_quote($url);
+ }
+ print $cgi->redirect(
+ -location => correct_urlbase() . "query.cgi?format=create-series$url");
}
else {
- ThrowUserError('unknown_action', {action => $action});
+ ThrowUserError('unknown_action', {action => $action});
}
exit;
# Find any selected series and return either the first or all of them.
sub getAndValidateSeriesIDs {
- my @series_ids = grep(/^\d+$/, $cgi->param("name"));
+ my @series_ids = grep(/^\d+$/, $cgi->param("name"));
- return wantarray ? @series_ids : $series_ids[0];
+ return wantarray ? @series_ids : $series_ids[0];
}
# Return a list of IDs of all the lines selected in the UI.
sub getSelectedLines {
- my @ids = map { /^select(\d+)$/ ? $1 : () } $cgi->param();
+ my @ids = map { /^select(\d+)$/ ? $1 : () } $cgi->param();
- return @ids;
+ return @ids;
}
-# Check if the user is the owner of series_id or is an admin.
+# Check if the user is the owner of series_id or is an admin.
sub assertCanEdit {
- my $series_id = shift;
- my $user = Bugzilla->user;
+ my $series_id = shift;
+ my $user = Bugzilla->user;
- my $series = new Bugzilla::Series($series_id)
- || ThrowCodeError('invalid_series_id');
+ my $series
+ = new Bugzilla::Series($series_id) || ThrowCodeError('invalid_series_id');
- if (!$user->in_group('admin') && $series->{creator_id} != $user->id) {
- ThrowUserError('illegal_series_edit');
- }
+ if (!$user->in_group('admin') && $series->{creator_id} != $user->id) {
+ ThrowUserError('illegal_series_edit');
+ }
- return $series;
+ return $series;
}
# Check if the user is permitted to create this series with these parameters.
sub assertCanCreate {
- my ($cgi) = shift;
- my $user = Bugzilla->user;
+ my ($cgi) = shift;
+ my $user = Bugzilla->user;
- $user->in_group("editbugs") || ThrowUserError("illegal_series_creation");
+ $user->in_group("editbugs") || ThrowUserError("illegal_series_creation");
- # Check permission for frequency
- my $min_freq = 7;
- if ($cgi->param('frequency') < $min_freq && !$user->in_group("admin")) {
- ThrowUserError("illegal_frequency", { 'minimum' => $min_freq });
- }
+ # Check permission for frequency
+ my $min_freq = 7;
+ if ($cgi->param('frequency') < $min_freq && !$user->in_group("admin")) {
+ ThrowUserError("illegal_frequency", {'minimum' => $min_freq});
+ }
}
sub validateWidthAndHeight {
- $vars->{'width'} = $cgi->param('width');
- $vars->{'height'} = $cgi->param('height');
-
- if (defined($vars->{'width'})) {
- (detaint_natural($vars->{'width'}) && $vars->{'width'} > 0)
- || ThrowUserError("invalid_dimensions");
- }
-
- if (defined($vars->{'height'})) {
- (detaint_natural($vars->{'height'}) && $vars->{'height'} > 0)
- || ThrowUserError("invalid_dimensions");
- }
-
- # The equivalent of 2000 square seems like a very reasonable maximum size.
- # This is merely meant to prevent accidental or deliberate DOS, and should
- # have no effect in practice.
- if ($vars->{'width'} && $vars->{'height'}) {
- (($vars->{'width'} * $vars->{'height'}) <= 4000000)
- || ThrowUserError("chart_too_large");
- }
+ $vars->{'width'} = $cgi->param('width');
+ $vars->{'height'} = $cgi->param('height');
+
+ if (defined($vars->{'width'})) {
+ (detaint_natural($vars->{'width'}) && $vars->{'width'} > 0)
+ || ThrowUserError("invalid_dimensions");
+ }
+
+ if (defined($vars->{'height'})) {
+ (detaint_natural($vars->{'height'}) && $vars->{'height'} > 0)
+ || ThrowUserError("invalid_dimensions");
+ }
+
+ # The equivalent of 2000 square seems like a very reasonable maximum size.
+ # This is merely meant to prevent accidental or deliberate DOS, and should
+ # have no effect in practice.
+ if ($vars->{'width'} && $vars->{'height'}) {
+ (($vars->{'width'} * $vars->{'height'}) <= 4000000)
+ || ThrowUserError("chart_too_large");
+ }
}
sub edit {
- my $series = shift;
+ my $series = shift;
- $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
- $vars->{'default'} = $series;
- $vars->{'message'} = 'series_updated' if $vars->{'changes_saved'};
+ $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
+ $vars->{'default'} = $series;
+ $vars->{'message'} = 'series_updated' if $vars->{'changes_saved'};
- print $cgi->header();
- $template->process("reports/edit-series.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ print $cgi->header();
+ $template->process("reports/edit-series.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
sub plot {
- validateWidthAndHeight();
- $vars->{'chart'} = new Bugzilla::Chart($cgi);
+ validateWidthAndHeight();
+ $vars->{'chart'} = new Bugzilla::Chart($cgi);
- my $format = $template->get_format("reports/chart", "", scalar($cgi->param('ctype')));
- $format->{'ctype'} = 'text/html' if $cgi->param('debug');
+ my $format
+ = $template->get_format("reports/chart", "", scalar($cgi->param('ctype')));
+ $format->{'ctype'} = 'text/html' if $cgi->param('debug');
- $cgi->set_dated_content_disp('inline', 'chart', $format->{extension});
- print $cgi->header($format->{'ctype'});
- disable_utf8() if ($format->{'ctype'} =~ /^image\//);
+ $cgi->set_dated_content_disp('inline', 'chart', $format->{extension});
+ print $cgi->header($format->{'ctype'});
+ disable_utf8() if ($format->{'ctype'} =~ /^image\//);
- # Debugging PNGs is a pain; we need to be able to see the error messages
- $vars->{'chart'}->dump() if $cgi->param('debug');
+ # Debugging PNGs is a pain; we need to be able to see the error messages
+ $vars->{'chart'}->dump() if $cgi->param('debug');
- $template->process($format->{'template'}, $vars)
- || ThrowTemplateError($template->error());
+ $template->process($format->{'template'}, $vars)
+ || ThrowTemplateError($template->error());
}
sub wrap {
- validateWidthAndHeight();
-
- # We create a Chart object so we can validate the parameters
- my $chart = new Bugzilla::Chart($cgi);
-
- $vars->{'time'} = localtime(time());
-
- $vars->{'imagebase'} = $cgi->canonicalise_query(
- "action", "action-wrap", "ctype", "format", "width", "height");
-
- print $cgi->header();
- $template->process("reports/chart.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ validateWidthAndHeight();
+
+ # We create a Chart object so we can validate the parameters
+ my $chart = new Bugzilla::Chart($cgi);
+
+ $vars->{'time'} = localtime(time());
+
+ $vars->{'imagebase'}
+ = $cgi->canonicalise_query("action", "action-wrap", "ctype", "format",
+ "width", "height");
+
+ print $cgi->header();
+ $template->process("reports/chart.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
sub view {
- my $chart = shift;
+ my $chart = shift;
- # Set defaults
- foreach my $field ('category', 'subcategory', 'name', 'ctype') {
- $vars->{'default'}{$field} = $cgi->param($field) || 0;
- }
+ # Set defaults
+ foreach my $field ('category', 'subcategory', 'name', 'ctype') {
+ $vars->{'default'}{$field} = $cgi->param($field) || 0;
+ }
- # Pass the state object to the display UI.
- $vars->{'chart'} = $chart;
- $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
+ # Pass the state object to the display UI.
+ $vars->{'chart'} = $chart;
+ $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
- print $cgi->header();
+ print $cgi->header();
- # If we have having problems with bad data, we can set debug=1 to dump
- # the data structure.
- $chart->dump() if $cgi->param('debug');
+ # If we have having problems with bad data, we can set debug=1 to dump
+ # the data structure.
+ $chart->dump() if $cgi->param('debug');
- $template->process("reports/create-chart.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $template->process("reports/create-chart.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
diff --git a/checksetup.pl b/checksetup.pl
index 5dda0df6f..857584937 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -26,8 +26,8 @@ use Safe;
use Bugzilla::Constants;
use Bugzilla::Install::Requirements;
-use Bugzilla::Install::Util qw(install_string get_version_and_os
- init_console success);
+use Bugzilla::Install::Util qw(install_string get_version_and_os
+ init_console success);
######################################################################
# Live Code
@@ -42,24 +42,25 @@ init_console();
my %switch;
GetOptions(\%switch, 'help|h|?', 'check-modules', 'no-templates|t',
- 'verbose|v|no-silent', 'make-admin=s',
- 'reset-password=s', 'version|V');
+ 'verbose|v|no-silent', 'make-admin=s', 'reset-password=s', 'version|V');
# Print the help message if that switch was selected.
pod2usage({-verbose => 1, -exitval => 1}) if $switch{'help'};
-# Read in the "answers" file if it exists, for running in
+# Read in the "answers" file if it exists, for running in
# non-interactive mode.
my $answers_file = $ARGV[0];
-my $silent = $answers_file && !$switch{'verbose'};
+my $silent = $answers_file && !$switch{'verbose'};
print(install_string('header', get_version_and_os()) . "\n") unless $silent;
exit 0 if $switch{'version'};
+
# Check required --MODULES--
my $module_results = check_requirements(!$silent);
-Bugzilla::Install::Requirements::print_module_instructions(
- $module_results, !$silent);
+Bugzilla::Install::Requirements::print_module_instructions($module_results,
+ !$silent);
exit 1 if !$module_results->{pass};
+
# Break out if checking the modules is all we have been asked to do.
exit 0 if $switch{'check-modules'};
@@ -86,7 +87,7 @@ import Bugzilla::Install::Localconfig qw(update_localconfig);
require Bugzilla::Install::Filesystem;
import Bugzilla::Install::Filesystem qw(update_filesystem create_htaccess
- fix_all_file_permissions);
+ fix_all_file_permissions);
require Bugzilla::Install::DB;
require Bugzilla::DB;
require Bugzilla::Template;
@@ -100,8 +101,8 @@ Bugzilla->installation_answers($answers_file);
# Check and update --LOCAL-- configuration
###########################################################################
-print "Reading " . bz_locations()->{'localconfig'} . "...\n" unless $silent;
-update_localconfig({ output => !$silent });
+print "Reading " . bz_locations()->{'localconfig'} . "...\n" unless $silent;
+update_localconfig({output => !$silent});
my $lc_hash = Bugzilla->localconfig;
###########################################################################
@@ -117,8 +118,10 @@ Bugzilla::DB::bz_create_database() if $lc_hash->{'db_check'};
# now get a handle to the database:
my $dbh = Bugzilla->dbh;
+
# Create the tables, and do any database-specific schema changes.
$dbh->bz_setup_database();
+
# Populate the tables that hold the values for the <select> fields.
$dbh->bz_populate_enum_tables();
@@ -126,7 +129,7 @@ $dbh->bz_populate_enum_tables();
# Check --DATA-- directory
###########################################################################
-update_filesystem({ index_html => $lc_hash->{'index_html'} });
+update_filesystem({index_html => $lc_hash->{'index_html'}});
create_htaccess() if $lc_hash->{'create_htaccess'};
# Remove parameters from the params file that no longer exist in Bugzilla,
@@ -138,7 +141,7 @@ my %old_params = update_params();
###########################################################################
Bugzilla::Template::precompile_templates(!$silent)
- unless $switch{'no-templates'};
+ unless $switch{'no-templates'};
###########################################################################
# Set proper rights (--CHMOD--)
@@ -195,7 +198,7 @@ Bugzilla::Install::make_admin($switch{'make-admin'}) if $switch{'make-admin'};
Bugzilla::Install::create_admin();
Bugzilla::Install::reset_password($switch{'reset-password'})
- if $switch{'reset-password'};
+ if $switch{'reset-password'};
###########################################################################
# Create default Product
@@ -203,7 +206,7 @@ Bugzilla::Install::reset_password($switch{'reset-password'})
Bugzilla::Install::create_default_product();
-Bugzilla::Hook::process('install_before_final_checks', { silent => $silent });
+Bugzilla::Hook::process('install_before_final_checks', {silent => $silent});
###########################################################################
# Final checks
@@ -213,13 +216,12 @@ Bugzilla::Hook::process('install_before_final_checks', { silent => $silent });
Bugzilla->memcached->clear_all();
# Check if the default parameter for urlbase is still set, and if so, give
-# notification that they should go and visit editparams.cgi
+# notification that they should go and visit editparams.cgi
if (Bugzilla->params->{'urlbase'} eq '') {
- print "\n" . get_text('install_urlbase_default') . "\n"
- unless $silent;
+ print "\n" . get_text('install_urlbase_default') . "\n" unless $silent;
}
if (!$silent) {
- success(get_text('install_success'));
+ success(get_text('install_success'));
}
__END__
diff --git a/clean-bug-user-last-visit.pl b/clean-bug-user-last-visit.pl
index 57486bfed..d9d9f83be 100755
--- a/clean-bug-user-last-visit.pl
+++ b/clean-bug-user-last-visit.pl
@@ -31,8 +31,6 @@ Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
my $dbh = Bugzilla->dbh;
my $sql = 'DELETE FROM bug_user_last_visit WHERE last_visit_ts < '
- . $dbh->sql_date_math('NOW()',
- '-',
- Bugzilla->params->{last_visit_keep_days},
- 'DAY');
+ . $dbh->sql_date_math('NOW()', '-', Bugzilla->params->{last_visit_keep_days},
+ 'DAY');
$dbh->do($sql);
diff --git a/colchange.cgi b/colchange.cgi
index 77d9f11ee..c5f08b794 100755
--- a/colchange.cgi
+++ b/colchange.cgi
@@ -24,161 +24,169 @@ use Storable qw(dclone);
# Maps parameters that control columns to the names of columns.
use constant COLUMN_PARAMS => {
- 'useclassification' => ['classification'],
- 'usetargetmilestone' => ['target_milestone'],
- 'useqacontact' => ['qa_contact', 'qa_contact_realname'],
- 'usestatuswhiteboard' => ['status_whiteboard'],
- 'timetrackinggroup' => ['deadline'],
+ 'useclassification' => ['classification'],
+ 'usetargetmilestone' => ['target_milestone'],
+ 'useqacontact' => ['qa_contact', 'qa_contact_realname'],
+ 'usestatuswhiteboard' => ['status_whiteboard'],
+ 'timetrackinggroup' => ['deadline'],
};
# We only show these columns if an object of this type exists in the
# database.
-use constant COLUMN_CLASSES => {
- 'Bugzilla::Flag' => 'flagtypes.name',
- 'Bugzilla::Keyword' => 'keywords',
-};
+use constant COLUMN_CLASSES =>
+ {'Bugzilla::Flag' => 'flagtypes.name', 'Bugzilla::Keyword' => 'keywords',};
my $user = Bugzilla->login();
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
my $columns = dclone(Bugzilla::Search::COLUMNS);
# You can't manually select "relevance" as a column you want to see.
delete $columns->{'relevance'};
-foreach my $param (keys %{ COLUMN_PARAMS() }) {
- next if Bugzilla->params->{$param};
- foreach my $column (@{ COLUMN_PARAMS->{$param} }) {
- delete $columns->{$column};
- }
+foreach my $param (keys %{COLUMN_PARAMS()}) {
+ next if Bugzilla->params->{$param};
+ foreach my $column (@{COLUMN_PARAMS->{$param}}) {
+ delete $columns->{$column};
+ }
}
-foreach my $class (keys %{ COLUMN_CLASSES() }) {
- eval("use $class; 1;") || die $@;
- my $column = COLUMN_CLASSES->{$class};
- delete $columns->{$column} if !$class->any_exist;
+foreach my $class (keys %{COLUMN_CLASSES()}) {
+ eval("use $class; 1;") || die $@;
+ my $column = COLUMN_CLASSES->{$class};
+ delete $columns->{$column} if !$class->any_exist;
}
if (!$user->is_timetracker) {
- foreach my $column (TIMETRACKING_FIELDS) {
- delete $columns->{$column};
- }
+ foreach my $column (TIMETRACKING_FIELDS) {
+ delete $columns->{$column};
+ }
}
$vars->{'columns'} = $columns;
my @collist;
if (defined $cgi->param('rememberedquery')) {
- my $search;
- if (defined $cgi->param('saved_search')) {
- $search = new Bugzilla::Search::Saved($cgi->param('saved_search'));
- }
-
- my $token = $cgi->param('token');
- if ($search) {
- check_hash_token($token, [$search->id, $search->name]);
- }
- else {
- check_hash_token($token, ['default-list']);
- }
-
- my $splitheader = 0;
- if (defined $cgi->param('resetit')) {
- @collist = DEFAULT_COLUMN_LIST;
- } else {
- if (defined $cgi->param("selected_columns")) {
- @collist = grep { exists $columns->{$_} }
- $cgi->param("selected_columns");
- }
- if (defined $cgi->param('splitheader')) {
- $splitheader = $cgi->param('splitheader')? 1: 0;
- }
- }
- my $list = join(" ", @collist);
-
- if ($list) {
- # Only set the cookie if this is not a saved search.
- # Saved searches have their own column list
- if (!$cgi->param('save_columns_for_search')) {
- $cgi->send_cookie(-name => 'COLUMNLIST',
- -value => $list,
- -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
- }
- }
- else {
- $cgi->remove_cookie('COLUMNLIST');
- }
- if ($splitheader) {
- $cgi->send_cookie(-name => 'SPLITHEADER',
- -value => $splitheader,
- -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
+ my $search;
+ if (defined $cgi->param('saved_search')) {
+ $search = new Bugzilla::Search::Saved($cgi->param('saved_search'));
+ }
+
+ my $token = $cgi->param('token');
+ if ($search) {
+ check_hash_token($token, [$search->id, $search->name]);
+ }
+ else {
+ check_hash_token($token, ['default-list']);
+ }
+
+ my $splitheader = 0;
+ if (defined $cgi->param('resetit')) {
+ @collist = DEFAULT_COLUMN_LIST;
+ }
+ else {
+ if (defined $cgi->param("selected_columns")) {
+ @collist = grep { exists $columns->{$_} } $cgi->param("selected_columns");
}
- else {
- $cgi->remove_cookie('SPLITHEADER');
+ if (defined $cgi->param('splitheader')) {
+ $splitheader = $cgi->param('splitheader') ? 1 : 0;
}
-
- $vars->{'message'} = "change_columns";
-
- if ($cgi->param('save_columns_for_search')
- && defined $search && $search->user->id == $user->id)
- {
- my $params = new Bugzilla::CGI($search->url);
- $params->param('columnlist', join(",", @collist));
- $search->set_url($params->query_string());
- $search->update();
+ }
+ my $list = join(" ", @collist);
+
+ if ($list) {
+
+ # Only set the cookie if this is not a saved search.
+ # Saved searches have their own column list
+ if (!$cgi->param('save_columns_for_search')) {
+ $cgi->send_cookie(
+ -name => 'COLUMNLIST',
+ -value => $list,
+ -expires => 'Fri, 01-Jan-2038 00:00:00 GMT'
+ );
}
-
- my $params = new Bugzilla::CGI($cgi->param('rememberedquery'));
+ }
+ else {
+ $cgi->remove_cookie('COLUMNLIST');
+ }
+ if ($splitheader) {
+ $cgi->send_cookie(
+ -name => 'SPLITHEADER',
+ -value => $splitheader,
+ -expires => 'Fri, 01-Jan-2038 00:00:00 GMT'
+ );
+ }
+ else {
+ $cgi->remove_cookie('SPLITHEADER');
+ }
+
+ $vars->{'message'} = "change_columns";
+
+ if ( $cgi->param('save_columns_for_search')
+ && defined $search
+ && $search->user->id == $user->id)
+ {
+ my $params = new Bugzilla::CGI($search->url);
$params->param('columnlist', join(",", @collist));
- $vars->{'redirect_url'} = "buglist.cgi?".$params->query_string();
-
-
- # If we're running on Microsoft IIS, $cgi->redirect discards
- # the Set-Cookie lines. In mod_perl, $cgi->redirect with cookies
- # causes the page to be rendered as text/plain.
- # Workaround is to use the old-fashioned redirection mechanism.
- # See bug 214466 and bug 376044 for details.
- if ($ENV{'MOD_PERL'}
- || $ENV{'SERVER_SOFTWARE'} =~ /Microsoft-IIS/
- || $ENV{'SERVER_SOFTWARE'} =~ /Sun ONE Web/)
- {
- print $cgi->header(-type => "text/html",
- -refresh => "0; URL=$vars->{'redirect_url'}");
- }
- else {
- print $cgi->redirect($vars->{'redirect_url'});
- exit;
- }
-
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $search->set_url($params->query_string());
+ $search->update();
+ }
+
+ my $params = new Bugzilla::CGI($cgi->param('rememberedquery'));
+ $params->param('columnlist', join(",", @collist));
+ $vars->{'redirect_url'} = "buglist.cgi?" . $params->query_string();
+
+
+ # If we're running on Microsoft IIS, $cgi->redirect discards
+ # the Set-Cookie lines. In mod_perl, $cgi->redirect with cookies
+ # causes the page to be rendered as text/plain.
+ # Workaround is to use the old-fashioned redirection mechanism.
+ # See bug 214466 and bug 376044 for details.
+ if ( $ENV{'MOD_PERL'}
+ || $ENV{'SERVER_SOFTWARE'} =~ /Microsoft-IIS/
+ || $ENV{'SERVER_SOFTWARE'} =~ /Sun ONE Web/)
+ {
+ print $cgi->header(
+ -type => "text/html",
+ -refresh => "0; URL=$vars->{'redirect_url'}"
+ );
+ }
+ else {
+ print $cgi->redirect($vars->{'redirect_url'});
exit;
+ }
+
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if (defined $cgi->param('columnlist')) {
- @collist = split(/[ ,]+/, $cgi->param('columnlist'));
-} elsif (defined $cgi->cookie('COLUMNLIST')) {
- @collist = split(/ /, $cgi->cookie('COLUMNLIST'));
-} else {
- @collist = DEFAULT_COLUMN_LIST;
+ @collist = split(/[ ,]+/, $cgi->param('columnlist'));
+}
+elsif (defined $cgi->cookie('COLUMNLIST')) {
+ @collist = split(/ /, $cgi->cookie('COLUMNLIST'));
+}
+else {
+ @collist = DEFAULT_COLUMN_LIST;
}
-$vars->{'collist'} = \@collist;
+$vars->{'collist'} = \@collist;
$vars->{'splitheader'} = $cgi->cookie('SPLITHEADER') ? 1 : 0;
$vars->{'buffer'} = $cgi->query_string();
my $search;
if (defined $cgi->param('query_based_on')) {
- my $searches = $user->queries;
- my ($search) = grep($_->name eq $cgi->param('query_based_on'), @$searches);
+ my $searches = $user->queries;
+ my ($search) = grep($_->name eq $cgi->param('query_based_on'), @$searches);
- if ($search) {
- $vars->{'saved_search'} = $search;
- }
+ if ($search) {
+ $vars->{'saved_search'} = $search;
+ }
}
# Generate and return the UI (HTML page) from the appropriate template.
diff --git a/collectstats.pl b/collectstats.pl
index 339e428bc..f7a926499 100755
--- a/collectstats.pl
+++ b/collectstats.pl
@@ -37,7 +37,7 @@ pod2usage({-verbose => 1, -exitval => 1}) if $switch{'help'};
# in the regenerate mode).
$| = 1;
-my $datadir = bz_locations()->{'datadir'};
+my $datadir = bz_locations()->{'datadir'};
my $graphsdir = bz_locations()->{'graphsdir'};
# We use a dummy product instance with ID 0, representing all products
@@ -47,10 +47,11 @@ bless($product_all, 'Bugzilla::Product');
# Tidy up after graphing module
my $cwd = Cwd::getcwd();
if (chdir($graphsdir)) {
- unlink <./*.gif>;
- unlink <./*.png>;
- # chdir("..") doesn't work if graphs is a symlink, see bug 429378
- chdir($cwd);
+ unlink <./*.gif>;
+ unlink <./*.png>;
+
+ # chdir("..") doesn't work if graphs is a symlink, see bug 429378
+ chdir($cwd);
}
my $dbh = Bugzilla->switch_to_shadow_db();
@@ -60,9 +61,9 @@ my $dbh = Bugzilla->switch_to_shadow_db();
# may have existed in the past, or have been renamed. We want them all.
my $fields = {};
foreach my $field ('bug_status', 'resolution') {
- my $values = get_legal_field_values($field);
- my $old_values = $dbh->selectcol_arrayref(
- "SELECT bugs_activity.added
+ my $values = get_legal_field_values($field);
+ my $old_values = $dbh->selectcol_arrayref(
+ "SELECT bugs_activity.added
FROM bugs_activity
INNER JOIN fielddefs
ON fielddefs.id = bugs_activity.fieldid
@@ -80,15 +81,16 @@ foreach my $field ('bug_status', 'resolution') {
LEFT JOIN $field
ON $field.value = bugs_activity.removed
WHERE fielddefs.name = ?
- AND $field.id IS NULL",
- undef, ($field, $field));
+ AND $field.id IS NULL", undef, ($field, $field)
+ );
- push(@$values, @$old_values);
- $fields->{$field} = $values;
+ push(@$values, @$old_values);
+ $fields->{$field} = $values;
}
-my @statuses = @{$fields->{'bug_status'}};
+my @statuses = @{$fields->{'bug_status'}};
my @resolutions = @{$fields->{'resolution'}};
+
# Exclude "" from the resolution list.
@resolutions = grep {$_} @resolutions;
@@ -97,30 +99,34 @@ my @resolutions = @{$fields->{'resolution'}};
# at once and stuff it into some data structures.
my (%bug_status, %bug_resolution, %removed);
if ($switch{'regenerate'}) {
- %bug_resolution = @{ $dbh->selectcol_arrayref(
- 'SELECT bug_id, resolution FROM bugs', {Columns=>[1,2]}) };
- %bug_status = @{ $dbh->selectcol_arrayref(
- 'SELECT bug_id, bug_status FROM bugs', {Columns=>[1,2]}) };
-
- my $removed_sth = $dbh->prepare(
+ %bug_resolution = @{
+ $dbh->selectcol_arrayref('SELECT bug_id, resolution FROM bugs',
+ {Columns => [1, 2]})
+ };
+ %bug_status = @{
+ $dbh->selectcol_arrayref('SELECT bug_id, bug_status FROM bugs',
+ {Columns => [1, 2]})
+ };
+
+ my $removed_sth = $dbh->prepare(
q{SELECT bugs_activity.bug_id, bugs_activity.removed,}
- . $dbh->sql_to_days('bugs_activity.bug_when')
- . q{ FROM bugs_activity
+ . $dbh->sql_to_days('bugs_activity.bug_when')
+ . q{ FROM bugs_activity
WHERE bugs_activity.fieldid = ?
- ORDER BY bugs_activity.bug_when});
-
- %removed = (bug_status => {}, resolution => {});
- foreach my $field (qw(bug_status resolution)) {
- my $field_id = Bugzilla::Field->check($field)->id;
- my $rows = $dbh->selectall_arrayref($removed_sth, undef, $field_id);
- my $hash = $removed{$field};
- foreach my $row (@$rows) {
- my ($bug_id, $removed, $when) = @$row;
- $hash->{$bug_id} ||= [];
- push(@{ $hash->{$bug_id} }, { when => int($when),
- removed => $removed });
- }
+ ORDER BY bugs_activity.bug_when}
+ );
+
+ %removed = (bug_status => {}, resolution => {});
+ foreach my $field (qw(bug_status resolution)) {
+ my $field_id = Bugzilla::Field->check($field)->id;
+ my $rows = $dbh->selectall_arrayref($removed_sth, undef, $field_id);
+ my $hash = $removed{$field};
+ foreach my $row (@$rows) {
+ my ($bug_id, $removed, $when) = @$row;
+ $hash->{$bug_id} ||= [];
+ push(@{$hash->{$bug_id}}, {when => int($when), removed => $removed});
}
+ }
}
my $tstart = time;
@@ -130,84 +136,87 @@ unshift(@myproducts, $product_all);
my $dir = "$datadir/mining";
if (!-d $dir) {
- mkdir $dir or die "mkdir $dir failed: $!";
- fix_dir_permissions($dir);
+ mkdir $dir or die "mkdir $dir failed: $!";
+ fix_dir_permissions($dir);
}
foreach (@myproducts) {
- if ($switch{'regenerate'}) {
- regenerate_stats($dir, $_, \%bug_resolution, \%bug_status, \%removed);
- } else {
- &collect_stats($dir, $_);
- }
+ if ($switch{'regenerate'}) {
+ regenerate_stats($dir, $_, \%bug_resolution, \%bug_status, \%removed);
+ }
+ else {
+ &collect_stats($dir, $_);
+ }
}
+
# Fix permissions for all files in mining/.
fix_dir_permissions($dir);
my $tend = time;
+
# Uncomment the following line for performance testing.
#say "Total time taken " . delta_time($tstart, $tend);
CollectSeriesData();
sub collect_stats {
- my $dir = shift;
- my $product = shift;
- my $when = localtime (time);
- my $dbh = Bugzilla->dbh;
-
- my $file = join '/', $dir, $product->id;
- my $exists = -f $file;
-
- # if the file exists, get the old status and resolution list for that product.
- my @data;
- @data = get_old_data($file) if $exists;
-
- # If @data is not empty, then we have to recreate the data file.
- if (scalar(@data)) {
- open(DATA, '>', $file)
- || ThrowCodeError('chart_file_open_fail', {'filename' => $file});
- }
- else {
- open(DATA, '>>', $file)
- || ThrowCodeError('chart_file_open_fail', {'filename' => $file});
- }
+ my $dir = shift;
+ my $product = shift;
+ my $when = localtime(time);
+ my $dbh = Bugzilla->dbh;
- if (Bugzilla->params->{'utf8'}) {
- binmode DATA, ':utf8';
- }
-
- # Now collect current data.
- my @row = (today());
- my $status_sql = q{SELECT COUNT(*) FROM bugs WHERE bug_status = ?};
- my $reso_sql = q{SELECT COUNT(*) FROM bugs WHERE resolution = ?};
+ my $file = join '/', $dir, $product->id;
+ my $exists = -f $file;
- if ($product->id) {
- $status_sql .= q{ AND product_id = ?};
- $reso_sql .= q{ AND product_id = ?};
- }
+ # if the file exists, get the old status and resolution list for that product.
+ my @data;
+ @data = get_old_data($file) if $exists;
- my $sth_status = $dbh->prepare($status_sql);
- my $sth_reso = $dbh->prepare($reso_sql);
-
- my @values ;
- foreach my $status (@statuses) {
- @values = ($status);
- push (@values, $product->id) if ($product->id);
- my $count = $dbh->selectrow_array($sth_status, undef, @values);
- push(@row, $count);
- }
- foreach my $resolution (@resolutions) {
- @values = ($resolution);
- push (@values, $product->id) if ($product->id);
- my $count = $dbh->selectrow_array($sth_reso, undef, @values);
- push(@row, $count);
- }
-
- if (!$exists || scalar(@data)) {
- my $fields = join('|', ('DATE', @statuses, @resolutions));
- my $product_name = $product->name;
- print DATA <<FIN;
+ # If @data is not empty, then we have to recreate the data file.
+ if (scalar(@data)) {
+ open(DATA, '>', $file)
+ || ThrowCodeError('chart_file_open_fail', {'filename' => $file});
+ }
+ else {
+ open(DATA, '>>', $file)
+ || ThrowCodeError('chart_file_open_fail', {'filename' => $file});
+ }
+
+ if (Bugzilla->params->{'utf8'}) {
+ binmode DATA, ':utf8';
+ }
+
+ # Now collect current data.
+ my @row = (today());
+ my $status_sql = q{SELECT COUNT(*) FROM bugs WHERE bug_status = ?};
+ my $reso_sql = q{SELECT COUNT(*) FROM bugs WHERE resolution = ?};
+
+ if ($product->id) {
+ $status_sql .= q{ AND product_id = ?};
+ $reso_sql .= q{ AND product_id = ?};
+ }
+
+ my $sth_status = $dbh->prepare($status_sql);
+ my $sth_reso = $dbh->prepare($reso_sql);
+
+ my @values;
+ foreach my $status (@statuses) {
+ @values = ($status);
+ push(@values, $product->id) if ($product->id);
+ my $count = $dbh->selectrow_array($sth_status, undef, @values);
+ push(@row, $count);
+ }
+ foreach my $resolution (@resolutions) {
+ @values = ($resolution);
+ push(@values, $product->id) if ($product->id);
+ my $count = $dbh->selectrow_array($sth_reso, undef, @values);
+ push(@row, $count);
+ }
+
+ if (!$exists || scalar(@data)) {
+ my $fields = join('|', ('DATE', @statuses, @resolutions));
+ my $product_name = $product->name;
+ print DATA <<FIN;
# Bugzilla Daily Bug Stats
#
# Do not edit me! This file is generated.
@@ -216,103 +225,109 @@ sub collect_stats {
# Product: $product_name
# Created: $when
FIN
- }
-
- # Add existing data, if needed. Note that no count is not treated
- # the same way as a count with 0 bug.
- foreach my $data (@data) {
- print DATA join('|', map {defined $data->{$_} ? $data->{$_} : ''}
- ('DATE', @statuses, @resolutions)) . "\n";
- }
- print DATA (join '|', @row) . "\n";
- close DATA;
+ }
+
+ # Add existing data, if needed. Note that no count is not treated
+ # the same way as a count with 0 bug.
+ foreach my $data (@data) {
+ print DATA join('|',
+ map { defined $data->{$_} ? $data->{$_} : '' }
+ ('DATE', @statuses, @resolutions))
+ . "\n";
+ }
+ print DATA (join '|', @row) . "\n";
+ close DATA;
}
sub get_old_data {
- my $file = shift;
-
- open(DATA, '<', $file)
- || ThrowCodeError('chart_file_open_fail', {'filename' => $file});
-
- if (Bugzilla->params->{'utf8'}) {
- binmode DATA, ':utf8';
- }
-
- my @data;
- my @columns;
- my $recreate = 0;
- while (<DATA>) {
- chomp;
- next unless $_;
- if (/^# fields?:\s*(.+)\s*$/) {
- @columns = split(/\|/, $1);
- # Compare this list with @statuses and @resolutions.
- # If they are identical, then we can safely append new data
- # to the end of the file; else we have to recreate it.
- $recreate = 1;
- my @new_cols = ($columns[0], @statuses, @resolutions);
- if (scalar(@columns) == scalar(@new_cols)) {
- my $identical = 1;
- for (0 .. $#columns) {
- $identical = 0 if ($columns[$_] ne $new_cols[$_]);
- }
- last if $identical;
- }
- }
- next unless $recreate;
- next if (/^#/); # Ignore comments.
- # If we have to recreate the file, we have to load all existing
- # data first.
- my @line = split /\|/;
- my %data;
- foreach my $column (@columns) {
- $data{$column} = shift @line;
+ my $file = shift;
+
+ open(DATA, '<', $file)
+ || ThrowCodeError('chart_file_open_fail', {'filename' => $file});
+
+ if (Bugzilla->params->{'utf8'}) {
+ binmode DATA, ':utf8';
+ }
+
+ my @data;
+ my @columns;
+ my $recreate = 0;
+ while (<DATA>) {
+ chomp;
+ next unless $_;
+ if (/^# fields?:\s*(.+)\s*$/) {
+ @columns = split(/\|/, $1);
+
+ # Compare this list with @statuses and @resolutions.
+ # If they are identical, then we can safely append new data
+ # to the end of the file; else we have to recreate it.
+ $recreate = 1;
+ my @new_cols = ($columns[0], @statuses, @resolutions);
+ if (scalar(@columns) == scalar(@new_cols)) {
+ my $identical = 1;
+ for (0 .. $#columns) {
+ $identical = 0 if ($columns[$_] ne $new_cols[$_]);
}
- push(@data, \%data);
+ last if $identical;
+ }
+ }
+ next unless $recreate;
+ next if (/^#/); # Ignore comments.
+ # If we have to recreate the file, we have to load all existing
+ # data first.
+ my @line = split /\|/;
+ my %data;
+ foreach my $column (@columns) {
+ $data{$column} = shift @line;
}
- close(DATA);
- return @data;
+ push(@data, \%data);
+ }
+ close(DATA);
+ return @data;
}
# This regenerates all statistics from the database.
sub regenerate_stats {
- my ($dir, $product, $bug_resolution, $bug_status, $removed) = @_;
-
- my $dbh = Bugzilla->dbh;
- my $when = localtime(time());
- my $tstart = time();
-
- my $file = join '/', $dir, $product->id;
-
- my $and_product = "";
-
- my @values = ();
- if ($product->id) {
- $and_product = q{ AND product_id = ?};
- push (@values, $product->id);
- }
-
- # Determine the start date from the date the first bug in the
- # database was created, and the end date from the current day.
- # If there were no bugs in the search, return early.
- my $query = q{SELECT } .
- $dbh->sql_to_days('creation_ts') . q{ AS start_day, } .
- $dbh->sql_to_days('current_date') . q{ AS end_day, } .
- $dbh->sql_to_days("'1970-01-01'") .
- qq{ FROM bugs
- WHERE } . $dbh->sql_to_days('creation_ts') .
- qq{ IS NOT NULL $and_product
+ my ($dir, $product, $bug_resolution, $bug_status, $removed) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ my $when = localtime(time());
+ my $tstart = time();
+
+ my $file = join '/', $dir, $product->id;
+
+ my $and_product = "";
+
+ my @values = ();
+ if ($product->id) {
+ $and_product = q{ AND product_id = ?};
+ push(@values, $product->id);
+ }
+
+ # Determine the start date from the date the first bug in the
+ # database was created, and the end date from the current day.
+ # If there were no bugs in the search, return early.
+ my $query
+ = q{SELECT }
+ . $dbh->sql_to_days('creation_ts')
+ . q{ AS start_day, }
+ . $dbh->sql_to_days('current_date')
+ . q{ AS end_day, }
+ . $dbh->sql_to_days("'1970-01-01'")
+ . qq{ FROM bugs
+ WHERE }
+ . $dbh->sql_to_days('creation_ts') . qq{ IS NOT NULL $and_product
ORDER BY start_day } . $dbh->sql_limit(1);
- my ($start, $end, $base) = $dbh->selectrow_array($query, undef, @values);
+ my ($start, $end, $base) = $dbh->selectrow_array($query, undef, @values);
- if (!defined $start) {
- return;
- }
+ if (!defined $start) {
+ return;
+ }
- if (open DATA, ">", $file) {
- my $fields = join('|', ('DATE', @statuses, @resolutions));
- my $product_name = $product->name;
- print DATA <<FIN;
+ if (open DATA, ">", $file) {
+ my $fields = join('|', ('DATE', @statuses, @resolutions));
+ my $product_name = $product->name;
+ print DATA <<FIN;
# Bugzilla Daily Bug Stats
#
# Do not edit me! This file is generated.
@@ -321,66 +336,69 @@ sub regenerate_stats {
# Product: $product_name
# Created: $when
FIN
- # For each day, generate a line of statistics.
- my $total_days = $end - $start;
- my @bugs;
- for (my $day = $start + 1; $day <= $end; $day++) {
- # Some output feedback
- my $percent_done = ($day - $start - 1) * 100 / $total_days;
- printf "\rRegenerating %s \[\%.1f\%\%]", $product_name,
- $percent_done;
-
- # Get a list of bugs that were created the previous day, and
- # add those bugs to the list of bugs for this product.
- $query = qq{SELECT bug_id
+
+ # For each day, generate a line of statistics.
+ my $total_days = $end - $start;
+ my @bugs;
+ for (my $day = $start + 1; $day <= $end; $day++) {
+
+ # Some output feedback
+ my $percent_done = ($day - $start - 1) * 100 / $total_days;
+ printf "\rRegenerating %s \[\%.1f\%\%]", $product_name, $percent_done;
+
+ # Get a list of bugs that were created the previous day, and
+ # add those bugs to the list of bugs for this product.
+ $query = qq{SELECT bug_id
FROM bugs
- WHERE bugs.creation_ts < } .
- $dbh->sql_from_days($day - 1) .
- q{ AND bugs.creation_ts >= } .
- $dbh->sql_from_days($day - 2) .
- $and_product . q{ ORDER BY bug_id};
-
- my $bug_ids = $dbh->selectcol_arrayref($query, undef, @values);
- push(@bugs, @$bug_ids);
-
- my %bugcount;
- foreach (@statuses) { $bugcount{$_} = 0; }
- foreach (@resolutions) { $bugcount{$_} = 0; }
- # Get information on bug states and resolutions.
- for my $bug (@bugs) {
- my $status = _get_value(
- $removed->{'bug_status'}->{$bug},
- $bug_status, $day, $bug);
-
- if (defined $bugcount{$status}) {
- $bugcount{$status}++;
- }
-
- my $resolution = _get_value(
- $removed->{'resolution'}->{$bug},
- $bug_resolution, $day, $bug);
-
- if (defined $bugcount{$resolution}) {
- $bugcount{$resolution}++;
- }
- }
-
- # Generate a line of output containing the date and counts
- # of bugs in each state.
- my $date = sqlday($day, $base);
- print DATA "$date";
- foreach (@statuses) { print DATA "|$bugcount{$_}"; }
- foreach (@resolutions) { print DATA "|$bugcount{$_}"; }
- print DATA "\n";
+ WHERE bugs.creation_ts < }
+ . $dbh->sql_from_days($day - 1)
+ . q{ AND bugs.creation_ts >= }
+ . $dbh->sql_from_days($day - 2)
+ . $and_product
+ . q{ ORDER BY bug_id};
+
+ my $bug_ids = $dbh->selectcol_arrayref($query, undef, @values);
+ push(@bugs, @$bug_ids);
+
+ my %bugcount;
+ foreach (@statuses) { $bugcount{$_} = 0; }
+ foreach (@resolutions) { $bugcount{$_} = 0; }
+
+ # Get information on bug states and resolutions.
+ for my $bug (@bugs) {
+ my $status
+ = _get_value($removed->{'bug_status'}->{$bug}, $bug_status, $day, $bug);
+
+ if (defined $bugcount{$status}) {
+ $bugcount{$status}++;
}
- # Finish up output feedback for this product.
- my $tend = time;
- say "\rRegenerating " . $product_name . ' [100.0%] - ' .
- delta_time($tstart, $tend);
+ my $resolution
+ = _get_value($removed->{'resolution'}->{$bug}, $bug_resolution, $day, $bug);
- close DATA;
+ if (defined $bugcount{$resolution}) {
+ $bugcount{$resolution}++;
+ }
+ }
+
+ # Generate a line of output containing the date and counts
+ # of bugs in each state.
+ my $date = sqlday($day, $base);
+ print DATA "$date";
+ foreach (@statuses) { print DATA "|$bugcount{$_}"; }
+ foreach (@resolutions) { print DATA "|$bugcount{$_}"; }
+ print DATA "\n";
}
+
+ # Finish up output feedback for this product.
+ my $tend = time;
+ say "\rRegenerating "
+ . $product_name
+ . ' [100.0%] - '
+ . delta_time($tstart, $tend);
+
+ close DATA;
+ }
}
# A helper for --regenerate.
@@ -388,105 +406,117 @@ FIN
# at the beginning of the day. If there were no status/resolution
# changes on or after that day, the status was the same as it
# is today (the "current" value). Otherwise, the status was equal to the
-# first "previous value" entry in the bugs_activity table for that
+# first "previous value" entry in the bugs_activity table for that
# bug made on or after that day.
sub _get_value {
- my ($removed, $current, $day, $bug) = @_;
+ my ($removed, $current, $day, $bug) = @_;
- # Get the first change that's on or after this day.
- my $item = first { $_->{when} >= $day } @{ $removed || [] };
+ # Get the first change that's on or after this day.
+ my $item = first { $_->{when} >= $day } @{$removed || []};
- # If there's no change on or after this day, then we just return the
- # current value.
- return $item ? $item->{removed} : $current->{$bug};
+ # If there's no change on or after this day, then we just return the
+ # current value.
+ return $item ? $item->{removed} : $current->{$bug};
}
sub today {
- my ($dom, $mon, $year) = (localtime(time))[3, 4, 5];
- return sprintf "%04d%02d%02d", 1900 + $year, ++$mon, $dom;
+ my ($dom, $mon, $year) = (localtime(time))[3, 4, 5];
+ return sprintf "%04d%02d%02d", 1900 + $year, ++$mon, $dom;
}
sub today_dash {
- my ($dom, $mon, $year) = (localtime(time))[3, 4, 5];
- return sprintf "%04d-%02d-%02d", 1900 + $year, ++$mon, $dom;
+ my ($dom, $mon, $year) = (localtime(time))[3, 4, 5];
+ return sprintf "%04d-%02d-%02d", 1900 + $year, ++$mon, $dom;
}
sub sqlday {
- my ($day, $base) = @_;
- $day = ($day - $base) * 86400;
- my ($dom, $mon, $year) = (gmtime($day))[3, 4, 5];
- return sprintf "%04d%02d%02d", 1900 + $year, ++$mon, $dom;
+ my ($day, $base) = @_;
+ $day = ($day - $base) * 86400;
+ my ($dom, $mon, $year) = (gmtime($day))[3, 4, 5];
+ return sprintf "%04d%02d%02d", 1900 + $year, ++$mon, $dom;
}
sub delta_time {
- my $tstart = shift;
- my $tend = shift;
- my $delta = $tend - $tstart;
- my $hours = int($delta/3600);
- my $minutes = int($delta/60) - ($hours * 60);
- my $seconds = $delta - ($minutes * 60) - ($hours * 3600);
- return sprintf("%02d:%02d:%02d" , $hours, $minutes, $seconds);
+ my $tstart = shift;
+ my $tend = shift;
+ my $delta = $tend - $tstart;
+ my $hours = int($delta / 3600);
+ my $minutes = int($delta / 60) - ($hours * 60);
+ my $seconds = $delta - ($minutes * 60) - ($hours * 3600);
+ return sprintf("%02d:%02d:%02d", $hours, $minutes, $seconds);
}
sub CollectSeriesData {
- # We need some way of randomising the distribution of series, such that
- # all of the series which are to be run every 7 days don't run on the same
- # day. This is because this might put the server under severe load if a
- # particular frequency, such as once a week, is very common. We achieve
- # this by only running queries when:
- # (days_since_epoch + series_id) % frequency = 0. So they'll run every
- # <frequency> days, but the start date depends on the series_id.
- my $days_since_epoch = int(time() / (60 * 60 * 24));
- my $today = today_dash();
-
- # We save a copy of the main $dbh and then switch to the shadow and get
- # that one too. Remember, these may be the same.
- my $dbh = Bugzilla->switch_to_main_db();
- my $shadow_dbh = Bugzilla->switch_to_shadow_db();
-
- my $serieses = $dbh->selectall_hashref("SELECT series_id, query, creator " .
- "FROM series " .
- "WHERE frequency != 0 AND " .
- "MOD(($days_since_epoch + series_id), frequency) = 0",
- "series_id");
-
- # We prepare the insertion into the data table, for efficiency.
- my $sth = $dbh->prepare("INSERT INTO series_data " .
- "(series_id, series_date, series_value) " .
- "VALUES (?, " . $dbh->quote($today) . ", ?)");
-
- # We delete from the table beforehand, to avoid SQL errors if people run
- # collectstats.pl twice on the same day.
- my $deletesth = $dbh->prepare("DELETE FROM series_data
- WHERE series_id = ? AND series_date = " .
- $dbh->quote($today));
-
- foreach my $series_id (keys %$serieses) {
- # We set up the user for Search.pm's permission checking - each series
- # runs with the permissions of its creator.
- my $user = new Bugzilla::User($serieses->{$series_id}->{'creator'});
- my $cgi = new Bugzilla::CGI($serieses->{$series_id}->{'query'});
- my $data;
-
- # Do not die if Search->new() detects invalid data, such as an obsolete
- # login name or a renamed product or component, etc.
- eval {
- my $search = new Bugzilla::Search('params' => scalar $cgi->Vars,
- 'fields' => ["bug_id"],
- 'allow_unlimited' => 1,
- 'user' => $user);
- $data = $search->data;
- };
-
- if (!$@) {
- # We need to count the returned rows. Without subselects, we can't
- # do this directly in the SQL for all queries. So we do it by hand.
- my $count = scalar(@$data) || 0;
-
- $deletesth->execute($series_id);
- $sth->execute($series_id, $count);
- }
+
+ # We need some way of randomising the distribution of series, such that
+ # all of the series which are to be run every 7 days don't run on the same
+ # day. This is because this might put the server under severe load if a
+ # particular frequency, such as once a week, is very common. We achieve
+ # this by only running queries when:
+ # (days_since_epoch + series_id) % frequency = 0. So they'll run every
+ # <frequency> days, but the start date depends on the series_id.
+ my $days_since_epoch = int(time() / (60 * 60 * 24));
+ my $today = today_dash();
+
+ # We save a copy of the main $dbh and then switch to the shadow and get
+ # that one too. Remember, these may be the same.
+ my $dbh = Bugzilla->switch_to_main_db();
+ my $shadow_dbh = Bugzilla->switch_to_shadow_db();
+
+ my $serieses = $dbh->selectall_hashref(
+ "SELECT series_id, query, creator "
+ . "FROM series "
+ . "WHERE frequency != 0 AND "
+ . "MOD(($days_since_epoch + series_id), frequency) = 0",
+ "series_id"
+ );
+
+ # We prepare the insertion into the data table, for efficiency.
+ my $sth
+ = $dbh->prepare("INSERT INTO series_data "
+ . "(series_id, series_date, series_value) "
+ . "VALUES (?, "
+ . $dbh->quote($today)
+ . ", ?)");
+
+ # We delete from the table beforehand, to avoid SQL errors if people run
+ # collectstats.pl twice on the same day.
+ my $deletesth = $dbh->prepare(
+ "DELETE FROM series_data
+ WHERE series_id = ? AND series_date = "
+ . $dbh->quote($today)
+ );
+
+ foreach my $series_id (keys %$serieses) {
+
+ # We set up the user for Search.pm's permission checking - each series
+ # runs with the permissions of its creator.
+ my $user = new Bugzilla::User($serieses->{$series_id}->{'creator'});
+ my $cgi = new Bugzilla::CGI($serieses->{$series_id}->{'query'});
+ my $data;
+
+ # Do not die if Search->new() detects invalid data, such as an obsolete
+ # login name or a renamed product or component, etc.
+ eval {
+ my $search = new Bugzilla::Search(
+ 'params' => scalar $cgi->Vars,
+ 'fields' => ["bug_id"],
+ 'allow_unlimited' => 1,
+ 'user' => $user
+ );
+ $data = $search->data;
+ };
+
+ if (!$@) {
+
+ # We need to count the returned rows. Without subselects, we can't
+ # do this directly in the SQL for all queries. So we do it by hand.
+ my $count = scalar(@$data) || 0;
+
+ $deletesth->execute($series_id);
+ $sth->execute($series_id, $count);
}
+ }
}
__END__
diff --git a/config.cgi b/config.cgi
index 56a9a3f8a..3a939803d 100755
--- a/config.cgi
+++ b/config.cgi
@@ -31,60 +31,64 @@ Bugzilla->switch_to_shadow_db;
# If the 'requirelogin' parameter is on and the user is not
# authenticated, return empty fields.
if (Bugzilla->params->{'requirelogin'} && !$user->id) {
- display_data();
- exit;
+ display_data();
+ exit;
}
# Pass a bunch of Bugzilla configuration to the templates.
my $vars = {};
-$vars->{'priority'} = get_legal_field_values('priority');
-$vars->{'severity'} = get_legal_field_values('bug_severity');
-$vars->{'platform'} = get_legal_field_values('rep_platform');
-$vars->{'op_sys'} = get_legal_field_values('op_sys');
-$vars->{'keywords'} = [Bugzilla::Keyword->get_all];
+$vars->{'priority'} = get_legal_field_values('priority');
+$vars->{'severity'} = get_legal_field_values('bug_severity');
+$vars->{'platform'} = get_legal_field_values('rep_platform');
+$vars->{'op_sys'} = get_legal_field_values('op_sys');
+$vars->{'keywords'} = [Bugzilla::Keyword->get_all];
$vars->{'resolution'} = get_legal_field_values('resolution');
-$vars->{'status'} = get_legal_field_values('bug_status');
-$vars->{'custom_fields'} =
- [ grep {$_->is_select} Bugzilla->active_custom_fields ];
+$vars->{'status'} = get_legal_field_values('bug_status');
+$vars->{'custom_fields'}
+ = [grep { $_->is_select } Bugzilla->active_custom_fields];
# Include a list of product objects.
if ($cgi->param('product')) {
- my @products = $cgi->param('product');
- foreach my $product_name (@products) {
- # We don't use check() because config.cgi outputs mostly
- # in XML and JS and we don't want to display an HTML error
- # instead of that.
- my $product = new Bugzilla::Product({ name => $product_name });
- if ($product && $user->can_see_product($product->name)) {
- push (@{$vars->{'products'}}, $product);
- }
+ my @products = $cgi->param('product');
+ foreach my $product_name (@products) {
+
+ # We don't use check() because config.cgi outputs mostly
+ # in XML and JS and we don't want to display an HTML error
+ # instead of that.
+ my $product = new Bugzilla::Product({name => $product_name});
+ if ($product && $user->can_see_product($product->name)) {
+ push(@{$vars->{'products'}}, $product);
}
-} else {
- $vars->{'products'} = $user->get_selectable_products;
+ }
+}
+else {
+ $vars->{'products'} = $user->get_selectable_products;
}
# We set the 2nd argument to 1 to also preload flag types.
Bugzilla::Product::preload($vars->{'products'}, 1);
if (Bugzilla->params->{'useclassification'}) {
- my $class = {};
- # Get all classifications with at least one selectable product.
- foreach my $product (@{$vars->{'products'}}) {
- $class->{$product->classification_id} ||= $product->classification;
- }
- my @classifications = sort {$a->sortkey <=> $b->sortkey
- || lc($a->name) cmp lc($b->name)} (values %$class);
- $vars->{'class_names'} = $class;
- $vars->{'classifications'} = \@classifications;
+ my $class = {};
+
+ # Get all classifications with at least one selectable product.
+ foreach my $product (@{$vars->{'products'}}) {
+ $class->{$product->classification_id} ||= $product->classification;
+ }
+ my @classifications
+ = sort { $a->sortkey <=> $b->sortkey || lc($a->name) cmp lc($b->name) }
+ (values %$class);
+ $vars->{'class_names'} = $class;
+ $vars->{'classifications'} = \@classifications;
}
# Allow consumers to specify whether or not they want flag data.
if (defined $cgi->param('flags')) {
- $vars->{'show_flags'} = $cgi->param('flags');
+ $vars->{'show_flags'} = $cgi->param('flags');
}
else {
- # We default to sending flag data.
- $vars->{'show_flags'} = 1;
+ # We default to sending flag data.
+ $vars->{'show_flags'} = 1;
}
# Create separate lists of open versus resolved statuses. This should really
@@ -92,31 +96,33 @@ else {
my @open_status;
my @closed_status;
foreach my $status (@{$vars->{'status'}}) {
- is_open_state($status) ? push(@open_status, $status)
- : push(@closed_status, $status);
+ is_open_state($status)
+ ? push(@open_status, $status)
+ : push(@closed_status, $status);
}
-$vars->{'open_status'} = \@open_status;
+$vars->{'open_status'} = \@open_status;
$vars->{'closed_status'} = \@closed_status;
# Generate a list of fields that can be queried.
my @fields = @{Bugzilla::Field->match({obsolete => 0})};
+
# Exclude fields the user cannot query.
if (!$user->is_timetracker) {
- foreach my $tt_field (TIMETRACKING_FIELDS) {
- @fields = grep { $_->name ne $tt_field } @fields;
- }
+ foreach my $tt_field (TIMETRACKING_FIELDS) {
+ @fields = grep { $_->name ne $tt_field } @fields;
+ }
}
my %FIELD_PARAMS = (
- classification => 'useclassification',
- target_milestone => 'usetargetmilestone',
- qa_contact => 'useqacontact',
- status_whiteboard => 'usestatuswhiteboard',
- see_also => 'use_see_also',
+ classification => 'useclassification',
+ target_milestone => 'usetargetmilestone',
+ qa_contact => 'useqacontact',
+ status_whiteboard => 'usestatuswhiteboard',
+ see_also => 'use_see_also',
);
foreach my $field (@fields) {
- my $param = $FIELD_PARAMS{$field->name};
- $field->{is_active} = Bugzilla->params->{$param} if $param;
+ my $param = $FIELD_PARAMS{$field->name};
+ $field->{is_active} = Bugzilla->params->{$param} if $param;
}
$vars->{'field'} = \@fields;
@@ -124,33 +130,34 @@ display_data($vars);
sub display_data {
- my $vars = shift;
-
- my $cgi = Bugzilla->cgi;
- my $template = Bugzilla->template;
-
- # Determine how the user would like to receive the output;
- # default is JavaScript.
- my $format = $template->get_format("config", scalar($cgi->param('format')),
- scalar($cgi->param('ctype')) || "js");
-
- # Generate the configuration data.
- my $output;
- $template->process($format->{'template'}, $vars, \$output)
- || ThrowTemplateError($template->error());
-
- # Wide characters cause md5_base64() to die.
- my $digest_data = $output;
- utf8::encode($digest_data) if utf8::is_utf8($digest_data);
- my $digest = md5_base64($digest_data);
-
- if ($cgi->check_etag($digest)) {
- print $cgi->header(-ETag => $digest,
- -status => '304 Not Modified');
- exit;
- }
+ my $vars = shift;
+
+ my $cgi = Bugzilla->cgi;
+ my $template = Bugzilla->template;
+
+ # Determine how the user would like to receive the output;
+ # default is JavaScript.
+ my $format = $template->get_format(
+ "config",
+ scalar($cgi->param('format')),
+ scalar($cgi->param('ctype')) || "js"
+ );
+
+ # Generate the configuration data.
+ my $output;
+ $template->process($format->{'template'}, $vars, \$output)
+ || ThrowTemplateError($template->error());
+
+ # Wide characters cause md5_base64() to die.
+ my $digest_data = $output;
+ utf8::encode($digest_data) if utf8::is_utf8($digest_data);
+ my $digest = md5_base64($digest_data);
+
+ if ($cgi->check_etag($digest)) {
+ print $cgi->header(-ETag => $digest, -status => '304 Not Modified');
+ exit;
+ }
- print $cgi->header (-ETag => $digest,
- -type => $format->{'ctype'});
- print $output;
+ print $cgi->header(-ETag => $digest, -type => $format->{'ctype'});
+ print $output;
}
diff --git a/contrib/Bugzilla.pm b/contrib/Bugzilla.pm
index 31e0a0f6d..e7452bbb8 100644
--- a/contrib/Bugzilla.pm
+++ b/contrib/Bugzilla.pm
@@ -36,7 +36,7 @@ use warnings;
#######################################################################
use constant BZ_ROOT_DIR => '/usr/share/bugzilla/lib';
-use constant BZ_LIB_DIR => BZ_ROOT_DIR . '/lib';
+use constant BZ_LIB_DIR => BZ_ROOT_DIR . '/lib';
#######################################################################
# DO NOT EDIT THE CODE BELOW, UNLESS YOU KNOW WHAT YOU ARE DOING!! #
diff --git a/contrib/bz_webservice_demo.pl b/contrib/bz_webservice_demo.pl
index 6c8c21dfa..bf6d504ad 100755
--- a/contrib/bz_webservice_demo.pl
+++ b/contrib/bz_webservice_demo.pl
@@ -50,21 +50,22 @@ my $work_time;
my $fetch_extension_info = 0;
my $debug;
-GetOptions('help|h|?' => \$help,
- 'uri=s' => \$Bugzilla_uri,
- 'login:s' => \$Bugzilla_login,
- 'password=s' => \$Bugzilla_password,
- 'restrictlogin!' => \$Bugzilla_restrict,
- 'bug_id:s' => \$bug_id,
- 'product_name:s' => \$product_name,
- 'create:s' => \$create_file_name,
- 'field:s' => \$legal_field_values,
- 'comment:s' => \$add_comment,
- 'private:i' => \$private,
- 'worktime:f' => \$work_time,
- 'extension_info' => \$fetch_extension_info,
- 'debug' => \$debug
- ) or pod2usage({'-verbose' => 0, '-exitval' => 1});
+GetOptions(
+ 'help|h|?' => \$help,
+ 'uri=s' => \$Bugzilla_uri,
+ 'login:s' => \$Bugzilla_login,
+ 'password=s' => \$Bugzilla_password,
+ 'restrictlogin!' => \$Bugzilla_restrict,
+ 'bug_id:s' => \$bug_id,
+ 'product_name:s' => \$product_name,
+ 'create:s' => \$create_file_name,
+ 'field:s' => \$legal_field_values,
+ 'comment:s' => \$add_comment,
+ 'private:i' => \$private,
+ 'worktime:f' => \$work_time,
+ 'extension_info' => \$fetch_extension_info,
+ 'debug' => \$debug
+) or pod2usage({'-verbose' => 0, '-exitval' => 1});
=head1 OPTIONS
@@ -171,7 +172,7 @@ Enable tracing at the debug level of XMLRPC requests and responses if requested.
=cut
if ($debug) {
- $proxy->import(+trace => 'debug');
+ $proxy->import(+trace => 'debug');
}
=head2 Checking Bugzilla's version
@@ -184,7 +185,8 @@ minimum required version your application needs.
$soapresult = $proxy->call('Bugzilla.version');
_die_on_fault($soapresult);
-print 'Connecting to a Bugzilla of version ' . $soapresult->result()->{version} . ".\n";
+print 'Connecting to a Bugzilla of version '
+ . $soapresult->result()->{version} . ".\n";
=head2 Checking Bugzilla's timezone
@@ -217,22 +219,27 @@ parameter).
=cut
if (defined($Bugzilla_login)) {
- if ($Bugzilla_login ne '') {
- # Log in.
- $soapresult = $proxy->call('User.login',
- { login => $Bugzilla_login,
- password => $Bugzilla_password,
- restrict_login => $Bugzilla_restrict } );
- $Bugzilla_token = $soapresult->result->{token};
- _die_on_fault($soapresult);
- print "Login successful.\n";
- }
- else {
- # Log out.
- $soapresult = $proxy->call('User.logout');
- _die_on_fault($soapresult);
- print "Logout successful.\n";
- }
+ if ($Bugzilla_login ne '') {
+
+ # Log in.
+ $soapresult = $proxy->call(
+ 'User.login',
+ {
+ login => $Bugzilla_login,
+ password => $Bugzilla_password,
+ restrict_login => $Bugzilla_restrict
+ }
+ );
+ $Bugzilla_token = $soapresult->result->{token};
+ _die_on_fault($soapresult);
+ print "Login successful.\n";
+ }
+ else {
+ # Log out.
+ $soapresult = $proxy->call('User.logout');
+ _die_on_fault($soapresult);
+ print "Logout successful.\n";
+ }
}
=head2 Getting Extension Information
@@ -242,16 +249,16 @@ Returns all the information any extensions have decided to provide to the webser
=cut
if ($fetch_extension_info) {
- $soapresult = $proxy->call('Bugzilla.extensions', {token => $Bugzilla_token});
- _die_on_fault($soapresult);
- my $extensions = $soapresult->result()->{extensions};
- foreach my $extensionname (keys(%$extensions)) {
- print "Extension '$extensionname' information\n";
- my $extension = $extensions->{$extensionname};
- foreach my $data (keys(%$extension)) {
- print ' ' . $data . ' => ' . $extension->{$data} . "\n";
- }
+ $soapresult = $proxy->call('Bugzilla.extensions', {token => $Bugzilla_token});
+ _die_on_fault($soapresult);
+ my $extensions = $soapresult->result()->{extensions};
+ foreach my $extensionname (keys(%$extensions)) {
+ print "Extension '$extensionname' information\n";
+ my $extension = $extensions->{$extensionname};
+ foreach my $data (keys(%$extension)) {
+ print ' ' . $data . ' => ' . $extension->{$data} . "\n";
}
+ }
}
=head2 Retrieving Bug Information
@@ -262,21 +269,22 @@ The call will return a C<Bugzilla::Bug> object.
=cut
if ($bug_id) {
- $soapresult = $proxy->call('Bug.get', { ids => [$bug_id], token => $Bugzilla_token});
- _die_on_fault($soapresult);
- $result = $soapresult->result;
- my $bug = $result->{bugs}->[0];
- foreach my $field (keys(%$bug)) {
- my $value = $bug->{$field};
- if (ref($value) eq 'HASH') {
- foreach (keys %$value) {
- print "$_: " . $value->{$_} . "\n";
- }
- }
- else {
- print "$field: $value\n";
- }
+ $soapresult
+ = $proxy->call('Bug.get', {ids => [$bug_id], token => $Bugzilla_token});
+ _die_on_fault($soapresult);
+ $result = $soapresult->result;
+ my $bug = $result->{bugs}->[0];
+ foreach my $field (keys(%$bug)) {
+ my $value = $bug->{$field};
+ if (ref($value) eq 'HASH') {
+ foreach (keys %$value) {
+ print "$_: " . $value->{$_} . "\n";
+ }
}
+ else {
+ print "$field: $value\n";
+ }
+ }
}
=head2 Retrieving Product Information
@@ -287,27 +295,28 @@ The call will return a C<Bugzilla::Product> object.
=cut
if ($product_name) {
- $soapresult = $proxy->call('Product.get', {'names' => [$product_name], token => $Bugzilla_token});
- _die_on_fault($soapresult);
- $result = $soapresult->result()->{'products'}->[0];
-
- # Iterate all entries, the values may be scalars or array refs with hash refs.
- foreach my $key (sort(keys %$result)) {
- my $value = $result->{$key};
-
- if (ref($value)) {
- my $counter = 0;
- foreach my $hash (@$value) {
- while (my ($innerKey, $innerValue) = each %$hash) {
- print "$key.$counter.$innerKey: $innerValue\n";
- }
- ++$counter;
+ $soapresult = $proxy->call('Product.get',
+ {'names' => [$product_name], token => $Bugzilla_token});
+ _die_on_fault($soapresult);
+ $result = $soapresult->result()->{'products'}->[0];
+
+ # Iterate all entries, the values may be scalars or array refs with hash refs.
+ foreach my $key (sort(keys %$result)) {
+ my $value = $result->{$key};
+
+ if (ref($value)) {
+ my $counter = 0;
+ foreach my $hash (@$value) {
+ while (my ($innerKey, $innerValue) = each %$hash) {
+ print "$key.$counter.$innerKey: $innerValue\n";
}
- }
- else {
- print "$key: $value\n"
+ ++$counter;
}
}
+ else {
+ print "$key: $value\n";
+ }
+ }
}
=head2 Creating A Bug
@@ -320,20 +329,20 @@ The call will return a hash with a bug id for the newly created bug.
=cut
if ($create_file_name) {
- my $bug_fields = do "$create_file_name";
- $bug_fields->{Bugzilla_token} = $Bugzilla_token;
- $soapresult = $proxy->call('Bug.create', \%$bug_fields);
- _die_on_fault($soapresult);
- $result = $soapresult->result;
-
- if (ref($result) eq 'HASH') {
- foreach (keys(%$result)) {
- print "$_: $$result{$_}\n";
- }
- }
- else {
- print "$result\n";
+ my $bug_fields = do "$create_file_name";
+ $bug_fields->{Bugzilla_token} = $Bugzilla_token;
+ $soapresult = $proxy->call('Bug.create', \%$bug_fields);
+ _die_on_fault($soapresult);
+ $result = $soapresult->result;
+
+ if (ref($result) eq 'HASH') {
+ foreach (keys(%$result)) {
+ print "$_: $$result{$_}\n";
}
+ }
+ else {
+ print "$result\n";
+ }
}
@@ -346,11 +355,12 @@ list of legal values for this field.
=cut
if ($legal_field_values) {
- $soapresult = $proxy->call('Bug.legal_values', {field => $legal_field_values, token => $Bugzilla_token} );
- _die_on_fault($soapresult);
- $result = $soapresult->result;
+ $soapresult = $proxy->call('Bug.legal_values',
+ {field => $legal_field_values, token => $Bugzilla_token});
+ _die_on_fault($soapresult);
+ $result = $soapresult->result;
- print join("\n", @{$result->{values}}) . "\n";
+ print join("\n", @{$result->{values}}) . "\n";
}
=head2 Adding a comment to a bug
@@ -362,15 +372,23 @@ or not.
=cut
if ($add_comment) {
- if ($bug_id) {
- $soapresult = $proxy->call('Bug.add_comment', {id => $bug_id,
- comment => $add_comment, private => $private, work_time => $work_time, token => $Bugzilla_token});
- _die_on_fault($soapresult);
- print "Comment added.\n";
- }
- else {
- print "A --bug_id must be supplied to add a comment.";
- }
+ if ($bug_id) {
+ $soapresult = $proxy->call(
+ 'Bug.add_comment',
+ {
+ id => $bug_id,
+ comment => $add_comment,
+ private => $private,
+ work_time => $work_time,
+ token => $Bugzilla_token
+ }
+ );
+ _die_on_fault($soapresult);
+ print "Comment added.\n";
+ }
+ else {
+ print "A --bug_id must be supplied to add a comment.";
+ }
}
=head1 NOTES
@@ -407,18 +425,19 @@ help to you.
=cut
sub _die_on_fault {
- my $soapresult = shift;
-
- if ($soapresult->fault) {
- my ($package, $filename, $line) = caller;
- die $soapresult->faultcode . ' ' . $soapresult->faultstring .
- " in SOAP call near $filename line $line.\n";
- }
+ my $soapresult = shift;
+
+ if ($soapresult->fault) {
+ my ($package, $filename, $line) = caller;
+ die $soapresult->faultcode . ' '
+ . $soapresult->faultstring
+ . " in SOAP call near $filename line $line.\n";
+ }
}
sub _syntaxhelp {
- my $msg = shift;
+ my $msg = shift;
- print "Error: $msg\n";
- pod2usage({'-verbose' => 0, '-exitval' => 1});
+ print "Error: $msg\n";
+ pod2usage({'-verbose' => 0, '-exitval' => 1});
}
diff --git a/contrib/bzdbcopy.pl b/contrib/bzdbcopy.pl
index fcdbefd56..b92d9205d 100755
--- a/contrib/bzdbcopy.pl
+++ b/contrib/bzdbcopy.pl
@@ -22,51 +22,56 @@ use Bugzilla::Util;
#####################################################################
# Settings for the 'Source' DB that you are copying from.
-use constant SOURCE_DB_TYPE => 'Mysql';
-use constant SOURCE_DB_NAME => 'bugs';
-use constant SOURCE_DB_USER => 'bugs';
+use constant SOURCE_DB_TYPE => 'Mysql';
+use constant SOURCE_DB_NAME => 'bugs';
+use constant SOURCE_DB_USER => 'bugs';
use constant SOURCE_DB_PASSWORD => '';
-use constant SOURCE_DB_HOST => 'localhost';
+use constant SOURCE_DB_HOST => 'localhost';
# Settings for the 'Target' DB that you are copying to.
-use constant TARGET_DB_TYPE => 'Pg';
-use constant TARGET_DB_NAME => 'bugs';
-use constant TARGET_DB_USER => 'bugs';
+use constant TARGET_DB_TYPE => 'Pg';
+use constant TARGET_DB_NAME => 'bugs';
+use constant TARGET_DB_USER => 'bugs';
use constant TARGET_DB_PASSWORD => '';
-use constant TARGET_DB_HOST => 'localhost';
+use constant TARGET_DB_HOST => 'localhost';
#####################################################################
# MAIN SCRIPT
#####################################################################
-print "Connecting to the '" . SOURCE_DB_NAME . "' source database on "
- . SOURCE_DB_TYPE . "...\n";
+print "Connecting to the '"
+ . SOURCE_DB_NAME
+ . "' source database on "
+ . SOURCE_DB_TYPE . "...\n";
my $source_db = Bugzilla::DB::_connect({
- db_driver => SOURCE_DB_TYPE,
- db_host => SOURCE_DB_HOST,
- db_name => SOURCE_DB_NAME,
- db_user => SOURCE_DB_USER,
- db_pass => SOURCE_DB_PASSWORD,
+ db_driver => SOURCE_DB_TYPE,
+ db_host => SOURCE_DB_HOST,
+ db_name => SOURCE_DB_NAME,
+ db_user => SOURCE_DB_USER,
+ db_pass => SOURCE_DB_PASSWORD,
});
+
# Don't read entire tables into memory.
if (SOURCE_DB_TYPE eq 'Mysql') {
- $source_db->{'mysql_use_result'} = 1;
+ $source_db->{'mysql_use_result'} = 1;
- # MySQL cannot have two queries running at the same time. Ensure the schema
- # is loaded from the database so bz_column_info will not execute a query
- $source_db->_bz_real_schema;
+ # MySQL cannot have two queries running at the same time. Ensure the schema
+ # is loaded from the database so bz_column_info will not execute a query
+ $source_db->_bz_real_schema;
}
-print "Connecting to the '" . TARGET_DB_NAME . "' target database on "
- . TARGET_DB_TYPE . "...\n";
+print "Connecting to the '"
+ . TARGET_DB_NAME
+ . "' target database on "
+ . TARGET_DB_TYPE . "...\n";
my $target_db = Bugzilla::DB::_connect({
- db_driver => TARGET_DB_TYPE,
- db_host => TARGET_DB_HOST,
- db_name => TARGET_DB_NAME,
- db_user => TARGET_DB_USER,
- db_pass => TARGET_DB_PASSWORD,
+ db_driver => TARGET_DB_TYPE,
+ db_host => TARGET_DB_HOST,
+ db_name => TARGET_DB_NAME,
+ db_user => TARGET_DB_USER,
+ db_pass => TARGET_DB_PASSWORD,
});
-my $ident_char = $target_db->get_info( 29 ); # SQL_IDENTIFIER_QUOTE_CHAR
+my $ident_char = $target_db->get_info(29); # SQL_IDENTIFIER_QUOTE_CHAR
# We use the table list from the target DB, because if somebody
# has customized their source DB, we still want the script to work,
@@ -79,136 +84,145 @@ my @table_list = grep { $_ ne 'bz_schema' } $target_db->bz_table_list_real();
# Instead of figuring out some fancy algorithm to insert data in the right
# order and not break FK integrity, we just drop them all.
$target_db->bz_drop_foreign_keys();
+
# We start a transaction on the target DB, which helps when we're doing
# so many inserts.
$target_db->bz_start_transaction();
foreach my $table (@table_list) {
- my @serial_cols;
- print "Reading data from the source '$table' table on "
- . SOURCE_DB_TYPE . "...\n";
- my @table_columns = $target_db->bz_table_columns_real($table);
- # The column names could be quoted using the quote identifier char
- # Remove these chars as different databases use different quote chars
- @table_columns = map { s/^\Q$ident_char\E?(.*?)\Q$ident_char\E?$/$1/; $_ }
- @table_columns;
-
- my ($total) = $source_db->selectrow_array("SELECT COUNT(*) FROM $table");
- my $select_query = "SELECT " . join(',', @table_columns) . " FROM $table";
- my $select_sth = $source_db->prepare($select_query);
- $select_sth->execute();
-
- my $insert_query = "INSERT INTO $table ( " . join(',', @table_columns)
- . " ) VALUES (";
- $insert_query .= '?,' foreach (@table_columns);
- # Remove the last comma.
- chop($insert_query);
- $insert_query .= ")";
- my $insert_sth = $target_db->prepare($insert_query);
-
- print "Clearing out the target '$table' table on "
- . TARGET_DB_TYPE . "...\n";
- $target_db->do("DELETE FROM $table");
-
- # Oracle doesn't like us manually inserting into tables that have
- # auto-increment PKs set, because of the way we made auto-increment
- # fields work.
- if ($target_db->isa('Bugzilla::DB::Oracle')) {
- foreach my $column (@table_columns) {
- my $col_info = $source_db->bz_column_info($table, $column);
- if ($col_info && $col_info->{TYPE} =~ /SERIAL/i) {
- print "Dropping the sequence + trigger on $table.$column...\n";
- $target_db->do("DROP TRIGGER ${table}_${column}_TR");
- $target_db->do("DROP SEQUENCE ${table}_${column}_SEQ");
- }
- }
+ my @serial_cols;
+ print "Reading data from the source '$table' table on "
+ . SOURCE_DB_TYPE . "...\n";
+ my @table_columns = $target_db->bz_table_columns_real($table);
+
+ # The column names could be quoted using the quote identifier char
+ # Remove these chars as different databases use different quote chars
+ @table_columns
+ = map { s/^\Q$ident_char\E?(.*?)\Q$ident_char\E?$/$1/; $_ } @table_columns;
+
+ my ($total) = $source_db->selectrow_array("SELECT COUNT(*) FROM $table");
+ my $select_query = "SELECT " . join(',', @table_columns) . " FROM $table";
+ my $select_sth = $source_db->prepare($select_query);
+ $select_sth->execute();
+
+ my $insert_query
+ = "INSERT INTO $table ( " . join(',', @table_columns) . " ) VALUES (";
+ $insert_query .= '?,' foreach (@table_columns);
+
+ # Remove the last comma.
+ chop($insert_query);
+ $insert_query .= ")";
+ my $insert_sth = $target_db->prepare($insert_query);
+
+ print "Clearing out the target '$table' table on " . TARGET_DB_TYPE . "...\n";
+ $target_db->do("DELETE FROM $table");
+
+ # Oracle doesn't like us manually inserting into tables that have
+ # auto-increment PKs set, because of the way we made auto-increment
+ # fields work.
+ if ($target_db->isa('Bugzilla::DB::Oracle')) {
+ foreach my $column (@table_columns) {
+ my $col_info = $source_db->bz_column_info($table, $column);
+ if ($col_info && $col_info->{TYPE} =~ /SERIAL/i) {
+ print "Dropping the sequence + trigger on $table.$column...\n";
+ $target_db->do("DROP TRIGGER ${table}_${column}_TR");
+ $target_db->do("DROP SEQUENCE ${table}_${column}_SEQ");
+ }
}
-
- print "Writing data to the target '$table' table on "
- . TARGET_DB_TYPE . "...\n";
- my $count = 0;
- while (my $row = $select_sth->fetchrow_arrayref) {
- # Each column needs to be bound separately, because
- # many columns need to be dealt with specially.
- my $colnum = 0;
- foreach my $column (@table_columns) {
- # bind_param args start at 1, but arrays start at 0.
- my $param_num = $colnum + 1;
- my $already_bound;
-
- # Certain types of columns need special handling.
- my $col_info = $source_db->bz_column_info($table, $column);
- if ($col_info && $col_info->{TYPE} eq 'LONGBLOB') {
- $insert_sth->bind_param($param_num,
- $row->[$colnum], $target_db->BLOB_TYPE);
- $already_bound = 1;
- }
- elsif ($col_info && $col_info->{TYPE} =~ /decimal/) {
- # In MySQL, decimal cols can be too long.
- my $col_type = $col_info->{TYPE};
- $col_type =~ /decimal\((\d+),(\d+)\)/;
- my ($precision, $decimals) = ($1, $2);
- # If it's longer than precision + decimal point
- if ( length($row->[$colnum]) > ($precision + 1) ) {
- # Truncate it to the highest allowed value.
- my $orig_value = $row->[$colnum];
- $row->[$colnum] = '';
- my $non_decimal = $precision - $decimals;
- $row->[$colnum] .= '9' while ($non_decimal--);
- $row->[$colnum] .= '.';
- $row->[$colnum] .= '9' while ($decimals--);
- print "Truncated value $orig_value to " . $row->[$colnum]
- . " for $table.$column.\n";
- }
- }
- elsif ($col_info && $col_info->{TYPE} =~ /DATETIME/i) {
- my $date = $row->[$colnum];
- # MySQL can have strange invalid values for Datetimes.
- $row->[$colnum] = '1901-01-01 00:00:00'
- if $date && $date eq '0000-00-00 00:00:00';
- }
-
- $insert_sth->bind_param($param_num, $row->[$colnum])
- unless $already_bound;
- $colnum++;
- }
+ }
- $insert_sth->execute();
- $count++;
- indicate_progress({ current => $count, total => $total, every => 100 });
- }
+ print "Writing data to the target '$table' table on "
+ . TARGET_DB_TYPE . "...\n";
+ my $count = 0;
+ while (my $row = $select_sth->fetchrow_arrayref) {
- # For some DBs, we have to do clever things with auto-increment fields.
+ # Each column needs to be bound separately, because
+ # many columns need to be dealt with specially.
+ my $colnum = 0;
foreach my $column (@table_columns) {
- next if $target_db->isa('Bugzilla::DB::Mysql');
- my $col_info = $source_db->bz_column_info($table, $column);
- if ($col_info && $col_info->{TYPE} =~ /SERIAL/i) {
- my ($max_val) = $target_db->selectrow_array(
- "SELECT MAX($column) FROM $table");
- # Set the sequence to the current max value + 1.
- $max_val = 0 if !defined $max_val;
- $max_val++;
- print "\nSetting the next value for $table.$column to $max_val.";
- if ($target_db->isa('Bugzilla::DB::Pg')) {
- # PostgreSQL doesn't like it when you insert values into
- # a serial field; it doesn't increment the counter
- # automatically.
- $target_db->bz_set_next_serial_value($table, $column);
- }
- elsif ($target_db->isa('Bugzilla::DB::Oracle')) {
- # Oracle increments the counter on every insert, and *always*
- # sets the field, even if you gave it a value. So if there
- # were already rows in the target DB (like the default rows
- # created by checksetup), you'll get crazy values in your
- # id columns. So we just dropped the sequences above and
- # we re-create them here, starting with the right number.
- my @sql = $target_db->_bz_real_schema->_get_create_seq_ddl(
- $table, $column, $max_val);
- $target_db->do($_) foreach @sql;
- }
+
+ # bind_param args start at 1, but arrays start at 0.
+ my $param_num = $colnum + 1;
+ my $already_bound;
+
+ # Certain types of columns need special handling.
+ my $col_info = $source_db->bz_column_info($table, $column);
+ if ($col_info && $col_info->{TYPE} eq 'LONGBLOB') {
+ $insert_sth->bind_param($param_num, $row->[$colnum], $target_db->BLOB_TYPE);
+ $already_bound = 1;
+ }
+ elsif ($col_info && $col_info->{TYPE} =~ /decimal/) {
+
+ # In MySQL, decimal cols can be too long.
+ my $col_type = $col_info->{TYPE};
+ $col_type =~ /decimal\((\d+),(\d+)\)/;
+ my ($precision, $decimals) = ($1, $2);
+
+ # If it's longer than precision + decimal point
+ if (length($row->[$colnum]) > ($precision + 1)) {
+
+ # Truncate it to the highest allowed value.
+ my $orig_value = $row->[$colnum];
+ $row->[$colnum] = '';
+ my $non_decimal = $precision - $decimals;
+ $row->[$colnum] .= '9' while ($non_decimal--);
+ $row->[$colnum] .= '.';
+ $row->[$colnum] .= '9' while ($decimals--);
+ print "Truncated value $orig_value to "
+ . $row->[$colnum]
+ . " for $table.$column.\n";
}
+ }
+ elsif ($col_info && $col_info->{TYPE} =~ /DATETIME/i) {
+ my $date = $row->[$colnum];
+
+ # MySQL can have strange invalid values for Datetimes.
+ $row->[$colnum] = '1901-01-01 00:00:00'
+ if $date && $date eq '0000-00-00 00:00:00';
+ }
+
+ $insert_sth->bind_param($param_num, $row->[$colnum]) unless $already_bound;
+ $colnum++;
+ }
+
+ $insert_sth->execute();
+ $count++;
+ indicate_progress({current => $count, total => $total, every => 100});
+ }
+
+ # For some DBs, we have to do clever things with auto-increment fields.
+ foreach my $column (@table_columns) {
+ next if $target_db->isa('Bugzilla::DB::Mysql');
+ my $col_info = $source_db->bz_column_info($table, $column);
+ if ($col_info && $col_info->{TYPE} =~ /SERIAL/i) {
+ my ($max_val) = $target_db->selectrow_array("SELECT MAX($column) FROM $table");
+
+ # Set the sequence to the current max value + 1.
+ $max_val = 0 if !defined $max_val;
+ $max_val++;
+ print "\nSetting the next value for $table.$column to $max_val.";
+ if ($target_db->isa('Bugzilla::DB::Pg')) {
+
+ # PostgreSQL doesn't like it when you insert values into
+ # a serial field; it doesn't increment the counter
+ # automatically.
+ $target_db->bz_set_next_serial_value($table, $column);
+ }
+ elsif ($target_db->isa('Bugzilla::DB::Oracle')) {
+
+ # Oracle increments the counter on every insert, and *always*
+ # sets the field, even if you gave it a value. So if there
+ # were already rows in the target DB (like the default rows
+ # created by checksetup), you'll get crazy values in your
+ # id columns. So we just dropped the sequences above and
+ # we re-create them here, starting with the right number.
+ my @sql
+ = $target_db->_bz_real_schema->_get_create_seq_ddl($table, $column, $max_val);
+ $target_db->do($_) foreach @sql;
+ }
}
+ }
- print "\n\n";
+ print "\n\n";
}
print "Committing changes to the target database...\n";
diff --git a/contrib/console.pl b/contrib/console.pl
index fe2342cd9..7c1748064 100755
--- a/contrib/console.pl
+++ b/contrib/console.pl
@@ -20,115 +20,120 @@ use Bugzilla::Bug;
use Term::ReadLine;
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
-$Data::Dumper::Terse = 1;
-$Data::Dumper::Indent = 1;
-$Data::Dumper::Useqq = 1;
+$Data::Dumper::Terse = 1;
+$Data::Dumper::Indent = 1;
+$Data::Dumper::Useqq = 1;
$Data::Dumper::Maxdepth = 1;
-$Data::Dumper::Deparse = 0;
+$Data::Dumper::Deparse = 0;
my $sysname = get_text('term', {term => 'Bugzilla'});
-my $term = new Term::ReadLine "$sysname Console";
+my $term = new Term::ReadLine "$sysname Console";
read_history($term);
END { write_history($term) }
-while ( defined (my $input = $term->readline("$sysname> ")) ) {
- my @res = eval($input);
- if ($@) {
- warn $@;
- }
- else {
- print Dumper(@res);
- }
+while (defined(my $input = $term->readline("$sysname> "))) {
+ my @res = eval($input);
+ if ($@) {
+ warn $@;
+ }
+ else {
+ print Dumper(@res);
+ }
}
print STDERR "\n";
exit 0;
# d: full dump (normal behavior is limited to depth of 1)
sub d {
- local $Data::Dumper::Maxdepth = 0;
- local $Data::Dumper::Deparse = 1;
- print Dumper(@_);
- return ();
+ local $Data::Dumper::Maxdepth = 0;
+ local $Data::Dumper::Deparse = 1;
+ print Dumper(@_);
+ return ();
}
# p: print as a single string (normal behavior puts list items on separate lines)
sub p {
- no warnings; # suppress possible undefined var message
- print(@_, "\n");
- return ();
+ no warnings; # suppress possible undefined var message
+ print(@_, "\n");
+ return ();
}
sub filter {
- my $name = shift;
- my $filter = Bugzilla->template->{SERVICE}->{CONTEXT}->{CONFIG}->{FILTERS}->{$name};
- if (scalar @_) {
- return $filter->(@_);
- }
- else {
- return $filter;
- }
+ my $name = shift;
+ my $filter
+ = Bugzilla->template->{SERVICE}->{CONTEXT}->{CONFIG}->{FILTERS}->{$name};
+ if (scalar @_) {
+ return $filter->(@_);
+ }
+ else {
+ return $filter;
+ }
}
-sub b { get_object('Bugzilla::Bug', @_) }
-sub u { get_object('Bugzilla::User', @_) }
+sub b { get_object('Bugzilla::Bug', @_) }
+sub u { get_object('Bugzilla::User', @_) }
sub f { get_object('Bugzilla::Field', @_) }
sub get_object {
- my $class = shift;
- $_ = shift;
- my @results = ();
-
- if (ref $_ eq 'HASH' && keys %$_) {
- @results = @{$class->match($_)};
- }
- elsif (m/^\d+$/) {
- @results = ($class->new($_));
- }
- elsif (m/\w/i && grep {$_ eq 'name'} ($class->_get_db_columns)) {
- @results = @{$class->match({name => $_})};
- }
- else {
- @results = ();
- }
-
- if (wantarray) {
- return @results;
- }
- else {
- return shift @results;
- }
+ my $class = shift;
+ $_ = shift;
+ my @results = ();
+
+ if (ref $_ eq 'HASH' && keys %$_) {
+ @results = @{$class->match($_)};
+ }
+ elsif (m/^\d+$/) {
+ @results = ($class->new($_));
+ }
+ elsif (m/\w/i && grep { $_ eq 'name' } ($class->_get_db_columns)) {
+ @results = @{$class->match({name => $_})};
+ }
+ else {
+ @results = ();
+ }
+
+ if (wantarray) {
+ return @results;
+ }
+ else {
+ return shift @results;
+ }
}
sub read_history {
- my ($term) = @_;
-
- if (open HIST, "<$ENV{HOME}/.bugzilla_console_history") {
- foreach (<HIST>) {
- chomp;
- $term->addhistory($_);
- }
- close HIST;
+ my ($term) = @_;
+
+ if (open HIST, "<$ENV{HOME}/.bugzilla_console_history") {
+ foreach (<HIST>) {
+ chomp;
+ $term->addhistory($_);
}
+ close HIST;
+ }
}
sub write_history {
- my ($term) = @_;
-
- if ($term->can('GetHistory') && open HIST, ">$ENV{HOME}/.bugzilla_console_history") {
- my %seen_hist = ();
- my @hist = ();
- foreach my $line (reverse $term->GetHistory()) {
- next unless $line =~ m/\S/;
- next if $seen_hist{$line};
- $seen_hist{$line} = 1;
- push @hist, $line;
- last if (scalar @hist > 500);
- }
- foreach (reverse @hist) {
- print HIST $_, "\n";
- }
- close HIST;
+ my ($term) = @_;
+
+ if (
+ $term->can('GetHistory') && open HIST,
+ ">$ENV{HOME}/.bugzilla_console_history"
+ )
+ {
+ my %seen_hist = ();
+ my @hist = ();
+ foreach my $line (reverse $term->GetHistory()) {
+ next unless $line =~ m/\S/;
+ next if $seen_hist{$line};
+ $seen_hist{$line} = 1;
+ push @hist, $line;
+ last if (scalar @hist > 500);
+ }
+ foreach (reverse @hist) {
+ print HIST $_, "\n";
}
+ close HIST;
+ }
}
__END__
diff --git a/contrib/convert-workflow.pl b/contrib/convert-workflow.pl
index d9bffb7bb..f25bb0ea7 100755
--- a/contrib/convert-workflow.pl
+++ b/contrib/convert-workflow.pl
@@ -18,14 +18,14 @@ use Bugzilla::Search::Saved;
use Bugzilla::Status;
use Getopt::Long;
-my $confirmed = new Bugzilla::Status({ name => 'CONFIRMED' });
-my $in_progress = new Bugzilla::Status({ name => 'IN_PROGRESS' });
+my $confirmed = new Bugzilla::Status({name => 'CONFIRMED'});
+my $in_progress = new Bugzilla::Status({name => 'IN_PROGRESS'});
if ($confirmed and $in_progress) {
- print "You are already using the new workflow.\n";
- exit 1;
+ print "You are already using the new workflow.\n";
+ exit 1;
}
-my $enable_unconfirmed = 0;
+my $enable_unconfirmed = 0;
my $result = GetOptions("enable-unconfirmed" => \$enable_unconfirmed);
print <<END;
@@ -44,9 +44,10 @@ Emails will not be sent for the change.
END
if ($enable_unconfirmed) {
- print "UNCONFIRMED will be enabled in all products.\n";
-} else {
- print <<END;
+ print "UNCONFIRMED will be enabled in all products.\n";
+}
+else {
+ print <<END;
If you also want to enable the UNCONFIRMED status in every product,
restart this script with the --enable-unconfirmed option.
END
@@ -55,103 +56,116 @@ print "\nTo continue, press any key, or press Ctrl-C to stop this program...";
getc;
my $dbh = Bugzilla->dbh;
+
# This is an array instead of a hash so that we can be sure that
# the translation happens in the right order. In particular, we
# want NEW to be renamed to CONFIRMED, instead of having REOPENED
# be the one that gets renamed.
my @translation = (
- [NEW => 'CONFIRMED'],
- [ASSIGNED => 'IN_PROGRESS'],
- [REOPENED => 'CONFIRMED'],
- [CLOSED => 'VERIFIED'],
+ [NEW => 'CONFIRMED'],
+ [ASSIGNED => 'IN_PROGRESS'],
+ [REOPENED => 'CONFIRMED'],
+ [CLOSED => 'VERIFIED'],
);
my $status_field = Bugzilla::Field->check('bug_status');
$dbh->bz_start_transaction();
foreach my $pair (@translation) {
- my ($from, $to) = @$pair;
- print "Converting $from to $to...\n";
- # There is no FK on bugs.bug_status pointing to bug_status.value,
- # so it's fine to update the bugs table first.
- $dbh->do('UPDATE bugs SET bug_status = ? WHERE bug_status = ?',
- undef, $to, $from);
-
- if (Bugzilla->params->{'duplicate_or_move_bug_status'} eq $from) {
- SetParam('duplicate_or_move_bug_status', $to);
- write_params();
- }
-
- foreach my $what (qw(added removed)) {
- $dbh->do("UPDATE bugs_activity SET $what = ?
- WHERE fieldid = ? AND $what = ?",
- undef, $to, $status_field->id, $from);
- }
-
- # Delete any transitions where it now appears that
- # a bug moved from a status to itself.
- $dbh->do('DELETE FROM bugs_activity WHERE fieldid = ? AND added = removed',
- undef, $status_field->id);
-
- # If the new status already exists, just delete the old one, but retain
- # the workflow items from it.
- my $new_status = new Bugzilla::Status({ name => $to });
- my $old_status = new Bugzilla::Status({ name => $from });
-
- if ($new_status && $old_status) {
- my $to_id = $new_status->id;
- my $from_id = $old_status->id;
- # The subselect collects existing transitions from the target bug status.
- # The main select collects existing transitions from the renamed bug status.
- # The diff tells us which transitions are missing from the target bug status.
- my $missing_transitions =
- $dbh->selectcol_arrayref('SELECT sw1.new_status
+ my ($from, $to) = @$pair;
+ print "Converting $from to $to...\n";
+
+ # There is no FK on bugs.bug_status pointing to bug_status.value,
+ # so it's fine to update the bugs table first.
+ $dbh->do('UPDATE bugs SET bug_status = ? WHERE bug_status = ?',
+ undef, $to, $from);
+
+ if (Bugzilla->params->{'duplicate_or_move_bug_status'} eq $from) {
+ SetParam('duplicate_or_move_bug_status', $to);
+ write_params();
+ }
+
+ foreach my $what (qw(added removed)) {
+ $dbh->do(
+ "UPDATE bugs_activity SET $what = ?
+ WHERE fieldid = ? AND $what = ?", undef, $to, $status_field->id,
+ $from
+ );
+ }
+
+ # Delete any transitions where it now appears that
+ # a bug moved from a status to itself.
+ $dbh->do('DELETE FROM bugs_activity WHERE fieldid = ? AND added = removed',
+ undef, $status_field->id);
+
+ # If the new status already exists, just delete the old one, but retain
+ # the workflow items from it.
+ my $new_status = new Bugzilla::Status({name => $to});
+ my $old_status = new Bugzilla::Status({name => $from});
+
+ if ($new_status && $old_status) {
+ my $to_id = $new_status->id;
+ my $from_id = $old_status->id;
+
+ # The subselect collects existing transitions from the target bug status.
+ # The main select collects existing transitions from the renamed bug status.
+ # The diff tells us which transitions are missing from the target bug status.
+ my $missing_transitions = $dbh->selectcol_arrayref(
+ 'SELECT sw1.new_status
FROM status_workflow sw1
WHERE sw1.old_status = ?
AND sw1.new_status NOT IN (SELECT sw2.new_status
FROM status_workflow sw2
WHERE sw2.old_status = ?)',
- undef, ($from_id, $to_id));
-
- $dbh->do('UPDATE status_workflow SET old_status = ? WHERE old_status = ? AND '
- . $dbh->sql_in('new_status', $missing_transitions),
- undef, ($to_id, $from_id)) if @$missing_transitions;
-
- # The subselect collects existing transitions to the target bug status.
- # The main select collects existing transitions to the renamed bug status.
- # The diff tells us which transitions are missing to the target bug status.
- # We have to explicitly exclude NULL from the subselect, because NOT IN
- # doesn't know what to do with it (neither true nor false) and no data is returned.
- $missing_transitions =
- $dbh->selectcol_arrayref('SELECT sw1.old_status
+ undef, ($from_id, $to_id)
+ );
+
+ $dbh->do(
+ 'UPDATE status_workflow SET old_status = ? WHERE old_status = ? AND '
+ . $dbh->sql_in('new_status', $missing_transitions),
+ undef,
+ ($to_id, $from_id)
+ ) if @$missing_transitions;
+
+ # The subselect collects existing transitions to the target bug status.
+ # The main select collects existing transitions to the renamed bug status.
+ # The diff tells us which transitions are missing to the target bug status.
+ # We have to explicitly exclude NULL from the subselect, because NOT IN
+ # doesn't know what to do with it (neither true nor false) and no data is returned.
+ $missing_transitions = $dbh->selectcol_arrayref(
+ 'SELECT sw1.old_status
FROM status_workflow sw1
WHERE sw1.new_status = ?
AND sw1.old_status NOT IN (SELECT sw2.old_status
FROM status_workflow sw2
WHERE sw2.new_status = ?
AND sw2.old_status IS NOT NULL)',
- undef, ($from_id, $to_id));
-
- $dbh->do('UPDATE status_workflow SET new_status = ? WHERE new_status = ? AND '
- . $dbh->sql_in('old_status', $missing_transitions),
- undef, ($to_id, $from_id)) if @$missing_transitions;
-
- # Delete rows where old_status = new_status, and then the old status itself.
- $dbh->do('DELETE FROM status_workflow WHERE old_status = new_status');
- $dbh->do('DELETE FROM bug_status WHERE value = ?', undef, $from);
- }
- # Otherwise, rename the old status to the new one.
- elsif ($old_status) {
- $dbh->do('UPDATE bug_status SET value = ? WHERE value = ?',
- undef, $to, $from);
- }
-
- Bugzilla::Search::Saved->rename_field_value('bug_status', $from, $to);
- Bugzilla::Series->Bugzilla::Search::Saved::rename_field_value('bug_status',
- $from, $to);
+ undef, ($from_id, $to_id)
+ );
+
+ $dbh->do(
+ 'UPDATE status_workflow SET new_status = ? WHERE new_status = ? AND '
+ . $dbh->sql_in('old_status', $missing_transitions),
+ undef,
+ ($to_id, $from_id)
+ ) if @$missing_transitions;
+
+ # Delete rows where old_status = new_status, and then the old status itself.
+ $dbh->do('DELETE FROM status_workflow WHERE old_status = new_status');
+ $dbh->do('DELETE FROM bug_status WHERE value = ?', undef, $from);
+ }
+
+ # Otherwise, rename the old status to the new one.
+ elsif ($old_status) {
+ $dbh->do('UPDATE bug_status SET value = ? WHERE value = ?', undef, $to, $from);
+ }
+
+ Bugzilla::Search::Saved->rename_field_value('bug_status', $from, $to);
+ Bugzilla::Series->Bugzilla::Search::Saved::rename_field_value('bug_status',
+ $from, $to);
}
if ($enable_unconfirmed) {
- print "Enabling UNCONFIRMED in all products...\n";
- $dbh->do('UPDATE products SET allows_unconfirmed = 1');
+ print "Enabling UNCONFIRMED in all products...\n";
+ $dbh->do('UPDATE products SET allows_unconfirmed = 1');
}
$dbh->bz_commit_transaction();
Bugzilla->memcached->clear_all();
diff --git a/contrib/extension-convert.pl b/contrib/extension-convert.pl
index 91a77b839..a70888dc1 100755
--- a/contrib/extension-convert.pl
+++ b/contrib/extension-convert.pl
@@ -21,8 +21,7 @@ use File::Copy qw(move);
use File::Find;
use File::Path qw(mkpath rmtree);
-my $from = $ARGV[0]
- or die <<END;
+my $from = $ARGV[0] or die <<END;
You must specify the name of the extension you are converting from,
as the first argument.
END
@@ -32,33 +31,33 @@ my $extdir = bz_locations()->{'extensionsdir'};
my $from_dir = "$extdir/$from";
if (!-d $from_dir) {
- die "$from_dir does not exist.\n";
+ die "$from_dir does not exist.\n";
}
my $to_dir = "$extdir/$extension_name";
if (-d $to_dir) {
- die "$to_dir already exists, not converting.\n";
+ die "$to_dir already exists, not converting.\n";
}
if (ON_WINDOWS) {
- # There's no easy way to recursively copy a directory on Windows.
- print "WARNING: This will modify the contents of $from_dir.\n",
- "Press Ctrl-C to stop or any other key to continue...\n";
- getc;
- move($from_dir, $to_dir)
- || die "rename of $from_dir to $to_dir failed: $!";
+
+ # There's no easy way to recursively copy a directory on Windows.
+ print "WARNING: This will modify the contents of $from_dir.\n",
+ "Press Ctrl-C to stop or any other key to continue...\n";
+ getc;
+ move($from_dir, $to_dir) || die "rename of $from_dir to $to_dir failed: $!";
}
else {
- print "Copying $from_dir to $to_dir...\n";
- system("cp", "-r", $from_dir, $to_dir);
+ print "Copying $from_dir to $to_dir...\n";
+ system("cp", "-r", $from_dir, $to_dir);
}
-# Make sure we don't accidentally modify the $from_dir anywhere else
+# Make sure we don't accidentally modify the $from_dir anywhere else
# in this script.
undef $from_dir;
if (!-d $to_dir) {
- die "$to_dir was not created.\n";
+ die "$to_dir was not created.\n";
}
my $version = get_version($to_dir);
@@ -96,7 +95,7 @@ END
open(my $config_fh, '>', "$to_dir/Config.pm") || die "$to_dir/Config.pm: $!";
print $config_fh $config_pm;
close($config_fh);
-open(my $extension_fh, '>', "$to_dir/Extension.pm")
+open(my $extension_fh, '>', "$to_dir/Extension.pm")
|| die "$to_dir/Extension.pm: $!";
print $extension_fh $extension_pm;
close($extension_fh);
@@ -109,176 +108,179 @@ unlink("$to_dir/info.pl");
###############
sub rename_module_packages {
- my ($dir, $name) = @_;
- my $lib_dir = "$dir/lib";
-
- # We don't want things like Bugzilla::Extension::Testopia::Testopia.
- if (-d "$lib_dir/$name") {
- print "Moving contents of $lib_dir/$name into $lib_dir...\n";
- foreach my $file (glob("$lib_dir/$name/*")) {
- my $dirname = dirname($file);
- my $basename = basename($file);
- rename($file, "$dirname/../$basename") || warn "$file: $!\n";
- }
+ my ($dir, $name) = @_;
+ my $lib_dir = "$dir/lib";
+
+ # We don't want things like Bugzilla::Extension::Testopia::Testopia.
+ if (-d "$lib_dir/$name") {
+ print "Moving contents of $lib_dir/$name into $lib_dir...\n";
+ foreach my $file (glob("$lib_dir/$name/*")) {
+ my $dirname = dirname($file);
+ my $basename = basename($file);
+ rename($file, "$dirname/../$basename") || warn "$file: $!\n";
}
+ }
- my @modules;
- find({ wanted => sub { $_ =~ /\.pm$/i and push(@modules, $_) },
- no_chdir => 1 }, $lib_dir);
- my %module_rename;
- foreach my $file (@modules) {
- open(my $fh, '<', $file) || die "$file: $!";
- my $content = do { local $/ = undef; <$fh> };
- close($fh);
- if ($content =~ /^package (\S+);/m) {
- my $package = $1;
- my $new_name = $file;
- $new_name =~ s/^$lib_dir\///;
- $new_name =~ s/\.pm$//;
- $new_name = join('::', File::Spec->splitdir($new_name));
- $new_name = "Bugzilla::Extension::${name}::$new_name";
- print "Renaming $package to $new_name...\n";
- $content =~ s/^package \Q$package\E;/package \Q$new_name\E;/;
- open(my $write_fh, '>', $file) || die "$file: $!";
- print $write_fh $content;
- close($write_fh);
- $module_rename{$package} = $new_name;
- }
+ my @modules;
+ find({wanted => sub { $_ =~ /\.pm$/i and push(@modules, $_) }, no_chdir => 1},
+ $lib_dir);
+ my %module_rename;
+ foreach my $file (@modules) {
+ open(my $fh, '<', $file) || die "$file: $!";
+ my $content = do { local $/ = undef; <$fh> };
+ close($fh);
+ if ($content =~ /^package (\S+);/m) {
+ my $package = $1;
+ my $new_name = $file;
+ $new_name =~ s/^$lib_dir\///;
+ $new_name =~ s/\.pm$//;
+ $new_name = join('::', File::Spec->splitdir($new_name));
+ $new_name = "Bugzilla::Extension::${name}::$new_name";
+ print "Renaming $package to $new_name...\n";
+ $content =~ s/^package \Q$package\E;/package \Q$new_name\E;/;
+ open(my $write_fh, '>', $file) || die "$file: $!";
+ print $write_fh $content;
+ close($write_fh);
+ $module_rename{$package} = $new_name;
}
+ }
- print "Renaming module names inside of library and code files...\n";
- my @code_files = glob("$dir/code/*.pl");
- rename_modules_internally(\%module_rename, [@modules, @code_files]);
+ print "Renaming module names inside of library and code files...\n";
+ my @code_files = glob("$dir/code/*.pl");
+ rename_modules_internally(\%module_rename, [@modules, @code_files]);
}
sub rename_modules_internally {
- my ($rename, $files) = @_;
-
- # We can't use \b because :: matches \b.
- my $break = qr/^|[^\w:]|$/;
- foreach my $file (@$files) {
- open(my $fh, '<', $file) || die "$file: $!";
- my $content = do { local $/ = undef; <$fh> };
- close($fh);
- foreach my $old_name (keys %$rename) {
- my $new_name = $rename->{$old_name};
- $content =~ s/($break)\Q$old_name\E($break)/$1$new_name$2/gms;
- }
- open(my $write_fh, '>', $file) || die "$file: $!";
- print $write_fh $content;
- close($write_fh);
+ my ($rename, $files) = @_;
+
+ # We can't use \b because :: matches \b.
+ my $break = qr/^|[^\w:]|$/;
+ foreach my $file (@$files) {
+ open(my $fh, '<', $file) || die "$file: $!";
+ my $content = do { local $/ = undef; <$fh> };
+ close($fh);
+ foreach my $old_name (keys %$rename) {
+ my $new_name = $rename->{$old_name};
+ $content =~ s/($break)\Q$old_name\E($break)/$1$new_name$2/gms;
}
+ open(my $write_fh, '>', $file) || die "$file: $!";
+ print $write_fh $content;
+ close($write_fh);
+ }
}
sub get_version {
- my ($dir) = @_;
- print "Getting version info from info.pl...\n";
- my $info;
- {
- local @INC = ("$dir/lib", @INC);
- $info = do "$dir/info.pl"; die $@ if $@;
- }
- return $info->{version};
+ my ($dir) = @_;
+ print "Getting version info from info.pl...\n";
+ my $info;
+ {
+ local @INC = ("$dir/lib", @INC);
+ $info = do "$dir/info.pl";
+ die $@ if $@;
+ }
+ return $info->{version};
}
sub get_install_requirements {
- my ($dir) = @_;
- my $file = "$dir/code/install-requirements.pl";
- return '' if !-f $file;
-
- print "Moving install-requirements.pl code into Config.pm...\n";
- my ($modules, $code) = process_code_file($file);
- $modules = join('', @$modules);
- $code = join('', @$code);
- if ($modules) {
- return "$modules\n\n$code";
- }
- return $code;
+ my ($dir) = @_;
+ my $file = "$dir/code/install-requirements.pl";
+ return '' if !-f $file;
+
+ print "Moving install-requirements.pl code into Config.pm...\n";
+ my ($modules, $code) = process_code_file($file);
+ $modules = join('', @$modules);
+ $code = join('', @$code);
+ if ($modules) {
+ return "$modules\n\n$code";
+ }
+ return $code;
}
sub process_code_file {
- my ($file) = @_;
- open(my $fh, '<', $file) || die "$file: $!";
- my $stuff_started;
- my (@modules, @code);
- foreach my $line (<$fh>) {
- $stuff_started = 1 if $line !~ /^#/;
- next if !$stuff_started;
- next if $line =~ /^use (warnings|strict|lib|Bugzilla)[^\w:]/;
- if ($line =~ /^(?:use|require)\b/) {
- push(@modules, $line);
- }
- else {
- push(@code, $line);
- }
+ my ($file) = @_;
+ open(my $fh, '<', $file) || die "$file: $!";
+ my $stuff_started;
+ my (@modules, @code);
+ foreach my $line (<$fh>) {
+ $stuff_started = 1 if $line !~ /^#/;
+ next if !$stuff_started;
+ next if $line =~ /^use (warnings|strict|lib|Bugzilla)[^\w:]/;
+ if ($line =~ /^(?:use|require)\b/) {
+ push(@modules, $line);
+ }
+ else {
+ push(@code, $line);
}
- close $fh;
- return (\@modules, \@code);
+ }
+ close $fh;
+ return (\@modules, \@code);
}
sub code_files_to_subroutines {
- my ($dir) = @_;
-
- my @dir_files = glob("$dir/code/*.pl");
- my (@all_modules, @subroutines);
- foreach my $file (@dir_files) {
- next if $file =~ /install-requirements/;
- print "Moving $file code into Extension.pm...\n";
- my ($modules, $code) = process_code_file($file);
- my @code_lines = map { " $_" } @$code;
- my $code_string = join('', @code_lines);
- $code_string =~ s/Bugzilla->hook_args/\$args/g;
- $code_string =~ s/my\s+\$args\s+=\s+\$args;//gs;
- chomp($code_string);
- push(@all_modules, @$modules);
- my $name = basename($file);
- $name =~ s/-/_/;
- $name =~ s/\.pl$//;
-
- my $subroutine = <<END;
+ my ($dir) = @_;
+
+ my @dir_files = glob("$dir/code/*.pl");
+ my (@all_modules, @subroutines);
+ foreach my $file (@dir_files) {
+ next if $file =~ /install-requirements/;
+ print "Moving $file code into Extension.pm...\n";
+ my ($modules, $code) = process_code_file($file);
+ my @code_lines = map {" $_"} @$code;
+ my $code_string = join('', @code_lines);
+ $code_string =~ s/Bugzilla->hook_args/\$args/g;
+ $code_string =~ s/my\s+\$args\s+=\s+\$args;//gs;
+ chomp($code_string);
+ push(@all_modules, @$modules);
+ my $name = basename($file);
+ $name =~ s/-/_/;
+ $name =~ s/\.pl$//;
+
+ my $subroutine = <<END;
sub $name {
my (\$self, \$args) = \@_;
$code_string
}
END
- push(@subroutines, $subroutine);
- }
+ push(@subroutines, $subroutine);
+ }
- my %seen_modules = map { trim($_) => 1 } @all_modules;
- my $module_string = join("\n", sort keys %seen_modules);
- my $subroutine_string = join("\n", @subroutines);
- return ($module_string, $subroutine_string);
+ my %seen_modules = map { trim($_) => 1 } @all_modules;
+ my $module_string = join("\n", sort keys %seen_modules);
+ my $subroutine_string = join("\n", @subroutines);
+ return ($module_string, $subroutine_string);
}
sub move_template_hooks {
- my ($dir) = @_;
- foreach my $lang (glob("$dir/template/*")) {
- next if !_file_matters($lang);
- my $hook_container = "$lang/default/hook";
- mkpath($hook_container) || warn "$hook_container: $!";
- # Hooks can be in all sorts of weird places, including
- # template/default/hook.
- foreach my $file (glob("$lang/*")) {
- next if !_file_matters($file, 1);
- my $dirname = basename($file);
- print "Moving $file to $hook_container/$dirname...\n";
- rename($file, "$hook_container/$dirname") || die "move failed: $!";
- }
+ my ($dir) = @_;
+ foreach my $lang (glob("$dir/template/*")) {
+ next if !_file_matters($lang);
+ my $hook_container = "$lang/default/hook";
+ mkpath($hook_container) || warn "$hook_container: $!";
+
+ # Hooks can be in all sorts of weird places, including
+ # template/default/hook.
+ foreach my $file (glob("$lang/*")) {
+ next if !_file_matters($file, 1);
+ my $dirname = basename($file);
+ print "Moving $file to $hook_container/$dirname...\n";
+ rename($file, "$hook_container/$dirname") || die "move failed: $!";
}
+ }
}
sub _file_matters {
- my ($path, $tmpl) = @_;
- my @ignore = qw(default custom CVS);
- my $file = basename($path);
- return 0 if grep(lc($_) eq lc($file), @ignore);
- # Hidden files
- return 0 if $file =~ /^\./;
- if ($tmpl) {
- return 1 if $file =~ /\.tmpl$/;
- }
- return 0 if !-d $path;
- return 1;
+ my ($path, $tmpl) = @_;
+ my @ignore = qw(default custom CVS);
+ my $file = basename($path);
+ return 0 if grep(lc($_) eq lc($file), @ignore);
+
+ # Hidden files
+ return 0 if $file =~ /^\./;
+ if ($tmpl) {
+ return 1 if $file =~ /\.tmpl$/;
+ }
+ return 0 if !-d $path;
+ return 1;
}
__END__
diff --git a/contrib/jb2bz.py b/contrib/jb2bz.py
index caaa0c5e2..170e82d70 100755
--- a/contrib/jb2bz.py
+++ b/contrib/jb2bz.py
@@ -30,7 +30,7 @@ if not mimetypes.encodings_map.has_key('.bz2'):
bug_status='CONFIRMED'
component="default"
-version=""
+version="unspecified"
product="" # this is required, the rest of these are defaulted as above
"""
@@ -230,6 +230,7 @@ def process_jitterbug(filename):
"bug_id=%s," \
"priority='---'," \
"bug_severity='normal'," \
+ "op_sys='All'," \
"bug_status=%s," \
"creation_ts=%s," \
"delta_ts=%s," \
diff --git a/contrib/merge-users.pl b/contrib/merge-users.pl
index 86b209ab2..02129980d 100755
--- a/contrib/merge-users.pl
+++ b/contrib/merge-users.pl
@@ -44,7 +44,7 @@ use Pod::Usage;
my $dbh = Bugzilla->dbh;
# Display the help if called with --help or -?.
-my $help = 0;
+my $help = 0;
my $result = GetOptions("help|?" => \$help);
pod2usage(0) if $help;
@@ -53,49 +53,54 @@ pod2usage(0) if $help;
my $old = $ARGV[0] || die "You must specify an old user account.\n";
my $old_id;
if ($old =~ /^id:(\d+)$/) {
- # As the old user account may be a deleted one, we don't
- # check whether this user ID is valid or not.
- # If it never existed, no damage will be done.
- $old_id = $1;
+
+ # As the old user account may be a deleted one, we don't
+ # check whether this user ID is valid or not.
+ # If it never existed, no damage will be done.
+ $old_id = $1;
}
else {
- trick_taint($old);
- $old_id = $dbh->selectrow_array('SELECT userid FROM profiles
- WHERE login_name = ?',
- undef, $old);
+ trick_taint($old);
+ $old_id = $dbh->selectrow_array(
+ 'SELECT userid FROM profiles
+ WHERE login_name = ?', undef, $old
+ );
}
if ($old_id) {
- print "OK, old user account $old found; user ID: $old_id.\n";
+ print "OK, old user account $old found; user ID: $old_id.\n";
}
else {
- die "The old user account $old does not exist.\n";
+ die "The old user account $old does not exist.\n";
}
my $new = $ARGV[1] || die "You must specify a new user account.\n";
my $new_id;
if ($new =~ /^id:(\d+)$/) {
- $new_id = $1;
- # Make sure this user ID exists.
- $new_id = $dbh->selectrow_array('SELECT userid FROM profiles
- WHERE userid = ?',
- undef, $new_id);
+ $new_id = $1;
+
+ # Make sure this user ID exists.
+ $new_id = $dbh->selectrow_array(
+ 'SELECT userid FROM profiles
+ WHERE userid = ?', undef, $new_id
+ );
}
else {
- trick_taint($new);
- $new_id = $dbh->selectrow_array('SELECT userid FROM profiles
- WHERE login_name = ?',
- undef, $new);
+ trick_taint($new);
+ $new_id = $dbh->selectrow_array(
+ 'SELECT userid FROM profiles
+ WHERE login_name = ?', undef, $new
+ );
}
if ($new_id) {
- print "OK, new user account $new found; user ID: $new_id.\n";
+ print "OK, new user account $new found; user ID: $new_id.\n";
}
else {
- die "The new user account $new does not exist.\n";
+ die "The new user account $new does not exist.\n";
}
# Make sure the old and new accounts are different.
if ($old_id == $new_id) {
- die "\nBoth accounts are identical. There is nothing to migrate.\n";
+ die "\nBoth accounts are identical. There is nothing to migrate.\n";
}
@@ -112,32 +117,34 @@ if ($old_id == $new_id) {
# We set the tables that require custom stuff (multiple columns to check)
# here, but the simple stuff is all handled below by bz_get_related_fks.
my %changes = (
- cc => ['who bug_id'],
- # Tables affecting global behavior / other users.
- component_cc => ['user_id component_id'],
- watch => ['watcher watched', 'watched watcher'],
- # Tables affecting the user directly.
- namedqueries => ['userid name'],
- namedqueries_link_in_footer => ['user_id namedquery_id'],
- user_group_map => ['user_id group_id isbless grant_type'],
- email_setting => ['user_id relationship event'],
- profile_setting => ['user_id setting_name'],
-
- # Only do it if mailto_type = 0, i.e is pointing to a user account!
- # This requires to be done separately due to this condition.
- whine_schedules => [], # ['mailto'],
+ cc => ['who bug_id'],
+
+ # Tables affecting global behavior / other users.
+ component_cc => ['user_id component_id'],
+ watch => ['watcher watched', 'watched watcher'],
+
+ # Tables affecting the user directly.
+ namedqueries => ['userid name'],
+ namedqueries_link_in_footer => ['user_id namedquery_id'],
+ user_group_map => ['user_id group_id isbless grant_type'],
+ email_setting => ['user_id relationship event'],
+ profile_setting => ['user_id setting_name'],
+
+ # Only do it if mailto_type = 0, i.e is pointing to a user account!
+ # This requires to be done separately due to this condition.
+ whine_schedules => [], # ['mailto'],
);
my $userid_fks = $dbh->bz_get_related_fks('profiles', 'userid');
foreach my $item (@$userid_fks) {
- my ($table, $column) = @$item;
- $changes{$table} ||= [];
- push(@{ $changes{$table} }, $column);
+ my ($table, $column) = @$item;
+ $changes{$table} ||= [];
+ push(@{$changes{$table}}, $column);
}
# Delete all old records for these tables; no migration.
foreach my $table (qw(logincookies tokens profiles)) {
- $changes{$table} = [];
+ $changes{$table} = [];
}
# Start the transaction
@@ -145,7 +152,7 @@ $dbh->bz_start_transaction();
# Delete old records from logincookies and tokens tables.
$dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $old_id);
-$dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $old_id);
+$dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $old_id);
# Special care needs to be done with bug_user_last_visit table as the
# source user and destination user may have visited the same bug id at one time.
@@ -158,78 +165,87 @@ my $dupe_ids = $dbh->selectcol_arrayref("
AND earlier.last_visit_ts < later.last_visit_ts
AND earlier.bug_id = later.bug_id)
WHERE (earlier.user_id = ? OR earlier.user_id = ?)
- AND (later.user_id = ? OR later.user_id = ?)",
- undef, $old_id, $new_id, $old_id, $new_id);
+ AND (later.user_id = ? OR later.user_id = ?)", undef, $old_id,
+ $new_id, $old_id, $new_id);
if (@$dupe_ids) {
- $dbh->do("DELETE FROM bug_user_last_visit WHERE " .
- $dbh->sql_in('id', $dupe_ids));
+ $dbh->do(
+ "DELETE FROM bug_user_last_visit WHERE " . $dbh->sql_in('id', $dupe_ids));
}
# Migrate records from old user to new user.
foreach my $table (keys %changes) {
- foreach my $column_list (@{ $changes{$table} }) {
- # Get all columns to consider. There is always at least
- # one column given: the one to update.
- my @columns = split(/[\s]+/, $column_list);
- my $cols_to_check = join(' AND ', map {"$_ = ?"} @columns);
- # The first column of the list is the one to update.
- my $col_to_update = shift @columns;
-
- # Will be used to migrate the old user account to the new one.
- my $sth_update = $dbh->prepare("UPDATE $table
+ foreach my $column_list (@{$changes{$table}}) {
+
+ # Get all columns to consider. There is always at least
+ # one column given: the one to update.
+ my @columns = split(/[\s]+/, $column_list);
+ my $cols_to_check = join(' AND ', map {"$_ = ?"} @columns);
+
+ # The first column of the list is the one to update.
+ my $col_to_update = shift @columns;
+
+ # Will be used to migrate the old user account to the new one.
+ my $sth_update = $dbh->prepare(
+ "UPDATE $table
SET $col_to_update = ?
- WHERE $cols_to_check");
+ WHERE $cols_to_check"
+ );
- # Do we have additional columns to take care of?
- if (scalar(@columns)) {
- my $cols_to_query = join(', ', @columns);
+ # Do we have additional columns to take care of?
+ if (scalar(@columns)) {
+ my $cols_to_query = join(', ', @columns);
- # Get existing entries for the old user account.
- my $old_entries =
- $dbh->selectall_arrayref("SELECT $cols_to_query
+ # Get existing entries for the old user account.
+ my $old_entries = $dbh->selectall_arrayref(
+ "SELECT $cols_to_query
FROM $table
- WHERE $col_to_update = ?",
- undef, $old_id);
+ WHERE $col_to_update = ?", undef, $old_id
+ );
- # Will be used to check whether the same entry exists
- # for the new user account.
- my $sth_select = $dbh->prepare("SELECT COUNT(*)
+ # Will be used to check whether the same entry exists
+ # for the new user account.
+ my $sth_select = $dbh->prepare(
+ "SELECT COUNT(*)
FROM $table
- WHERE $cols_to_check");
-
- # Will be used to delete duplicated entries.
- my $sth_delete = $dbh->prepare("DELETE FROM $table
- WHERE $cols_to_check");
-
- foreach my $entry (@$old_entries) {
- my $exists = $dbh->selectrow_array($sth_select, undef,
- ($new_id, @$entry));
-
- if ($exists) {
- $sth_delete->execute($old_id, @$entry);
- }
- else {
- $sth_update->execute($new_id, $old_id, @$entry);
- }
- }
+ WHERE $cols_to_check"
+ );
+
+ # Will be used to delete duplicated entries.
+ my $sth_delete = $dbh->prepare(
+ "DELETE FROM $table
+ WHERE $cols_to_check"
+ );
+
+ foreach my $entry (@$old_entries) {
+ my $exists = $dbh->selectrow_array($sth_select, undef, ($new_id, @$entry));
+
+ if ($exists) {
+ $sth_delete->execute($old_id, @$entry);
}
- # No check required. Update the column directly.
else {
- $sth_update->execute($new_id, $old_id);
+ $sth_update->execute($new_id, $old_id, @$entry);
}
- print "OK, records in the '$col_to_update' column of the '$table' table\n" .
- "have been migrated to the new user account.\n";
+ }
}
+
+ # No check required. Update the column directly.
+ else {
+ $sth_update->execute($new_id, $old_id);
+ }
+ print "OK, records in the '$col_to_update' column of the '$table' table\n"
+ . "have been migrated to the new user account.\n";
+ }
}
# Only update 'whine_schedules' if mailto_type = 0.
# (i.e. is pointing to a user ID).
-$dbh->do('UPDATE whine_schedules SET mailto = ?
- WHERE mailto = ? AND mailto_type = ?',
- undef, ($new_id, $old_id, 0));
-print "OK, records in the 'mailto' column of the 'whine_schedules' table\n" .
- "have been migrated to the new user account.\n";
+$dbh->do(
+ 'UPDATE whine_schedules SET mailto = ?
+ WHERE mailto = ? AND mailto_type = ?', undef, ($new_id, $old_id, 0)
+);
+print "OK, records in the 'mailto' column of the 'whine_schedules' table\n"
+ . "have been migrated to the new user account.\n";
# Delete the old record from the profiles table.
$dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $old_id);
diff --git a/contrib/mysqld-watcher.pl b/contrib/mysqld-watcher.pl
index be93dcbb5..5dbbe320a 100755
--- a/contrib/mysqld-watcher.pl
+++ b/contrib/mysqld-watcher.pl
@@ -8,12 +8,12 @@
# mysqld-watcher.pl - a script that watches the running instance of
# mysqld and kills off any long-running SELECTs against the shadow_db
-#
+#
use 5.10.1;
use strict;
use warnings;
-# some configurables:
+# some configurables:
# length of time before a thread is eligible to be killed, in seconds
#
@@ -40,65 +40,70 @@ my $long = {};
# queries so we know which queries are taking too long to run, but complete
# queries with line breaks get missed by this script, so we get abbreviated
# queries as well to make sure we don't miss any.
-foreach my $command ("/opt/mysql/bin/mysqladmin --verbose processlist",
- "/opt/mysql/bin/mysqladmin processlist")
+foreach my $command (
+ "/opt/mysql/bin/mysqladmin --verbose processlist",
+ "/opt/mysql/bin/mysqladmin processlist"
+ )
{
- close(STDIN);
- open(STDIN, "$command |");
+ close(STDIN);
+ open(STDIN, "$command |");
- # iterate through the running threads
- #
- while ( <STDIN> ) {
- my @F = split(/\|/);
+ # iterate through the running threads
+ #
+ while (<STDIN>) {
+ my @F = split(/\|/);
- # if this line is not the correct number of fields, or if the thread-id
- # field contains Id, skip this line. both these cases indicate that this
- # line contains pretty-printing gunk and not thread info.
- #
- next if ( $#F != 9 || $F[1] =~ /Id/);
+ # if this line is not the correct number of fields, or if the thread-id
+ # field contains Id, skip this line. both these cases indicate that this
+ # line contains pretty-printing gunk and not thread info.
+ #
+ next if ($#F != 9 || $F[1] =~ /Id/);
- if ( $F[4] =~ /shadow_bugs/ # shadowbugs database in use
- && $F[5] =~ /Query/ # this is actually a query
- && $F[6] > $long_query_time # this query has taken too long
- && $F[8] =~ /(select|SELECT)/ # only kill a select
- && !defined($long->{$F[1]}) ) # haven't seen this one already
- {
- $long->{$F[1]} = \@F;
- system("/opt/mysql/bin/mysqladmin", "kill", $F[1]);
- }
+ if (
+ $F[4] =~ /shadow_bugs/ # shadowbugs database in use
+ && $F[5] =~ /Query/ # this is actually a query
+ && $F[6] > $long_query_time # this query has taken too long
+ && $F[8] =~ /(select|SELECT)/ # only kill a select
+ && !defined($long->{$F[1]})
+ ) # haven't seen this one already
+ {
+ $long->{$F[1]} = \@F;
+ system("/opt/mysql/bin/mysqladmin", "kill", $F[1]);
}
+ }
}
# send an email message
#
-# should perhaps be moved to somewhere more global for use in bugzilla as a
+# should perhaps be moved to somewhere more global for use in bugzilla as a
# whole; should also do more error-checking
#
sub sendEmail($$$$) {
- ($#_ == 3) || die("sendEmail: invalid number of arguments");
- my ($from, $to, $subject, $body) = @_;
+ ($#_ == 3) || die("sendEmail: invalid number of arguments");
+ my ($from, $to, $subject, $body) = @_;
+
+ open(MTA, "|$mta_program");
+ print MTA "From: $from\n";
+ print MTA "To: $to\n";
+ print MTA "Subject: $subject\n";
+ print MTA "\n";
+ print MTA $body;
+ print MTA "\n";
+ close(MTA);
- open(MTA, "|$mta_program");
- print MTA "From: $from\n";
- print MTA "To: $to\n";
- print MTA "Subject: $subject\n";
- print MTA "\n";
- print MTA $body;
- print MTA "\n";
- close(MTA);
-
}
# if we found anything, kill the database thread and send mail about it
#
if (scalar(keys(%$long))) {
- my $message = "";
- foreach my $process_id (keys(%$long)) {
- my $qry = $long->{$process_id};
- $message .= join(" ", @$qry) . "\n\n";
- }
+ my $message = "";
+ foreach my $process_id (keys(%$long)) {
+ my $qry = $long->{$process_id};
+ $message .= join(" ", @$qry) . "\n\n";
+ }
- # fire off an email telling the maintainer that we had to kill some threads
- #
- sendEmail($mail_from, $mail_to, "long running MySQL thread(s) killed", $message);
+ # fire off an email telling the maintainer that we had to kill some threads
+ #
+ sendEmail($mail_from, $mail_to, "long running MySQL thread(s) killed",
+ $message);
}
diff --git a/contrib/perl-fmt b/contrib/perl-fmt
new file mode 100755
index 000000000..7ac47eeb8
--- /dev/null
+++ b/contrib/perl-fmt
@@ -0,0 +1,24 @@
+#!/usr/bin/env perl
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+use 5.10.1;
+use strict;
+use warnings;
+
+use File::Basename qw(dirname);
+use Cwd qw(realpath);
+use File::Spec::Functions qw(catfile catdir);
+use Env qw(@PATH @PERL5LIB);
+
+my $bugzilla_dir = realpath(catdir( dirname(__FILE__), '..' ));
+unshift @PERL5LIB, catdir($bugzilla_dir, 'local', 'lib', 'perl5');
+unshift @PATH, catdir($bugzilla_dir, 'local', 'bin');
+
+my $profile = catfile($bugzilla_dir, ".perltidyrc" );
+warn "formatting @ARGV\n";
+exec( perltidy => "--profile=$profile", '-nst', '-b', '-bext=/', '-conv', @ARGV );
diff --git a/contrib/recode.pl b/contrib/recode.pl
index e6da47b92..fdb3ae0ac 100755
--- a/contrib/recode.pl
+++ b/contrib/recode.pl
@@ -32,21 +32,26 @@ use constant MAX_STRING_LEN => 25;
# For certain tables, we can't automatically determine their Primary Key.
# So, we specify it here as a string.
use constant SPECIAL_KEYS => {
- # bugs_activity since 4.4 has a unique primary key added
- bugs_activity => 'bug_id,bug_when,fieldid',
- profile_setting => 'user_id,setting_name',
- # profiles_activity since 4.4 has a unique primary key added
- profiles_activity => 'userid,profiles_when,fieldid',
- setting_value => 'name,value',
- # longdescs didn't used to have a PK, before 2.20.
- longdescs => 'bug_id,bug_when',
- # The 2.16 versions table lacked a PK
- versions => 'product_id,value',
- # These are all for earlier versions of Bugzilla. On a modern
- # version of Bugzilla, this script will ignore these (thanks to
- # code further down).
- components => 'program,value',
- products => 'product',
+
+ # bugs_activity since 4.4 has a unique primary key added
+ bugs_activity => 'bug_id,bug_when,fieldid',
+ profile_setting => 'user_id,setting_name',
+
+ # profiles_activity since 4.4 has a unique primary key added
+ profiles_activity => 'userid,profiles_when,fieldid',
+ setting_value => 'name,value',
+
+ # longdescs didn't used to have a PK, before 2.20.
+ longdescs => 'bug_id,bug_when',
+
+ # The 2.16 versions table lacked a PK
+ versions => 'product_id,value',
+
+ # These are all for earlier versions of Bugzilla. On a modern
+ # version of Bugzilla, this script will ignore these (thanks to
+ # code further down).
+ components => 'program,value',
+ products => 'product',
};
###############
@@ -55,18 +60,18 @@ use constant SPECIAL_KEYS => {
# "truncate" is a file operation in perl, so we can't use that name.
sub trunc {
- my ($str) = @_;
- my $truncated = substr($str, 0, MAX_STRING_LEN);
- if (length($truncated) ne length($str)) {
- $truncated .= '...';
- }
- return $truncated;
+ my ($str) = @_;
+ my $truncated = substr($str, 0, MAX_STRING_LEN);
+ if (length($truncated) ne length($str)) {
+ $truncated .= '...';
+ }
+ return $truncated;
}
sub is_valid_utf8 {
- my ($str) = @_;
- Encode::_utf8_on($str);
- return is_utf8($str, 1);
+ my ($str) = @_;
+ Encode::_utf8_on($str);
+ return is_utf8($str, 1);
}
###############
@@ -75,170 +80,172 @@ sub is_valid_utf8 {
my %switch;
GetOptions(\%switch, 'dry-run', 'guess', 'charset=s', 'show-failures',
- 'overrides=s', 'help|h');
+ 'overrides=s', 'help|h');
-pod2usage({ -verbose => 1 }) if $switch{'help'};
+pod2usage({-verbose => 1}) if $switch{'help'};
# You have to specify at least one of these switches.
-pod2usage({ -verbose => 0 }) if (!$switch{'charset'} && !$switch{'guess'});
+pod2usage({-verbose => 0}) if (!$switch{'charset'} && !$switch{'guess'});
if (exists $switch{'charset'}) {
- $switch{'charset'} = resolve_alias($switch{'charset'})
- || die "'$switch{charset}' is not a valid charset.";
+ $switch{'charset'} = resolve_alias($switch{'charset'})
+ || die "'$switch{charset}' is not a valid charset.";
}
if ($switch{'guess'}) {
- if (!eval { require Encode::Detect::Detector }) {
- my $root = ROOT_USER;
- print STDERR <<EOT;
+ if (!eval { require Encode::Detect::Detector }) {
+ my $root = ROOT_USER;
+ print STDERR <<EOT;
Using --guess requires that Encode::Detect be installed. To install
Encode::Detect, run the following command:
$^X install-module.pl Encode::Detect
EOT
- exit;
- }
+ exit;
+ }
}
my %overrides;
if (exists $switch{'overrides'}) {
- my $file = new IO::File($switch{'overrides'}, 'r')
- || die "$switch{overrides}: $!";
- my @lines = $file->getlines();
- $file->close();
- foreach my $line (@lines) {
- chomp($line);
- my ($digest, $encoding) = split(' ', $line);
- $overrides{$digest} = $encoding;
- }
+ my $file = new IO::File($switch{'overrides'}, 'r')
+ || die "$switch{overrides}: $!";
+ my @lines = $file->getlines();
+ $file->close();
+ foreach my $line (@lines) {
+ chomp($line);
+ my ($digest, $encoding) = split(' ', $line);
+ $overrides{$digest} = $encoding;
+ }
}
my $dbh = Bugzilla->dbh;
if ($dbh->isa('Bugzilla::DB::Mysql')) {
- # Get the actual current encoding of the DB.
- my $collation_data = $dbh->selectrow_arrayref(
- "SHOW VARIABLES LIKE 'character_set_database'");
- my $db_charset = $collation_data->[1];
- # Set our connection encoding to *that* encoding, so that MySQL
- # correctly accepts our changes.
- $dbh->do("SET NAMES $db_charset");
- # Make the database give us raw bytes.
- $dbh->do('SET character_set_results = NULL')
+
+ # Get the actual current encoding of the DB.
+ my $collation_data
+ = $dbh->selectrow_arrayref("SHOW VARIABLES LIKE 'character_set_database'");
+ my $db_charset = $collation_data->[1];
+
+ # Set our connection encoding to *that* encoding, so that MySQL
+ # correctly accepts our changes.
+ $dbh->do("SET NAMES $db_charset");
+
+ # Make the database give us raw bytes.
+ $dbh->do('SET character_set_results = NULL');
}
$dbh->begin_work;
foreach my $table ($dbh->bz_table_list_real) {
- my @columns = $dbh->bz_table_columns($table);
-
- my $pk = SPECIAL_KEYS->{$table};
- if ($pk) {
- # Assure that we're on a version of Bugzilla where those keys
- # actually exist.
- foreach my $column (split ',', $pk) {
- $pk = undef if !$dbh->bz_column_info($table, $column);
- }
+ my @columns = $dbh->bz_table_columns($table);
+
+ my $pk = SPECIAL_KEYS->{$table};
+ if ($pk) {
+
+ # Assure that we're on a version of Bugzilla where those keys
+ # actually exist.
+ foreach my $column (split ',', $pk) {
+ $pk = undef if !$dbh->bz_column_info($table, $column);
}
+ }
- # Figure out the primary key.
+ # Figure out the primary key.
+ foreach my $column (@columns) {
+ my $def = $dbh->bz_column_info($table, $column);
+ $pk = $column if $def->{PRIMARYKEY};
+ }
+
+ # If there's no PK, it's defined by a UNIQUE index.
+ if (!$pk) {
foreach my $column (@columns) {
- my $def = $dbh->bz_column_info($table, $column);
- $pk = $column if $def->{PRIMARYKEY};
- }
- # If there's no PK, it's defined by a UNIQUE index.
- if (!$pk) {
- foreach my $column (@columns) {
- my $index = $dbh->bz_index_info($table, "${table}_${column}_idx");
- if ($index && ref($index) eq 'HASH') {
- $pk = join(',', @{$index->{FIELDS}})
- if $index->{TYPE} eq 'UNIQUE';
- }
- }
+ my $index = $dbh->bz_index_info($table, "${table}_${column}_idx");
+ if ($index && ref($index) eq 'HASH') {
+ $pk = join(',', @{$index->{FIELDS}}) if $index->{TYPE} eq 'UNIQUE';
+ }
}
+ }
- foreach my $column (@columns) {
- my $def = $dbh->bz_column_info($table, $column);
- # If this is a text column, it may need work.
- if ($def->{TYPE} =~ /text|char/i) {
- # If there's still no PK, we're upgrading from 2.14 or earlier.
- # We can't reliably determine the PK (or at least, I don't want to
- # maintain code to record what the PK was at all points in history).
- # So instead we just use the field itself.
- $pk = $column if !$pk;
-
- print "Converting $table.$column...\n";
- my $sth = $dbh->prepare("SELECT $column, $pk FROM $table
+ foreach my $column (@columns) {
+ my $def = $dbh->bz_column_info($table, $column);
+
+ # If this is a text column, it may need work.
+ if ($def->{TYPE} =~ /text|char/i) {
+
+ # If there's still no PK, we're upgrading from 2.14 or earlier.
+ # We can't reliably determine the PK (or at least, I don't want to
+ # maintain code to record what the PK was at all points in history).
+ # So instead we just use the field itself.
+ $pk = $column if !$pk;
+
+ print "Converting $table.$column...\n";
+ my $sth = $dbh->prepare(
+ "SELECT $column, $pk FROM $table
WHERE $column IS NOT NULL
- AND $column != ''");
-
- my @pk_array = map {"$_ = ?"} split(',', $pk);
- my $pk_where = join(' AND ', @pk_array);
- my $update_sth = $dbh->prepare(
- "UPDATE $table SET $column = ? WHERE $pk_where");
-
- $sth->execute();
-
- while (my @result = $sth->fetchrow_array) {
- my $data = shift @result;
- # Wide characters cause md5_base64() to die.
- my $digest_data = utf8::is_utf8($data)
- ? Encode::encode_utf8($data) : $data;
- my $digest = md5_base64($digest_data);
-
- my @primary_keys = reverse split(',', $pk);
- # We copy the array so that we can pop things from it without
- # affecting the original.
- my @pk_data = @result;
- my $pk_line = join (', ',
- map { "$_ = " . pop @pk_data } @primary_keys);
-
- my $encoding;
- if ($switch{'guess'}) {
- $encoding = detect_encoding($data);
-
- # We only show failures if they don't appear to be
- # ASCII.
- if ($switch{'show-failures'} && !$encoding
- && !is_valid_utf8($data))
- {
- my $truncated = trunc($data);
- print "Row: [$pk_line]\n",
- "Failed to guess: Key: $digest",
- " DATA: $truncated\n";
- }
-
- # If we fail a guess, and the data is valid UTF-8,
- # just assume we failed because it's UTF-8.
- next if is_valid_utf8($data);
- }
-
- # If we couldn't detect the charset (or were instructed
- # not to try), we fall back to --charset. If there's no
- # fallback, we just do nothing.
- if (!$encoding && $switch{'charset'}) {
- $encoding = $switch{'charset'};
- }
-
- $encoding = $overrides{$digest} if $overrides{$digest};
-
- # We only fix it if it's not ASCII or UTF-8 already.
- if ($encoding && !grep($_ eq $encoding, IGNORE_ENCODINGS)) {
- my $decoded = encode('utf8', decode($encoding, $data));
- if ($switch{'dry-run'} && $data ne $decoded) {
- print "Row: [$pk_line]\n",
- "From: [" . trunc($data) . "] Key: $digest\n",
- "To: [" . trunc($decoded) . "]",
- " Encoding : $encoding\n";
- }
- else {
- $update_sth->execute($decoded, @result);
- }
- }
- } # while (my @result = $sth->fetchrow_array)
- } # if ($column->{TYPE} =~ /text|char/i)
- } # foreach my $column (@columns)
+ AND $column != ''"
+ );
+
+ my @pk_array = map {"$_ = ?"} split(',', $pk);
+ my $pk_where = join(' AND ', @pk_array);
+ my $update_sth = $dbh->prepare("UPDATE $table SET $column = ? WHERE $pk_where");
+
+ $sth->execute();
+
+ while (my @result = $sth->fetchrow_array) {
+ my $data = shift @result;
+
+ # Wide characters cause md5_base64() to die.
+ my $digest_data = utf8::is_utf8($data) ? Encode::encode_utf8($data) : $data;
+ my $digest = md5_base64($digest_data);
+
+ my @primary_keys = reverse split(',', $pk);
+
+ # We copy the array so that we can pop things from it without
+ # affecting the original.
+ my @pk_data = @result;
+ my $pk_line = join(', ', map { "$_ = " . pop @pk_data } @primary_keys);
+
+ my $encoding;
+ if ($switch{'guess'}) {
+ $encoding = detect_encoding($data);
+
+ # We only show failures if they don't appear to be
+ # ASCII.
+ if ($switch{'show-failures'} && !$encoding && !is_valid_utf8($data)) {
+ my $truncated = trunc($data);
+ print "Row: [$pk_line]\n", "Failed to guess: Key: $digest",
+ " DATA: $truncated\n";
+ }
+
+ # If we fail a guess, and the data is valid UTF-8,
+ # just assume we failed because it's UTF-8.
+ next if is_valid_utf8($data);
+ }
+
+ # If we couldn't detect the charset (or were instructed
+ # not to try), we fall back to --charset. If there's no
+ # fallback, we just do nothing.
+ if (!$encoding && $switch{'charset'}) {
+ $encoding = $switch{'charset'};
+ }
+
+ $encoding = $overrides{$digest} if $overrides{$digest};
+
+ # We only fix it if it's not ASCII or UTF-8 already.
+ if ($encoding && !grep($_ eq $encoding, IGNORE_ENCODINGS)) {
+ my $decoded = encode('utf8', decode($encoding, $data));
+ if ($switch{'dry-run'} && $data ne $decoded) {
+ print "Row: [$pk_line]\n", "From: [" . trunc($data) . "] Key: $digest\n",
+ "To: [" . trunc($decoded) . "]", " Encoding : $encoding\n";
+ }
+ else {
+ $update_sth->execute($decoded, @result);
+ }
+ }
+ } # while (my @result = $sth->fetchrow_array)
+ } # if ($column->{TYPE} =~ /text|char/i)
+ } # foreach my $column (@columns)
}
$dbh->commit;
diff --git a/contrib/sendbugmail.pl b/contrib/sendbugmail.pl
index 223d91f6c..b1b5fa8a5 100755
--- a/contrib/sendbugmail.pl
+++ b/contrib/sendbugmail.pl
@@ -20,16 +20,16 @@ use Bugzilla::User;
my $dbh = Bugzilla->dbh;
sub usage {
- say STDERR "Usage: $0 bug_id user_email";
- exit;
+ say STDERR "Usage: $0 bug_id user_email";
+ exit;
}
if (($#ARGV < 1) || ($#ARGV > 2)) {
- usage();
+ usage();
}
# Get the arguments.
-my $bugnum = $ARGV[0];
+my $bugnum = $ARGV[0];
my $changer = $ARGV[1];
# Validate the bug number.
@@ -40,8 +40,8 @@ if (!($bugnum =~ /^(\d+)$/)) {
detaint_natural($bugnum);
-my ($id) = $dbh->selectrow_array("SELECT bug_id FROM bugs WHERE bug_id = ?",
- undef, $bugnum);
+my ($id) = $dbh->selectrow_array("SELECT bug_id FROM bugs WHERE bug_id = ?",
+ undef, $bugnum);
if (!$id) {
say STDERR "Bug number $bugnum does not exist.";
@@ -51,25 +51,26 @@ if (!$id) {
# Validate the changer address.
my $match = Bugzilla->params->{'emailregexp'};
if ($changer !~ /$match/) {
- say STDERR "Changer \"$changer\" doesn't match email regular expression.";
- usage();
+ say STDERR "Changer \"$changer\" doesn't match email regular expression.";
+ usage();
}
-my $changer_user = new Bugzilla::User({ name => $changer });
+my $changer_user = new Bugzilla::User({name => $changer});
unless ($changer_user) {
- say STDERR "\"$changer\" is not a valid user.";
- usage();
+ say STDERR "\"$changer\" is not a valid user.";
+ usage();
}
# Send the email.
-my $outputref = Bugzilla::BugMail::Send($bugnum, {'changer' => $changer_user });
+my $outputref = Bugzilla::BugMail::Send($bugnum, {'changer' => $changer_user});
# Report the results.
my $sent = scalar(@{$outputref->{sent}});
if ($sent) {
- say "email sent to $sent recipients:";
-} else {
- say "No email sent.";
+ say "email sent to $sent recipients:";
+}
+else {
+ say "No email sent.";
}
foreach my $sent (@{$outputref->{sent}}) {
@@ -78,12 +79,12 @@ foreach my $sent (@{$outputref->{sent}}) {
# This document is copyright (C) 2004 Perforce Software, Inc. All rights
# reserved.
-#
+#
# Redistribution and use of this document in any form, with or without
# modification, is permitted provided that redistributions of this
# document retain the above copyright notice, this condition and the
# following disclaimer.
-#
+#
# THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
diff --git a/contrib/sendunsentbugmail.pl b/contrib/sendunsentbugmail.pl
index b9034aa8d..58bde594c 100755
--- a/contrib/sendunsentbugmail.pl
+++ b/contrib/sendunsentbugmail.pl
@@ -19,28 +19,28 @@ use Bugzilla::BugMail;
my $dbh = Bugzilla->dbh;
my $list = $dbh->selectcol_arrayref(
- 'SELECT bug_id FROM bugs
+ 'SELECT bug_id FROM bugs
WHERE (lastdiffed IS NULL OR lastdiffed < delta_ts)
AND delta_ts < '
- . $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE') .
- ' ORDER BY bug_id');
+ . $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE') . ' ORDER BY bug_id'
+);
if (scalar(@$list) > 0) {
- say "OK, now attempting to send unsent mail";
- say scalar(@$list) . " bugs found with possibly unsent mail.\n";
- foreach my $bugid (@$list) {
- my $start_time = time;
- say "Sending mail for bug $bugid...";
- my $outputref = Bugzilla::BugMail::Send($bugid);
- if ($ARGV[0] && $ARGV[0] eq "--report") {
- say "Mail sent to:";
- say $_ foreach (sort @{$outputref->{sent}});
- }
- else {
- my $sent = scalar @{$outputref->{sent}};
- say "$sent mails sent.";
- say "Took " . (time - $start_time) . " seconds.\n";
- }
+ say "OK, now attempting to send unsent mail";
+ say scalar(@$list) . " bugs found with possibly unsent mail.\n";
+ foreach my $bugid (@$list) {
+ my $start_time = time;
+ say "Sending mail for bug $bugid...";
+ my $outputref = Bugzilla::BugMail::Send($bugid);
+ if ($ARGV[0] && $ARGV[0] eq "--report") {
+ say "Mail sent to:";
+ say $_ foreach (sort @{$outputref->{sent}});
}
- say "Unsent mail has been sent.";
+ else {
+ my $sent = scalar @{$outputref->{sent}};
+ say "$sent mails sent.";
+ say "Took " . (time - $start_time) . " seconds.\n";
+ }
+ }
+ say "Unsent mail has been sent.";
}
diff --git a/contrib/syncLDAP.pl b/contrib/syncLDAP.pl
index f618624ec..0ffcba5be 100755
--- a/contrib/syncLDAP.pl
+++ b/contrib/syncLDAP.pl
@@ -19,47 +19,49 @@ use Bugzilla::User;
my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh;
-my $readonly = 0;
+my $readonly = 0;
my $nodisable = 0;
-my $noupdate = 0;
-my $nocreate = 0;
-my $quiet = 0;
+my $noupdate = 0;
+my $nocreate = 0;
+my $quiet = 0;
###
# Do some preparations
###
-foreach my $arg (@ARGV)
-{
- if($arg eq '-r') {
- $readonly = 1;
- }
- elsif($arg eq '-d') {
- $nodisable = 1;
- }
- elsif($arg eq '-u') {
- $noupdate = 1;
- }
- elsif($arg eq '-c') {
- $nocreate = 1;
- }
- elsif($arg eq '-q') {
- $quiet = 1;
- }
- else {
- print "LDAP Sync Script\n";
- print "Syncronizes the users table from the LDAP server with the Bugzilla users.\n";
- print "Takes mail-attribute from preferences and description from 'cn' or,\n";
- print "if not available, from the uid-attribute.\n\n";
- print "usage:\n syncLDAP.pl [options]\n\n";
- print "options:\n";
- print " -r Readonly, do not make changes to Bugzilla tables\n";
- print " -d No disable, don't disable login by users who are not in LDAP\n";
- print " -u No update, don't update users, which have different description in LDAP\n";
- print " -c No create, don't create users, which are in LDAP but not in Bugzilla\n";
- print " -q Quiet mode, give less output\n";
- print "\n";
- exit;
- }
+foreach my $arg (@ARGV) {
+ if ($arg eq '-r') {
+ $readonly = 1;
+ }
+ elsif ($arg eq '-d') {
+ $nodisable = 1;
+ }
+ elsif ($arg eq '-u') {
+ $noupdate = 1;
+ }
+ elsif ($arg eq '-c') {
+ $nocreate = 1;
+ }
+ elsif ($arg eq '-q') {
+ $quiet = 1;
+ }
+ else {
+ print "LDAP Sync Script\n";
+ print
+ "Syncronizes the users table from the LDAP server with the Bugzilla users.\n";
+ print "Takes mail-attribute from preferences and description from 'cn' or,\n";
+ print "if not available, from the uid-attribute.\n\n";
+ print "usage:\n syncLDAP.pl [options]\n\n";
+ print "options:\n";
+ print " -r Readonly, do not make changes to Bugzilla tables\n";
+ print " -d No disable, don't disable login by users who are not in LDAP\n";
+ print
+ " -u No update, don't update users, which have different description in LDAP\n";
+ print
+ " -c No create, don't create users, which are in LDAP but not in Bugzilla\n";
+ print " -q Quiet mode, give less output\n";
+ print "\n";
+ exit;
+ }
}
my %ldap_users;
@@ -67,13 +69,18 @@ my %ldap_users;
###
# Get current bugzilla users
###
-my %bugzilla_users = %{ $dbh->selectall_hashref(
- 'SELECT login_name AS new_login_name, realname, disabledtext ' .
- 'FROM profiles', 'new_login_name') };
+my %bugzilla_users = %{
+ $dbh->selectall_hashref(
+ 'SELECT login_name AS new_login_name, realname, disabledtext '
+ . 'FROM profiles',
+ 'new_login_name'
+ )
+};
foreach my $login_name (keys %bugzilla_users) {
- # remove whitespaces
- $bugzilla_users{$login_name}{'realname'} =~ s/^\s+|\s+$//g;
+
+ # remove whitespaces
+ $bugzilla_users{$login_name}{'realname'} =~ s/^\s+|\s+$//g;
}
###
@@ -81,72 +88,79 @@ foreach my $login_name (keys %bugzilla_users) {
###
my $LDAPserver = Bugzilla->params->{"LDAPserver"};
if ($LDAPserver eq "") {
- print "No LDAP server defined in bugzilla preferences.\n";
- exit;
+ print "No LDAP server defined in bugzilla preferences.\n";
+ exit;
}
my $LDAPconn;
-if($LDAPserver =~ /:\/\//) {
- # if the "LDAPserver" parameter is in uri scheme
- $LDAPconn = Net::LDAP->new($LDAPserver, version => 3);
-} else {
- my $LDAPport = "389"; # default LDAP port
- if($LDAPserver =~ /:/) {
- ($LDAPserver, $LDAPport) = split(":",$LDAPserver);
- }
- $LDAPconn = Net::LDAP->new($LDAPserver, port => $LDAPport, version => 3);
+if ($LDAPserver =~ /:\/\//) {
+
+ # if the "LDAPserver" parameter is in uri scheme
+ $LDAPconn = Net::LDAP->new($LDAPserver, version => 3);
+}
+else {
+ my $LDAPport = "389"; # default LDAP port
+ if ($LDAPserver =~ /:/) {
+ ($LDAPserver, $LDAPport) = split(":", $LDAPserver);
+ }
+ $LDAPconn = Net::LDAP->new($LDAPserver, port => $LDAPport, version => 3);
}
-if(!$LDAPconn) {
- print "Connecting to LDAP server failed. Check LDAPserver setting.\n";
- exit;
+if (!$LDAPconn) {
+ print "Connecting to LDAP server failed. Check LDAPserver setting.\n";
+ exit;
}
my $mesg;
if (Bugzilla->params->{"LDAPbinddn"}) {
- my ($LDAPbinddn,$LDAPbindpass) = split(":",Bugzilla->params->{"LDAPbinddn"});
- $mesg = $LDAPconn->bind($LDAPbinddn, password => $LDAPbindpass);
+ my ($LDAPbinddn, $LDAPbindpass) = split(":", Bugzilla->params->{"LDAPbinddn"});
+ $mesg = $LDAPconn->bind($LDAPbinddn, password => $LDAPbindpass);
}
else {
- $mesg = $LDAPconn->bind();
+ $mesg = $LDAPconn->bind();
}
-if($mesg->code) {
- print "Binding to LDAP server failed: " . $mesg->error . "\nCheck LDAPbinddn setting.\n";
- exit;
+if ($mesg->code) {
+ print "Binding to LDAP server failed: "
+ . $mesg->error
+ . "\nCheck LDAPbinddn setting.\n";
+ exit;
}
# We've got our anonymous bind; let's look up the users.
-$mesg = $LDAPconn->search( base => Bugzilla->params->{"LDAPBaseDN"},
- scope => "sub",
- filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"} . "=*)" . Bugzilla->params->{"LDAPfilter"} . ')',
- );
-
-
-if(! $mesg->count) {
- print "LDAP lookup failure. Check LDAPBaseDN setting.\n";
- exit;
+$mesg = $LDAPconn->search(
+ base => Bugzilla->params->{"LDAPBaseDN"},
+ scope => "sub",
+ filter => '(&('
+ . Bugzilla->params->{"LDAPuidattribute"} . "=*)"
+ . Bugzilla->params->{"LDAPfilter"} . ')',
+);
+
+
+if (!$mesg->count) {
+ print "LDAP lookup failure. Check LDAPBaseDN setting.\n";
+ exit;
}
-
-my %val = %{ $mesg->as_struct };
-
-while( my ($key, $value) = each(%val) ) {
-
- my @login_name = @{ $value->{Bugzilla->params->{"LDAPmailattribute"}} };
- my @realname = @{ $value->{"cn"} };
-
- # no mail entered? go to next
- if(! @login_name) {
- print "$key has no valid mail address\n";
- next;
- }
-
- # no cn entered? use uid instead
- if(! @realname) {
- @realname = @{ $value->{Bugzilla->params->{"LDAPuidattribute"}} };
- }
-
- my $login = shift @login_name;
- my $real = shift @realname;
- $ldap_users{$login} = { realname => $real };
+
+my %val = %{$mesg->as_struct};
+
+while (my ($key, $value) = each(%val)) {
+
+ my @login_name = @{$value->{Bugzilla->params->{"LDAPmailattribute"}}};
+ my @realname = @{$value->{"cn"}};
+
+ # no mail entered? go to next
+ if (!@login_name) {
+ print "$key has no valid mail address\n";
+ next;
+ }
+
+ # no cn entered? use uid instead
+ if (!@realname) {
+ @realname = @{$value->{Bugzilla->params->{"LDAPuidattribute"}}};
+ }
+
+ my $login = shift @login_name;
+ my $real = shift @realname;
+ $ldap_users{$login} = {realname => $real};
}
print "\n" unless $quiet;
@@ -159,120 +173,125 @@ my %update_users;
my %create_users;
print "Bugzilla-Users: \n" unless $quiet;
-while( my ($key, $value) = each(%bugzilla_users) ) {
- print " " . $key . " '" . $value->{'realname'} . "' " . $value->{'disabledtext'} ."\n" unless $quiet==1;
- if(!exists $ldap_users{$key}){
- if($value->{'disabledtext'} eq '') {
- $disable_users{$key} = $value;
- }
+while (my ($key, $value) = each(%bugzilla_users)) {
+ print " "
+ . $key . " '"
+ . $value->{'realname'} . "' "
+ . $value->{'disabledtext'} . "\n"
+ unless $quiet == 1;
+ if (!exists $ldap_users{$key}) {
+ if ($value->{'disabledtext'} eq '') {
+ $disable_users{$key} = $value;
+ }
}
}
print "\nLDAP-Users: \n" unless $quiet;
-while( my ($key, $value) = each(%ldap_users) ) {
- print " " . $key . " '" . $value->{'realname'} . "'\n" unless $quiet==1;
- if(!defined $bugzilla_users{$key}){
+while (my ($key, $value) = each(%ldap_users)) {
+ print " " . $key . " '" . $value->{'realname'} . "'\n" unless $quiet == 1;
+ if (!defined $bugzilla_users{$key}) {
$create_users{$key} = $value;
}
- else {
+ else {
my $bugzilla_user_value = $bugzilla_users{$key};
- if($bugzilla_user_value->{'realname'} ne $value->{'realname'}) {
+ if ($bugzilla_user_value->{'realname'} ne $value->{'realname'}) {
$update_users{$key} = $value;
}
}
}
print "\nDetecting email changes: \n" unless $quiet;
-while( my ($create_key, $create_value) = each(%create_users) ) {
- while( my ($disable_key, $disable_value) = each(%disable_users) ) {
- if($create_value->{'realname'} eq $disable_value->{'realname'}) {
- print " " . $disable_key . " => " . $create_key ."'\n" unless $quiet==1;
- $update_users{$disable_key} = { realname => $create_value->{'realname'},
- new_login_name => $create_key };
- delete $create_users{$create_key};
- delete $disable_users{$disable_key};
+while (my ($create_key, $create_value) = each(%create_users)) {
+ while (my ($disable_key, $disable_value) = each(%disable_users)) {
+ if ($create_value->{'realname'} eq $disable_value->{'realname'}) {
+ print " " . $disable_key . " => " . $create_key . "'\n" unless $quiet == 1;
+ $update_users{$disable_key}
+ = {realname => $create_value->{'realname'}, new_login_name => $create_key};
+ delete $create_users{$create_key};
+ delete $disable_users{$disable_key};
}
}
}
-if($quiet == 0) {
- print "\nUsers to disable login for: \n";
- while( my ($key, $value) = each(%disable_users) ) {
- print " " . $key . " '" . $value->{'realname'} . "'\n";
- }
-
- print "\nUsers to update: \n";
- while( my ($key, $value) = each(%update_users) ) {
- print " " . $key . " '" . $value->{'realname'} . "' ";
- if(defined $value->{'new_login_name'}) {
- print "has changed email to " . $value->{'new_login_name'};
- }
- print "\n";
- }
-
- print "\nUsers to create: \n";
- while( my ($key, $value) = each(%create_users) ) {
- print " " . $key . " '" . $value->{'realname'} . "'\n";
- }
-
- print "\n\n";
+if ($quiet == 0) {
+ print "\nUsers to disable login for: \n";
+ while (my ($key, $value) = each(%disable_users)) {
+ print " " . $key . " '" . $value->{'realname'} . "'\n";
+ }
+
+ print "\nUsers to update: \n";
+ while (my ($key, $value) = each(%update_users)) {
+ print " " . $key . " '" . $value->{'realname'} . "' ";
+ if (defined $value->{'new_login_name'}) {
+ print "has changed email to " . $value->{'new_login_name'};
+ }
+ print "\n";
+ }
+
+ print "\nUsers to create: \n";
+ while (my ($key, $value) = each(%create_users)) {
+ print " " . $key . " '" . $value->{'realname'} . "'\n";
+ }
+
+ print "\n\n";
}
###
# now do the DB-Update
###
-if($readonly == 0) {
- print "Performing DB update:\nPhase 1: disabling login for users not in LDAP... " unless $quiet;
+if ($readonly == 0) {
+ print
+ "Performing DB update:\nPhase 1: disabling login for users not in LDAP... "
+ unless $quiet;
- my $sth_disable = $dbh->prepare(
- 'UPDATE profiles
+ my $sth_disable = $dbh->prepare(
+ 'UPDATE profiles
SET disabledtext = ?
- WHERE ' . $dbh->sql_istrcmp('login_name', '?'));
+ WHERE ' . $dbh->sql_istrcmp('login_name', '?')
+ );
- if($nodisable == 0) {
- while( my ($key, $value) = each(%disable_users) ) {
- $sth_disable->execute('auto-disabled by ldap sync', $key);
- }
- print "done!\n" unless $quiet;
- }
- else {
- print "disabled!\n" unless $quiet;
- }
-
- print "Phase 2: updating existing users... " unless $quiet;
-
- if($noupdate == 0) {
- while( my ($key, $value) = each(%update_users) ) {
- my $user = Bugzilla::User->check($key);
- if(defined $value->{'new_login_name'}) {
- $user->set_login($value->{'new_login_name'});
- } else {
- $user->set_name($value->{'realname'});
- }
- $user->update();
+ if ($nodisable == 0) {
+ while (my ($key, $value) = each(%disable_users)) {
+ $sth_disable->execute('auto-disabled by ldap sync', $key);
+ }
+ print "done!\n" unless $quiet;
+ }
+ else {
+ print "disabled!\n" unless $quiet;
+ }
+
+ print "Phase 2: updating existing users... " unless $quiet;
+
+ if ($noupdate == 0) {
+ while (my ($key, $value) = each(%update_users)) {
+ my $user = Bugzilla::User->check($key);
+ if (defined $value->{'new_login_name'}) {
+ $user->set_login($value->{'new_login_name'});
}
- print "done!\n" unless $quiet;
- }
- else {
- print "disabled!\n" unless $quiet;
- }
-
- print "Phase 3: creating new users... " unless $quiet;
- if($nocreate == 0) {
- while( my ($key, $value) = each(%create_users) ) {
- Bugzilla::User->create({
- login_name => $key,
- realname => $value->{'realname'},
- cryptpassword => '*'});
+ else {
+ $user->set_name($value->{'realname'});
}
- print "done!\n" unless $quiet;
- }
- else {
- print "disabled!\n" unless $quiet;
- }
+ $user->update();
+ }
+ print "done!\n" unless $quiet;
+ }
+ else {
+ print "disabled!\n" unless $quiet;
+ }
+
+ print "Phase 3: creating new users... " unless $quiet;
+ if ($nocreate == 0) {
+ while (my ($key, $value) = each(%create_users)) {
+ Bugzilla::User->create(
+ {login_name => $key, realname => $value->{'realname'}, cryptpassword => '*'});
+ }
+ print "done!\n" unless $quiet;
+ }
+ else {
+ print "disabled!\n" unless $quiet;
+ }
}
-else
-{
- print "No changes to DB because readonly mode\n" unless $quiet;
+else {
+ print "No changes to DB because readonly mode\n" unless $quiet;
}
diff --git a/createaccount.cgi b/createaccount.cgi
index a15396384..0251114e8 100755
--- a/createaccount.cgi
+++ b/createaccount.cgi
@@ -20,10 +20,10 @@ use Bugzilla::Token;
# Just in case someone already has an account, let them get the correct footer
# on an error message. The user is logged out just after the account is
# actually created.
-my $user = Bugzilla->login(LOGIN_OPTIONAL);
-my $cgi = Bugzilla->cgi;
+my $user = Bugzilla->login(LOGIN_OPTIONAL);
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = { doc_section => 'using/creating-an-account.html' };
+my $vars = {doc_section => 'using/creating-an-account.html'};
print $cgi->header();
@@ -31,17 +31,18 @@ $user->check_account_creation_enabled;
my $login = $cgi->param('login');
if (defined($login)) {
- # Check the hash token to make sure this user actually submitted
- # the create account form.
- my $token = $cgi->param('token');
- check_hash_token($token, ['create_account']);
- $user->check_and_send_account_creation_confirmation($login);
- $vars->{'login'} = $login;
+ # Check the hash token to make sure this user actually submitted
+ # the create account form.
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['create_account']);
- $template->process("account/created.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $user->check_and_send_account_creation_confirmation($login);
+ $vars->{'login'} = $login;
+
+ $template->process("account/created.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
# Show the standard "would you like to create an account?" form.
diff --git a/custom_buglist.cgi b/custom_buglist.cgi
index 2e383fd74..81631032e 100755
--- a/custom_buglist.cgi
+++ b/custom_buglist.cgi
@@ -10,45 +10,45 @@ use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Util;
-my $cgi = Bugzilla->cgi;
-my $template = Bugzilla->template;
-my $vars = {};
-my $dbh = Bugzilla->switch_to_shadow_db();
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+my $dbh = Bugzilla->switch_to_shadow_db();
my @bindValues;
-print $cgi->header(-type=>'text/html');
-my $reso = $cgi->param('reso');
+print $cgi->header(-type => 'text/html');
+my $reso = $cgi->param('reso');
my $status = $cgi->param('status');
-my $since = $cgi->param('since');
+my $since = $cgi->param('since');
$status = undef unless defined($status);
-$reso = undef unless defined($reso);
-$since = undef unless defined($since) and $since =~ /^[0-9]+/;
+$reso = undef unless defined($reso);
+$since = undef unless defined($since) and $since =~ /^[0-9]+/;
-trick_taint($reso) if defined($reso);
+trick_taint($reso) if defined($reso);
trick_taint($status) if defined($status);
-trick_taint($since) if defined($since);
+trick_taint($since) if defined($since);
# SELECT profiles.login_name,bugs.bug_id,short_desc,priority,resolution,rep_platform,bug_status,bug_severity,profilesb.login_name AS reporter FROM bugs LEFT JOIN profiles ON bugs.assigned_to = profiles.userid LEFT JOIN profiles AS profilesb ON bugs.reporter = profilesb.userid LEFT JOIN bug_group_map ON bug_group_map.bug_id = bugs.bug_id WHERE bugs.bug_id=160786 AND (bug_group_map.group_id IS NULL OR bug_group_map.group_id!=24)
my @bindValues2;
-my ($reso_sql, $status_sql, $since_sql);
+my ($reso_sql, $status_sql, $since_sql);
my ($reso_desc, $status_desc, $since_desc);
-$reso_sql = $status_sql = $since_sql = '1';
+$reso_sql = $status_sql = $since_sql = '1';
$reso_desc = $status_desc = $since_desc = '';
-if(defined($reso)) {
- $reso_sql = 'resolution=?';
- push(@bindValues2, $reso);
- $reso_desc = 'with resolution '.$reso;
+if (defined($reso)) {
+ $reso_sql = 'resolution=?';
+ push(@bindValues2, $reso);
+ $reso_desc = 'with resolution ' . $reso;
}
-if(defined($status)) {
- $status_sql = 'bug_status=?';
- push(@bindValues2, $status);
- $status_desc = 'with status '.$status;
+if (defined($status)) {
+ $status_sql = 'bug_status=?';
+ push(@bindValues2, $status);
+ $status_desc = 'with status ' . $status;
}
-if(defined($since)) {
- $since_sql = 'since=?';
- push(@bindValues2, $since);
- $since_desc = 'since '.$since;
+if (defined($since)) {
+ $since_sql = 'since=?';
+ push(@bindValues2, $since);
+ $since_desc = 'since ' . $since;
}
my $query2 = sprintf 'SELECT
bugs.bug_id, short_desc, priority,
@@ -58,33 +58,27 @@ my $query2 = sprintf 'SELECT
WHERE bug_group_map.group_id IS NULL
AND %s
AND %s
- AND %s',
- $reso_sql,
- $status_sql,
- $since_sql;
+ AND %s', $reso_sql, $status_sql, $since_sql;
#print Dumper($vars);
-my $title = sprintf "Bug listing %s %s %s as at %s",$status_desc,$reso_desc,$since_desc,DateTime->now->strftime("%Y/%m/%d %H:%M:%S");
-print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">'."\n";
+my $title = sprintf "Bug listing %s %s %s as at %s", $status_desc, $reso_desc,
+ $since_desc, DateTime->now->strftime("%Y/%m/%d %H:%M:%S");
+print
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">'
+ . "\n";
printf "<head><title>$title</title></head>\n";
-printf "<body><h1>%s</h1>\n",$title;
-my $actions = $dbh->selectall_arrayref(
- $query2,
- { Slice => {} },
- @bindValues2
-);
+printf "<body><h1>%s</h1>\n", $title;
+my $actions = $dbh->selectall_arrayref($query2, {Slice => {}}, @bindValues2);
my $counter = 0;
print "<div><ul>";
foreach my $row (@$actions) {
- printf "<li><a href='%s%d'>Bug:%d - \"<em>%s</em>\" status:%s resolution:%s severity:%s</a></li>\n",
- correct_urlbase(),
- $row->{'bug_id'}, $row->{'bug_id'},
- html_quote($row->{'short_desc'}),
- $row->{'bug_status'},
- $row->{'resolution'},
- $row->{'bug_severity'};
- $counter++;
+ printf
+ "<li><a href='%s%d'>Bug:%d - \"<em>%s</em>\" status:%s resolution:%s severity:%s</a></li>\n",
+ correct_urlbase(), $row->{'bug_id'}, $row->{'bug_id'},
+ html_quote($row->{'short_desc'}), $row->{'bug_status'}, $row->{'resolution'},
+ $row->{'bug_severity'};
+ $counter++;
}
-printf "</ul>Done. Count=%d</div></body></html>\n",$counter;
+printf "</ul>Done. Count=%d</div></body></html>\n", $counter;
diff --git a/custom_disabled.cgi b/custom_disabled.cgi
index 2c134c7f0..1173e3620 100755
--- a/custom_disabled.cgi
+++ b/custom_disabled.cgi
@@ -6,36 +6,41 @@ use lib qw(. lib);
use Bugzilla;
use Bugzilla::Constants;
-my $cgi = Bugzilla->cgi;
-my $vars = {};
+my $cgi = Bugzilla->cgi;
+my $vars = {};
my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $dbh = Bugzilla->switch_to_shadow_db();
+my $dbh = Bugzilla->switch_to_shadow_db();
-print $cgi->header(-type=>'text/html');
+print $cgi->header(-type => 'text/html');
-$user->in_group('admin')
+ $user->in_group('admin')
|| $user->in_group('editusers')
|| $user->in_group('gentoo-dev')
- || ThrowUserError('auth_failure', {action => 'access', object => 'administrative_pages'});
+ || ThrowUserError('auth_failure',
+ {action => 'access', object => 'administrative_pages'});
-my $query = 'SELECT DISTINCT userid, login_name, realname, disabledtext, disable_mail ' .
- 'FROM profiles '.
- 'WHERE LENGTH(profiles.disabledtext) > 0';
-$vars->{'users'} = $dbh->selectall_arrayref($query, { Slice => {} });
+my $query
+ = 'SELECT DISTINCT userid, login_name, realname, disabledtext, disable_mail '
+ . 'FROM profiles '
+ . 'WHERE LENGTH(profiles.disabledtext) > 0';
+$vars->{'users'} = $dbh->selectall_arrayref($query, {Slice => {}});
#use Data::Dumper;
#print Dumper($vars);
foreach my $user (@{$vars->{'users'}}) {
- next if($user->{'realname'} =~ m/\(RETIRED\)$/ and $user->{'disabledtext'} =~ m/retired/i);
+ next
+ if ($user->{'realname'} =~ m/\(RETIRED\)$/
+ and $user->{'disabledtext'} =~ m/retired/i);
- $user->{'disabledtext'} =~ s/\n/<br>/g;
+ $user->{'disabledtext'} =~ s/\n/<br>/g;
- # Add bug links
- $user->{'disabledtext'} =~ s/(bug (\d+(#c\d+)?))/<a href="\/$2">$1<\/a>/g;
+ # Add bug links
+ $user->{'disabledtext'} =~ s/(bug (\d+(#c\d+)?))/<a href="\/$2">$1<\/a>/g;
- printf("Login=<a href=\"/editusers.cgi?action=edit&userid=%i\">%s</a><br>", $user->{'userid'}, $user->{'login_name'});
- printf("Real Name=%s<br>", $user->{'realname'});
- printf("Bugmail Disabled: %s<br>", $user->{'disable_mail'} eq 1 ? "Yes" : "No");
- printf("Disabled Text=%s<br><br>", $user->{'disabledtext'});
+ printf("Login=<a href=\"/editusers.cgi?action=edit&userid=%i\">%s</a><br>",
+ $user->{'userid'}, $user->{'login_name'});
+ printf("Real Name=%s<br>", $user->{'realname'});
+ printf("Bugmail Disabled: %s<br>", $user->{'disable_mail'} eq 1 ? "Yes" : "No");
+ printf("Disabled Text=%s<br><br>", $user->{'disabledtext'});
}
diff --git a/custom_extraperms.cgi b/custom_extraperms.cgi
index 01ffcbe76..e064ecbf3 100755
--- a/custom_extraperms.cgi
+++ b/custom_extraperms.cgi
@@ -8,18 +8,19 @@ use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $dbh = Bugzilla->switch_to_shadow_db();
+my $dbh = Bugzilla->switch_to_shadow_db();
my @bindValues;
-print $cgi->header(-type=>'text/html');
+print $cgi->header(-type => 'text/html');
-$user->in_group('admin')
+ $user->in_group('admin')
|| $user->in_group('editusers')
|| $user->in_group('gentoo-dev')
- || ThrowUserError('auth_failure', {action => 'access', object => 'administrative_pages'});
+ || ThrowUserError('auth_failure',
+ {action => 'access', object => 'administrative_pages'});
my $sql_archtesters = "
SELECT
@@ -51,23 +52,18 @@ GROUP BY login_name
ORDER BY login_name;";
my $users;
-$users = $dbh->selectall_arrayref(
- $sql_archtesters,
- { Slice => {} },
- @bindValues
-);
+$users = $dbh->selectall_arrayref($sql_archtesters, {Slice => {}}, @bindValues);
printf "<h3>Arch Testers that are not \@gentoo.org</h3>\n";
foreach my $row (@$users) {
- printf "<a href='%scustom_userhistory.cgi?matchstr=%s'>%s</a><br />\n", correct_urlbase(), $row->{'login_name'}, $row->{'login_name'};
+ printf "<a href='%scustom_userhistory.cgi?matchstr=%s'>%s</a><br />\n",
+ correct_urlbase(), $row->{'login_name'}, $row->{'login_name'};
}
-$users = $dbh->selectall_arrayref(
- $sql_otherperm,
- { Slice => {} },
- @bindValues
-);
+$users = $dbh->selectall_arrayref($sql_otherperm, {Slice => {}}, @bindValues);
printf "<h3>Users with Other Groups</h3>\n";
foreach my $row (@$users) {
- printf "<a href='%scustom_userhistory.cgi?matchstr=%s'>%s</a>: %s<br />\n", correct_urlbase(), $row->{'login_name'}, $row->{'login_name'}, $row->{'group_name'};
+ printf "<a href='%scustom_userhistory.cgi?matchstr=%s'>%s</a>: %s<br />\n",
+ correct_urlbase(), $row->{'login_name'}, $row->{'login_name'},
+ $row->{'group_name'};
}
diff --git a/custom_userhistory.cgi b/custom_userhistory.cgi
index f47076e6b..e5fa14d7d 100755
--- a/custom_userhistory.cgi
+++ b/custom_userhistory.cgi
@@ -9,21 +9,21 @@ use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::User;
-my $cgi = Bugzilla->cgi;
-my $vars = {};
+my $cgi = Bugzilla->cgi;
+my $vars = {};
my $myuser = Bugzilla->login(LOGIN_REQUIRED);
-my $dbh = Bugzilla->switch_to_shadow_db();
+my $dbh = Bugzilla->switch_to_shadow_db();
my ($query, $matchstr, $userid, $limit, $login_name);
print $cgi->header();
$matchstr = $cgi->param('matchstr');
-$userid = $cgi->param('userid');
-$userid = undef unless defined($userid) and $userid =~ /^\d+$/;
-if(!defined($matchstr) and !defined($userid)) {
- print "No search parameters specified!<br/>\n";
- print "Put <tt>matchstr</tt> or <tt>userid</tt> in the URL parameters.<br/>\n";
- exit(0);
+$userid = $cgi->param('userid');
+$userid = undef unless defined($userid) and $userid =~ /^\d+$/;
+if (!defined($matchstr) and !defined($userid)) {
+ print "No search parameters specified!<br/>\n";
+ print "Put <tt>matchstr</tt> or <tt>userid</tt> in the URL parameters.<br/>\n";
+ exit(0);
}
exit 0 if !defined($matchstr) and !defined($userid);
@@ -31,15 +31,15 @@ $limit = $cgi->param('limit');
$limit = 50 unless defined($limit) and $limit =~ /^\d+$/;
trick_taint($matchstr) if defined($matchstr);
-trick_taint($userid) if defined($userid);
+trick_taint($userid) if defined($userid);
trick_taint($limit);
$userid = $matchstr ? login_to_id($matchstr) : $userid;
$login_name = $matchstr ? $matchstr : Bugzilla::User->new($userid)->login;
-if(!$userid || !$login_name) {
- print "Bad user!<br/>";
- exit(0);
+if (!$userid || !$login_name) {
+ print "Bad user!<br/>";
+ exit(0);
}
$query = qq{
@@ -81,21 +81,18 @@ UNION
LIMIT $limit)
ORDER BY bug_when DESC
LIMIT $limit};
-my $actions = $dbh->selectall_arrayref(
- $query,
- { Slice => {} },
- ($userid, $userid, $userid),
-);
+my $actions = $dbh->selectall_arrayref($query, {Slice => {}},
+ ($userid, $userid, $userid),);
#print Dumper($vars);
printf "<h1>Custom User History: %s</h1>\n", $login_name;
print "<table>\n";
-printf "<tr><th>login_name</th><td>%s</td></tr>\n",$login_name;
-printf "<tr><th>userid</th><td>%s</td></tr>\n",$userid;
+printf "<tr><th>login_name</th><td>%s</td></tr>\n", $login_name;
+printf "<tr><th>userid</th><td>%s</td></tr>\n", $userid;
print "</table>\n";
sub show_bug_url {
- return "/show_bug.cgi?id=".shift;
+ return "/show_bug.cgi?id=" . shift;
}
print q{
@@ -110,16 +107,18 @@ print q{
};
my $counter = 0;
foreach my $row (@$actions) {
- $counter++;
- my $url = show_bug_url($row->{'bug_id'});
- (my $message = qq{
+ $counter++;
+ my $url = show_bug_url($row->{'bug_id'});
+ (
+ my $message = qq{
<tr>
<td>$row->{'bug_when'}</td>
<td><a href="${url}">$row->{'bug_id'}</a></td>
<td>$row->{'field'}</td>
</tr>
- }) =~ s/^\t{1}//mg;
- print $message;
+ }
+ ) =~ s/^\t{1}//mg;
+ print $message;
}
print qq{
</table>
@@ -147,11 +146,7 @@ WHERE
OR p2.userid = ?
ORDER BY
profiles_when};
-$actions = $dbh->selectall_arrayref(
- $query,
- { Slice => {} },
- ($userid, $userid),
-);
+$actions = $dbh->selectall_arrayref($query, {Slice => {}}, ($userid, $userid),);
print qq{
<hr/>
@@ -167,8 +162,9 @@ print qq{
</tr>
};
foreach my $row (@$actions) {
- next unless $row->{'grantee_id'} == $userid;
- (my $message = qq{
+ next unless $row->{'grantee_id'} == $userid;
+ (
+ my $message = qq{
<tr>
<td>$row->{'profiles_when'}</td>
<td>$row->{'grantor'}</td>
@@ -176,8 +172,9 @@ foreach my $row (@$actions) {
<td>$row->{'oldvalue'}</td>
<td>$row->{'newvalue'}</td>
</tr>
- }) =~ s/^\t{1}//mg;
- print $message;
+ }
+ ) =~ s/^\t{1}//mg;
+ print $message;
}
print qq{
@@ -194,8 +191,9 @@ print qq{
</tr>
};
foreach my $row (@$actions) {
- next unless $row->{'grantor_id'} == $userid;
- (my $message = qq{
+ next unless $row->{'grantor_id'} == $userid;
+ (
+ my $message = qq{
<tr>
<td>$row->{'profiles_when'}</td>
<td>$row->{'grantor'}</td>
@@ -203,8 +201,9 @@ foreach my $row (@$actions) {
<td>$row->{'oldvalue'}</td>
<td>$row->{'newvalue'}</td>
</tr>
- }) =~ s/^\t{1}//mg;
- print $message;
+ }
+ ) =~ s/^\t{1}//mg;
+ print $message;
}
print "</table>\n";
@@ -225,21 +224,17 @@ WHERE
ORDER BY
watcher,watched
};
-$actions = $dbh->selectall_arrayref(
- $query,
- { Slice => {} },
- ($userid, $userid),
-);
+$actions = $dbh->selectall_arrayref($query, {Slice => {}}, ($userid, $userid),);
print "<hr/><h2>Watch status</h2>\n";
printf "<h3>Watchers of %s:</h3>\n", $login_name;
foreach my $row (@$actions) {
-printf "%s<br/>\n", $row->{'watcher'} if $row->{'watched_id'} == $userid;
+ printf "%s<br/>\n", $row->{'watcher'} if $row->{'watched_id'} == $userid;
}
printf "<br/>\n";
printf "<h3>Watched by %s:</h3>", $login_name;
foreach my $row (@$actions) {
-printf "%s<br/>\n", $row->{'watched'} if $row->{'watcher_id'} == $userid;
+ printf "%s<br/>\n", $row->{'watched'} if $row->{'watcher_id'} == $userid;
}
printf "<br/>\n";
@@ -262,11 +257,8 @@ WHERE
ORDER BY
at_time
};
-my $audits = $dbh->selectall_arrayref(
- $query,
- { Slice => {} },
- ($userid, $userid),
-);
+my $audits
+ = $dbh->selectall_arrayref($query, {Slice => {}}, ($userid, $userid),);
print qq{<hr/>
<h2>Audit log</h2>
@@ -280,16 +272,18 @@ print qq{<hr/>
</tr>
};
foreach my $row (@$audits) {
- next unless $row->{'user_id'} == $userid;
- (my $message = qq{
+ next unless $row->{'user_id'} == $userid;
+ (
+ my $message = qq{
<tr>
<td><tt>$row->{'at_time'}</tt></td>
<td><tt>$row->{'login_name'}</tt> ($row->{'user_id'})</td>
<td><tt>$row->{'class'}/$row->{'object_id'}</tt></td>
<td><tt>$row->{'field'}</tt></td>
</tr>
- }) =~ s/^\t{1}//mg;
- print $message;
+ }
+ ) =~ s/^\t{1}//mg;
+ print $message;
}
print qq{
@@ -305,16 +299,19 @@ print qq{
</tr>
};
foreach my $row (@$audits) {
- next unless $row->{'object_id'} == $userid && $row->{'class'} eq 'Bugzilla::User';
- (my $message = qq{
+ next
+ unless $row->{'object_id'} == $userid && $row->{'class'} eq 'Bugzilla::User';
+ (
+ my $message = qq{
<tr>
<td><tt>$row->{'at_time'}</tt></td>
<td><tt>$row->{'login_name'}</tt> ($row->{'user_id'})</td>
<td><tt>$row->{'class'}/$row->{'object_id'}</tt></td>
<td><tt>$row->{'field'}</tt></td>
</tr>
- }) =~ s/^\t{1}//mg;
- print $message;
+ }
+ ) =~ s/^\t{1}//mg;
+ print $message;
}
print qq{
</table>
diff --git a/describecomponents.cgi b/describecomponents.cgi
index f74dc75f4..19e1af4a5 100755
--- a/describecomponents.cgi
+++ b/describecomponents.cgi
@@ -19,10 +19,10 @@ use Bugzilla::Error;
use Bugzilla::Classification;
use Bugzilla::Product;
-my $user = Bugzilla->login();
-my $cgi = Bugzilla->cgi;
+my $user = Bugzilla->login();
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
print $cgi->header();
@@ -33,37 +33,41 @@ my $product_name = trim($cgi->param('product') || '');
my $product = new Bugzilla::Product({'name' => $product_name});
unless ($product && $user->can_access_product($product->name)) {
- # Products which the user is allowed to see.
- my @products = @{$user->get_accessible_products};
- if (scalar(@products) == 0) {
- ThrowUserError("no_products");
- }
- # If there is only one product available but the user entered
- # another product name, we display a list with this single
- # product only, to not confuse the user with components of a
- # product they didn't request.
- elsif (scalar(@products) > 1 || $product_name) {
- $vars->{'classifications'} = sort_products_by_classification(\@products);
- $vars->{'target'} = "describecomponents.cgi";
- # If an invalid product name is given, or the user is not
- # allowed to access that product, a message is displayed
- # with a list of the products the user can choose from.
- if ($product_name) {
- $vars->{'message'} = "product_invalid";
- # Do not use $product->name here, else you could use
- # this way to determine whether the product exists or not.
- $vars->{'product'} = $product_name;
- }
-
- $template->process("global/choose-product.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ # Products which the user is allowed to see.
+ my @products = @{$user->get_accessible_products};
+
+ if (scalar(@products) == 0) {
+ ThrowUserError("no_products");
+ }
+
+ # If there is only one product available but the user entered
+ # another product name, we display a list with this single
+ # product only, to not confuse the user with components of a
+ # product they didn't request.
+ elsif (scalar(@products) > 1 || $product_name) {
+ $vars->{'classifications'} = sort_products_by_classification(\@products);
+ $vars->{'target'} = "describecomponents.cgi";
+
+ # If an invalid product name is given, or the user is not
+ # allowed to access that product, a message is displayed
+ # with a list of the products the user can choose from.
+ if ($product_name) {
+ $vars->{'message'} = "product_invalid";
+
+ # Do not use $product->name here, else you could use
+ # this way to determine whether the product exists or not.
+ $vars->{'product'} = $product_name;
}
- # If there is only one product available and the user didn't specify
- # any product name, we show this product.
- $product = $products[0];
+ $template->process("global/choose-product.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+
+ # If there is only one product available and the user didn't specify
+ # any product name, we show this product.
+ $product = $products[0];
}
######################################################################
diff --git a/describekeywords.cgi b/describekeywords.cgi
index 31bf0c13e..ac18d5f22 100755
--- a/describekeywords.cgi
+++ b/describekeywords.cgi
@@ -18,16 +18,16 @@ use Bugzilla::Keyword;
my $user = Bugzilla->login();
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
# Run queries against the shadow DB.
Bugzilla->switch_to_shadow_db;
$vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
if (!@{$vars->{keywords}}) {
- ThrowUserError("no_keywords");
+ ThrowUserError("no_keywords");
}
print $cgi->header();
diff --git a/docs/lib/Pod/Simple/HTML/Bugzilla.pm b/docs/lib/Pod/Simple/HTML/Bugzilla.pm
index ffbd0775c..5a2203473 100644
--- a/docs/lib/Pod/Simple/HTML/Bugzilla.pm
+++ b/docs/lib/Pod/Simple/HTML/Bugzilla.pm
@@ -16,53 +16,53 @@ use parent qw(Pod::Simple::HTML);
# Without this constant, HTMLBatch will throw undef warnings.
use constant VERSION => $Pod::Simple::HTML::VERSION;
use constant CODE_CLASS => ' class="code"';
-use constant META_CT => '<meta http-equiv="Content-Type" content="text/html;'
- . ' charset=UTF-8">';
-use constant DOCTYPE => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01'
- . ' Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">';
+use constant META_CT => '<meta http-equiv="Content-Type" content="text/html;'
+ . ' charset=UTF-8">';
+use constant DOCTYPE => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01'
+ . ' Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">';
sub new {
- my $self = shift->SUPER::new(@_);
+ my $self = shift->SUPER::new(@_);
- my $doctype = $self->DOCTYPE;
- my $content_type = $self->META_CT;
+ my $doctype = $self->DOCTYPE;
+ my $content_type = $self->META_CT;
- my $html_pre_title = <<END_HTML;
+ my $html_pre_title = <<END_HTML;
$doctype
<html>
<head>
<title>
END_HTML
- my $html_post_title = <<END_HTML;
+ my $html_post_title = <<END_HTML;
</title>
$content_type
</head>
<body id="pod">
END_HTML
- $self->html_header_before_title($html_pre_title);
- $self->html_header_after_title($html_post_title);
+ $self->html_header_before_title($html_pre_title);
+ $self->html_header_after_title($html_post_title);
- # Fix some tags to have classes so that we can adjust them.
- my $code = CODE_CLASS;
- $self->{'Tagmap'}->{'Verbatim'} = "\n<pre $code>";
- $self->{'Tagmap'}->{'VerbatimFormatted'} = "\n<pre $code>";
- $self->{'Tagmap'}->{'F'} = "<em $code>";
- $self->{'Tagmap'}->{'C'} = "<code $code>";
+ # Fix some tags to have classes so that we can adjust them.
+ my $code = CODE_CLASS;
+ $self->{'Tagmap'}->{'Verbatim'} = "\n<pre $code>";
+ $self->{'Tagmap'}->{'VerbatimFormatted'} = "\n<pre $code>";
+ $self->{'Tagmap'}->{'F'} = "<em $code>";
+ $self->{'Tagmap'}->{'C'} = "<code $code>";
- # Don't put head4 tags into the Table of Contents. We have this
- delete $Pod::Simple::HTML::ToIndex{'head4'};
+ # Don't put head4 tags into the Table of Contents. We have this
+ delete $Pod::Simple::HTML::ToIndex{'head4'};
- return $self;
+ return $self;
}
# Override do_beginning to put the name of the module at the top
sub do_beginning {
- my $self = shift;
- $self->SUPER::do_beginning(@_);
- print {$self->{'output_fh'}} "<h1>" . $self->get_short_title . "</h1>";
- return 1;
+ my $self = shift;
+ $self->SUPER::do_beginning(@_);
+ print {$self->{'output_fh'}} "<h1>" . $self->get_short_title . "</h1>";
+ return 1;
}
1;
diff --git a/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm b/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
index cb61082df..bc96c9a0e 100644
--- a/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
+++ b/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
@@ -21,90 +21,92 @@ BEGIN { *esc = \&Pod::Simple::HTML::esc }
# Note that if you leave out a category here, it will not be indexed
# in the contents file, even though its HTML POD will still exist.
use constant FILE_TRANSLATION => {
- Files => ['importxml', 'contrib', 'checksetup', 'email_in',
- 'install-module', 'sanitycheck', 'jobqueue', 'migrate',
- 'collectstats'],
- Modules => ['bugzilla'],
- Extensions => ['extensions'],
+ Files => [
+ 'importxml', 'contrib', 'checksetup', 'email_in',
+ 'install-module', 'sanitycheck', 'jobqueue', 'migrate',
+ 'collectstats'
+ ],
+ Modules => ['bugzilla'],
+ Extensions => ['extensions'],
};
# This is basically copied from Pod::Simple::HTMLBatch, and overridden
# so that we can format things more nicely.
sub _write_contents_middle {
- my ($self, $Contents, $outfile, $toplevel2submodules) = @_;
-
- my $file_trans = FILE_TRANSLATION;
-
- # For every top-level category...
- foreach my $category (sort keys %$file_trans) {
- # Get all of the HTMLBatch categories that should be in this
- # category.
- my @category_data;
- foreach my $b_category (@{$file_trans->{$category}}) {
- my $data = $toplevel2submodules->{$b_category};
- push(@category_data, @$data) if $data;
- }
- next unless @category_data;
-
- my @downlines = sort {$a->[-1] cmp $b->[-1]} @category_data;
-
- # And finally, actually print out the table for this category.
- printf $Contents qq[<dt><a name="%s">%s</a></dt>\n<dd>\n],
- esc($category), esc($category);
- print $Contents '<table class="pod_desc_table">' . "\n";
-
- # For every POD...
- my $row_count = 0;
- foreach my $e (@downlines) {
- $row_count++;
- my $even_or_odd = $row_count % 2 ? 'even' : 'odd';
- my $name = esc($e->[0]);
- my $path = join( "/", '.', esc(@{$e->[3]}) )
- . $Pod::Simple::HTML::HTML_EXTENSION;
- my $description = $self->{bugzilla_desc}->{$name} || '';
- $description = esc($description);
- my $html = <<END_HTML;
+ my ($self, $Contents, $outfile, $toplevel2submodules) = @_;
+
+ my $file_trans = FILE_TRANSLATION;
+
+ # For every top-level category...
+ foreach my $category (sort keys %$file_trans) {
+
+ # Get all of the HTMLBatch categories that should be in this
+ # category.
+ my @category_data;
+ foreach my $b_category (@{$file_trans->{$category}}) {
+ my $data = $toplevel2submodules->{$b_category};
+ push(@category_data, @$data) if $data;
+ }
+ next unless @category_data;
+
+ my @downlines = sort { $a->[-1] cmp $b->[-1] } @category_data;
+
+ # And finally, actually print out the table for this category.
+ printf $Contents qq[<dt><a name="%s">%s</a></dt>\n<dd>\n], esc($category),
+ esc($category);
+ print $Contents '<table class="pod_desc_table">' . "\n";
+
+ # For every POD...
+ my $row_count = 0;
+ foreach my $e (@downlines) {
+ $row_count++;
+ my $even_or_odd = $row_count % 2 ? 'even' : 'odd';
+ my $name = esc($e->[0]);
+ my $path = join("/", '.', esc(@{$e->[3]})) . $Pod::Simple::HTML::HTML_EXTENSION;
+ my $description = $self->{bugzilla_desc}->{$name} || '';
+ $description = esc($description);
+ my $html = <<END_HTML;
<tr class="$even_or_odd">
<th><a href="$path">$name</a></th>
<td>$description</td>
</tr>
END_HTML
-
- print $Contents $html;
- }
- print $Contents "</table></dd>\n\n";
+
+ print $Contents $html;
}
+ print $Contents "</table></dd>\n\n";
+ }
- return 1;
+ return 1;
}
# This stores the name and description for each file, so that
# we can get that information out later.
sub note_for_contents_file {
- my $self = shift;
- my $retval = $self->SUPER::note_for_contents_file(@_);
-
- my ($namelets, $infile) = @_;
- my $parser = $self->html_render_class->new;
- $parser->set_source($infile);
- my $full_title = $parser->get_title;
- $full_title =~ /^\S+\s+-+\s+(.+)/;
- my $description = $1;
-
- $self->{bugzilla_desc} ||= {};
- $self->{bugzilla_desc}->{join('::', @$namelets)} = $description;
-
- return $retval;
+ my $self = shift;
+ my $retval = $self->SUPER::note_for_contents_file(@_);
+
+ my ($namelets, $infile) = @_;
+ my $parser = $self->html_render_class->new;
+ $parser->set_source($infile);
+ my $full_title = $parser->get_title;
+ $full_title =~ /^\S+\s+-+\s+(.+)/;
+ my $description = $1;
+
+ $self->{bugzilla_desc} ||= {};
+ $self->{bugzilla_desc}->{join('::', @$namelets)} = $description;
+
+ return $retval;
}
# Exclude modules being in lib/.
sub find_all_pods {
- my($self, $dirs) = @_;
- my $mod2path = $self->SUPER::find_all_pods($dirs);
- foreach my $mod (keys %$mod2path) {
- delete $mod2path->{$mod} if $mod =~ /^lib::/;
- }
- return $mod2path;
+ my ($self, $dirs) = @_;
+ my $mod2path = $self->SUPER::find_all_pods($dirs);
+ foreach my $mod (keys %$mod2path) {
+ delete $mod2path->{$mod} if $mod =~ /^lib::/;
+ }
+ return $mod2path;
}
1;
diff --git a/docs/makedocs.pl b/docs/makedocs.pl
index 36ffc45b5..87e346e23 100755
--- a/docs/makedocs.pl
+++ b/docs/makedocs.pl
@@ -48,28 +48,29 @@ use Pod::Simple::HTML::Bugzilla;
###############################################################################
my $error_found = 0;
+
sub MakeDocs {
- my ($name, $cmdline) = @_;
+ my ($name, $cmdline) = @_;
- say "Creating $name documentation ..." if defined $name;
- system('make', $cmdline) == 0
- or $error_found = 1;
- print "\n";
+ say "Creating $name documentation ..." if defined $name;
+ system('make', $cmdline) == 0 or $error_found = 1;
+ print "\n";
}
sub make_pod {
- say "Creating API documentation...";
+ say "Creating API documentation...";
+
+ my $converter = Pod::Simple::HTMLBatch::Bugzilla->new;
- my $converter = Pod::Simple::HTMLBatch::Bugzilla->new;
- # Don't output progress information.
- $converter->verbose(0);
- $converter->html_render_class('Pod::Simple::HTML::Bugzilla');
+ # Don't output progress information.
+ $converter->verbose(0);
+ $converter->html_render_class('Pod::Simple::HTML::Bugzilla');
- my $doctype = Pod::Simple::HTML::Bugzilla->DOCTYPE;
- my $content_type = Pod::Simple::HTML::Bugzilla->META_CT;
- my $bz_version = BUGZILLA_VERSION;
+ my $doctype = Pod::Simple::HTML::Bugzilla->DOCTYPE;
+ my $content_type = Pod::Simple::HTML::Bugzilla->META_CT;
+ my $bz_version = BUGZILLA_VERSION;
- my $contents_start = <<END_HTML;
+ my $contents_start = <<END_HTML;
$doctype
<html>
<head>
@@ -80,15 +81,15 @@ $doctype
<h1>Bugzilla $bz_version API Documentation</h1>
END_HTML
- $converter->contents_page_start($contents_start);
- $converter->contents_page_end("</body></html>");
- $converter->add_css('./../../../../style.css');
- $converter->javascript_flurry(0);
- $converter->css_flurry(0);
- make_path('html/integrating/api');
- $converter->batch_convert(['../../'], 'html/integrating/api');
+ $converter->contents_page_start($contents_start);
+ $converter->contents_page_end("</body></html>");
+ $converter->add_css('./../../../../style.css');
+ $converter->javascript_flurry(0);
+ $converter->css_flurry(0);
+ make_path('html/integrating/api');
+ $converter->batch_convert(['../../'], 'html/integrating/api');
- print "\n";
+ print "\n";
}
###############################################################################
@@ -96,67 +97,68 @@ END_HTML
###############################################################################
my @langs;
+
# search for sub directories which have a 'rst' sub-directory
opendir(LANGS, './');
foreach my $dir (readdir(LANGS)) {
- next if (($dir eq '.') || ($dir eq '..') || (! -d $dir));
- if (-d "$dir/rst") {
- push(@langs, $dir);
- }
+ next if (($dir eq '.') || ($dir eq '..') || (!-d $dir));
+ if (-d "$dir/rst") {
+ push(@langs, $dir);
+ }
}
closedir(LANGS);
my $docparent = getcwd();
foreach my $lang (@langs) {
- chdir "$docparent/$lang";
+ chdir "$docparent/$lang";
- make_pod();
+ make_pod();
- next if grep { $_ eq '--pod-only' } @ARGV;
+ next if grep { $_ eq '--pod-only' } @ARGV;
- chdir $docparent;
+ chdir $docparent;
- # Generate extension documentation, both normal and API
- my $ext_dir = bz_locations()->{'extensionsdir'};
- my @ext_paths = grep { $_ !~ /\/create\.pl$/ && ! -e "$_/disabled" }
- glob("$ext_dir/*");
- my %extensions;
- foreach my $item (@ext_paths) {
- my $basename = basename($item);
- if (-d "$item/docs/$lang/rst") {
- $extensions{$basename} = "$item/docs/$lang/rst";
- }
+ # Generate extension documentation, both normal and API
+ my $ext_dir = bz_locations()->{'extensionsdir'};
+ my @ext_paths
+ = grep { $_ !~ /\/create\.pl$/ && !-e "$_/disabled" } glob("$ext_dir/*");
+ my %extensions;
+ foreach my $item (@ext_paths) {
+ my $basename = basename($item);
+ if (-d "$item/docs/$lang/rst") {
+ $extensions{$basename} = "$item/docs/$lang/rst";
}
+ }
- # Collect up local extension documentation into the extensions/ dir.
- rmtree("$lang/rst/extensions", 0, 1);
+ # Collect up local extension documentation into the extensions/ dir.
+ rmtree("$lang/rst/extensions", 0, 1);
- foreach my $ext_name (keys %extensions) {
- my $src = $extensions{$ext_name} . "/*";
- my $dst = "$docparent/$lang/rst/extensions/$ext_name";
- mkdir($dst) unless -d $dst;
- rcopy($src, $dst);
- }
+ foreach my $ext_name (keys %extensions) {
+ my $src = $extensions{$ext_name} . "/*";
+ my $dst = "$docparent/$lang/rst/extensions/$ext_name";
+ mkdir($dst) unless -d $dst;
+ rcopy($src, $dst);
+ }
- chdir "$docparent/$lang";
-
- MakeDocs('HTML', 'html');
- MakeDocs('TXT', 'text');
-
- if (grep { $_ eq '--with-pdf' } @ARGV) {
- if (which('pdflatex')) {
- MakeDocs('PDF', 'latexpdf');
- }
- elsif (which('rst2pdf')) {
- rmtree('pdf', 0, 1);
- MakeDocs('PDF', 'pdf');
- }
- else {
- say 'pdflatex or rst2pdf not found. Skipping PDF file creation';
- }
+ chdir "$docparent/$lang";
+
+ MakeDocs('HTML', 'html');
+ MakeDocs('TXT', 'text');
+
+ if (grep { $_ eq '--with-pdf' } @ARGV) {
+ if (which('pdflatex')) {
+ MakeDocs('PDF', 'latexpdf');
+ }
+ elsif (which('rst2pdf')) {
+ rmtree('pdf', 0, 1);
+ MakeDocs('PDF', 'pdf');
+ }
+ else {
+ say 'pdflatex or rst2pdf not found. Skipping PDF file creation';
}
+ }
- rmtree('doctrees', 0, 1);
+ rmtree('doctrees', 0, 1);
}
die "Error occurred building the documentation\n" if $error_found;
diff --git a/duplicates.cgi b/duplicates.cgi
index 88159539d..794a0d86f 100755
--- a/duplicates.cgi
+++ b/duplicates.cgi
@@ -21,22 +21,23 @@ use Bugzilla::Field;
use Bugzilla::Product;
use constant DEFAULTS => {
- # We want to show bugs which:
- # a) Aren't CLOSED; and
- # b) i) Aren't VERIFIED; OR
- # ii) Were resolved INVALID/WONTFIX
- #
- # The rationale behind this is that people will eventually stop
- # reporting fixed bugs when they get newer versions of the software,
- # but if the bug is determined to be erroneous, people will still
- # keep reporting it, so we do need to show it here.
- # gentoo bug 166593
- fully_exclude_status => [], # ['CLOSED'],
- partly_exclude_status => ['VERIFIED'],
- except_resolution => ['INVALID', 'WONTFIX'],
- changedsince => 7,
- maxrows => 20,
- sortby => 'count',
+
+ # We want to show bugs which:
+ # a) Aren't CLOSED; and
+ # b) i) Aren't VERIFIED; OR
+ # ii) Were resolved INVALID/WONTFIX
+ #
+ # The rationale behind this is that people will eventually stop
+ # reporting fixed bugs when they get newer versions of the software,
+ # but if the bug is determined to be erroneous, people will still
+ # keep reporting it, so we do need to show it here.
+ # gentoo bug 166593
+ fully_exclude_status => [], # ['CLOSED'],
+ partly_exclude_status => ['VERIFIED'],
+ except_resolution => ['INVALID', 'WONTFIX'],
+ changedsince => 7,
+ maxrows => 20,
+ sortby => 'count',
};
###############
@@ -49,92 +50,93 @@ use constant DEFAULTS => {
# in $count is a duplicate of another bug in $count, we add their counts
# together under the target bug.
sub add_indirect_dups {
- my ($counts, $dups) = @_;
+ my ($counts, $dups) = @_;
- foreach my $add_from (keys %$dups) {
- my $add_to = walk_dup_chain($dups, $add_from);
- my $add_amount = delete $counts->{$add_from} || 0;
- $counts->{$add_to} += $add_amount;
- }
+ foreach my $add_from (keys %$dups) {
+ my $add_to = walk_dup_chain($dups, $add_from);
+ my $add_amount = delete $counts->{$add_from} || 0;
+ $counts->{$add_to} += $add_amount;
+ }
}
sub walk_dup_chain {
- my ($dups, $from_id) = @_;
- my $to_id = $dups->{$from_id};
- my %seen;
- while (my $bug_id = $dups->{$to_id}) {
- if ($seen{$bug_id}) {
- warn "Duplicate loop: $to_id -> $bug_id\n";
- last;
- }
- $seen{$bug_id} = 1;
- $to_id = $bug_id;
+ my ($dups, $from_id) = @_;
+ my $to_id = $dups->{$from_id};
+ my %seen;
+ while (my $bug_id = $dups->{$to_id}) {
+ if ($seen{$bug_id}) {
+ warn "Duplicate loop: $to_id -> $bug_id\n";
+ last;
}
- # Optimize for future calls to add_indirect_dups.
- $dups->{$from_id} = $to_id;
- return $to_id;
+ $seen{$bug_id} = 1;
+ $to_id = $bug_id;
+ }
+
+ # Optimize for future calls to add_indirect_dups.
+ $dups->{$from_id} = $to_id;
+ return $to_id;
}
# Get params from URL
sub formvalue {
- my ($name) = (@_);
- my $cgi = Bugzilla->cgi;
- if (defined $cgi->param($name)) {
- return $cgi->param($name);
- }
- elsif (exists DEFAULTS->{$name}) {
- return ref DEFAULTS->{$name} ? @{ DEFAULTS->{$name} }
- : DEFAULTS->{$name};
- }
- return undef;
+ my ($name) = (@_);
+ my $cgi = Bugzilla->cgi;
+ if (defined $cgi->param($name)) {
+ return $cgi->param($name);
+ }
+ elsif (exists DEFAULTS->{$name}) {
+ return ref DEFAULTS->{$name} ? @{DEFAULTS->{$name}} : DEFAULTS->{$name};
+ }
+ return undef;
}
sub sort_duplicates {
- my ($a, $b, $sort_by) = @_;
- if ($sort_by eq 'count' or $sort_by eq 'delta') {
- return $a->{$sort_by} <=> $b->{$sort_by};
- }
- if ($sort_by =~ /^(bug_)?id$/) {
- return $a->{'bug'}->$sort_by <=> $b->{'bug'}->$sort_by;
- }
- return $a->{'bug'}->$sort_by cmp $b->{'bug'}->$sort_by;
-
+ my ($a, $b, $sort_by) = @_;
+ if ($sort_by eq 'count' or $sort_by eq 'delta') {
+ return $a->{$sort_by} <=> $b->{$sort_by};
+ }
+ if ($sort_by =~ /^(bug_)?id$/) {
+ return $a->{'bug'}->$sort_by <=> $b->{'bug'}->$sort_by;
+ }
+ return $a->{'bug'}->$sort_by cmp $b->{'bug'}->$sort_by;
+
}
###############
# Main Script #
###############
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $user = Bugzilla->login();
+my $user = Bugzilla->login();
my $dbh = Bugzilla->switch_to_shadow_db();
my $changedsince = formvalue("changedsince");
-my $maxrows = formvalue("maxrows");
-my $openonly = formvalue("openonly");
-my $sortby = formvalue("sortby");
+my $maxrows = formvalue("maxrows");
+my $openonly = formvalue("openonly");
+my $sortby = formvalue("sortby");
if (!grep(lc($_) eq lc($sortby), qw(count delta id))) {
- Bugzilla::Field->check($sortby);
+ Bugzilla::Field->check($sortby);
}
my $reverse = formvalue("reverse");
+
# Reverse count and delta by default.
if (!defined $reverse) {
- if ($sortby eq 'count' or $sortby eq 'delta') {
- $reverse = 1;
- }
- else {
- $reverse = 0;
- }
+ if ($sortby eq 'count' or $sortby eq 'delta') {
+ $reverse = 1;
+ }
+ else {
+ $reverse = 0;
+ }
}
my @query_products = $cgi->param('product');
-my $sortvisible = formvalue("sortvisible");
+my $sortvisible = formvalue("sortvisible");
my @bugs;
if ($sortvisible) {
- my @limit_to_ids = (split(/[:,]/, formvalue("bug_id") || ''));
- @bugs = @{ Bugzilla::Bug->new_from_list(\@limit_to_ids) };
- @bugs = @{ $user->visible_bugs(\@bugs) };
+ my @limit_to_ids = (split(/[:,]/, formvalue("bug_id") || ''));
+ @bugs = @{Bugzilla::Bug->new_from_list(\@limit_to_ids)};
+ @bugs = @{$user->visible_bugs(\@bugs)};
}
# Make sure all products are valid.
@@ -145,101 +147,117 @@ $sortby = "count" if $sortby eq "dup_count";
my $origmaxrows = $maxrows;
detaint_natural($maxrows)
- || ThrowUserError("invalid_maxrows", { maxrows => $origmaxrows});
+ || ThrowUserError("invalid_maxrows", {maxrows => $origmaxrows});
my $origchangedsince = $changedsince;
detaint_natural($changedsince)
- || ThrowUserError("invalid_changedsince",
- { changedsince => $origchangedsince });
+ || ThrowUserError("invalid_changedsince",
+ {changedsince => $origchangedsince});
-my %total_dups = @{$dbh->selectcol_arrayref(
+my %total_dups = @{
+ $dbh->selectcol_arrayref(
"SELECT dupe_of, COUNT(dupe)
FROM duplicates
- GROUP BY dupe_of", {Columns => [1,2]})};
+ GROUP BY dupe_of", {Columns => [1, 2]}
+ )
+};
-my %dupe_relation = @{$dbh->selectcol_arrayref(
+my %dupe_relation = @{
+ $dbh->selectcol_arrayref(
"SELECT dupe, dupe_of FROM duplicates
- WHERE dupe IN (SELECT dupe_of FROM duplicates)",
- {Columns => [1,2]})};
+ WHERE dupe IN (SELECT dupe_of FROM duplicates)", {Columns => [1, 2]}
+ )
+};
add_indirect_dups(\%total_dups, \%dupe_relation);
my $reso_field_id = get_field_id('resolution');
-my %since_dups = @{$dbh->selectcol_arrayref(
+my %since_dups = @{
+ $dbh->selectcol_arrayref(
"SELECT dupe_of, COUNT(dupe)
FROM duplicates INNER JOIN bugs_activity
ON bugs_activity.bug_id = duplicates.dupe
WHERE added = 'DUPLICATE' AND fieldid = ?
AND bug_when >= "
- . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '?', 'DAY') .
- " GROUP BY dupe_of", {Columns=>[1,2]},
- $reso_field_id, $changedsince)};
+ . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '?', 'DAY')
+ . " GROUP BY dupe_of", {Columns => [1, 2]}, $reso_field_id, $changedsince
+ )
+};
add_indirect_dups(\%since_dups, \%dupe_relation);
# Enforce the MOST_FREQUENT_THRESHOLD constant and the "bug_id" cgi param.
foreach my $id (keys %total_dups) {
- if ($total_dups{$id} < MOST_FREQUENT_THRESHOLD) {
- delete $total_dups{$id};
- next;
- }
- if ($sortvisible and !grep($_->id == $id, @bugs)) {
- delete $total_dups{$id};
- }
+ if ($total_dups{$id} < MOST_FREQUENT_THRESHOLD) {
+ delete $total_dups{$id};
+ next;
+ }
+ if ($sortvisible and !grep($_->id == $id, @bugs)) {
+ delete $total_dups{$id};
+ }
}
if (!@bugs) {
- @bugs = @{ Bugzilla::Bug->new_from_list([keys %total_dups]) };
- @bugs = @{ $user->visible_bugs(\@bugs) };
+ @bugs = @{Bugzilla::Bug->new_from_list([keys %total_dups])};
+ @bugs = @{$user->visible_bugs(\@bugs)};
}
-my @fully_exclude_status = formvalue('fully_exclude_status');
+my @fully_exclude_status = formvalue('fully_exclude_status');
my @partly_exclude_status = formvalue('partly_exclude_status');
-my @except_resolution = formvalue('except_resolution');
+my @except_resolution = formvalue('except_resolution');
# Filter bugs by criteria
my @result_bugs;
foreach my $bug (@bugs) {
- # It's possible, if somebody specified a bug ID that wasn't a dup
- # in the "buglist" parameter and specified $sortvisible that there
- # would be bugs in the list with 0 dups, so we want to avoid that.
- next if !$total_dups{$bug->id};
-
- next if ($openonly and !$bug->isopened);
- # If the bug has a status in @fully_exclude_status, we skip it,
- # no question.
- next if grep($_ eq $bug->bug_status, @fully_exclude_status);
- # If the bug has a status in @partly_exclude_status, we skip it...
- if (grep($_ eq $bug->bug_status, @partly_exclude_status)) {
- # ...unless it has a resolution in @except_resolution.
- next if !grep($_ eq $bug->resolution, @except_resolution);
- }
- if (scalar @query_products) {
- next if !grep($_->id == $bug->product_id, @query_products);
- }
+ # It's possible, if somebody specified a bug ID that wasn't a dup
+ # in the "buglist" parameter and specified $sortvisible that there
+ # would be bugs in the list with 0 dups, so we want to avoid that.
+ next if !$total_dups{$bug->id};
- # Note: maximum row count is dealt with later.
- push (@result_bugs, { bug => $bug,
- count => $total_dups{$bug->id},
- delta => $since_dups{$bug->id} || 0 });
+ next if ($openonly and !$bug->isopened);
+
+ # If the bug has a status in @fully_exclude_status, we skip it,
+ # no question.
+ next if grep($_ eq $bug->bug_status, @fully_exclude_status);
+
+ # If the bug has a status in @partly_exclude_status, we skip it...
+ if (grep($_ eq $bug->bug_status, @partly_exclude_status)) {
+
+ # ...unless it has a resolution in @except_resolution.
+ next if !grep($_ eq $bug->resolution, @except_resolution);
+ }
+
+ if (scalar @query_products) {
+ next if !grep($_->id == $bug->product_id, @query_products);
+ }
+
+ # Note: maximum row count is dealt with later.
+ push(
+ @result_bugs,
+ {
+ bug => $bug,
+ count => $total_dups{$bug->id},
+ delta => $since_dups{$bug->id} || 0
+ }
+ );
}
@bugs = @result_bugs;
@bugs = sort { sort_duplicates($a, $b, $sortby) } @bugs;
if ($reverse) {
- @bugs = reverse @bugs;
+ @bugs = reverse @bugs;
}
-@bugs = @bugs[0..$maxrows-1] if scalar(@bugs) > $maxrows;
+@bugs = @bugs[0 .. $maxrows - 1] if scalar(@bugs) > $maxrows;
my %vars = (
- bugs => \@bugs,
- bug_ids => [map { $_->{'bug'}->id } @bugs],
- sortby => $sortby,
- openonly => $openonly,
- maxrows => $maxrows,
- reverse => $reverse,
- format => scalar $cgi->param('format'),
- product => [map { $_->name } @query_products],
- sortvisible => $sortvisible,
- changedsince => $changedsince,
+ bugs => \@bugs,
+ bug_ids => [map { $_->{'bug'}->id } @bugs],
+ sortby => $sortby,
+ openonly => $openonly,
+ maxrows => $maxrows,
+ reverse => $reverse,
+ format => scalar $cgi->param('format'),
+ product => [map { $_->name } @query_products],
+ sortvisible => $sortvisible,
+ changedsince => $changedsince,
);
my $format = $template->get_format("reports/duplicates", $vars{'format'});
diff --git a/editclassifications.cgi b/editclassifications.cgi
index 640b8b8cd..b2797af3d 100755
--- a/editclassifications.cgi
+++ b/editclassifications.cgi
@@ -20,26 +20,27 @@ use Bugzilla::Error;
use Bugzilla::Classification;
use Bugzilla::Token;
-my $dbh = Bugzilla->dbh;
-my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
local our $vars = {};
sub LoadTemplate {
- my $action = shift;
- my $template = Bugzilla->template;
-
- $vars->{'classifications'} = [Bugzilla::Classification->get_all]
- if ($action eq 'select');
- # There is currently only one section about classifications,
- # so all pages point to it. Let's define it here.
- $vars->{'doc_section'} = 'administering/categorization.html#classifications';
-
- $action =~ /(\w+)/;
- $action = $1;
- $template->process("admin/classifications/$action.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my $action = shift;
+ my $template = Bugzilla->template;
+
+ $vars->{'classifications'} = [Bugzilla::Classification->get_all]
+ if ($action eq 'select');
+
+ # There is currently only one section about classifications,
+ # so all pages point to it. Let's define it here.
+ $vars->{'doc_section'} = 'administering/categorization.html#classifications';
+
+ $action =~ /(\w+)/;
+ $action = $1;
+ $template->process("admin/classifications/$action.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -50,13 +51,12 @@ my $user = Bugzilla->login(LOGIN_REQUIRED);
print $cgi->header();
-$user->in_group('editclassifications')
- || ThrowUserError("auth_failure", {group => "editclassifications",
- action => "edit",
- object => "classifications"});
+$user->in_group('editclassifications') || ThrowUserError("auth_failure",
+ {group => "editclassifications", action => "edit", object => "classifications"}
+);
-ThrowUserError("auth_classification_not_enabled")
- unless Bugzilla->params->{"useclassification"};
+ThrowUserError("auth_classification_not_enabled")
+ unless Bugzilla->params->{"useclassification"};
#
# often used variables
@@ -77,8 +77,8 @@ LoadTemplate('select') unless $action;
#
if ($action eq 'add') {
- $vars->{'token'} = issue_session_token('add_classification');
- LoadTemplate($action);
+ $vars->{'token'} = issue_session_token('add_classification');
+ LoadTemplate($action);
}
#
@@ -86,20 +86,21 @@ if ($action eq 'add') {
#
if ($action eq 'new') {
- check_token_data($token, 'add_classification');
+ check_token_data($token, 'add_classification');
- my $classification =
- Bugzilla::Classification->create({name => $class_name,
- description => scalar $cgi->param('description'),
- sortkey => scalar $cgi->param('sortkey')});
+ my $classification = Bugzilla::Classification->create({
+ name => $class_name,
+ description => scalar $cgi->param('description'),
+ sortkey => scalar $cgi->param('sortkey')
+ });
- delete_token($token);
+ delete_token($token);
- $vars->{'message'} = 'classification_created';
- $vars->{'classification'} = $classification;
- $vars->{'classifications'} = [Bugzilla::Classification->get_all];
- $vars->{'token'} = issue_session_token('reclassify_classifications');
- LoadTemplate('reclassify');
+ $vars->{'message'} = 'classification_created';
+ $vars->{'classification'} = $classification;
+ $vars->{'classifications'} = [Bugzilla::Classification->get_all];
+ $vars->{'token'} = issue_session_token('reclassify_classifications');
+ LoadTemplate('reclassify');
}
#
@@ -110,20 +111,20 @@ if ($action eq 'new') {
if ($action eq 'del') {
- my $classification = Bugzilla::Classification->check($class_name);
+ my $classification = Bugzilla::Classification->check($class_name);
- if ($classification->id == 1) {
- ThrowUserError("classification_not_deletable");
- }
+ if ($classification->id == 1) {
+ ThrowUserError("classification_not_deletable");
+ }
- if ($classification->product_count()) {
- ThrowUserError("classification_has_products");
- }
+ if ($classification->product_count()) {
+ ThrowUserError("classification_has_products");
+ }
- $vars->{'classification'} = $classification;
- $vars->{'token'} = issue_session_token('delete_classification');
+ $vars->{'classification'} = $classification;
+ $vars->{'token'} = issue_session_token('delete_classification');
- LoadTemplate($action);
+ LoadTemplate($action);
}
#
@@ -131,15 +132,15 @@ if ($action eq 'del') {
#
if ($action eq 'delete') {
- check_token_data($token, 'delete_classification');
+ check_token_data($token, 'delete_classification');
- my $classification = Bugzilla::Classification->check($class_name);
- $classification->remove_from_db;
- delete_token($token);
+ my $classification = Bugzilla::Classification->check($class_name);
+ $classification->remove_from_db;
+ delete_token($token);
- $vars->{'message'} = 'classification_deleted';
- $vars->{'classification'} = $classification;
- LoadTemplate('select');
+ $vars->{'message'} = 'classification_deleted';
+ $vars->{'classification'} = $classification;
+ LoadTemplate('select');
}
#
@@ -149,12 +150,12 @@ if ($action eq 'delete') {
#
if ($action eq 'edit') {
- my $classification = Bugzilla::Classification->check($class_name);
+ my $classification = Bugzilla::Classification->check($class_name);
- $vars->{'classification'} = $classification;
- $vars->{'token'} = issue_session_token('edit_classification');
+ $vars->{'classification'} = $classification;
+ $vars->{'token'} = issue_session_token('edit_classification');
- LoadTemplate($action);
+ LoadTemplate($action);
}
#
@@ -162,24 +163,24 @@ if ($action eq 'edit') {
#
if ($action eq 'update') {
- check_token_data($token, 'edit_classification');
+ check_token_data($token, 'edit_classification');
- my $class_old_name = trim($cgi->param('classificationold') || '');
- my $classification = Bugzilla::Classification->check($class_old_name);
+ my $class_old_name = trim($cgi->param('classificationold') || '');
+ my $classification = Bugzilla::Classification->check($class_old_name);
- $classification->set_all({
- name => $class_name,
- description => scalar $cgi->param('description'),
- sortkey => scalar $cgi->param('sortkey'),
- });
+ $classification->set_all({
+ name => $class_name,
+ description => scalar $cgi->param('description'),
+ sortkey => scalar $cgi->param('sortkey'),
+ });
- my $changes = $classification->update;
- delete_token($token);
+ my $changes = $classification->update;
+ delete_token($token);
- $vars->{'message'} = 'classification_updated';
- $vars->{'classification'} = $classification;
- $vars->{'changes'} = $changes;
- LoadTemplate('select');
+ $vars->{'message'} = 'classification_updated';
+ $vars->{'classification'} = $classification;
+ $vars->{'changes'} = $changes;
+ LoadTemplate('select');
}
#
@@ -187,44 +188,47 @@ if ($action eq 'update') {
#
if ($action eq 'reclassify') {
- my $classification = Bugzilla::Classification->check($class_name);
-
- my $sth = $dbh->prepare("UPDATE products SET classification_id = ?
- WHERE name = ?");
- my @names;
-
- if (defined $cgi->param('add_products')) {
- check_token_data($token, 'reclassify_classifications');
- if (defined $cgi->param('prodlist')) {
- foreach my $prod ($cgi->param("prodlist")) {
- trick_taint($prod);
- $sth->execute($classification->id, $prod);
- push @names, $prod;
- }
- }
- delete_token($token);
- } elsif (defined $cgi->param('remove_products')) {
- check_token_data($token, 'reclassify_classifications');
- if (defined $cgi->param('myprodlist')) {
- foreach my $prod ($cgi->param("myprodlist")) {
- trick_taint($prod);
- $sth->execute(1, $prod);
- push @names, $prod;
- }
- }
- delete_token($token);
+ my $classification = Bugzilla::Classification->check($class_name);
+
+ my $sth = $dbh->prepare(
+ "UPDATE products SET classification_id = ?
+ WHERE name = ?"
+ );
+ my @names;
+
+ if (defined $cgi->param('add_products')) {
+ check_token_data($token, 'reclassify_classifications');
+ if (defined $cgi->param('prodlist')) {
+ foreach my $prod ($cgi->param("prodlist")) {
+ trick_taint($prod);
+ $sth->execute($classification->id, $prod);
+ push @names, $prod;
+ }
+ }
+ delete_token($token);
+ }
+ elsif (defined $cgi->param('remove_products')) {
+ check_token_data($token, 'reclassify_classifications');
+ if (defined $cgi->param('myprodlist')) {
+ foreach my $prod ($cgi->param("myprodlist")) {
+ trick_taint($prod);
+ $sth->execute(1, $prod);
+ push @names, $prod;
+ }
}
+ delete_token($token);
+ }
- $vars->{'classifications'} = [Bugzilla::Classification->get_all];
- $vars->{'classification'} = $classification;
- $vars->{'token'} = issue_session_token('reclassify_classifications');
+ $vars->{'classifications'} = [Bugzilla::Classification->get_all];
+ $vars->{'classification'} = $classification;
+ $vars->{'token'} = issue_session_token('reclassify_classifications');
- foreach my $name (@names) {
- Bugzilla->memcached->clear({ table => 'products', name => $name });
- }
- Bugzilla->memcached->clear_config();
+ foreach my $name (@names) {
+ Bugzilla->memcached->clear({table => 'products', name => $name});
+ }
+ Bugzilla->memcached->clear_config();
- LoadTemplate($action);
+ LoadTemplate($action);
}
#
diff --git a/editcomponents.cgi b/editcomponents.cgi
index aebc0b647..c0e3b30c0 100755
--- a/editcomponents.cgi
+++ b/editcomponents.cgi
@@ -20,9 +20,10 @@ use Bugzilla::User;
use Bugzilla::Component;
use Bugzilla::Token;
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
+
# There is only one section about components in the documentation,
# so all actions point to the same page.
$vars->{'doc_section'} = 'administering/categorization.html#components';
@@ -37,16 +38,15 @@ print $cgi->header();
$user->in_group('editcomponents')
|| scalar(@{$user->get_products_by_permission('editcomponents')})
- || ThrowUserError("auth_failure", {group => "editcomponents",
- action => "edit",
- object => "components"});
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "edit", object => "components"});
#
# often used variables
#
-my $product_name = trim($cgi->param('product') || '');
-my $comp_name = trim($cgi->param('component') || '');
-my $action = trim($cgi->param('action') || '');
+my $product_name = trim($cgi->param('product') || '');
+my $comp_name = trim($cgi->param('component') || '');
+my $action = trim($cgi->param('action') || '');
my $showbugcounts = (defined $cgi->param('showbugcounts'));
my $token = $cgi->param('token');
@@ -55,18 +55,19 @@ my $token = $cgi->param('token');
#
unless ($product_name) {
- my $selectable_products = $user->get_selectable_products;
- # If the user has editcomponents privs for some products only,
- # we have to restrict the list of products to display.
- unless ($user->in_group('editcomponents')) {
- $selectable_products = $user->get_products_by_permission('editcomponents');
- }
- $vars->{'products'} = $selectable_products;
- $vars->{'showbugcounts'} = $showbugcounts;
-
- $template->process("admin/components/select-product.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my $selectable_products = $user->get_selectable_products;
+
+ # If the user has editcomponents privs for some products only,
+ # we have to restrict the list of products to display.
+ unless ($user->in_group('editcomponents')) {
+ $selectable_products = $user->get_products_by_permission('editcomponents');
+ }
+ $vars->{'products'} = $selectable_products;
+ $vars->{'showbugcounts'} = $showbugcounts;
+
+ $template->process("admin/components/select-product.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
my $product = $user->check_can_admin_product($product_name);
@@ -76,11 +77,11 @@ my $product = $user->check_can_admin_product($product_name);
#
unless ($action) {
- $vars->{'showbugcounts'} = $showbugcounts;
- $vars->{'product'} = $product;
- $template->process("admin/components/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $vars->{'showbugcounts'} = $showbugcounts;
+ $vars->{'product'} = $product;
+ $template->process("admin/components/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -90,11 +91,11 @@ unless ($action) {
#
if ($action eq 'add') {
- $vars->{'token'} = issue_session_token('add_component');
- $vars->{'product'} = $product;
- $template->process("admin/components/create.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $vars->{'token'} = issue_session_token('add_component');
+ $vars->{'product'} = $product;
+ $template->process("admin/components/create.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -102,40 +103,42 @@ if ($action eq 'add') {
#
if ($action eq 'new') {
- check_token_data($token, 'add_component');
- # Do the user matching
- Bugzilla::User::match_field ({
- 'initialowner' => { 'type' => 'single' },
- 'initialqacontact' => { 'type' => 'single' },
- 'initialcc' => { 'type' => 'multi' },
- });
-
- my $default_assignee = trim($cgi->param('initialowner') || '');
- my $default_qa_contact = trim($cgi->param('initialqacontact') || '');
- my $description = trim($cgi->param('description') || '');
- my @initial_cc = $cgi->param('initialcc');
- my $isactive = $cgi->param('isactive');
-
- my $component = Bugzilla::Component->create({
- name => $comp_name,
- product => $product,
- description => $description,
- initialowner => $default_assignee,
- initialqacontact => $default_qa_contact,
- initial_cc => \@initial_cc,
- # XXX We should not be creating series for products that we
- # didn't create series for.
- create_series => 1,
- });
-
- $vars->{'message'} = 'component_created';
- $vars->{'comp'} = $component;
- $vars->{'product'} = $product;
- delete_token($token);
-
- $template->process("admin/components/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ check_token_data($token, 'add_component');
+
+ # Do the user matching
+ Bugzilla::User::match_field({
+ 'initialowner' => {'type' => 'single'},
+ 'initialqacontact' => {'type' => 'single'},
+ 'initialcc' => {'type' => 'multi'},
+ });
+
+ my $default_assignee = trim($cgi->param('initialowner') || '');
+ my $default_qa_contact = trim($cgi->param('initialqacontact') || '');
+ my $description = trim($cgi->param('description') || '');
+ my @initial_cc = $cgi->param('initialcc');
+ my $isactive = $cgi->param('isactive');
+
+ my $component = Bugzilla::Component->create({
+ name => $comp_name,
+ product => $product,
+ description => $description,
+ initialowner => $default_assignee,
+ initialqacontact => $default_qa_contact,
+ initial_cc => \@initial_cc,
+
+ # XXX We should not be creating series for products that we
+ # didn't create series for.
+ create_series => 1,
+ });
+
+ $vars->{'message'} = 'component_created';
+ $vars->{'comp'} = $component;
+ $vars->{'product'} = $product;
+ delete_token($token);
+
+ $template->process("admin/components/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -145,14 +148,14 @@ if ($action eq 'new') {
#
if ($action eq 'del') {
- $vars->{'token'} = issue_session_token('delete_component');
- $vars->{'comp'} =
- Bugzilla::Component->check({ product => $product, name => $comp_name });
- $vars->{'product'} = $product;
-
- $template->process("admin/components/confirm-delete.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $vars->{'token'} = issue_session_token('delete_component');
+ $vars->{'comp'}
+ = Bugzilla::Component->check({product => $product, name => $comp_name});
+ $vars->{'product'} = $product;
+
+ $template->process("admin/components/confirm-delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -160,21 +163,21 @@ if ($action eq 'del') {
#
if ($action eq 'delete') {
- check_token_data($token, 'delete_component');
- my $component =
- Bugzilla::Component->check({ product => $product, name => $comp_name });
+ check_token_data($token, 'delete_component');
+ my $component
+ = Bugzilla::Component->check({product => $product, name => $comp_name});
- $component->remove_from_db;
+ $component->remove_from_db;
- $vars->{'message'} = 'component_deleted';
- $vars->{'comp'} = $component;
- $vars->{'product'} = $product;
- $vars->{'no_edit_component_link'} = 1;
- delete_token($token);
+ $vars->{'message'} = 'component_deleted';
+ $vars->{'comp'} = $component;
+ $vars->{'product'} = $product;
+ $vars->{'no_edit_component_link'} = 1;
+ delete_token($token);
- $template->process("admin/components/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/components/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -184,19 +187,19 @@ if ($action eq 'delete') {
#
if ($action eq 'edit') {
- $vars->{'token'} = issue_session_token('edit_component');
- my $component =
- Bugzilla::Component->check({ product => $product, name => $comp_name });
- $vars->{'comp'} = $component;
+ $vars->{'token'} = issue_session_token('edit_component');
+ my $component
+ = Bugzilla::Component->check({product => $product, name => $comp_name});
+ $vars->{'comp'} = $component;
- $vars->{'initial_cc_names'} =
- join(', ', map($_->login, @{$component->initial_cc}));
+ $vars->{'initial_cc_names'}
+ = join(', ', map($_->login, @{$component->initial_cc}));
- $vars->{'product'} = $product;
+ $vars->{'product'} = $product;
- $template->process("admin/components/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/components/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -204,41 +207,42 @@ if ($action eq 'edit') {
#
if ($action eq 'update') {
- check_token_data($token, 'edit_component');
- # Do the user matching
- Bugzilla::User::match_field ({
- 'initialowner' => { 'type' => 'single' },
- 'initialqacontact' => { 'type' => 'single' },
- 'initialcc' => { 'type' => 'multi' },
- });
-
- my $comp_old_name = trim($cgi->param('componentold') || '');
- my $default_assignee = trim($cgi->param('initialowner') || '');
- my $default_qa_contact = trim($cgi->param('initialqacontact') || '');
- my $description = trim($cgi->param('description') || '');
- my @initial_cc = $cgi->param('initialcc');
- my $isactive = $cgi->param('isactive');
-
- my $component =
- Bugzilla::Component->check({ product => $product, name => $comp_old_name });
-
- $component->set_name($comp_name);
- $component->set_description($description);
- $component->set_default_assignee($default_assignee);
- $component->set_default_qa_contact($default_qa_contact);
- $component->set_cc_list(\@initial_cc);
- $component->set_is_active($isactive);
- my $changes = $component->update();
-
- $vars->{'message'} = 'component_updated';
- $vars->{'comp'} = $component;
- $vars->{'product'} = $product;
- $vars->{'changes'} = $changes;
- delete_token($token);
-
- $template->process("admin/components/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ check_token_data($token, 'edit_component');
+
+ # Do the user matching
+ Bugzilla::User::match_field({
+ 'initialowner' => {'type' => 'single'},
+ 'initialqacontact' => {'type' => 'single'},
+ 'initialcc' => {'type' => 'multi'},
+ });
+
+ my $comp_old_name = trim($cgi->param('componentold') || '');
+ my $default_assignee = trim($cgi->param('initialowner') || '');
+ my $default_qa_contact = trim($cgi->param('initialqacontact') || '');
+ my $description = trim($cgi->param('description') || '');
+ my @initial_cc = $cgi->param('initialcc');
+ my $isactive = $cgi->param('isactive');
+
+ my $component
+ = Bugzilla::Component->check({product => $product, name => $comp_old_name});
+
+ $component->set_name($comp_name);
+ $component->set_description($description);
+ $component->set_default_assignee($default_assignee);
+ $component->set_default_qa_contact($default_qa_contact);
+ $component->set_cc_list(\@initial_cc);
+ $component->set_is_active($isactive);
+ my $changes = $component->update();
+
+ $vars->{'message'} = 'component_updated';
+ $vars->{'comp'} = $component;
+ $vars->{'product'} = $product;
+ $vars->{'changes'} = $changes;
+ delete_token($token);
+
+ $template->process("admin/components/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
# No valid action found
diff --git a/editfields.cgi b/editfields.cgi
index e8351bdd6..633887db2 100755
--- a/editfields.cgi
+++ b/editfields.cgi
@@ -19,16 +19,15 @@ use Bugzilla::Util;
use Bugzilla::Field;
use Bugzilla::Token;
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
# Make sure the user is logged in and is an administrator.
my $user = Bugzilla->login(LOGIN_REQUIRED);
$user->in_group('admin')
- || ThrowUserError('auth_failure', {group => 'admin',
- action => 'edit',
- object => 'custom_fields'});
+ || ThrowUserError('auth_failure',
+ {group => 'admin', action => 'edit', object => 'custom_fields'});
my $action = trim($cgi->param('action') || '');
my $token = $cgi->param('token');
@@ -37,135 +36,140 @@ print $cgi->header();
# List all existing custom fields if no action is given.
if (!$action) {
- $template->process('admin/custom_fields/list.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ $template->process('admin/custom_fields/list.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
+
# Interface to add a new custom field.
elsif ($action eq 'add') {
- $vars->{'token'} = issue_session_token('add_field');
+ $vars->{'token'} = issue_session_token('add_field');
- $template->process('admin/custom_fields/create.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ $template->process('admin/custom_fields/create.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
elsif ($action eq 'new') {
- check_token_data($token, 'add_field');
- $vars->{'field'} = Bugzilla::Field->create({
- name => scalar $cgi->param('name'),
- description => scalar $cgi->param('desc'),
- long_desc => scalar $cgi->param('long_desc'),
- type => scalar $cgi->param('type'),
- sortkey => scalar $cgi->param('sortkey'),
- mailhead => scalar $cgi->param('new_bugmail'),
- enter_bug => scalar $cgi->param('enter_bug'),
- obsolete => scalar $cgi->param('obsolete'),
- custom => 1,
- buglist => 1,
- visibility_field_id => scalar $cgi->param('visibility_field_id'),
- visibility_values => [ $cgi->param('visibility_values') ],
- value_field_id => scalar $cgi->param('value_field_id'),
- reverse_desc => scalar $cgi->param('reverse_desc'),
- is_mandatory => scalar $cgi->param('is_mandatory'),
- });
-
- delete_token($token);
-
- $vars->{'message'} = 'custom_field_created';
-
- $template->process('admin/custom_fields/list.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ check_token_data($token, 'add_field');
+ $vars->{'field'} = Bugzilla::Field->create({
+ name => scalar $cgi->param('name'),
+ description => scalar $cgi->param('desc'),
+ long_desc => scalar $cgi->param('long_desc'),
+ type => scalar $cgi->param('type'),
+ sortkey => scalar $cgi->param('sortkey'),
+ mailhead => scalar $cgi->param('new_bugmail'),
+ enter_bug => scalar $cgi->param('enter_bug'),
+ obsolete => scalar $cgi->param('obsolete'),
+ custom => 1,
+ buglist => 1,
+ visibility_field_id => scalar $cgi->param('visibility_field_id'),
+ visibility_values => [$cgi->param('visibility_values')],
+ value_field_id => scalar $cgi->param('value_field_id'),
+ reverse_desc => scalar $cgi->param('reverse_desc'),
+ is_mandatory => scalar $cgi->param('is_mandatory'),
+ });
+
+ delete_token($token);
+
+ $vars->{'message'} = 'custom_field_created';
+
+ $template->process('admin/custom_fields/list.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
elsif ($action eq 'edit') {
- my $name = $cgi->param('name') || ThrowUserError('field_missing_name');
- # Custom field names must start with "cf_".
- if ($name !~ /^cf_/) {
- $name = 'cf_' . $name;
- }
- my $field = new Bugzilla::Field({'name' => $name});
- $field || ThrowUserError('customfield_nonexistent', {'name' => $name});
-
- $vars->{'field'} = $field;
- $vars->{'token'} = issue_session_token('edit_field');
-
- $template->process('admin/custom_fields/edit.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ my $name = $cgi->param('name') || ThrowUserError('field_missing_name');
+
+ # Custom field names must start with "cf_".
+ if ($name !~ /^cf_/) {
+ $name = 'cf_' . $name;
+ }
+ my $field = new Bugzilla::Field({'name' => $name});
+ $field || ThrowUserError('customfield_nonexistent', {'name' => $name});
+
+ $vars->{'field'} = $field;
+ $vars->{'token'} = issue_session_token('edit_field');
+
+ $template->process('admin/custom_fields/edit.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
elsif ($action eq 'update') {
- check_token_data($token, 'edit_field');
- my $name = $cgi->param('name');
-
- # Validate fields.
- $name || ThrowUserError('field_missing_name');
- # Custom field names must start with "cf_".
- if ($name !~ /^cf_/) {
- $name = 'cf_' . $name;
- }
- my $field = new Bugzilla::Field({'name' => $name});
- $field || ThrowUserError('customfield_nonexistent', {'name' => $name});
-
- $field->set_description($cgi->param('desc'));
- $field->set_long_desc($cgi->param('long_desc'));
- $field->set_sortkey($cgi->param('sortkey'));
- $field->set_in_new_bugmail($cgi->param('new_bugmail'));
- $field->set_enter_bug($cgi->param('enter_bug'));
- $field->set_obsolete($cgi->param('obsolete'));
- $field->set_is_mandatory($cgi->param('is_mandatory'));
- $field->set_visibility_field($cgi->param('visibility_field_id'));
- $field->set_visibility_values([ $cgi->param('visibility_values') ]);
- $field->set_value_field($cgi->param('value_field_id'));
- $field->set_reverse_desc($cgi->param('reverse_desc'));
- $field->update();
-
- delete_token($token);
-
- $vars->{'field'} = $field;
- $vars->{'message'} = 'custom_field_updated';
-
- $template->process('admin/custom_fields/list.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ check_token_data($token, 'edit_field');
+ my $name = $cgi->param('name');
+
+ # Validate fields.
+ $name || ThrowUserError('field_missing_name');
+
+ # Custom field names must start with "cf_".
+ if ($name !~ /^cf_/) {
+ $name = 'cf_' . $name;
+ }
+ my $field = new Bugzilla::Field({'name' => $name});
+ $field || ThrowUserError('customfield_nonexistent', {'name' => $name});
+
+ $field->set_description($cgi->param('desc'));
+ $field->set_long_desc($cgi->param('long_desc'));
+ $field->set_sortkey($cgi->param('sortkey'));
+ $field->set_in_new_bugmail($cgi->param('new_bugmail'));
+ $field->set_enter_bug($cgi->param('enter_bug'));
+ $field->set_obsolete($cgi->param('obsolete'));
+ $field->set_is_mandatory($cgi->param('is_mandatory'));
+ $field->set_visibility_field($cgi->param('visibility_field_id'));
+ $field->set_visibility_values([$cgi->param('visibility_values')]);
+ $field->set_value_field($cgi->param('value_field_id'));
+ $field->set_reverse_desc($cgi->param('reverse_desc'));
+ $field->update();
+
+ delete_token($token);
+
+ $vars->{'field'} = $field;
+ $vars->{'message'} = 'custom_field_updated';
+
+ $template->process('admin/custom_fields/list.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
elsif ($action eq 'del') {
- my $name = $cgi->param('name');
-
- # Validate field.
- $name || ThrowUserError('field_missing_name');
- # Custom field names must start with "cf_".
- if ($name !~ /^cf_/) {
- $name = 'cf_' . $name;
- }
- my $field = new Bugzilla::Field({'name' => $name});
- $field || ThrowUserError('customfield_nonexistent', {'name' => $name});
-
- $vars->{'field'} = $field;
- $vars->{'token'} = issue_session_token('delete_field');
-
- $template->process('admin/custom_fields/confirm-delete.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ my $name = $cgi->param('name');
+
+ # Validate field.
+ $name || ThrowUserError('field_missing_name');
+
+ # Custom field names must start with "cf_".
+ if ($name !~ /^cf_/) {
+ $name = 'cf_' . $name;
+ }
+ my $field = new Bugzilla::Field({'name' => $name});
+ $field || ThrowUserError('customfield_nonexistent', {'name' => $name});
+
+ $vars->{'field'} = $field;
+ $vars->{'token'} = issue_session_token('delete_field');
+
+ $template->process('admin/custom_fields/confirm-delete.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
elsif ($action eq 'delete') {
- check_token_data($token, 'delete_field');
- my $name = $cgi->param('name');
-
- # Validate fields.
- $name || ThrowUserError('field_missing_name');
- # Custom field names must start with "cf_".
- if ($name !~ /^cf_/) {
- $name = 'cf_' . $name;
- }
- my $field = new Bugzilla::Field({'name' => $name});
- $field || ThrowUserError('customfield_nonexistent', {'name' => $name});
-
- # Calling remove_from_db will check if field can be deleted.
- # If the field cannot be deleted, it will throw an error.
- $field->remove_from_db();
-
- $vars->{'field'} = $field;
- $vars->{'message'} = 'custom_field_deleted';
-
- delete_token($token);
-
- $template->process('admin/custom_fields/list.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ check_token_data($token, 'delete_field');
+ my $name = $cgi->param('name');
+
+ # Validate fields.
+ $name || ThrowUserError('field_missing_name');
+
+ # Custom field names must start with "cf_".
+ if ($name !~ /^cf_/) {
+ $name = 'cf_' . $name;
+ }
+ my $field = new Bugzilla::Field({'name' => $name});
+ $field || ThrowUserError('customfield_nonexistent', {'name' => $name});
+
+ # Calling remove_from_db will check if field can be deleted.
+ # If the field cannot be deleted, it will throw an error.
+ $field->remove_from_db();
+
+ $vars->{'field'} = $field;
+ $vars->{'message'} = 'custom_field_deleted';
+
+ delete_token($token);
+
+ $template->process('admin/custom_fields/list.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
else {
- ThrowUserError('unknown_action', {action => $action});
+ ThrowUserError('unknown_action', {action => $action});
}
diff --git a/editflagtypes.cgi b/editflagtypes.cgi
index 71f7cb655..edb031fcd 100755
--- a/editflagtypes.cgi
+++ b/editflagtypes.cgi
@@ -23,405 +23,430 @@ use Bugzilla::Product;
use Bugzilla::Token;
# Make sure the user is logged in and has the right privileges.
-my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $cgi = Bugzilla->cgi;
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
print $cgi->header();
$user->in_group('editcomponents')
|| scalar(@{$user->get_products_by_permission('editcomponents')})
- || ThrowUserError("auth_failure", {group => "editcomponents",
- action => "edit",
- object => "flagtypes"});
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "edit", object => "flagtypes"});
# We need this everywhere.
-my $vars = get_products_and_components();
+my $vars = get_products_and_components();
my @products = @{$vars->{products}};
-my $action = $cgi->param('action') || 'list';
-my $token = $cgi->param('token');
+my $action = $cgi->param('action') || 'list';
+my $token = $cgi->param('token');
my $prod_name = $cgi->param('product');
my $comp_name = $cgi->param('component');
-my $flag_id = $cgi->param('id');
+my $flag_id = $cgi->param('id');
my ($product, $component);
if ($prod_name) {
- # Make sure the user is allowed to view this product name.
- # Users with global editcomponents privs can see all product names.
- ($product) = grep { lc($_->name) eq lc($prod_name) } @products;
- $product || ThrowUserError('product_access_denied', { name => $prod_name });
+
+ # Make sure the user is allowed to view this product name.
+ # Users with global editcomponents privs can see all product names.
+ ($product) = grep { lc($_->name) eq lc($prod_name) } @products;
+ $product || ThrowUserError('product_access_denied', {name => $prod_name});
}
if ($comp_name) {
- $product || ThrowUserError('flag_type_component_without_product');
- ($component) = grep { lc($_->name) eq lc($comp_name) } @{$product->components};
- $component || ThrowUserError('product_unknown_component', { product => $product->name,
- comp => $comp_name });
+ $product || ThrowUserError('flag_type_component_without_product');
+ ($component) = grep { lc($_->name) eq lc($comp_name) } @{$product->components};
+ $component
+ || ThrowUserError('product_unknown_component',
+ {product => $product->name, comp => $comp_name});
}
# If 'categoryAction' is set, it has priority over 'action'.
-if (my ($category_action) = grep { $_ =~ /^categoryAction-(?:\w+)$/ } $cgi->param()) {
- $category_action =~ s/^categoryAction-//;
-
- my @inclusions = $cgi->param('inclusions');
- my @exclusions = $cgi->param('exclusions');
- my @categories;
- if ($category_action =~ /^(in|ex)clude$/) {
- if (!$user->in_group('editcomponents') && !$product) {
- # The user can only add the flag type to products they can administrate.
- foreach my $prod (@products) {
- push(@categories, $prod->id . ':0')
- }
- }
- else {
- my $category = ($product ? $product->id : 0) . ':' .
- ($component ? $component->id : 0);
- push(@categories, $category);
- }
+if (my ($category_action)
+ = grep { $_ =~ /^categoryAction-(?:\w+)$/ } $cgi->param())
+{
+ $category_action =~ s/^categoryAction-//;
+
+ my @inclusions = $cgi->param('inclusions');
+ my @exclusions = $cgi->param('exclusions');
+ my @categories;
+ if ($category_action =~ /^(in|ex)clude$/) {
+ if (!$user->in_group('editcomponents') && !$product) {
+
+ # The user can only add the flag type to products they can administrate.
+ foreach my $prod (@products) {
+ push(@categories, $prod->id . ':0');
+ }
+ }
+ else {
+ my $category
+ = ($product ? $product->id : 0) . ':' . ($component ? $component->id : 0);
+ push(@categories, $category);
}
+ }
- if ($category_action eq 'include') {
- foreach my $category (@categories) {
- push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
- }
+ if ($category_action eq 'include') {
+ foreach my $category (@categories) {
+ push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
}
- elsif ($category_action eq 'exclude') {
- foreach my $category (@categories) {
- push(@exclusions, $category) unless grep($_ eq $category, @exclusions);
- }
+ }
+ elsif ($category_action eq 'exclude') {
+ foreach my $category (@categories) {
+ push(@exclusions, $category) unless grep($_ eq $category, @exclusions);
}
- elsif ($category_action eq 'removeInclusion') {
- my @inclusion_to_remove = $cgi->param('inclusion_to_remove');
- foreach my $remove (@inclusion_to_remove) {
- @inclusions = grep { $_ ne $remove } @inclusions;
- }
+ }
+ elsif ($category_action eq 'removeInclusion') {
+ my @inclusion_to_remove = $cgi->param('inclusion_to_remove');
+ foreach my $remove (@inclusion_to_remove) {
+ @inclusions = grep { $_ ne $remove } @inclusions;
}
- elsif ($category_action eq 'removeExclusion') {
- my @exclusion_to_remove = $cgi->param('exclusion_to_remove');
- foreach my $remove (@exclusion_to_remove) {
- @exclusions = grep { $_ ne $remove } @exclusions;
- }
+ }
+ elsif ($category_action eq 'removeExclusion') {
+ my @exclusion_to_remove = $cgi->param('exclusion_to_remove');
+ foreach my $remove (@exclusion_to_remove) {
+ @exclusions = grep { $_ ne $remove } @exclusions;
}
-
- $vars->{'groups'} = get_settable_groups();
- $vars->{'action'} = $action;
-
- my $type = {};
- $type->{$_} = $cgi->param($_) foreach $cgi->param();
- # Make sure boolean fields are defined, else they fall back to 1.
- foreach my $boolean (qw(is_active is_requestable is_requesteeble is_multiplicable)) {
- $type->{$boolean} ||= 0;
- }
-
- # That's what I call a big hack. The template expects to see a group object.
- $type->{'grant_group'} = {};
- $type->{'grant_group'}->{'name'} = $cgi->param('grant_group');
- $type->{'request_group'} = {};
- $type->{'request_group'}->{'name'} = $cgi->param('request_group');
-
- $vars->{'inclusions'} = clusion_array_to_hash(\@inclusions, \@products);
- $vars->{'exclusions'} = clusion_array_to_hash(\@exclusions, \@products);
-
- $vars->{'type'} = $type;
- $vars->{'token'} = $token;
- $vars->{'check_clusions'} = 1;
- $vars->{'can_fully_edit'} = $cgi->param('can_fully_edit');
-
- $template->process("admin/flag-type/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ }
+
+ $vars->{'groups'} = get_settable_groups();
+ $vars->{'action'} = $action;
+
+ my $type = {};
+ $type->{$_} = $cgi->param($_) foreach $cgi->param();
+
+ # Make sure boolean fields are defined, else they fall back to 1.
+ foreach
+ my $boolean (qw(is_active is_requestable is_requesteeble is_multiplicable))
+ {
+ $type->{$boolean} ||= 0;
+ }
+
+ # That's what I call a big hack. The template expects to see a group object.
+ $type->{'grant_group'} = {};
+ $type->{'grant_group'}->{'name'} = $cgi->param('grant_group');
+ $type->{'request_group'} = {};
+ $type->{'request_group'}->{'name'} = $cgi->param('request_group');
+
+ $vars->{'inclusions'} = clusion_array_to_hash(\@inclusions, \@products);
+ $vars->{'exclusions'} = clusion_array_to_hash(\@exclusions, \@products);
+
+ $vars->{'type'} = $type;
+ $vars->{'token'} = $token;
+ $vars->{'check_clusions'} = 1;
+ $vars->{'can_fully_edit'} = $cgi->param('can_fully_edit');
+
+ $template->process("admin/flag-type/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'list') {
- my $product_id = $product ? $product->id : 0;
- my $component_id = $component ? $component->id : 0;
- my $show_flag_counts = $cgi->param('show_flag_counts') ? 1 : 0;
- my $group_id = $cgi->param('group');
- if ($group_id) {
- detaint_natural($group_id) || ThrowUserError('invalid_group_ID');
- }
+ my $product_id = $product ? $product->id : 0;
+ my $component_id = $component ? $component->id : 0;
+ my $show_flag_counts = $cgi->param('show_flag_counts') ? 1 : 0;
+ my $group_id = $cgi->param('group');
+ if ($group_id) {
+ detaint_natural($group_id) || ThrowUserError('invalid_group_ID');
+ }
+
+ my $bug_flagtypes;
+ my $attach_flagtypes;
+
+ # If a component is given, restrict the list to flag types available
+ # for this component.
+ if ($component) {
+ $bug_flagtypes = $component->flag_types->{'bug'};
+ $attach_flagtypes = $component->flag_types->{'attachment'};
- my $bug_flagtypes;
- my $attach_flagtypes;
+ # Filter flag types if a group ID is given.
+ $bug_flagtypes = filter_group($bug_flagtypes, $group_id);
+ $attach_flagtypes = filter_group($attach_flagtypes, $group_id);
- # If a component is given, restrict the list to flag types available
- # for this component.
- if ($component) {
- $bug_flagtypes = $component->flag_types->{'bug'};
- $attach_flagtypes = $component->flag_types->{'attachment'};
+ }
- # Filter flag types if a group ID is given.
- $bug_flagtypes = filter_group($bug_flagtypes, $group_id);
- $attach_flagtypes = filter_group($attach_flagtypes, $group_id);
+ # If only a product is specified but no component, then restrict the list
+ # to flag types available in at least one component of that product.
+ elsif ($product) {
+ $bug_flagtypes = $product->flag_types->{'bug'};
+ $attach_flagtypes = $product->flag_types->{'attachment'};
+ # Filter flag types if a group ID is given.
+ $bug_flagtypes = filter_group($bug_flagtypes, $group_id);
+ $attach_flagtypes = filter_group($attach_flagtypes, $group_id);
+ }
+
+ # If no product is given, then show all flag types available.
+ else {
+ my $flagtypes = get_editable_flagtypes(\@products, $group_id);
+ $bug_flagtypes = [grep { $_->target_type eq 'bug' } @$flagtypes];
+ $attach_flagtypes = [grep { $_->target_type eq 'attachment' } @$flagtypes];
+ }
+
+ if ($show_flag_counts) {
+ my %bug_lists;
+ my %map = ('+' => 'granted', '-' => 'denied', '?' => 'pending');
+
+ foreach my $flagtype (@$bug_flagtypes, @$attach_flagtypes) {
+ $bug_lists{$flagtype->id} = {};
+ my $flags = Bugzilla::Flag->match({type_id => $flagtype->id});
+
+ # Build lists of bugs, triaged by flag status.
+ push(@{$bug_lists{$flagtype->id}->{$map{$_->status}}}, $_->bug_id)
+ foreach @$flags;
}
- # If only a product is specified but no component, then restrict the list
- # to flag types available in at least one component of that product.
- elsif ($product) {
- $bug_flagtypes = $product->flag_types->{'bug'};
- $attach_flagtypes = $product->flag_types->{'attachment'};
-
- # Filter flag types if a group ID is given.
- $bug_flagtypes = filter_group($bug_flagtypes, $group_id);
- $attach_flagtypes = filter_group($attach_flagtypes, $group_id);
- }
- # If no product is given, then show all flag types available.
- else {
- my $flagtypes = get_editable_flagtypes(\@products, $group_id);
- $bug_flagtypes = [grep { $_->target_type eq 'bug' } @$flagtypes];
- $attach_flagtypes = [grep { $_->target_type eq 'attachment' } @$flagtypes];
- }
-
- if ($show_flag_counts) {
- my %bug_lists;
- my %map = ('+' => 'granted', '-' => 'denied', '?' => 'pending');
-
- foreach my $flagtype (@$bug_flagtypes, @$attach_flagtypes) {
- $bug_lists{$flagtype->id} = {};
- my $flags = Bugzilla::Flag->match({type_id => $flagtype->id});
- # Build lists of bugs, triaged by flag status.
- push(@{$bug_lists{$flagtype->id}->{$map{$_->status}}}, $_->bug_id) foreach @$flags;
- }
- $vars->{'bug_lists'} = \%bug_lists;
- $vars->{'show_flag_counts'} = 1;
- }
-
- $vars->{'selected_product'} = $product ? $product->name : '';
- $vars->{'selected_component'} = $component ? $component->name : '';
- $vars->{'bug_types'} = $bug_flagtypes;
- $vars->{'attachment_types'} = $attach_flagtypes;
-
- $template->process("admin/flag-type/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $vars->{'bug_lists'} = \%bug_lists;
+ $vars->{'show_flag_counts'} = 1;
+ }
+
+ $vars->{'selected_product'} = $product ? $product->name : '';
+ $vars->{'selected_component'} = $component ? $component->name : '';
+ $vars->{'bug_types'} = $bug_flagtypes;
+ $vars->{'attachment_types'} = $attach_flagtypes;
+
+ $template->process("admin/flag-type/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'enter') {
- my $type = $cgi->param('target_type');
- ($type eq 'bug' || $type eq 'attachment')
- || ThrowCodeError('flag_type_target_type_invalid', { target_type => $type });
-
- $vars->{'action'} = 'insert';
- $vars->{'token'} = issue_session_token('add_flagtype');
- $vars->{'type'} = { 'target_type' => $type };
- # Only users with global editcomponents privs can add a flagtype
- # to all products.
- $vars->{'inclusions'} = { '__Any__:__Any__' => '0:0' }
- if $user->in_group('editcomponents');
- $vars->{'can_fully_edit'} = 1;
- # Get a list of groups available to restrict this flag type against.
- $vars->{'groups'} = get_settable_groups();
-
- $template->process("admin/flag-type/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my $type = $cgi->param('target_type');
+ ($type eq 'bug' || $type eq 'attachment')
+ || ThrowCodeError('flag_type_target_type_invalid', {target_type => $type});
+
+ $vars->{'action'} = 'insert';
+ $vars->{'token'} = issue_session_token('add_flagtype');
+ $vars->{'type'} = {'target_type' => $type};
+
+ # Only users with global editcomponents privs can add a flagtype
+ # to all products.
+ $vars->{'inclusions'} = {'__Any__:__Any__' => '0:0'}
+ if $user->in_group('editcomponents');
+ $vars->{'can_fully_edit'} = 1;
+
+ # Get a list of groups available to restrict this flag type against.
+ $vars->{'groups'} = get_settable_groups();
+
+ $template->process("admin/flag-type/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'edit' || $action eq 'copy') {
- my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
- $vars->{'type'} = $flagtype;
- $vars->{'can_fully_edit'} = $can_fully_edit;
-
- if ($user->in_group('editcomponents')) {
- $vars->{'inclusions'} = $flagtype->inclusions;
- $vars->{'exclusions'} = $flagtype->exclusions;
- }
- else {
- # Filter products the user shouldn't know about.
- $vars->{'inclusions'} = clusion_array_to_hash([values %{$flagtype->inclusions}], \@products);
- $vars->{'exclusions'} = clusion_array_to_hash([values %{$flagtype->exclusions}], \@products);
- }
-
- if ($action eq 'copy') {
- $vars->{'action'} = "insert";
- $vars->{'token'} = issue_session_token('add_flagtype');
- }
- else {
- $vars->{'action'} = "update";
- $vars->{'token'} = issue_session_token('edit_flagtype');
- }
-
- # Get a list of groups available to restrict this flag type against.
- $vars->{'groups'} = get_settable_groups();
-
- $template->process("admin/flag-type/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
+ $vars->{'type'} = $flagtype;
+ $vars->{'can_fully_edit'} = $can_fully_edit;
+
+ if ($user->in_group('editcomponents')) {
+ $vars->{'inclusions'} = $flagtype->inclusions;
+ $vars->{'exclusions'} = $flagtype->exclusions;
+ }
+ else {
+ # Filter products the user shouldn't know about.
+ $vars->{'inclusions'}
+ = clusion_array_to_hash([values %{$flagtype->inclusions}], \@products);
+ $vars->{'exclusions'}
+ = clusion_array_to_hash([values %{$flagtype->exclusions}], \@products);
+ }
+
+ if ($action eq 'copy') {
+ $vars->{'action'} = "insert";
+ $vars->{'token'} = issue_session_token('add_flagtype');
+ }
+ else {
+ $vars->{'action'} = "update";
+ $vars->{'token'} = issue_session_token('edit_flagtype');
+ }
+
+ # Get a list of groups available to restrict this flag type against.
+ $vars->{'groups'} = get_settable_groups();
+
+ $template->process("admin/flag-type/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'insert') {
- check_token_data($token, 'add_flagtype');
-
- my $name = $cgi->param('name');
- my $description = $cgi->param('description');
- my $target_type = $cgi->param('target_type');
- my $cc_list = $cgi->param('cc_list');
- my $sortkey = $cgi->param('sortkey');
- my $is_active = $cgi->param('is_active');
- my $is_requestable = $cgi->param('is_requestable');
- my $is_specifically = $cgi->param('is_requesteeble');
- my $is_multiplicable = $cgi->param('is_multiplicable');
- my $grant_group = $cgi->param('grant_group');
- my $request_group = $cgi->param('request_group');
- my @inclusions = $cgi->param('inclusions');
- my @exclusions = $cgi->param('exclusions');
-
- # Filter inclusion and exclusion lists to products the user can see.
- unless ($user->in_group('editcomponents')) {
- @inclusions = values %{clusion_array_to_hash(\@inclusions, \@products)};
- @exclusions = values %{clusion_array_to_hash(\@exclusions, \@products)};
- }
-
- my $flagtype = Bugzilla::FlagType->create({
- name => $name,
- description => $description,
- target_type => $target_type,
- cc_list => $cc_list,
- sortkey => $sortkey,
- is_active => $is_active,
- is_requestable => $is_requestable,
- is_requesteeble => $is_specifically,
- is_multiplicable => $is_multiplicable,
- grant_group => $grant_group,
- request_group => $request_group,
- inclusions => \@inclusions,
- exclusions => \@exclusions
- });
-
- delete_token($token);
-
- $vars->{'name'} = $flagtype->name;
- $vars->{'message'} = "flag_type_created";
-
- my $flagtypes = get_editable_flagtypes(\@products);
- $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @$flagtypes];
- $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @$flagtypes];
-
- $template->process("admin/flag-type/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ check_token_data($token, 'add_flagtype');
+
+ my $name = $cgi->param('name');
+ my $description = $cgi->param('description');
+ my $target_type = $cgi->param('target_type');
+ my $cc_list = $cgi->param('cc_list');
+ my $sortkey = $cgi->param('sortkey');
+ my $is_active = $cgi->param('is_active');
+ my $is_requestable = $cgi->param('is_requestable');
+ my $is_specifically = $cgi->param('is_requesteeble');
+ my $is_multiplicable = $cgi->param('is_multiplicable');
+ my $grant_group = $cgi->param('grant_group');
+ my $request_group = $cgi->param('request_group');
+ my @inclusions = $cgi->param('inclusions');
+ my @exclusions = $cgi->param('exclusions');
+
+ # Filter inclusion and exclusion lists to products the user can see.
+ unless ($user->in_group('editcomponents')) {
+ @inclusions = values %{clusion_array_to_hash(\@inclusions, \@products)};
+ @exclusions = values %{clusion_array_to_hash(\@exclusions, \@products)};
+ }
+
+ my $flagtype = Bugzilla::FlagType->create({
+ name => $name,
+ description => $description,
+ target_type => $target_type,
+ cc_list => $cc_list,
+ sortkey => $sortkey,
+ is_active => $is_active,
+ is_requestable => $is_requestable,
+ is_requesteeble => $is_specifically,
+ is_multiplicable => $is_multiplicable,
+ grant_group => $grant_group,
+ request_group => $request_group,
+ inclusions => \@inclusions,
+ exclusions => \@exclusions
+ });
+
+ delete_token($token);
+
+ $vars->{'name'} = $flagtype->name;
+ $vars->{'message'} = "flag_type_created";
+
+ my $flagtypes = get_editable_flagtypes(\@products);
+ $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @$flagtypes];
+ $vars->{'attachment_types'}
+ = [grep { $_->target_type eq 'attachment' } @$flagtypes];
+
+ $template->process("admin/flag-type/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'update') {
- check_token_data($token, 'edit_flagtype');
-
- my $name = $cgi->param('name');
- my $description = $cgi->param('description');
- my $cc_list = $cgi->param('cc_list');
- my $sortkey = $cgi->param('sortkey');
- my $is_active = $cgi->param('is_active');
- my $is_requestable = $cgi->param('is_requestable');
- my $is_specifically = $cgi->param('is_requesteeble');
- my $is_multiplicable = $cgi->param('is_multiplicable');
- my $grant_group = $cgi->param('grant_group');
- my $request_group = $cgi->param('request_group');
- my @inclusions = $cgi->param('inclusions');
- my @exclusions = $cgi->param('exclusions');
-
- my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
- if ($cgi->param('check_clusions') && !$user->in_group('editcomponents')) {
- # Filter inclusion and exclusion lists to products the user can edit.
- @inclusions = values %{clusion_array_to_hash(\@inclusions, \@products)};
- @exclusions = values %{clusion_array_to_hash(\@exclusions, \@products)};
- # Bring back the products the user cannot edit.
- foreach my $item (values %{$flagtype->inclusions}) {
- my ($prod_id, $comp_id) = split(':', $item);
- push(@inclusions, $item) unless grep { $_->id == $prod_id } @products;
- }
- foreach my $item (values %{$flagtype->exclusions}) {
- my ($prod_id, $comp_id) = split(':', $item);
- push(@exclusions, $item) unless grep { $_->id == $prod_id } @products;
- }
+ check_token_data($token, 'edit_flagtype');
+
+ my $name = $cgi->param('name');
+ my $description = $cgi->param('description');
+ my $cc_list = $cgi->param('cc_list');
+ my $sortkey = $cgi->param('sortkey');
+ my $is_active = $cgi->param('is_active');
+ my $is_requestable = $cgi->param('is_requestable');
+ my $is_specifically = $cgi->param('is_requesteeble');
+ my $is_multiplicable = $cgi->param('is_multiplicable');
+ my $grant_group = $cgi->param('grant_group');
+ my $request_group = $cgi->param('request_group');
+ my @inclusions = $cgi->param('inclusions');
+ my @exclusions = $cgi->param('exclusions');
+
+ my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
+ if ($cgi->param('check_clusions') && !$user->in_group('editcomponents')) {
+
+ # Filter inclusion and exclusion lists to products the user can edit.
+ @inclusions = values %{clusion_array_to_hash(\@inclusions, \@products)};
+ @exclusions = values %{clusion_array_to_hash(\@exclusions, \@products)};
+
+ # Bring back the products the user cannot edit.
+ foreach my $item (values %{$flagtype->inclusions}) {
+ my ($prod_id, $comp_id) = split(':', $item);
+ push(@inclusions, $item) unless grep { $_->id == $prod_id } @products;
}
-
- if ($can_fully_edit) {
- $flagtype->set_name($name);
- $flagtype->set_description($description);
- $flagtype->set_cc_list($cc_list);
- $flagtype->set_sortkey($sortkey);
- $flagtype->set_is_active($is_active);
- $flagtype->set_is_requestable($is_requestable);
- $flagtype->set_is_specifically_requestable($is_specifically);
- $flagtype->set_is_multiplicable($is_multiplicable);
- $flagtype->set_grant_group($grant_group);
- $flagtype->set_request_group($request_group);
+ foreach my $item (values %{$flagtype->exclusions}) {
+ my ($prod_id, $comp_id) = split(':', $item);
+ push(@exclusions, $item) unless grep { $_->id == $prod_id } @products;
}
- $flagtype->set_clusions({ inclusions => \@inclusions, exclusions => \@exclusions})
- if $cgi->param('check_clusions');
- my $changes = $flagtype->update();
-
- delete_token($token);
-
- $vars->{'flagtype'} = $flagtype;
- $vars->{'changes'} = $changes;
- $vars->{'message'} = 'flag_type_updated';
-
- my $flagtypes = get_editable_flagtypes(\@products);
- $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @$flagtypes];
- $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @$flagtypes];
-
- $template->process("admin/flag-type/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ }
+
+ if ($can_fully_edit) {
+ $flagtype->set_name($name);
+ $flagtype->set_description($description);
+ $flagtype->set_cc_list($cc_list);
+ $flagtype->set_sortkey($sortkey);
+ $flagtype->set_is_active($is_active);
+ $flagtype->set_is_requestable($is_requestable);
+ $flagtype->set_is_specifically_requestable($is_specifically);
+ $flagtype->set_is_multiplicable($is_multiplicable);
+ $flagtype->set_grant_group($grant_group);
+ $flagtype->set_request_group($request_group);
+ }
+ $flagtype->set_clusions(
+ {inclusions => \@inclusions, exclusions => \@exclusions})
+ if $cgi->param('check_clusions');
+ my $changes = $flagtype->update();
+
+ delete_token($token);
+
+ $vars->{'flagtype'} = $flagtype;
+ $vars->{'changes'} = $changes;
+ $vars->{'message'} = 'flag_type_updated';
+
+ my $flagtypes = get_editable_flagtypes(\@products);
+ $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @$flagtypes];
+ $vars->{'attachment_types'}
+ = [grep { $_->target_type eq 'attachment' } @$flagtypes];
+
+ $template->process("admin/flag-type/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'confirmdelete') {
- my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
- ThrowUserError('flag_type_cannot_delete', { flagtype => $flagtype }) unless $can_fully_edit;
+ my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
+ ThrowUserError('flag_type_cannot_delete', {flagtype => $flagtype})
+ unless $can_fully_edit;
- $vars->{'flag_type'} = $flagtype;
- $vars->{'token'} = issue_session_token('delete_flagtype');
+ $vars->{'flag_type'} = $flagtype;
+ $vars->{'token'} = issue_session_token('delete_flagtype');
- $template->process("admin/flag-type/confirm-delete.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/flag-type/confirm-delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'delete') {
- check_token_data($token, 'delete_flagtype');
+ check_token_data($token, 'delete_flagtype');
- my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
- ThrowUserError('flag_type_cannot_delete', { flagtype => $flagtype }) unless $can_fully_edit;
+ my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
+ ThrowUserError('flag_type_cannot_delete', {flagtype => $flagtype})
+ unless $can_fully_edit;
- $flagtype->remove_from_db();
+ $flagtype->remove_from_db();
- delete_token($token);
+ delete_token($token);
- $vars->{'name'} = $flagtype->name;
- $vars->{'message'} = "flag_type_deleted";
+ $vars->{'name'} = $flagtype->name;
+ $vars->{'message'} = "flag_type_deleted";
- my @flagtypes = Bugzilla::FlagType->get_all;
- $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes];
- $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @flagtypes];
+ my @flagtypes = Bugzilla::FlagType->get_all;
+ $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes];
+ $vars->{'attachment_types'}
+ = [grep { $_->target_type eq 'attachment' } @flagtypes];
- $template->process("admin/flag-type/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/flag-type/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'deactivate') {
- check_token_data($token, 'delete_flagtype');
+ check_token_data($token, 'delete_flagtype');
- my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
- ThrowUserError('flag_type_cannot_deactivate', { flagtype => $flagtype }) unless $can_fully_edit;
+ my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
+ ThrowUserError('flag_type_cannot_deactivate', {flagtype => $flagtype})
+ unless $can_fully_edit;
- $flagtype->set_is_active(0);
- $flagtype->update();
+ $flagtype->set_is_active(0);
+ $flagtype->update();
- delete_token($token);
+ delete_token($token);
- $vars->{'message'} = "flag_type_deactivated";
- $vars->{'flag_type'} = $flagtype;
+ $vars->{'message'} = "flag_type_deactivated";
+ $vars->{'flag_type'} = $flagtype;
- my @flagtypes = Bugzilla::FlagType->get_all;
- $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes];
- $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @flagtypes];
+ my @flagtypes = Bugzilla::FlagType->get_all;
+ $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes];
+ $vars->{'attachment_types'}
+ = [grep { $_->target_type eq 'attachment' } @flagtypes];
- $template->process("admin/flag-type/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/flag-type/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
ThrowUserError('unknown_action', {action => $action});
@@ -431,115 +456,124 @@ ThrowUserError('unknown_action', {action => $action});
#####################
sub get_products_and_components {
- my $vars = {};
- my $user = Bugzilla->user;
-
- my @products;
- if ($user->in_group('editcomponents')) {
- if (Bugzilla->params->{useclassification}) {
- # We want products grouped by classifications.
- @products = map { @{ $_->products } } Bugzilla::Classification->get_all;
- }
- else {
- @products = Bugzilla::Product->get_all;
- }
- }
- else {
- @products = @{$user->get_products_by_permission('editcomponents')};
+ my $vars = {};
+ my $user = Bugzilla->user;
- if (Bugzilla->params->{useclassification}) {
- my %class;
- push(@{$class{$_->classification_id}}, $_) foreach @products;
+ my @products;
+ if ($user->in_group('editcomponents')) {
+ if (Bugzilla->params->{useclassification}) {
- # Let's sort the list by classifications.
- @products = ();
- push(@products, @{$class{$_->id} || []}) foreach Bugzilla::Classification->get_all;
- }
+ # We want products grouped by classifications.
+ @products = map { @{$_->products} } Bugzilla::Classification->get_all;
}
-
- my %components;
- foreach my $product (@products) {
- $components{$_->name} = 1 foreach @{$product->components};
+ else {
+ @products = Bugzilla::Product->get_all;
}
- $vars->{'products'} = \@products;
- $vars->{'components'} = [sort(keys %components)];
- return $vars;
+ }
+ else {
+ @products = @{$user->get_products_by_permission('editcomponents')};
+
+ if (Bugzilla->params->{useclassification}) {
+ my %class;
+ push(@{$class{$_->classification_id}}, $_) foreach @products;
+
+ # Let's sort the list by classifications.
+ @products = ();
+ push(@products, @{$class{$_->id} || []})
+ foreach Bugzilla::Classification->get_all;
+ }
+ }
+
+ my %components;
+ foreach my $product (@products) {
+ $components{$_->name} = 1 foreach @{$product->components};
+ }
+ $vars->{'products'} = \@products;
+ $vars->{'components'} = [sort(keys %components)];
+ return $vars;
}
sub get_editable_flagtypes {
- my ($products, $group_id) = @_;
- my $flagtypes;
+ my ($products, $group_id) = @_;
+ my $flagtypes;
- if (Bugzilla->user->in_group('editcomponents')) {
- $flagtypes = Bugzilla::FlagType::match({ group => $group_id });
- return $flagtypes;
- }
+ if (Bugzilla->user->in_group('editcomponents')) {
+ $flagtypes = Bugzilla::FlagType::match({group => $group_id});
+ return $flagtypes;
+ }
- my %visible_flagtypes;
- foreach my $product (@$products) {
- foreach my $target ('bug', 'attachment') {
- my $prod_flagtypes = $product->flag_types->{$target};
- $visible_flagtypes{$_->id} ||= $_ foreach @$prod_flagtypes;
- }
+ my %visible_flagtypes;
+ foreach my $product (@$products) {
+ foreach my $target ('bug', 'attachment') {
+ my $prod_flagtypes = $product->flag_types->{$target};
+ $visible_flagtypes{$_->id} ||= $_ foreach @$prod_flagtypes;
}
- @$flagtypes = sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name }
- values %visible_flagtypes;
- # Filter flag types if a group ID is given.
- $flagtypes = filter_group($flagtypes, $group_id);
- return $flagtypes;
+ }
+ @$flagtypes = sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name }
+ values %visible_flagtypes;
+
+ # Filter flag types if a group ID is given.
+ $flagtypes = filter_group($flagtypes, $group_id);
+ return $flagtypes;
}
sub get_settable_groups {
- my $user = Bugzilla->user;
- my $groups = $user->in_group('editcomponents') ? [Bugzilla::Group->get_all] : $user->groups;
- return $groups;
+ my $user = Bugzilla->user;
+ my $groups
+ = $user->in_group('editcomponents')
+ ? [Bugzilla::Group->get_all]
+ : $user->groups;
+ return $groups;
}
sub filter_group {
- my ($flag_types, $gid) = @_;
- return $flag_types unless $gid;
+ my ($flag_types, $gid) = @_;
+ return $flag_types unless $gid;
- my @flag_types = grep {($_->grant_group && $_->grant_group->id == $gid)
- || ($_->request_group && $_->request_group->id == $gid)} @$flag_types;
+ my @flag_types = grep {
+ ($_->grant_group && $_->grant_group->id == $gid)
+ || ($_->request_group && $_->request_group->id == $gid)
+ } @$flag_types;
- return \@flag_types;
+ return \@flag_types;
}
# Convert the array @clusions('prod_ID:comp_ID') back to a hash of
# the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
sub clusion_array_to_hash {
- my ($array, $visible_products) = @_;
- my $user = Bugzilla->user;
- my $has_privs = $user->in_group('editcomponents');
-
- my %hash;
- my %products;
- my %components;
-
- foreach my $ids (@$array) {
- my ($product_id, $component_id) = split(":", $ids);
- my $product_name = "__Any__";
- my $component_name = "__Any__";
-
- if ($product_id) {
- ($products{$product_id}) = grep { $_->id == $product_id } @$visible_products;
- next unless $products{$product_id};
- $product_name = $products{$product_id}->name;
-
- if ($component_id) {
- ($components{$component_id}) =
- grep { $_->id == $component_id } @{$products{$product_id}->components};
- next unless $components{$component_id};
- $component_name = $components{$component_id}->name;
- }
- }
- else {
- # Users with local editcomponents privs cannot use __Any__:__Any__.
- next unless $has_privs;
- # It's illegal to select a component without a product.
- next if $component_id;
- }
- $hash{"$product_name:$component_name"} = $ids;
+ my ($array, $visible_products) = @_;
+ my $user = Bugzilla->user;
+ my $has_privs = $user->in_group('editcomponents');
+
+ my %hash;
+ my %products;
+ my %components;
+
+ foreach my $ids (@$array) {
+ my ($product_id, $component_id) = split(":", $ids);
+ my $product_name = "__Any__";
+ my $component_name = "__Any__";
+
+ if ($product_id) {
+ ($products{$product_id}) = grep { $_->id == $product_id } @$visible_products;
+ next unless $products{$product_id};
+ $product_name = $products{$product_id}->name;
+
+ if ($component_id) {
+ ($components{$component_id})
+ = grep { $_->id == $component_id } @{$products{$product_id}->components};
+ next unless $components{$component_id};
+ $component_name = $components{$component_id}->name;
+ }
+ }
+ else {
+ # Users with local editcomponents privs cannot use __Any__:__Any__.
+ next unless $has_privs;
+
+ # It's illegal to select a component without a product.
+ next if $component_id;
}
- return \%hash;
+ $hash{"$product_name:$component_name"} = $ids;
+ }
+ return \%hash;
}
diff --git a/editgroups.cgi b/editgroups.cgi
index f2c915556..b93254ebc 100755
--- a/editgroups.cgi
+++ b/editgroups.cgi
@@ -22,19 +22,18 @@ use Bugzilla::Product;
use Bugzilla::User;
use Bugzilla::Token;
-my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
my $user = Bugzilla->login(LOGIN_REQUIRED);
print $cgi->header();
$user->in_group('creategroups')
- || ThrowUserError("auth_failure", {group => "creategroups",
- action => "edit",
- object => "groups"});
+ || ThrowUserError("auth_failure",
+ {group => "creategroups", action => "edit", object => "groups"});
my $action = trim($cgi->param('action') || '');
my $token = $cgi->param('token');
@@ -44,14 +43,16 @@ my $token = $cgi->param('token');
# trimmed group ID is returned.
sub CheckGroupID {
- my ($group_id) = @_;
- $group_id = trim($group_id || 0);
- ThrowUserError("group_not_specified") unless $group_id;
- (detaint_natural($group_id)
- && Bugzilla->dbh->selectrow_array("SELECT id FROM groups WHERE id = ?",
- undef, $group_id))
- || ThrowUserError("invalid_group_ID");
- return $group_id;
+ my ($group_id) = @_;
+ $group_id = trim($group_id || 0);
+ ThrowUserError("group_not_specified") unless $group_id;
+ (
+ detaint_natural($group_id) && Bugzilla->dbh->selectrow_array(
+ "SELECT id FROM groups WHERE id = ?",
+ undef, $group_id
+ )
+ ) || ThrowUserError("invalid_group_ID");
+ return $group_id;
}
# CheckGroupRegexp checks that the regular expression is valid
@@ -60,85 +61,86 @@ sub CheckGroupID {
# is returned.
sub CheckGroupRegexp {
- my ($regexp) = @_;
- $regexp = trim($regexp || '');
- trick_taint($regexp);
- ThrowUserError("invalid_regexp") unless (eval {qr/$regexp/});
- return $regexp;
+ my ($regexp) = @_;
+ $regexp = trim($regexp || '');
+ trick_taint($regexp);
+ ThrowUserError("invalid_regexp") unless (eval {qr/$regexp/});
+ return $regexp;
}
# A helper for displaying the edit.html.tmpl template.
sub get_current_and_available {
- my ($group, $vars) = @_;
-
- my @all_groups = Bugzilla::Group->get_all;
- my @members_current = @{$group->grant_direct(GROUP_MEMBERSHIP)};
- my @member_of_current = @{$group->granted_by_direct(GROUP_MEMBERSHIP)};
- my @bless_from_current = @{$group->grant_direct(GROUP_BLESS)};
- my @bless_to_current = @{$group->granted_by_direct(GROUP_BLESS)};
- my (@visible_from_current, @visible_to_me_current);
+ my ($group, $vars) = @_;
+
+ my @all_groups = Bugzilla::Group->get_all;
+ my @members_current = @{$group->grant_direct(GROUP_MEMBERSHIP)};
+ my @member_of_current = @{$group->granted_by_direct(GROUP_MEMBERSHIP)};
+ my @bless_from_current = @{$group->grant_direct(GROUP_BLESS)};
+ my @bless_to_current = @{$group->granted_by_direct(GROUP_BLESS)};
+ my (@visible_from_current, @visible_to_me_current);
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ @visible_from_current = @{$group->grant_direct(GROUP_VISIBLE)};
+ @visible_to_me_current = @{$group->granted_by_direct(GROUP_VISIBLE)};
+ }
+
+ # Figure out what groups are not currently a member of this group,
+ # and what groups this group is not currently a member of.
+ my (
+ @members_available, @member_of_available, @bless_from_available,
+ @bless_to_available, @visible_from_available, @visible_to_me_available
+ );
+ foreach my $group_option (@all_groups) {
if (Bugzilla->params->{'usevisibilitygroups'}) {
- @visible_from_current = @{$group->grant_direct(GROUP_VISIBLE)};
- @visible_to_me_current = @{$group->granted_by_direct(GROUP_VISIBLE)};
+ push(@visible_from_available, $group_option)
+ if !grep($_->id == $group_option->id, @visible_from_current);
+ push(@visible_to_me_available, $group_option)
+ if !grep($_->id == $group_option->id, @visible_to_me_current);
}
- # Figure out what groups are not currently a member of this group,
- # and what groups this group is not currently a member of.
- my (@members_available, @member_of_available,
- @bless_from_available, @bless_to_available,
- @visible_from_available, @visible_to_me_available);
- foreach my $group_option (@all_groups) {
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- push(@visible_from_available, $group_option)
- if !grep($_->id == $group_option->id, @visible_from_current);
- push(@visible_to_me_available, $group_option)
- if !grep($_->id == $group_option->id, @visible_to_me_current);
- }
-
- push(@bless_from_available, $group_option)
- if !grep($_->id == $group_option->id, @bless_from_current);
-
- # The group itself should never show up in the membership lists,
- # and should show up in only one of the bless lists (otherwise
- # you can try to allow it to bless itself twice, leading to a
- # database unique constraint error).
- next if $group_option->id == $group->id;
-
- push(@members_available, $group_option)
- if !grep($_->id == $group_option->id, @members_current);
- push(@member_of_available, $group_option)
- if !grep($_->id == $group_option->id, @member_of_current);
- push(@bless_to_available, $group_option)
- if !grep($_->id == $group_option->id, @bless_to_current);
- }
-
- $vars->{'members_current'} = \@members_current;
- $vars->{'members_available'} = \@members_available;
- $vars->{'member_of_current'} = \@member_of_current;
- $vars->{'member_of_available'} = \@member_of_available;
-
- $vars->{'bless_from_current'} = \@bless_from_current;
- $vars->{'bless_from_available'} = \@bless_from_available;
- $vars->{'bless_to_current'} = \@bless_to_current;
- $vars->{'bless_to_available'} = \@bless_to_available;
-
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $vars->{'visible_from_current'} = \@visible_from_current;
- $vars->{'visible_from_available'} = \@visible_from_available;
- $vars->{'visible_to_me_current'} = \@visible_to_me_current;
- $vars->{'visible_to_me_available'} = \@visible_to_me_available;
- }
+ push(@bless_from_available, $group_option)
+ if !grep($_->id == $group_option->id, @bless_from_current);
+
+ # The group itself should never show up in the membership lists,
+ # and should show up in only one of the bless lists (otherwise
+ # you can try to allow it to bless itself twice, leading to a
+ # database unique constraint error).
+ next if $group_option->id == $group->id;
+
+ push(@members_available, $group_option)
+ if !grep($_->id == $group_option->id, @members_current);
+ push(@member_of_available, $group_option)
+ if !grep($_->id == $group_option->id, @member_of_current);
+ push(@bless_to_available, $group_option)
+ if !grep($_->id == $group_option->id, @bless_to_current);
+ }
+
+ $vars->{'members_current'} = \@members_current;
+ $vars->{'members_available'} = \@members_available;
+ $vars->{'member_of_current'} = \@member_of_current;
+ $vars->{'member_of_available'} = \@member_of_available;
+
+ $vars->{'bless_from_current'} = \@bless_from_current;
+ $vars->{'bless_from_available'} = \@bless_from_available;
+ $vars->{'bless_to_current'} = \@bless_to_current;
+ $vars->{'bless_to_available'} = \@bless_to_available;
+
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $vars->{'visible_from_current'} = \@visible_from_current;
+ $vars->{'visible_from_available'} = \@visible_from_available;
+ $vars->{'visible_to_me_current'} = \@visible_to_me_current;
+ $vars->{'visible_to_me_available'} = \@visible_to_me_available;
+ }
}
# If no action is specified, get a list of all groups available.
unless ($action) {
- my @groups = Bugzilla::Group->get_all;
- $vars->{'groups'} = \@groups;
+ my @groups = Bugzilla::Group->get_all;
+ $vars->{'groups'} = \@groups;
- $template->process("admin/groups/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/groups/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -148,17 +150,18 @@ unless ($action) {
#
if ($action eq 'changeform') {
- # Check that an existing group ID is given
- my $group_id = CheckGroupID($cgi->param('group'));
- my $group = new Bugzilla::Group($group_id);
- get_current_and_available($group, $vars);
- $vars->{'group'} = $group;
- $vars->{'token'} = issue_session_token('edit_group');
+ # Check that an existing group ID is given
+ my $group_id = CheckGroupID($cgi->param('group'));
+ my $group = new Bugzilla::Group($group_id);
- $template->process("admin/groups/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ get_current_and_available($group, $vars);
+ $vars->{'group'} = $group;
+ $vars->{'token'} = issue_session_token('edit_group');
+
+ $template->process("admin/groups/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -168,41 +171,40 @@ if ($action eq 'changeform') {
#
if ($action eq 'add') {
- $vars->{'token'} = issue_session_token('add_group');
+ $vars->{'token'} = issue_session_token('add_group');
- $template->process("admin/groups/create.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/groups/create.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
-
#
# action='new' -> add group entered in the 'action=add' screen
#
if ($action eq 'new') {
- check_token_data($token, 'add_group');
- my $group = Bugzilla::Group->create({
- name => scalar $cgi->param('name'),
- description => scalar $cgi->param('desc'),
- userregexp => scalar $cgi->param('regexp'),
- isactive => scalar $cgi->param('isactive'),
- icon_url => scalar $cgi->param('icon_url'),
- isbuggroup => 1,
- use_in_all_products => scalar $cgi->param('insertnew'),
- });
-
- delete_token($token);
-
- $vars->{'message'} = 'group_created';
- $vars->{'group'} = $group;
- get_current_and_available($group, $vars);
- $vars->{'token'} = issue_session_token('edit_group');
-
- $template->process("admin/groups/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ check_token_data($token, 'add_group');
+ my $group = Bugzilla::Group->create({
+ name => scalar $cgi->param('name'),
+ description => scalar $cgi->param('desc'),
+ userregexp => scalar $cgi->param('regexp'),
+ isactive => scalar $cgi->param('isactive'),
+ icon_url => scalar $cgi->param('icon_url'),
+ isbuggroup => 1,
+ use_in_all_products => scalar $cgi->param('insertnew'),
+ });
+
+ delete_token($token);
+
+ $vars->{'message'} = 'group_created';
+ $vars->{'group'} = $group;
+ get_current_and_available($group, $vars);
+ $vars->{'token'} = issue_session_token('edit_group');
+
+ $template->process("admin/groups/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -212,20 +214,22 @@ if ($action eq 'new') {
#
if ($action eq 'del') {
- # Check that an existing group ID is given
- my $group = Bugzilla::Group->check({ id => scalar $cgi->param('group') });
- $group->check_remove({ test_only => 1 });
- $vars->{'shared_queries'} =
- $dbh->selectrow_array('SELECT COUNT(*)
+
+ # Check that an existing group ID is given
+ my $group = Bugzilla::Group->check({id => scalar $cgi->param('group')});
+ $group->check_remove({test_only => 1});
+ $vars->{'shared_queries'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*)
FROM namedquery_group_map
- WHERE group_id = ?', undef, $group->id);
+ WHERE group_id = ?', undef, $group->id
+ );
- $vars->{'group'} = $group;
- $vars->{'token'} = issue_session_token('delete_group');
+ $vars->{'group'} = $group;
+ $vars->{'token'} = issue_session_token('delete_group');
- $template->process("admin/groups/delete.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/groups/delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -233,24 +237,25 @@ if ($action eq 'del') {
#
if ($action eq 'delete') {
- check_token_data($token, 'delete_group');
- # Check that an existing group ID is given
- my $group = Bugzilla::Group->check({ id => scalar $cgi->param('group') });
- $vars->{'name'} = $group->name;
- $group->remove_from_db({
- remove_from_users => scalar $cgi->param('removeusers'),
- remove_from_bugs => scalar $cgi->param('removebugs'),
- remove_from_flags => scalar $cgi->param('removeflags'),
- remove_from_products => scalar $cgi->param('unbind'),
- });
- delete_token($token);
-
- $vars->{'message'} = 'group_deleted';
- $vars->{'groups'} = [Bugzilla::Group->get_all];
-
- $template->process("admin/groups/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ check_token_data($token, 'delete_group');
+
+ # Check that an existing group ID is given
+ my $group = Bugzilla::Group->check({id => scalar $cgi->param('group')});
+ $vars->{'name'} = $group->name;
+ $group->remove_from_db({
+ remove_from_users => scalar $cgi->param('removeusers'),
+ remove_from_bugs => scalar $cgi->param('removebugs'),
+ remove_from_flags => scalar $cgi->param('removeflags'),
+ remove_from_products => scalar $cgi->param('unbind'),
+ });
+ delete_token($token);
+
+ $vars->{'message'} = 'group_deleted';
+ $vars->{'groups'} = [Bugzilla::Group->get_all];
+
+ $template->process("admin/groups/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -258,69 +263,71 @@ if ($action eq 'delete') {
#
if ($action eq 'postchanges') {
- check_token_data($token, 'edit_group');
- my $changes = doGroupChanges();
- delete_token($token);
-
- my $group = new Bugzilla::Group($cgi->param('group_id'));
- get_current_and_available($group, $vars);
- $vars->{'message'} = 'group_updated';
- $vars->{'group'} = $group;
- $vars->{'changes'} = $changes;
- $vars->{'token'} = issue_session_token('edit_group');
-
- $template->process("admin/groups/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ check_token_data($token, 'edit_group');
+ my $changes = doGroupChanges();
+ delete_token($token);
+
+ my $group = new Bugzilla::Group($cgi->param('group_id'));
+ get_current_and_available($group, $vars);
+ $vars->{'message'} = 'group_updated';
+ $vars->{'group'} = $group;
+ $vars->{'changes'} = $changes;
+ $vars->{'token'} = issue_session_token('edit_group');
+
+ $template->process("admin/groups/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'confirm_remove') {
- my $group = new Bugzilla::Group(CheckGroupID($cgi->param('group_id')));
- $vars->{'group'} = $group;
- $vars->{'regexp'} = CheckGroupRegexp($cgi->param('regexp'));
- $vars->{'token'} = issue_session_token('remove_group_members');
-
- $template->process('admin/groups/confirm-remove.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my $group = new Bugzilla::Group(CheckGroupID($cgi->param('group_id')));
+ $vars->{'group'} = $group;
+ $vars->{'regexp'} = CheckGroupRegexp($cgi->param('regexp'));
+ $vars->{'token'} = issue_session_token('remove_group_members');
+
+ $template->process('admin/groups/confirm-remove.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'remove_regexp') {
- check_token_data($token, 'remove_group_members');
- # remove all explicit users from the group with
- # gid = $cgi->param('group') that match the regular expression
- # stored in the DB for that group or all of them period
-
- my $group = new Bugzilla::Group(CheckGroupID($cgi->param('group_id')));
- my $regexp = CheckGroupRegexp($cgi->param('regexp'));
-
- $dbh->bz_start_transaction();
-
- my $users = $group->members_direct();
- my $sth_delete = $dbh->prepare(
- "DELETE FROM user_group_map
- WHERE user_id = ? AND isbless = 0 AND group_id = ?");
-
- my @deleted;
- foreach my $member (@$users) {
- if ($regexp eq '' || $member->login =~ m/$regexp/i) {
- $sth_delete->execute($member->id, $group->id);
- push(@deleted, $member);
- }
+ check_token_data($token, 'remove_group_members');
+
+ # remove all explicit users from the group with
+ # gid = $cgi->param('group') that match the regular expression
+ # stored in the DB for that group or all of them period
+
+ my $group = new Bugzilla::Group(CheckGroupID($cgi->param('group_id')));
+ my $regexp = CheckGroupRegexp($cgi->param('regexp'));
+
+ $dbh->bz_start_transaction();
+
+ my $users = $group->members_direct();
+ my $sth_delete = $dbh->prepare(
+ "DELETE FROM user_group_map
+ WHERE user_id = ? AND isbless = 0 AND group_id = ?"
+ );
+
+ my @deleted;
+ foreach my $member (@$users) {
+ if ($regexp eq '' || $member->login =~ m/$regexp/i) {
+ $sth_delete->execute($member->id, $group->id);
+ push(@deleted, $member);
}
- $dbh->bz_commit_transaction();
+ }
+ $dbh->bz_commit_transaction();
- $vars->{'users'} = \@deleted;
- $vars->{'regexp'} = $regexp;
- delete_token($token);
+ $vars->{'users'} = \@deleted;
+ $vars->{'regexp'} = $regexp;
+ delete_token($token);
- $vars->{'message'} = 'group_membership_removed';
- $vars->{'group'} = $group->name;
- $vars->{'groups'} = [Bugzilla::Group->get_all];
+ $vars->{'message'} = 'group_membership_removed';
+ $vars->{'group'} = $group->name;
+ $vars->{'groups'} = [Bugzilla::Group->get_all];
- $template->process("admin/groups/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/groups/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
# No valid action found
@@ -328,111 +335,120 @@ ThrowUserError('unknown_action', {action => $action});
# Helper sub to handle the making of changes to a group
sub doGroupChanges {
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- # Check that the given group ID is valid and make a Group.
- my $group = new Bugzilla::Group(CheckGroupID($cgi->param('group_id')));
+ # Check that the given group ID is valid and make a Group.
+ my $group = new Bugzilla::Group(CheckGroupID($cgi->param('group_id')));
- if (defined $cgi->param('regexp')) {
- $group->set_user_regexp($cgi->param('regexp'));
- }
+ if (defined $cgi->param('regexp')) {
+ $group->set_user_regexp($cgi->param('regexp'));
+ }
- if ($group->is_bug_group) {
- if (defined $cgi->param('name')) {
- $group->set_name($cgi->param('name'));
- }
- if (defined $cgi->param('desc')) {
- $group->set_description($cgi->param('desc'));
- }
- # Only set isactive if we came from the right form.
- if (defined $cgi->param('regexp')) {
- $group->set_is_active($cgi->param('isactive'));
- }
+ if ($group->is_bug_group) {
+ if (defined $cgi->param('name')) {
+ $group->set_name($cgi->param('name'));
+ }
+ if (defined $cgi->param('desc')) {
+ $group->set_description($cgi->param('desc'));
}
- if (defined $cgi->param('icon_url')) {
- $group->set_icon_url($cgi->param('icon_url'));
+ # Only set isactive if we came from the right form.
+ if (defined $cgi->param('regexp')) {
+ $group->set_is_active($cgi->param('isactive'));
}
+ }
+
+ if (defined $cgi->param('icon_url')) {
+ $group->set_icon_url($cgi->param('icon_url'));
+ }
- my $changes = $group->update();
+ my $changes = $group->update();
- my $sth_insert = $dbh->prepare('INSERT INTO group_group_map
+ my $sth_insert = $dbh->prepare(
+ 'INSERT INTO group_group_map
(member_id, grantor_id, grant_type)
- VALUES (?, ?, ?)');
+ VALUES (?, ?, ?)'
+ );
- my $sth_delete = $dbh->prepare('DELETE FROM group_group_map
+ my $sth_delete = $dbh->prepare(
+ 'DELETE FROM group_group_map
WHERE member_id = ?
AND grantor_id = ?
- AND grant_type = ?');
-
- # First item is the type, second is whether or not it's "reverse"
- # (granted_by) (see _do_add for more explanation).
- my %fields = (
- members => [GROUP_MEMBERSHIP, 0],
- bless_from => [GROUP_BLESS, 0],
- visible_from => [GROUP_VISIBLE, 0],
- member_of => [GROUP_MEMBERSHIP, 1],
- bless_to => [GROUP_BLESS, 1],
- visible_to_me => [GROUP_VISIBLE, 1]
- );
- while (my ($field, $data) = each %fields) {
- _do_add($group, $changes, $sth_insert, "${field}_add",
- $data->[0], $data->[1]);
- _do_remove($group, $changes, $sth_delete, "${field}_remove",
- $data->[0], $data->[1]);
- }
-
- $dbh->bz_commit_transaction();
- return $changes;
+ AND grant_type = ?'
+ );
+
+ # First item is the type, second is whether or not it's "reverse"
+ # (granted_by) (see _do_add for more explanation).
+ my %fields = (
+ members => [GROUP_MEMBERSHIP, 0],
+ bless_from => [GROUP_BLESS, 0],
+ visible_from => [GROUP_VISIBLE, 0],
+ member_of => [GROUP_MEMBERSHIP, 1],
+ bless_to => [GROUP_BLESS, 1],
+ visible_to_me => [GROUP_VISIBLE, 1]
+ );
+ while (my ($field, $data) = each %fields) {
+ _do_add($group, $changes, $sth_insert, "${field}_add", $data->[0], $data->[1]);
+ _do_remove($group, $changes, $sth_delete, "${field}_remove", $data->[0],
+ $data->[1]);
+ }
+
+ $dbh->bz_commit_transaction();
+ return $changes;
}
sub _do_add {
- my ($group, $changes, $sth_insert, $field, $type, $reverse) = @_;
- my $cgi = Bugzilla->cgi;
-
- my $current;
- # $reverse means we're doing a granted_by--that is, somebody else
- # is granting us something.
- if ($reverse) {
- $current = $group->granted_by_direct($type);
- }
- else {
- $current = $group->grant_direct($type);
- }
+ my ($group, $changes, $sth_insert, $field, $type, $reverse) = @_;
+ my $cgi = Bugzilla->cgi;
- my $add_items = Bugzilla::Group->new_from_list([$cgi->param($field)]);
+ my $current;
- foreach my $add (@$add_items) {
- next if grep($_->id == $add->id, @$current);
+ # $reverse means we're doing a granted_by--that is, somebody else
+ # is granting us something.
+ if ($reverse) {
+ $current = $group->granted_by_direct($type);
+ }
+ else {
+ $current = $group->grant_direct($type);
+ }
- $changes->{$field} ||= [];
- push(@{$changes->{$field}}, $add->name);
- # They go this direction for a normal "This group is granting
- # $add something."
- my @ids = ($add->id, $group->id);
- # But they get reversed for "This group is being granted something
- # by $add."
- @ids = reverse @ids if $reverse;
- $sth_insert->execute(@ids, $type);
- }
+ my $add_items = Bugzilla::Group->new_from_list([$cgi->param($field)]);
+
+ foreach my $add (@$add_items) {
+ next if grep($_->id == $add->id, @$current);
+
+ $changes->{$field} ||= [];
+ push(@{$changes->{$field}}, $add->name);
+
+ # They go this direction for a normal "This group is granting
+ # $add something."
+ my @ids = ($add->id, $group->id);
+
+ # But they get reversed for "This group is being granted something
+ # by $add."
+ @ids = reverse @ids if $reverse;
+ $sth_insert->execute(@ids, $type);
+ }
}
sub _do_remove {
- my ($group, $changes, $sth_delete, $field, $type, $reverse) = @_;
- my $cgi = Bugzilla->cgi;
- my $remove_items = Bugzilla::Group->new_from_list([$cgi->param($field)]);
-
- foreach my $remove (@$remove_items) {
- my @ids = ($remove->id, $group->id);
- # See _do_add for an explanation of $reverse
- @ids = reverse @ids if $reverse;
- # Deletions always succeed and are harmless if they fail, so we
- # don't need to do any checks.
- $sth_delete->execute(@ids, $type);
- $changes->{$field} ||= [];
- push(@{$changes->{$field}}, $remove->name);
- }
+ my ($group, $changes, $sth_delete, $field, $type, $reverse) = @_;
+ my $cgi = Bugzilla->cgi;
+ my $remove_items = Bugzilla::Group->new_from_list([$cgi->param($field)]);
+
+ foreach my $remove (@$remove_items) {
+ my @ids = ($remove->id, $group->id);
+
+ # See _do_add for an explanation of $reverse
+ @ids = reverse @ids if $reverse;
+
+ # Deletions always succeed and are harmless if they fail, so we
+ # don't need to do any checks.
+ $sth_delete->execute(@ids, $type);
+ $changes->{$field} ||= [];
+ push(@{$changes->{$field}}, $remove->name);
+ }
}
diff --git a/editkeywords.cgi b/editkeywords.cgi
index 01f30dbed..6b308c15d 100755
--- a/editkeywords.cgi
+++ b/editkeywords.cgi
@@ -19,21 +19,20 @@ use Bugzilla::Error;
use Bugzilla::Keyword;
use Bugzilla::Token;
-my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
my $user = Bugzilla->login(LOGIN_REQUIRED);
print $cgi->header();
$user->in_group('editkeywords')
- || ThrowUserError("auth_failure", {group => "editkeywords",
- action => "edit",
- object => "keywords"});
+ || ThrowUserError("auth_failure",
+ {group => "editkeywords", action => "edit", object => "keywords"});
-my $action = trim($cgi->param('action') || '');
+my $action = trim($cgi->param('action') || '');
my $key_id = $cgi->param('id');
my $token = $cgi->param('token');
@@ -41,41 +40,40 @@ $vars->{'action'} = $action;
if ($action eq "") {
- $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
+ $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
- $template->process("admin/keywords/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/keywords/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'add') {
- $vars->{'token'} = issue_session_token('add_keyword');
+ $vars->{'token'} = issue_session_token('add_keyword');
- $template->process("admin/keywords/create.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/keywords/create.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
# action='new' -> add keyword entered in the 'action=add' screen
#
if ($action eq 'new') {
- check_token_data($token, 'add_keyword');
- my $name = $cgi->param('name') || '';
- my $desc = $cgi->param('description') || '';
+ check_token_data($token, 'add_keyword');
+ my $name = $cgi->param('name') || '';
+ my $desc = $cgi->param('description') || '';
- my $keyword = Bugzilla::Keyword->create(
- { name => $name, description => $desc });
+ my $keyword = Bugzilla::Keyword->create({name => $name, description => $desc});
- delete_token($token);
+ delete_token($token);
- $vars->{'message'} = 'keyword_created';
- $vars->{'name'} = $keyword->name;
- $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
+ $vars->{'message'} = 'keyword_created';
+ $vars->{'name'} = $keyword->name;
+ $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
- $template->process("admin/keywords/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/keywords/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
@@ -86,15 +84,15 @@ if ($action eq 'new') {
#
if ($action eq 'edit') {
- my $keyword = new Bugzilla::Keyword($key_id)
- || ThrowUserError('invalid_keyword_id', { id => $key_id });
+ my $keyword = new Bugzilla::Keyword($key_id)
+ || ThrowUserError('invalid_keyword_id', {id => $key_id});
- $vars->{'keyword'} = $keyword;
- $vars->{'token'} = issue_session_token('edit_keyword');
+ $vars->{'keyword'} = $keyword;
+ $vars->{'token'} = issue_session_token('edit_keyword');
- $template->process("admin/keywords/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/keywords/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
@@ -103,56 +101,56 @@ if ($action eq 'edit') {
#
if ($action eq 'update') {
- check_token_data($token, 'edit_keyword');
- my $keyword = new Bugzilla::Keyword($key_id)
- || ThrowUserError('invalid_keyword_id', { id => $key_id });
-
- $keyword->set_all({
- name => scalar $cgi->param('name'),
- description => scalar $cgi->param('description'),
- });
- my $changes = $keyword->update();
-
- delete_token($token);
-
- $vars->{'message'} = 'keyword_updated';
- $vars->{'keyword'} = $keyword;
- $vars->{'changes'} = $changes;
- $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
-
- $template->process("admin/keywords/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ check_token_data($token, 'edit_keyword');
+ my $keyword = new Bugzilla::Keyword($key_id)
+ || ThrowUserError('invalid_keyword_id', {id => $key_id});
+
+ $keyword->set_all({
+ name => scalar $cgi->param('name'),
+ description => scalar $cgi->param('description'),
+ });
+ my $changes = $keyword->update();
+
+ delete_token($token);
+
+ $vars->{'message'} = 'keyword_updated';
+ $vars->{'keyword'} = $keyword;
+ $vars->{'changes'} = $changes;
+ $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
+
+ $template->process("admin/keywords/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'del') {
- my $keyword = new Bugzilla::Keyword($key_id)
- || ThrowUserError('invalid_keyword_id', { id => $key_id });
+ my $keyword = new Bugzilla::Keyword($key_id)
+ || ThrowUserError('invalid_keyword_id', {id => $key_id});
- $vars->{'keyword'} = $keyword;
- $vars->{'token'} = issue_session_token('delete_keyword');
+ $vars->{'keyword'} = $keyword;
+ $vars->{'token'} = issue_session_token('delete_keyword');
- $template->process("admin/keywords/confirm-delete.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/keywords/confirm-delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'delete') {
- check_token_data($token, 'delete_keyword');
- my $keyword = new Bugzilla::Keyword($key_id)
- || ThrowUserError('invalid_keyword_id', { id => $key_id });
+ check_token_data($token, 'delete_keyword');
+ my $keyword = new Bugzilla::Keyword($key_id)
+ || ThrowUserError('invalid_keyword_id', {id => $key_id});
- $keyword->remove_from_db();
+ $keyword->remove_from_db();
- delete_token($token);
+ delete_token($token);
- $vars->{'message'} = 'keyword_deleted';
- $vars->{'keyword'} = $keyword;
- $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
+ $vars->{'message'} = 'keyword_deleted';
+ $vars->{'keyword'} = $keyword;
+ $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
- $template->process("admin/keywords/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/keywords/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
ThrowUserError('unknown_action', {action => $action});
diff --git a/editmilestones.cgi b/editmilestones.cgi
index 8052bd1ab..4da579ac2 100755
--- a/editmilestones.cgi
+++ b/editmilestones.cgi
@@ -19,10 +19,11 @@ use Bugzilla::Error;
use Bugzilla::Milestone;
use Bugzilla::Token;
-my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
+
# There is only one section about milestones in the documentation,
# so all actions point to the same page.
$vars->{'doc_section'} = 'administering/categorization.html#milestones';
@@ -37,18 +38,17 @@ print $cgi->header();
$user->in_group('editcomponents')
|| scalar(@{$user->get_products_by_permission('editcomponents')})
- || ThrowUserError("auth_failure", {group => "editcomponents",
- action => "edit",
- object => "milestones"});
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "edit", object => "milestones"});
#
# often used variables
#
-my $product_name = trim($cgi->param('product') || '');
-my $milestone_name = trim($cgi->param('milestone') || '');
-my $sortkey = trim($cgi->param('sortkey') || 0);
-my $action = trim($cgi->param('action') || '');
-my $showbugcounts = (defined $cgi->param('showbugcounts'));
+my $product_name = trim($cgi->param('product') || '');
+my $milestone_name = trim($cgi->param('milestone') || '');
+my $sortkey = trim($cgi->param('sortkey') || 0);
+my $action = trim($cgi->param('action') || '');
+my $showbugcounts = (defined $cgi->param('showbugcounts'));
my $token = $cgi->param('token');
my $isactive = $cgi->param('isactive');
@@ -57,18 +57,19 @@ my $isactive = $cgi->param('isactive');
#
unless ($product_name) {
- my $selectable_products = $user->get_selectable_products;
- # If the user has editcomponents privs for some products only,
- # we have to restrict the list of products to display.
- unless ($user->in_group('editcomponents')) {
- $selectable_products = $user->get_products_by_permission('editcomponents');
- }
- $vars->{'products'} = $selectable_products;
- $vars->{'showbugcounts'} = $showbugcounts;
-
- $template->process("admin/milestones/select-product.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my $selectable_products = $user->get_selectable_products;
+
+ # If the user has editcomponents privs for some products only,
+ # we have to restrict the list of products to display.
+ unless ($user->in_group('editcomponents')) {
+ $selectable_products = $user->get_products_by_permission('editcomponents');
+ }
+ $vars->{'products'} = $selectable_products;
+ $vars->{'showbugcounts'} = $showbugcounts;
+
+ $template->process("admin/milestones/select-product.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
my $product = $user->check_can_admin_product($product_name);
@@ -79,11 +80,11 @@ my $product = $user->check_can_admin_product($product_name);
unless ($action) {
- $vars->{'showbugcounts'} = $showbugcounts;
- $vars->{'product'} = $product;
- $template->process("admin/milestones/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $vars->{'showbugcounts'} = $showbugcounts;
+ $vars->{'product'} = $product;
+ $template->process("admin/milestones/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -93,11 +94,11 @@ unless ($action) {
#
if ($action eq 'add') {
- $vars->{'token'} = issue_session_token('add_milestone');
- $vars->{'product'} = $product;
- $template->process("admin/milestones/create.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $vars->{'token'} = issue_session_token('add_milestone');
+ $vars->{'product'} = $product;
+ $template->process("admin/milestones/create.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -105,20 +106,19 @@ if ($action eq 'add') {
#
if ($action eq 'new') {
- check_token_data($token, 'add_milestone');
+ check_token_data($token, 'add_milestone');
- my $milestone = Bugzilla::Milestone->create({ value => $milestone_name,
- product => $product,
- sortkey => $sortkey });
+ my $milestone = Bugzilla::Milestone->create(
+ {value => $milestone_name, product => $product, sortkey => $sortkey});
- delete_token($token);
+ delete_token($token);
- $vars->{'message'} = 'milestone_created';
- $vars->{'milestone'} = $milestone;
- $vars->{'product'} = $product;
- $template->process("admin/milestones/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $vars->{'message'} = 'milestone_created';
+ $vars->{'milestone'} = $milestone;
+ $vars->{'product'} = $product;
+ $template->process("admin/milestones/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -128,21 +128,21 @@ if ($action eq 'new') {
#
if ($action eq 'del') {
- my $milestone = Bugzilla::Milestone->check({ product => $product,
- name => $milestone_name });
-
- $vars->{'milestone'} = $milestone;
- $vars->{'product'} = $product;
-
- # The default milestone cannot be deleted.
- if ($product->default_milestone eq $milestone->name) {
- ThrowUserError("milestone_is_default", { milestone => $milestone });
- }
- $vars->{'token'} = issue_session_token('delete_milestone');
-
- $template->process("admin/milestones/confirm-delete.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my $milestone
+ = Bugzilla::Milestone->check({product => $product, name => $milestone_name});
+
+ $vars->{'milestone'} = $milestone;
+ $vars->{'product'} = $product;
+
+ # The default milestone cannot be deleted.
+ if ($product->default_milestone eq $milestone->name) {
+ ThrowUserError("milestone_is_default", {milestone => $milestone});
+ }
+ $vars->{'token'} = issue_session_token('delete_milestone');
+
+ $template->process("admin/milestones/confirm-delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -150,20 +150,20 @@ if ($action eq 'del') {
#
if ($action eq 'delete') {
- check_token_data($token, 'delete_milestone');
- my $milestone = Bugzilla::Milestone->check({ product => $product,
- name => $milestone_name });
- $milestone->remove_from_db;
- delete_token($token);
-
- $vars->{'message'} = 'milestone_deleted';
- $vars->{'milestone'} = $milestone;
- $vars->{'product'} = $product;
- $vars->{'no_edit_milestone_link'} = 1;
-
- $template->process("admin/milestones/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ check_token_data($token, 'delete_milestone');
+ my $milestone
+ = Bugzilla::Milestone->check({product => $product, name => $milestone_name});
+ $milestone->remove_from_db;
+ delete_token($token);
+
+ $vars->{'message'} = 'milestone_deleted';
+ $vars->{'milestone'} = $milestone;
+ $vars->{'product'} = $product;
+ $vars->{'no_edit_milestone_link'} = 1;
+
+ $template->process("admin/milestones/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -174,16 +174,16 @@ if ($action eq 'delete') {
if ($action eq 'edit') {
- my $milestone = Bugzilla::Milestone->check({ product => $product,
- name => $milestone_name });
+ my $milestone
+ = Bugzilla::Milestone->check({product => $product, name => $milestone_name});
- $vars->{'milestone'} = $milestone;
- $vars->{'product'} = $product;
- $vars->{'token'} = issue_session_token('edit_milestone');
+ $vars->{'milestone'} = $milestone;
+ $vars->{'product'} = $product;
+ $vars->{'token'} = issue_session_token('edit_milestone');
- $template->process("admin/milestones/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/milestones/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -191,28 +191,29 @@ if ($action eq 'edit') {
#
if ($action eq 'update') {
- check_token_data($token, 'edit_milestone');
- my $milestone_old_name = trim($cgi->param('milestoneold') || '');
- my $milestone = Bugzilla::Milestone->check({ product => $product,
- name => $milestone_old_name });
-
- $milestone->set_name($milestone_name);
- $milestone->set_sortkey($sortkey);
- $milestone->set_is_active($isactive);
- my $changes = $milestone->update();
- # Reloading the product since the default milestone name
- # could have been changed.
- $product = new Bugzilla::Product({ name => $product_name });
-
- delete_token($token);
-
- $vars->{'message'} = 'milestone_updated';
- $vars->{'milestone'} = $milestone;
- $vars->{'product'} = $product;
- $vars->{'changes'} = $changes;
- $template->process("admin/milestones/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ check_token_data($token, 'edit_milestone');
+ my $milestone_old_name = trim($cgi->param('milestoneold') || '');
+ my $milestone = Bugzilla::Milestone->check(
+ {product => $product, name => $milestone_old_name});
+
+ $milestone->set_name($milestone_name);
+ $milestone->set_sortkey($sortkey);
+ $milestone->set_is_active($isactive);
+ my $changes = $milestone->update();
+
+ # Reloading the product since the default milestone name
+ # could have been changed.
+ $product = new Bugzilla::Product({name => $product_name});
+
+ delete_token($token);
+
+ $vars->{'message'} = 'milestone_updated';
+ $vars->{'milestone'} = $milestone;
+ $vars->{'product'} = $product;
+ $vars->{'changes'} = $changes;
+ $template->process("admin/milestones/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
# No valid action found
diff --git a/editparams.cgi b/editparams.cgi
index ae569c2eb..eb34bd53a 100755
--- a/editparams.cgi
+++ b/editparams.cgi
@@ -24,131 +24,140 @@ use Bugzilla::User;
use Bugzilla::User::Setting;
use Bugzilla::Status;
-my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $cgi = Bugzilla->cgi;
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
print $cgi->header();
$user->in_group('tweakparams')
- || ThrowUserError("auth_failure", {group => "tweakparams",
- action => "access",
- object => "parameters"});
+ || ThrowUserError("auth_failure",
+ {group => "tweakparams", action => "access", object => "parameters"});
-my $action = trim($cgi->param('action') || '');
-my $token = $cgi->param('token');
+my $action = trim($cgi->param('action') || '');
+my $token = $cgi->param('token');
my $current_panel = $cgi->param('section') || 'core';
$current_panel =~ /^([A-Za-z0-9_-]+)$/;
$current_panel = $1;
my $current_module;
-my @panels = ();
+my @panels = ();
my $param_panels = Bugzilla::Config::param_panels();
foreach my $panel (keys %$param_panels) {
- my $module = $param_panels->{$panel};
- eval("require $module") || die $@;
- my @module_param_list = "$module"->get_param_list();
- my $item = { name => lc($panel),
- current => ($current_panel eq lc($panel)) ? 1 : 0,
- param_list => \@module_param_list,
- sortkey => eval "\$${module}::sortkey;"
- };
- defined($item->{'sortkey'}) || ($item->{'sortkey'} = 100000);
- push(@panels, $item);
- $current_module = $panel if ($current_panel eq lc($panel));
+ my $module = $param_panels->{$panel};
+ eval("require $module") || die $@;
+ my @module_param_list = "$module"->get_param_list();
+ my $item = {
+ name => lc($panel),
+ current => ($current_panel eq lc($panel)) ? 1 : 0,
+ param_list => \@module_param_list,
+ sortkey => eval "\$${module}::sortkey;"
+ };
+ defined($item->{'sortkey'}) || ($item->{'sortkey'} = 100000);
+ push(@panels, $item);
+ $current_module = $panel if ($current_panel eq lc($panel));
}
-my %hook_panels = map { $_->{name} => { params => $_->{param_list} } }
- @panels;
+my %hook_panels = map { $_->{name} => {params => $_->{param_list}} } @panels;
+
# Note that this hook is also called in Bugzilla::Config.
-Bugzilla::Hook::process('config_modify_panels', { panels => \%hook_panels });
+Bugzilla::Hook::process('config_modify_panels', {panels => \%hook_panels});
$vars->{panels} = \@panels;
if ($action eq 'save' && $current_module) {
- check_token_data($token, 'edit_parameters');
- my @changes = ();
- my @module_param_list = @{ $hook_panels{lc($current_module)}->{params} };
-
- foreach my $i (@module_param_list) {
- my $name = $i->{'name'};
- my $value = $cgi->param($name);
-
- if (defined $cgi->param("reset-$name") && !$i->{'no_reset'}) {
- $value = $i->{'default'};
- } else {
- if ($i->{'type'} eq 'm') {
- # This simplifies the code below
- $value = [ $cgi->param($name) ];
- } else {
- # Get rid of windows/mac-style line endings.
- $value =~ s/\r\n?/\n/g;
- # assume single linefeed is an empty string
- $value =~ s/^\n$//;
- }
- # Stop complaining if the URL has no trailing slash.
- # XXX - This hack can go away once bug 303662 is implemented.
- if ($name =~ /(?<!webdot)base$/) {
- $value = "$value/" if ($value && $value !~ m#/$#);
- }
- }
+ check_token_data($token, 'edit_parameters');
+ my @changes = ();
+ my @module_param_list = @{$hook_panels{lc($current_module)}->{params}};
+
+ foreach my $i (@module_param_list) {
+ my $name = $i->{'name'};
+ my $value = $cgi->param($name);
- my $changed;
- if ($i->{'type'} eq 'm') {
- my @old = sort @{Bugzilla->params->{$name}};
- my @new = sort @$value;
- if (scalar(@old) != scalar(@new)) {
- $changed = 1;
- } else {
- $changed = 0; # Assume not changed...
- for (my $cnt = 0; $cnt < scalar(@old); ++$cnt) {
- if ($old[$cnt] ne $new[$cnt]) {
- # entry is different, therefore changed
- $changed = 1;
- last;
- }
- }
- }
- } else {
- $changed = ($value eq Bugzilla->params->{$name})? 0 : 1;
+ if (defined $cgi->param("reset-$name") && !$i->{'no_reset'}) {
+ $value = $i->{'default'};
+ }
+ else {
+ if ($i->{'type'} eq 'm') {
+
+ # This simplifies the code below
+ $value = [$cgi->param($name)];
+ }
+ else {
+ # Get rid of windows/mac-style line endings.
+ $value =~ s/\r\n?/\n/g;
+
+ # assume single linefeed is an empty string
+ $value =~ s/^\n$//;
+ }
+
+ # Stop complaining if the URL has no trailing slash.
+ # XXX - This hack can go away once bug 303662 is implemented.
+ if ($name =~ /(?<!webdot)base$/) {
+ $value = "$value/" if ($value && $value !~ m#/$#);
+ }
+ }
+
+ my $changed;
+ if ($i->{'type'} eq 'm') {
+ my @old = sort @{Bugzilla->params->{$name}};
+ my @new = sort @$value;
+ if (scalar(@old) != scalar(@new)) {
+ $changed = 1;
+ }
+ else {
+ $changed = 0; # Assume not changed...
+ for (my $cnt = 0; $cnt < scalar(@old); ++$cnt) {
+ if ($old[$cnt] ne $new[$cnt]) {
+
+ # entry is different, therefore changed
+ $changed = 1;
+ last;
+ }
}
+ }
+ }
+ else {
+ $changed = ($value eq Bugzilla->params->{$name}) ? 0 : 1;
+ }
- if ($changed) {
- if (exists $i->{'checker'}) {
- my $ok = $i->{'checker'}->($value, $i);
- if ($ok ne "") {
- ThrowUserError('invalid_parameter', { name => $name, err => $ok });
- }
- } elsif ($name eq 'globalwatchers') {
- # can't check this as others, as Bugzilla::Config::Common
- # cannot use Bugzilla::User
- foreach my $watcher (split(/[,\s]+/, $value)) {
- ThrowUserError(
- 'invalid_parameter',
- { name => $name, err => "no such user $watcher" }
- ) unless login_to_id($watcher);
- }
- }
- push(@changes, $name);
- SetParam($name, $value);
- if (($name eq "shutdownhtml") && ($value ne "")) {
- $vars->{'shutdown_is_active'} = 1;
- }
- if ($name eq 'duplicate_or_move_bug_status') {
- Bugzilla::Status::add_missing_bug_status_transitions($value);
- }
+ if ($changed) {
+ if (exists $i->{'checker'}) {
+ my $ok = $i->{'checker'}->($value, $i);
+ if ($ok ne "") {
+ ThrowUserError('invalid_parameter', {name => $name, err => $ok});
+ }
+ }
+ elsif ($name eq 'globalwatchers') {
+
+ # can't check this as others, as Bugzilla::Config::Common
+ # cannot use Bugzilla::User
+ foreach my $watcher (split(/[,\s]+/, $value)) {
+ ThrowUserError('invalid_parameter',
+ {name => $name, err => "no such user $watcher"})
+ unless login_to_id($watcher);
}
+ }
+ push(@changes, $name);
+ SetParam($name, $value);
+ if (($name eq "shutdownhtml") && ($value ne "")) {
+ $vars->{'shutdown_is_active'} = 1;
+ }
+ if ($name eq 'duplicate_or_move_bug_status') {
+ Bugzilla::Status::add_missing_bug_status_transitions($value);
+ }
}
+ }
- $vars->{'message'} = 'parameters_updated';
- $vars->{'param_changed'} = \@changes;
+ $vars->{'message'} = 'parameters_updated';
+ $vars->{'param_changed'} = \@changes;
- write_params();
- delete_token($token);
+ write_params();
+ delete_token($token);
}
$vars->{'token'} = issue_session_token('edit_parameters');
$template->process("admin/params/editparams.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ || ThrowTemplateError($template->error());
diff --git a/editproducts.cgi b/editproducts.cgi
index 62aa1206d..f98225e6f 100755
--- a/editproducts.cgi
+++ b/editproducts.cgi
@@ -26,13 +26,14 @@ use Bugzilla::Token;
# Preliminary checks:
#
-my $user = Bugzilla->login(LOGIN_REQUIRED);
+my $user = Bugzilla->login(LOGIN_REQUIRED);
my $whoid = $user->id;
-my $dbh = Bugzilla->dbh;
-my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
+
# Remove this as soon as the documentation about products has been
# improved and each action has its own section.
$vars->{'doc_section'} = 'administering/categorization.html#products';
@@ -41,43 +42,42 @@ print $cgi->header();
$user->in_group('editcomponents')
|| scalar(@{$user->get_products_by_permission('editcomponents')})
- || ThrowUserError("auth_failure", {group => "editcomponents",
- action => "edit",
- object => "products"});
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "edit", object => "products"});
#
# often used variables
#
my $classification_name = trim($cgi->param('classification') || '');
-my $product_name = trim($cgi->param('product') || '');
-my $action = trim($cgi->param('action') || '');
-my $token = $cgi->param('token');
+my $product_name = trim($cgi->param('product') || '');
+my $action = trim($cgi->param('action') || '');
+my $token = $cgi->param('token');
#
# product = '' -> Show nice list of classifications (if
# classifications enabled)
#
-if (Bugzilla->params->{'useclassification'}
- && !$classification_name
- && !$product_name)
+if ( Bugzilla->params->{'useclassification'}
+ && !$classification_name
+ && !$product_name)
{
- my $class;
- if ($user->in_group('editcomponents')) {
- $class = [Bugzilla::Classification->get_all];
- }
- else {
- # Only keep classifications containing at least one product
- # which you can administer.
- my $products = $user->get_products_by_permission('editcomponents');
- my %class_ids = map { $_->classification_id => 1 } @$products;
- $class = Bugzilla::Classification->new_from_list([keys %class_ids]);
- }
- $vars->{'classifications'} = $class;
-
- $template->process("admin/products/list-classifications.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my $class;
+ if ($user->in_group('editcomponents')) {
+ $class = [Bugzilla::Classification->get_all];
+ }
+ else {
+ # Only keep classifications containing at least one product
+ # which you can administer.
+ my $products = $user->get_products_by_permission('editcomponents');
+ my %class_ids = map { $_->classification_id => 1 } @$products;
+ $class = Bugzilla::Classification->new_from_list([keys %class_ids]);
+ }
+ $vars->{'classifications'} = $class;
+
+ $template->process("admin/products/list-classifications.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
@@ -87,36 +87,35 @@ if (Bugzilla->params->{'useclassification'}
#
if (!$action && !$product_name) {
- my $classification;
- my $products;
-
+ my $classification;
+ my $products;
+
+ if (Bugzilla->params->{'useclassification'}) {
+ $classification = Bugzilla::Classification->check($classification_name);
+ $products = $user->get_selectable_products($classification->id);
+ $vars->{'classification'} = $classification;
+ }
+ else {
+ $products = $user->get_selectable_products;
+ }
+
+ # If the user has editcomponents privs for some products only,
+ # we have to restrict the list of products to display.
+ unless ($user->in_group('editcomponents')) {
+ $products = $user->get_products_by_permission('editcomponents');
if (Bugzilla->params->{'useclassification'}) {
- $classification = Bugzilla::Classification->check($classification_name);
- $products = $user->get_selectable_products($classification->id);
- $vars->{'classification'} = $classification;
- } else {
- $products = $user->get_selectable_products;
- }
-
- # If the user has editcomponents privs for some products only,
- # we have to restrict the list of products to display.
- unless ($user->in_group('editcomponents')) {
- $products = $user->get_products_by_permission('editcomponents');
- if (Bugzilla->params->{'useclassification'}) {
- @$products = grep {$_->classification_id == $classification->id} @$products;
- }
+ @$products = grep { $_->classification_id == $classification->id } @$products;
}
- $vars->{'products'} = $products;
- $vars->{'showbugcounts'} = $cgi->param('showbugcounts') ? 1 : 0;
+ }
+ $vars->{'products'} = $products;
+ $vars->{'showbugcounts'} = $cgi->param('showbugcounts') ? 1 : 0;
- $template->process("admin/products/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/products/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
-
-
#
# action='add' -> present form for parameters for new product
#
@@ -124,23 +123,23 @@ if (!$action && !$product_name) {
#
if ($action eq 'add') {
- # The user must have the global editcomponents privs to add
- # new products.
- $user->in_group('editcomponents')
- || ThrowUserError("auth_failure", {group => "editcomponents",
- action => "add",
- object => "products"});
- if (Bugzilla->params->{'useclassification'}) {
- my $classification = Bugzilla::Classification->check($classification_name);
- $vars->{'classification'} = $classification;
- }
- $vars->{'token'} = issue_session_token('add_product');
+ # The user must have the global editcomponents privs to add
+ # new products.
+ $user->in_group('editcomponents')
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "add", object => "products"});
- $template->process("admin/products/create.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ if (Bugzilla->params->{'useclassification'}) {
+ my $classification = Bugzilla::Classification->check($classification_name);
+ $vars->{'classification'} = $classification;
+ }
+ $vars->{'token'} = issue_session_token('add_product');
+
+ $template->process("admin/products/create.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
- exit;
+ exit;
}
@@ -149,61 +148,62 @@ if ($action eq 'add') {
#
if ($action eq 'new') {
- # The user must have the global editcomponents privs to add
- # new products.
- $user->in_group('editcomponents')
- || ThrowUserError("auth_failure", {group => "editcomponents",
- action => "add",
- object => "products"});
-
- check_token_data($token, 'add_product');
-
- Bugzilla::User::match_field ({
- 'initialowner' => { 'type' => 'single' },
- 'initialqacontact' => { 'type' => 'single' },
- 'initialcc' => { 'type' => 'multi' },
- });
-
- my %product_create_params = (
- classification => $classification_name,
- name => $product_name,
- description => scalar $cgi->param('description'),
- version => scalar $cgi->param('version'),
- defaultmilestone => scalar $cgi->param('defaultmilestone'),
- isactive => scalar $cgi->param('is_active'),
- create_series => scalar $cgi->param('createseries'),
- allows_unconfirmed => scalar $cgi->param('allows_unconfirmed'),
- );
-
- $dbh->bz_start_transaction();
- my $product = Bugzilla::Product->create(\%product_create_params);
- my @initial_cc = $cgi->param('initialcc');
- my %component_create_params = (
- product => $product,
- name => trim($cgi->param('component') || ''),
- description => scalar $cgi->param('comp_desc'),
- initialowner => scalar $cgi->param('initialowner'),
- initialqacontact => scalar $cgi->param('initialqacontact'),
- initial_cc => \@initial_cc,
- create_series => scalar $cgi->param('createseries'),
- );
-
- Bugzilla::Component->create(\%component_create_params);
- $dbh->bz_commit_transaction();
-
- delete_token($token);
-
- $vars->{'message'} = 'product_created';
- $vars->{'product'} = $product;
- if (Bugzilla->params->{'useclassification'}) {
- $vars->{'classification'} = new Bugzilla::Classification($product->classification_id);
- }
- $vars->{'token'} = issue_session_token('edit_product');
-
- $template->process("admin/products/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ # The user must have the global editcomponents privs to add
+ # new products.
+ $user->in_group('editcomponents')
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "add", object => "products"});
+
+ check_token_data($token, 'add_product');
+
+ Bugzilla::User::match_field({
+ 'initialowner' => {'type' => 'single'},
+ 'initialqacontact' => {'type' => 'single'},
+ 'initialcc' => {'type' => 'multi'},
+ });
+
+ my %product_create_params = (
+ classification => $classification_name,
+ name => $product_name,
+ description => scalar $cgi->param('description'),
+ version => scalar $cgi->param('version'),
+ defaultmilestone => scalar $cgi->param('defaultmilestone'),
+ isactive => scalar $cgi->param('is_active'),
+ create_series => scalar $cgi->param('createseries'),
+ allows_unconfirmed => scalar $cgi->param('allows_unconfirmed'),
+ );
+
+ $dbh->bz_start_transaction();
+ my $product = Bugzilla::Product->create(\%product_create_params);
+
+ my @initial_cc = $cgi->param('initialcc');
+ my %component_create_params = (
+ product => $product,
+ name => trim($cgi->param('component') || ''),
+ description => scalar $cgi->param('comp_desc'),
+ initialowner => scalar $cgi->param('initialowner'),
+ initialqacontact => scalar $cgi->param('initialqacontact'),
+ initial_cc => \@initial_cc,
+ create_series => scalar $cgi->param('createseries'),
+ );
+
+ Bugzilla::Component->create(\%component_create_params);
+ $dbh->bz_commit_transaction();
+
+ delete_token($token);
+
+ $vars->{'message'} = 'product_created';
+ $vars->{'product'} = $product;
+ if (Bugzilla->params->{'useclassification'}) {
+ $vars->{'classification'}
+ = new Bugzilla::Classification($product->classification_id);
+ }
+ $vars->{'token'} = issue_session_token('edit_product');
+
+ $template->process("admin/products/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -213,19 +213,20 @@ if ($action eq 'new') {
#
if ($action eq 'del') {
- my $product = $user->check_can_admin_product($product_name);
+ my $product = $user->check_can_admin_product($product_name);
- if (Bugzilla->params->{'useclassification'}) {
- $vars->{'classification'} = new Bugzilla::Classification($product->classification_id);
- }
- $vars->{'product'} = $product;
- $vars->{'token'} = issue_session_token('delete_product');
-
- Bugzilla::Hook::process('product_confirm_delete', { vars => $vars });
-
- $template->process("admin/products/confirm-delete.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ if (Bugzilla->params->{'useclassification'}) {
+ $vars->{'classification'}
+ = new Bugzilla::Classification($product->classification_id);
+ }
+ $vars->{'product'} = $product;
+ $vars->{'token'} = issue_session_token('delete_product');
+
+ Bugzilla::Hook::process('product_confirm_delete', {vars => $vars});
+
+ $template->process("admin/products/confirm-delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -233,36 +234,40 @@ if ($action eq 'del') {
#
if ($action eq 'delete') {
- my $product = $user->check_can_admin_product($product_name);
- check_token_data($token, 'delete_product');
+ my $product = $user->check_can_admin_product($product_name);
+ check_token_data($token, 'delete_product');
- $product->remove_from_db({ delete_series => scalar $cgi->param('delete_series')});
- delete_token($token);
+ $product->remove_from_db(
+ {delete_series => scalar $cgi->param('delete_series')});
+ delete_token($token);
- $vars->{'message'} = 'product_deleted';
- $vars->{'product'} = $product;
- $vars->{'no_edit_product_link'} = 1;
+ $vars->{'message'} = 'product_deleted';
+ $vars->{'product'} = $product;
+ $vars->{'no_edit_product_link'} = 1;
- if (Bugzilla->params->{'useclassification'}) {
- $vars->{'classifications'} = $user->in_group('editcomponents') ?
- [Bugzilla::Classification->get_all] : $user->get_selectable_classifications;
+ if (Bugzilla->params->{'useclassification'}) {
+ $vars->{'classifications'}
+ = $user->in_group('editcomponents')
+ ? [Bugzilla::Classification->get_all]
+ : $user->get_selectable_classifications;
- $template->process("admin/products/list-classifications.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- }
- else {
- my $products = $user->get_selectable_products;
- # If the user has editcomponents privs for some products only,
- # we have to restrict the list of products to display.
- unless ($user->in_group('editcomponents')) {
- $products = $user->get_products_by_permission('editcomponents');
- }
- $vars->{'products'} = $products;
-
- $template->process("admin/products/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $template->process("admin/products/list-classifications.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ }
+ else {
+ my $products = $user->get_selectable_products;
+
+ # If the user has editcomponents privs for some products only,
+ # we have to restrict the list of products to display.
+ unless ($user->in_group('editcomponents')) {
+ $products = $user->get_products_by_permission('editcomponents');
}
- exit;
+ $vars->{'products'} = $products;
+
+ $template->process("admin/products/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ }
+ exit;
}
#
@@ -273,48 +278,50 @@ if ($action eq 'delete') {
#
if ($action eq 'edit' || (!$action && $product_name)) {
- my $product = $user->check_can_admin_product($product_name);
-
- if (Bugzilla->params->{'useclassification'}) {
- $vars->{'classification'} = new Bugzilla::Classification($product->classification_id);
- }
- $vars->{'product'} = $product;
- $vars->{'token'} = issue_session_token('edit_product');
-
- $template->process("admin/products/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my $product = $user->check_can_admin_product($product_name);
+
+ if (Bugzilla->params->{'useclassification'}) {
+ $vars->{'classification'}
+ = new Bugzilla::Classification($product->classification_id);
+ }
+ $vars->{'product'} = $product;
+ $vars->{'token'} = issue_session_token('edit_product');
+
+ $template->process("admin/products/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
# action='update' -> update the product
#
if ($action eq 'update') {
- check_token_data($token, 'edit_product');
- my $product_old_name = trim($cgi->param('product_old_name') || '');
- my $product = $user->check_can_admin_product($product_old_name);
-
- $product->set_all({
- name => $product_name,
- description => scalar $cgi->param('description'),
- is_active => scalar $cgi->param('is_active'),
- allows_unconfirmed => scalar $cgi->param('allows_unconfirmed'),
- default_milestone => scalar $cgi->param('defaultmilestone'),
- });
-
- my $changes = $product->update();
-
- delete_token($token);
-
- if (Bugzilla->params->{'useclassification'}) {
- $vars->{'classification'} = new Bugzilla::Classification($product->classification_id);
- }
- $vars->{'product'} = $product;
- $vars->{'changes'} = $changes;
-
- $template->process("admin/products/updated.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ check_token_data($token, 'edit_product');
+ my $product_old_name = trim($cgi->param('product_old_name') || '');
+ my $product = $user->check_can_admin_product($product_old_name);
+
+ $product->set_all({
+ name => $product_name,
+ description => scalar $cgi->param('description'),
+ is_active => scalar $cgi->param('is_active'),
+ allows_unconfirmed => scalar $cgi->param('allows_unconfirmed'),
+ default_milestone => scalar $cgi->param('defaultmilestone'),
+ });
+
+ my $changes = $product->update();
+
+ delete_token($token);
+
+ if (Bugzilla->params->{'useclassification'}) {
+ $vars->{'classification'}
+ = new Bugzilla::Classification($product->classification_id);
+ }
+ $vars->{'product'} = $product;
+ $vars->{'changes'} = $changes;
+
+ $template->process("admin/products/updated.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -322,14 +329,14 @@ if ($action eq 'update') {
#
if ($action eq 'editgroupcontrols') {
- my $product = $user->check_can_admin_product($product_name);
+ my $product = $user->check_can_admin_product($product_name);
- $vars->{'product'} = $product;
- $vars->{'token'} = issue_session_token('edit_group_controls');
+ $vars->{'product'} = $product;
+ $vars->{'token'} = issue_session_token('edit_group_controls');
- $template->process("admin/products/groupcontrol/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/products/groupcontrol/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
@@ -337,44 +344,45 @@ if ($action eq 'editgroupcontrols') {
#
if ($action eq 'updategroupcontrols') {
- my $product = $user->check_can_admin_product($product_name);
- check_token_data($token, 'edit_group_controls');
-
- my @now_na = ();
- my @now_mandatory = ();
- foreach my $f ($cgi->param()) {
- if ($f =~ /^membercontrol_(\d+)$/) {
- my $id = $1;
- if ($cgi->param($f) == CONTROLMAPNA) {
- push @now_na,$id;
- } elsif ($cgi->param($f) == CONTROLMAPMANDATORY) {
- push @now_mandatory,$id;
- }
- }
+ my $product = $user->check_can_admin_product($product_name);
+ check_token_data($token, 'edit_group_controls');
+
+ my @now_na = ();
+ my @now_mandatory = ();
+ foreach my $f ($cgi->param()) {
+ if ($f =~ /^membercontrol_(\d+)$/) {
+ my $id = $1;
+ if ($cgi->param($f) == CONTROLMAPNA) {
+ push @now_na, $id;
+ }
+ elsif ($cgi->param($f) == CONTROLMAPMANDATORY) {
+ push @now_mandatory, $id;
+ }
}
- if (!defined $cgi->param('confirmed')) {
- my $na_groups;
- if (@now_na) {
- $na_groups = $dbh->selectall_arrayref(
- 'SELECT groups.name, COUNT(bugs.bug_id) AS count
+ }
+ if (!defined $cgi->param('confirmed')) {
+ my $na_groups;
+ if (@now_na) {
+ $na_groups = $dbh->selectall_arrayref(
+ 'SELECT groups.name, COUNT(bugs.bug_id) AS count
FROM bugs
INNER JOIN bug_group_map
ON bug_group_map.bug_id = bugs.bug_id
INNER JOIN groups
ON bug_group_map.group_id = groups.id
WHERE groups.id IN (' . join(', ', @now_na) . ')
- AND bugs.product_id = ? ' .
- $dbh->sql_group_by('groups.name'),
- {'Slice' => {}}, $product->id);
- }
-
- # return the mandatory groups which need to have bug entries
- # added to the bug_group_map and the corresponding bug count
-
- my $mandatory_groups;
- if (@now_mandatory) {
- $mandatory_groups = $dbh->selectall_arrayref(
- 'SELECT groups.name,
+ AND bugs.product_id = ? ' . $dbh->sql_group_by('groups.name'),
+ {'Slice' => {}}, $product->id
+ );
+ }
+
+ # return the mandatory groups which need to have bug entries
+ # added to the bug_group_map and the corresponding bug count
+
+ my $mandatory_groups;
+ if (@now_mandatory) {
+ $mandatory_groups = $dbh->selectall_arrayref(
+ 'SELECT groups.name,
(SELECT COUNT(bugs.bug_id)
FROM bugs
WHERE bugs.product_id = ?
@@ -384,46 +392,51 @@ if ($action eq 'updategroupcontrols') {
AS count
FROM groups
WHERE groups.id IN (' . join(', ', @now_mandatory) . ')
- ORDER BY groups.name',
- {'Slice' => {}}, $product->id);
- # remove zero counts
- @$mandatory_groups = grep { $_->{count} } @$mandatory_groups;
-
- }
- if (($na_groups && scalar(@$na_groups))
- || ($mandatory_groups && scalar(@$mandatory_groups)))
- {
- $vars->{'product'} = $product;
- $vars->{'na_groups'} = $na_groups;
- $vars->{'mandatory_groups'} = $mandatory_groups;
- $template->process("admin/products/groupcontrol/confirm-edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
- }
+ ORDER BY groups.name', {'Slice' => {}}, $product->id
+ );
+
+ # remove zero counts
+ @$mandatory_groups = grep { $_->{count} } @$mandatory_groups;
- my $groups = Bugzilla::Group->match({isactive => 1, isbuggroup => 1});
- foreach my $group (@$groups) {
- my $group_id = $group->id;
- $product->set_group_controls($group,
- {entry => scalar $cgi->param("entry_$group_id") || 0,
- membercontrol => scalar $cgi->param("membercontrol_$group_id") || CONTROLMAPNA,
- othercontrol => scalar $cgi->param("othercontrol_$group_id") || CONTROLMAPNA,
- canedit => scalar $cgi->param("canedit_$group_id") || 0,
- editcomponents => scalar $cgi->param("editcomponents_$group_id") || 0,
- editbugs => scalar $cgi->param("editbugs_$group_id") || 0,
- canconfirm => scalar $cgi->param("canconfirm_$group_id") || 0});
}
- my $changes = $product->update;
+ if ( ($na_groups && scalar(@$na_groups))
+ || ($mandatory_groups && scalar(@$mandatory_groups)))
+ {
+ $vars->{'product'} = $product;
+ $vars->{'na_groups'} = $na_groups;
+ $vars->{'mandatory_groups'} = $mandatory_groups;
+ $template->process("admin/products/groupcontrol/confirm-edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+ }
+
+ my $groups = Bugzilla::Group->match({isactive => 1, isbuggroup => 1});
+ foreach my $group (@$groups) {
+ my $group_id = $group->id;
+ $product->set_group_controls(
+ $group,
+ {
+ entry => scalar $cgi->param("entry_$group_id") || 0,
+ membercontrol => scalar $cgi->param("membercontrol_$group_id") || CONTROLMAPNA,
+ othercontrol => scalar $cgi->param("othercontrol_$group_id") || CONTROLMAPNA,
+ canedit => scalar $cgi->param("canedit_$group_id") || 0,
+ editcomponents => scalar $cgi->param("editcomponents_$group_id") || 0,
+ editbugs => scalar $cgi->param("editbugs_$group_id") || 0,
+ canconfirm => scalar $cgi->param("canconfirm_$group_id") || 0
+ }
+ );
+ }
+ my $changes = $product->update;
- delete_token($token);
+ delete_token($token);
- $vars->{'product'} = $product;
- $vars->{'changes'} = $changes;
+ $vars->{'product'} = $product;
+ $vars->{'changes'} = $changes;
- $template->process("admin/products/groupcontrol/updated.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("admin/products/groupcontrol/updated.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
# No valid action found
diff --git a/editsettings.cgi b/editsettings.cgi
index b10a497ba..3f0ab6827 100755
--- a/editsettings.cgi
+++ b/editsettings.cgi
@@ -20,48 +20,47 @@ use Bugzilla::User::Setting;
use Bugzilla::Token;
my $template = Bugzilla->template;
-my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $cgi = Bugzilla->cgi;
-my $vars = {};
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+my $cgi = Bugzilla->cgi;
+my $vars = {};
print $cgi->header;
$user->in_group('tweakparams')
- || ThrowUserError("auth_failure", {group => "tweakparams",
- action => "modify",
- object => "settings"});
+ || ThrowUserError("auth_failure",
+ {group => "tweakparams", action => "modify", object => "settings"});
my $action = trim($cgi->param('action') || '');
-my $token = $cgi->param('token');
+my $token = $cgi->param('token');
if ($action eq 'update') {
- check_token_data($token, 'edit_settings');
- my $settings = Bugzilla::User::Setting::get_defaults();
- my $changed = 0;
+ check_token_data($token, 'edit_settings');
+ my $settings = Bugzilla::User::Setting::get_defaults();
+ my $changed = 0;
- foreach my $name (keys %$settings) {
- my $old_enabled = $settings->{$name}->{'is_enabled'};
- my $old_value = $settings->{$name}->{'default_value'};
- my $enabled = defined $cgi->param("${name}-enabled") || 0;
- my $value = $cgi->param("${name}");
- my $setting = new Bugzilla::User::Setting($name);
+ foreach my $name (keys %$settings) {
+ my $old_enabled = $settings->{$name}->{'is_enabled'};
+ my $old_value = $settings->{$name}->{'default_value'};
+ my $enabled = defined $cgi->param("${name}-enabled") || 0;
+ my $value = $cgi->param("${name}");
+ my $setting = new Bugzilla::User::Setting($name);
- $setting->validate_value($value);
+ $setting->validate_value($value);
- if ($old_enabled != $enabled || $old_value ne $value) {
- Bugzilla::User::Setting::set_default($name, $value, $enabled);
- $changed = 1;
- }
+ if ($old_enabled != $enabled || $old_value ne $value) {
+ Bugzilla::User::Setting::set_default($name, $value, $enabled);
+ $changed = 1;
}
- $vars->{'message'} = 'default_settings_updated';
- $vars->{'changes_saved'} = $changed;
- Bugzilla->memcached->clear_config();
- delete_token($token);
+ }
+ $vars->{'message'} = 'default_settings_updated';
+ $vars->{'changes_saved'} = $changed;
+ Bugzilla->memcached->clear_config();
+ delete_token($token);
}
# Don't use $settings as defaults may have changed.
$vars->{'settings'} = Bugzilla::User::Setting::get_defaults();
-$vars->{'token'} = issue_session_token('edit_settings');
+$vars->{'token'} = issue_session_token('edit_settings');
$template->process("admin/settings/edit.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
diff --git a/editusers.cgi b/editusers.cgi
index 37665b12d..4db7e4441 100755
--- a/editusers.cgi
+++ b/editusers.cgi
@@ -32,15 +32,18 @@ my $template = Bugzilla->template;
my $dbh = Bugzilla->dbh;
my $userid = $user->id;
my $editusers = $user->in_group('editusers');
-local our $vars = {};
+local our $vars = {};
# Reject access if there is no sense in continuing.
-$editusers
- || $user->can_bless()
- || ThrowUserError("auth_failure", {group => "editusers",
- reason => "cant_bless",
- action => "edit",
- object => "users"});
+$editusers || $user->can_bless() || ThrowUserError(
+ "auth_failure",
+ {
+ group => "editusers",
+ reason => "cant_bless",
+ action => "edit",
+ object => "users"
+ }
+);
print $cgi->header();
@@ -55,550 +58,579 @@ $vars->{'editusers'} = $editusers;
mirrorListSelectionValues();
Bugzilla::Hook::process('admin_editusers_action',
- { vars => $vars, user => $user, action => $action });
+ {vars => $vars, user => $user, action => $action});
###########################################################################
if ($action eq 'search') {
- # Allow to restrict the search to any group the user is allowed to bless.
- $vars->{'restrictablegroups'} = $user->bless_groups();
- $template->process('admin/users/search.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
-###########################################################################
-} elsif ($action eq 'list') {
- my $matchvalue = $cgi->param('matchvalue') || '';
- my $matchstr = trim($cgi->param('matchstr'));
- my $matchtype = $cgi->param('matchtype');
- my $grouprestrict = $cgi->param('grouprestrict') || '0';
- # 0 = disabled only, 1 = enabled only, 2 = everyone
- my $is_enabled = $cgi->param('is_enabled') // 2;
- my $query = 'SELECT DISTINCT userid, login_name, realname, is_enabled, ' .
- $dbh->sql_date_format('last_seen_date', '%Y-%m-%d') . ' AS last_seen_date ' .
- 'FROM profiles';
- my @bindValues;
- my $nextCondition;
- my $visibleGroups;
-
- # If a group ID is given, make sure it is a valid one.
- my $group;
- if ($grouprestrict) {
- $group = new Bugzilla::Group(scalar $cgi->param('groupid'));
- $group || ThrowUserError('invalid_group_ID');
- }
+ # Allow to restrict the search to any group the user is allowed to bless.
+ $vars->{'restrictablegroups'} = $user->bless_groups();
+ $template->process('admin/users/search.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
- if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) {
- # Show only users in visible groups.
- $visibleGroups = $user->visible_groups_as_string();
-
- if ($visibleGroups) {
- $query .= qq{, user_group_map AS ugm
+###########################################################################
+}
+elsif ($action eq 'list') {
+ my $matchvalue = $cgi->param('matchvalue') || '';
+ my $matchstr = trim($cgi->param('matchstr'));
+ my $matchtype = $cgi->param('matchtype');
+ my $grouprestrict = $cgi->param('grouprestrict') || '0';
+
+ # 0 = disabled only, 1 = enabled only, 2 = everyone
+ my $is_enabled = $cgi->param('is_enabled') // 2;
+ my $query
+ = 'SELECT DISTINCT userid, login_name, realname, is_enabled, '
+ . $dbh->sql_date_format('last_seen_date', '%Y-%m-%d')
+ . ' AS last_seen_date '
+ . 'FROM profiles';
+ my @bindValues;
+ my $nextCondition;
+ my $visibleGroups;
+
+ # If a group ID is given, make sure it is a valid one.
+ my $group;
+ if ($grouprestrict) {
+ $group = new Bugzilla::Group(scalar $cgi->param('groupid'));
+ $group || ThrowUserError('invalid_group_ID');
+ }
+
+ if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) {
+
+ # Show only users in visible groups.
+ $visibleGroups = $user->visible_groups_as_string();
+
+ if ($visibleGroups) {
+ $query .= qq{, user_group_map AS ugm
WHERE ugm.user_id = profiles.userid
AND ugm.isbless = 0
AND ugm.group_id IN ($visibleGroups)
};
- $nextCondition = 'AND';
- }
- } else {
- $visibleGroups = 1;
- if ($grouprestrict eq '1') {
- $query .= qq{, user_group_map AS ugm
+ $nextCondition = 'AND';
+ }
+ }
+ else {
+ $visibleGroups = 1;
+ if ($grouprestrict eq '1') {
+ $query .= qq{, user_group_map AS ugm
WHERE ugm.user_id = profiles.userid
AND ugm.isbless = 0
};
- $nextCondition = 'AND';
- }
- else {
- $nextCondition = 'WHERE';
- }
- }
-
- if (!$visibleGroups) {
- $vars->{'users'} = {};
+ $nextCondition = 'AND';
}
else {
- # Handle selection by login name, real name, or userid.
- if (defined($matchtype)) {
- $query .= " $nextCondition ";
- my $expr = "";
- if ($matchvalue eq 'userid') {
- if ($matchstr) {
- my $stored_matchstr = $matchstr;
- detaint_natural($matchstr)
- || ThrowUserError('illegal_user_id', {userid => $stored_matchstr});
- }
- $expr = "profiles.userid";
- } elsif ($matchvalue eq 'realname') {
- $expr = "profiles.realname";
- } else {
- $expr = "profiles.login_name";
- }
-
- if ($matchtype =~ /^(regexp|notregexp|exact)$/) {
- $matchstr ||= '.';
- }
- else {
- $matchstr = '' unless defined $matchstr;
- }
- # We can trick_taint because we use the value in a SELECT only,
- # using a placeholder.
- trick_taint($matchstr);
-
- if ($matchtype eq 'regexp') {
- $query .= $dbh->sql_regexp($expr, '?', 0, $dbh->quote($matchstr));
- } elsif ($matchtype eq 'notregexp') {
- $query .= $dbh->sql_not_regexp($expr, '?', 0, $dbh->quote($matchstr));
- } elsif ($matchtype eq 'exact') {
- $query .= $expr . ' = ?';
- } else { # substr or unknown
- $query .= $dbh->sql_iposition('?', $expr) . ' > 0';
- }
- $nextCondition = 'AND';
- push(@bindValues, $matchstr);
+ $nextCondition = 'WHERE';
+ }
+ }
+
+ if (!$visibleGroups) {
+ $vars->{'users'} = {};
+ }
+ else {
+ # Handle selection by login name, real name, or userid.
+ if (defined($matchtype)) {
+ $query .= " $nextCondition ";
+ my $expr = "";
+ if ($matchvalue eq 'userid') {
+ if ($matchstr) {
+ my $stored_matchstr = $matchstr;
+ detaint_natural($matchstr)
+ || ThrowUserError('illegal_user_id', {userid => $stored_matchstr});
}
+ $expr = "profiles.userid";
+ }
+ elsif ($matchvalue eq 'realname') {
+ $expr = "profiles.realname";
+ }
+ else {
+ $expr = "profiles.login_name";
+ }
+
+ if ($matchtype =~ /^(regexp|notregexp|exact)$/) {
+ $matchstr ||= '.';
+ }
+ else {
+ $matchstr = '' unless defined $matchstr;
+ }
+
+ # We can trick_taint because we use the value in a SELECT only,
+ # using a placeholder.
+ trick_taint($matchstr);
+
+ if ($matchtype eq 'regexp') {
+ $query .= $dbh->sql_regexp($expr, '?', 0, $dbh->quote($matchstr));
+ }
+ elsif ($matchtype eq 'notregexp') {
+ $query .= $dbh->sql_not_regexp($expr, '?', 0, $dbh->quote($matchstr));
+ }
+ elsif ($matchtype eq 'exact') {
+ $query .= $expr . ' = ?';
+ }
+ else { # substr or unknown
+ $query .= $dbh->sql_iposition('?', $expr) . ' > 0';
+ }
+ $nextCondition = 'AND';
+ push(@bindValues, $matchstr);
+ }
- # Handle selection by group.
- if ($grouprestrict eq '1') {
- my $grouplist = join(',',
- @{Bugzilla::Group->flatten_group_membership($group->id)});
- $query .= " $nextCondition ugm.group_id IN($grouplist) ";
- }
+ # Handle selection by group.
+ if ($grouprestrict eq '1') {
+ my $grouplist
+ = join(',', @{Bugzilla::Group->flatten_group_membership($group->id)});
+ $query .= " $nextCondition ugm.group_id IN($grouplist) ";
+ }
- detaint_natural($is_enabled);
- if ($is_enabled && ($is_enabled == 0 || $is_enabled == 1)) {
- $query .= " $nextCondition profiles.is_enabled = ?";
- $nextCondition = 'AND';
- push(@bindValues, $is_enabled);
- }
+ detaint_natural($is_enabled);
+ if ($is_enabled && ($is_enabled == 0 || $is_enabled == 1)) {
+ $query .= " $nextCondition profiles.is_enabled = ?";
+ $nextCondition = 'AND';
+ push(@bindValues, $is_enabled);
+ }
- $query .= ' ORDER BY profiles.login_name';
+ $query .= ' ORDER BY profiles.login_name';
- $vars->{'users'} = $dbh->selectall_arrayref($query,
- {'Slice' => {}},
- @bindValues);
+ $vars->{'users'}
+ = $dbh->selectall_arrayref($query, {'Slice' => {}}, @bindValues);
- }
+ }
- if ($matchtype && $matchtype eq 'exact' && scalar(@{$vars->{'users'}}) == 1) {
- my $match_user_id = $vars->{'users'}[0]->{'userid'};
- my $match_user = check_user($match_user_id);
- edit_processing($match_user);
- } else {
- $template->process('admin/users/list.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
- }
+ if ($matchtype && $matchtype eq 'exact' && scalar(@{$vars->{'users'}}) == 1) {
+ my $match_user_id = $vars->{'users'}[0]->{'userid'};
+ my $match_user = check_user($match_user_id);
+ edit_processing($match_user);
+ }
+ else {
+ $template->process('admin/users/list.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+ }
###########################################################################
-} elsif ($action eq 'add') {
- $editusers || ThrowUserError("auth_failure", {group => "editusers",
- action => "add",
- object => "users"});
+}
+elsif ($action eq 'add') {
+ $editusers
+ || ThrowUserError("auth_failure",
+ {group => "editusers", action => "add", object => "users"});
- $vars->{'token'} = issue_session_token('add_user');
+ $vars->{'token'} = issue_session_token('add_user');
- $template->process('admin/users/create.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ $template->process('admin/users/create.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
###########################################################################
-} elsif ($action eq 'new') {
- $editusers || ThrowUserError("auth_failure", {group => "editusers",
- action => "add",
- object => "users"});
-
- check_token_data($token, 'add_user');
-
- # When e.g. the 'Env' auth method is used, the password field
- # is not displayed. In that case, set the password to *.
- my $password = $cgi->param('password');
- $password = '*' if !defined $password;
-
- my $new_user = Bugzilla::User->create({
- login_name => scalar $cgi->param('login'),
- cryptpassword => $password,
- realname => scalar $cgi->param('name'),
- disabledtext => scalar $cgi->param('disabledtext'),
- disable_mail => scalar $cgi->param('disable_mail'),
- extern_id => scalar $cgi->param('extern_id'),
- });
-
- userDataToVars($new_user->id);
-
- delete_token($token);
-
- if ($cgi->param('notify_user')) {
- $vars->{'new_user'} = $new_user;
- my $message;
-
- $template->process('email/new-user-details.txt.tmpl', $vars, \$message)
- || ThrowTemplateError($template->error());
- MessageToMTA($message);
- }
-
- # We already display the updated page. We have to recreate a token now.
- $vars->{'token'} = issue_session_token('edit_user');
- $vars->{'message'} = 'account_created';
- $template->process('admin/users/edit.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+}
+elsif ($action eq 'new') {
+ $editusers
+ || ThrowUserError("auth_failure",
+ {group => "editusers", action => "add", object => "users"});
+
+ check_token_data($token, 'add_user');
+
+ # When e.g. the 'Env' auth method is used, the password field
+ # is not displayed. In that case, set the password to *.
+ my $password = $cgi->param('password');
+ $password = '*' if !defined $password;
+
+ my $new_user = Bugzilla::User->create({
+ login_name => scalar $cgi->param('login'),
+ cryptpassword => $password,
+ realname => scalar $cgi->param('name'),
+ disabledtext => scalar $cgi->param('disabledtext'),
+ disable_mail => scalar $cgi->param('disable_mail'),
+ extern_id => scalar $cgi->param('extern_id'),
+ });
+
+ userDataToVars($new_user->id);
+
+ delete_token($token);
+
+ if ($cgi->param('notify_user')) {
+ $vars->{'new_user'} = $new_user;
+ my $message;
+
+ $template->process('email/new-user-details.txt.tmpl', $vars, \$message)
+ || ThrowTemplateError($template->error());
+ MessageToMTA($message);
+ }
+
+ # We already display the updated page. We have to recreate a token now.
+ $vars->{'token'} = issue_session_token('edit_user');
+ $vars->{'message'} = 'account_created';
+ $template->process('admin/users/edit.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
###########################################################################
-} elsif ($action eq 'edit') {
- my $otherUser = check_user($otherUserID, $otherUserLogin);
- edit_processing($otherUser);
+}
+elsif ($action eq 'edit') {
+ my $otherUser = check_user($otherUserID, $otherUserLogin);
+ edit_processing($otherUser);
###########################################################################
-} elsif ($action eq 'update') {
- check_token_data($token, 'edit_user');
- my $otherUser = check_user($otherUserID, $otherUserLogin);
- $otherUserID = $otherUser->id;
-
- # Lock tables during the check+update session.
- $dbh->bz_start_transaction();
-
- $editusers || $user->can_see_user($otherUser)
- || ThrowUserError('auth_failure', {reason => "not_visible",
- action => "modify",
- object => "user"});
-
- $vars->{'loginold'} = $otherUser->login;
-
- # Update groups
- my @group_ids = grep { s/group_// } keys %{ Bugzilla->cgi->Vars };
- $otherUser->set_groups({ set => \@group_ids });
-
- # Update profiles table entry; silently skip doing this if the user
- # is not authorized.
- my $changes = {};
- if ($editusers) {
- $otherUser->set_login($cgi->param('login'));
- $otherUser->set_name($cgi->param('name'));
- $otherUser->set_password($cgi->param('password'))
- if $cgi->param('password');
- $otherUser->set_disabledtext($cgi->param('disabledtext'));
- $otherUser->set_disable_mail($cgi->param('disable_mail'));
- $otherUser->set_extern_id($cgi->param('extern_id'))
- if defined($cgi->param('extern_id'));
-
- # Update bless groups
- my @bless_ids = grep { s/bless_// } keys %{ Bugzilla->cgi->Vars };
- $otherUser->set_bless_groups({ set => \@bless_ids });
- }
- $changes = $otherUser->update();
-
- $dbh->bz_commit_transaction();
-
- # XXX: userDataToVars may be off when editing ourselves.
- userDataToVars($otherUserID);
- delete_token($token);
-
- $vars->{'message'} = 'account_updated';
- $vars->{'changes'} = \%$changes;
- # We already display the updated page. We have to recreate a token now.
- $vars->{'token'} = issue_session_token('edit_user');
-
- $template->process('admin/users/edit.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+}
+elsif ($action eq 'update') {
+ check_token_data($token, 'edit_user');
+ my $otherUser = check_user($otherUserID, $otherUserLogin);
+ $otherUserID = $otherUser->id;
+
+ # Lock tables during the check+update session.
+ $dbh->bz_start_transaction();
+
+ $editusers
+ || $user->can_see_user($otherUser)
+ || ThrowUserError('auth_failure',
+ {reason => "not_visible", action => "modify", object => "user"});
+
+ $vars->{'loginold'} = $otherUser->login;
+
+ # Update groups
+ my @group_ids = grep {s/group_//} keys %{Bugzilla->cgi->Vars};
+ $otherUser->set_groups({set => \@group_ids});
+
+ # Update profiles table entry; silently skip doing this if the user
+ # is not authorized.
+ my $changes = {};
+ if ($editusers) {
+ $otherUser->set_login($cgi->param('login'));
+ $otherUser->set_name($cgi->param('name'));
+ $otherUser->set_password($cgi->param('password')) if $cgi->param('password');
+ $otherUser->set_disabledtext($cgi->param('disabledtext'));
+ $otherUser->set_disable_mail($cgi->param('disable_mail'));
+ $otherUser->set_extern_id($cgi->param('extern_id'))
+ if defined($cgi->param('extern_id'));
+
+ # Update bless groups
+ my @bless_ids = grep {s/bless_//} keys %{Bugzilla->cgi->Vars};
+ $otherUser->set_bless_groups({set => \@bless_ids});
+ }
+ $changes = $otherUser->update();
+
+ $dbh->bz_commit_transaction();
+
+ # XXX: userDataToVars may be off when editing ourselves.
+ userDataToVars($otherUserID);
+ delete_token($token);
+
+ $vars->{'message'} = 'account_updated';
+ $vars->{'changes'} = \%$changes;
+
+ # We already display the updated page. We have to recreate a token now.
+ $vars->{'token'} = issue_session_token('edit_user');
+
+ $template->process('admin/users/edit.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
###########################################################################
-} elsif ($action eq 'del') {
- my $otherUser = check_user($otherUserID, $otherUserLogin);
- $otherUserID = $otherUser->id;
-
- Bugzilla->params->{'allowuserdeletion'}
- || ThrowUserError('users_deletion_disabled');
- $editusers || ThrowUserError('auth_failure', {group => "editusers",
- action => "delete",
- object => "users"});
- $vars->{'otheruser'} = $otherUser;
-
- # Find other cross references.
- $vars->{'attachments'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM attachments WHERE submitter_id = ?',
- undef, $otherUserID);
- $vars->{'assignee_or_qa'} = $dbh->selectrow_array(
- qq{SELECT COUNT(*)
+}
+elsif ($action eq 'del') {
+ my $otherUser = check_user($otherUserID, $otherUserLogin);
+ $otherUserID = $otherUser->id;
+
+ Bugzilla->params->{'allowuserdeletion'}
+ || ThrowUserError('users_deletion_disabled');
+ $editusers
+ || ThrowUserError('auth_failure',
+ {group => "editusers", action => "delete", object => "users"});
+ $vars->{'otheruser'} = $otherUser;
+
+ # Find other cross references.
+ $vars->{'attachments'}
+ = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM attachments WHERE submitter_id = ?',
+ undef, $otherUserID);
+ $vars->{'assignee_or_qa'} = $dbh->selectrow_array(
+ qq{SELECT COUNT(*)
FROM bugs
- WHERE assigned_to = ? OR qa_contact = ?},
- undef, ($otherUserID, $otherUserID));
- $vars->{'reporter'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM bugs WHERE reporter = ?',
- undef, $otherUserID);
- $vars->{'cc'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM cc WHERE who = ?',
- undef, $otherUserID);
- $vars->{'bugs_activity'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM bugs_activity WHERE who = ?',
- undef, $otherUserID);
- $vars->{'component_cc'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM component_cc WHERE user_id = ?',
- undef, $otherUserID);
- $vars->{'email_setting'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM email_setting WHERE user_id = ?',
- undef, $otherUserID);
- $vars->{'flags'}{'requestee'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM flags WHERE requestee_id = ?',
- undef, $otherUserID);
- $vars->{'flags'}{'setter'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM flags WHERE setter_id = ?',
- undef, $otherUserID);
- $vars->{'longdescs'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM longdescs WHERE who = ?',
- undef, $otherUserID);
- my $namedquery_ids = $dbh->selectcol_arrayref(
- 'SELECT id FROM namedqueries WHERE userid = ?',
- undef, $otherUserID);
- $vars->{'namedqueries'} = scalar(@$namedquery_ids);
- if (scalar(@$namedquery_ids)) {
- $vars->{'namedquery_group_map'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM namedquery_group_map WHERE namedquery_id IN' .
- ' (' . join(', ', @$namedquery_ids) . ')');
- }
- else {
- $vars->{'namedquery_group_map'} = 0;
- }
- $vars->{'profile_setting'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM profile_setting WHERE user_id = ?',
- undef, $otherUserID);
- $vars->{'profiles_activity'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM profiles_activity WHERE who = ? AND userid != ?',
- undef, ($otherUserID, $otherUserID));
- $vars->{'quips'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM quips WHERE userid = ?',
- undef, $otherUserID);
- $vars->{'series'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM series WHERE creator = ?',
- undef, $otherUserID);
- $vars->{'watch'}{'watched'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM watch WHERE watched = ?',
- undef, $otherUserID);
- $vars->{'watch'}{'watcher'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM watch WHERE watcher = ?',
- undef, $otherUserID);
- $vars->{'whine_events'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM whine_events WHERE owner_userid = ?',
- undef, $otherUserID);
- $vars->{'whine_schedules'} = $dbh->selectrow_array(
- qq{SELECT COUNT(distinct eventid)
+ WHERE assigned_to = ? OR qa_contact = ?}, undef,
+ ($otherUserID, $otherUserID)
+ );
+ $vars->{'reporter'}
+ = $dbh->selectrow_array('SELECT COUNT(*) FROM bugs WHERE reporter = ?',
+ undef, $otherUserID);
+ $vars->{'cc'} = $dbh->selectrow_array('SELECT COUNT(*) FROM cc WHERE who = ?',
+ undef, $otherUserID);
+ $vars->{'bugs_activity'}
+ = $dbh->selectrow_array('SELECT COUNT(*) FROM bugs_activity WHERE who = ?',
+ undef, $otherUserID);
+ $vars->{'component_cc'}
+ = $dbh->selectrow_array('SELECT COUNT(*) FROM component_cc WHERE user_id = ?',
+ undef, $otherUserID);
+ $vars->{'email_setting'}
+ = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM email_setting WHERE user_id = ?',
+ undef, $otherUserID);
+ $vars->{'flags'}{'requestee'}
+ = $dbh->selectrow_array('SELECT COUNT(*) FROM flags WHERE requestee_id = ?',
+ undef, $otherUserID);
+ $vars->{'flags'}{'setter'}
+ = $dbh->selectrow_array('SELECT COUNT(*) FROM flags WHERE setter_id = ?',
+ undef, $otherUserID);
+ $vars->{'longdescs'}
+ = $dbh->selectrow_array('SELECT COUNT(*) FROM longdescs WHERE who = ?',
+ undef, $otherUserID);
+ my $namedquery_ids
+ = $dbh->selectcol_arrayref('SELECT id FROM namedqueries WHERE userid = ?',
+ undef, $otherUserID);
+ $vars->{'namedqueries'} = scalar(@$namedquery_ids);
+
+ if (scalar(@$namedquery_ids)) {
+ $vars->{'namedquery_group_map'}
+ = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM namedquery_group_map WHERE namedquery_id IN' . ' ('
+ . join(', ', @$namedquery_ids)
+ . ')');
+ }
+ else {
+ $vars->{'namedquery_group_map'} = 0;
+ }
+ $vars->{'profile_setting'}
+ = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM profile_setting WHERE user_id = ?',
+ undef, $otherUserID);
+ $vars->{'profiles_activity'}
+ = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM profiles_activity WHERE who = ? AND userid != ?',
+ undef, ($otherUserID, $otherUserID));
+ $vars->{'quips'}
+ = $dbh->selectrow_array('SELECT COUNT(*) FROM quips WHERE userid = ?',
+ undef, $otherUserID);
+ $vars->{'series'}
+ = $dbh->selectrow_array('SELECT COUNT(*) FROM series WHERE creator = ?',
+ undef, $otherUserID);
+ $vars->{'watch'}{'watched'}
+ = $dbh->selectrow_array('SELECT COUNT(*) FROM watch WHERE watched = ?',
+ undef, $otherUserID);
+ $vars->{'watch'}{'watcher'}
+ = $dbh->selectrow_array('SELECT COUNT(*) FROM watch WHERE watcher = ?',
+ undef, $otherUserID);
+ $vars->{'whine_events'}
+ = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM whine_events WHERE owner_userid = ?',
+ undef, $otherUserID);
+ $vars->{'whine_schedules'} = $dbh->selectrow_array(
+ qq{SELECT COUNT(distinct eventid)
FROM whine_schedules
WHERE mailto = ?
AND mailto_type = ?
- },
- undef, ($otherUserID, MAILTO_USER));
- $vars->{'token'} = issue_session_token('delete_user');
+ }, undef, ($otherUserID, MAILTO_USER)
+ );
+ $vars->{'token'} = issue_session_token('delete_user');
- $template->process('admin/users/confirm-delete.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ $template->process('admin/users/confirm-delete.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
###########################################################################
-} elsif ($action eq 'delete') {
- check_token_data($token, 'delete_user');
- my $otherUser = check_user($otherUserID, $otherUserLogin);
- $otherUserID = $otherUser->id;
-
- # Cache for user accounts.
- my %usercache = (0 => new Bugzilla::User());
- my %updatedbugs;
-
- # Lock tables during the check+removal session.
- # XXX: if there was some change on these tables after the deletion
- # confirmation checks, we may do something here we haven't warned
- # about.
- $dbh->bz_start_transaction();
-
- Bugzilla->params->{'allowuserdeletion'}
- || ThrowUserError('users_deletion_disabled');
- $editusers || ThrowUserError('auth_failure',
- {group => "editusers",
- action => "delete",
- object => "users"});
- @{$otherUser->product_responsibilities()}
- && ThrowUserError('user_has_responsibility');
-
- Bugzilla->logout_user($otherUser);
-
- # Get the named query list so we can delete namedquery_group_map entries.
- my $namedqueries_as_string = join(', ', @{$dbh->selectcol_arrayref(
- 'SELECT id FROM namedqueries WHERE userid = ?', undef, $otherUserID)});
-
- # Get the timestamp for LogActivityEntry.
- my $timestamp = $dbh->selectrow_array('SELECT NOW()');
-
- # When we update a bug_activity entry, we update the bug timestamp, too.
- my $sth_set_bug_timestamp =
- $dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
-
- # Flags
- my $flag_ids =
- $dbh->selectcol_arrayref('SELECT id FROM flags WHERE requestee_id = ?',
- undef, $otherUserID);
-
- my $flags = Bugzilla::Flag->new_from_list($flag_ids);
-
- $dbh->do('UPDATE flags SET requestee_id = NULL, modification_date = ?
- WHERE requestee_id = ?', undef, ($timestamp, $otherUserID));
-
- # We want to remove the requestee but leave the requester alone,
- # so we have to log these changes manually.
- my %bugs;
- push(@{$bugs{$_->bug_id}->{$_->attach_id || 0}}, $_) foreach @$flags;
- foreach my $bug_id (keys %bugs) {
- foreach my $attach_id (keys %{$bugs{$bug_id}}) {
- my @old_summaries = Bugzilla::Flag->snapshot($bugs{$bug_id}->{$attach_id});
- $_->_set_requestee() foreach @{$bugs{$bug_id}->{$attach_id}};
- my @new_summaries = Bugzilla::Flag->snapshot($bugs{$bug_id}->{$attach_id});
- my ($removed, $added) =
- Bugzilla::Flag->update_activity(\@old_summaries, \@new_summaries);
- LogActivityEntry($bug_id, 'flagtypes.name', $removed, $added,
- $userid, $timestamp, undef, $attach_id);
- }
- $sth_set_bug_timestamp->execute($timestamp, $bug_id);
- $updatedbugs{$bug_id} = 1;
- }
-
- # Simple deletions in referred tables.
- $dbh->do('DELETE FROM email_setting WHERE user_id = ?', undef,
- $otherUserID);
- $dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $otherUserID);
- $dbh->do('DELETE FROM namedqueries WHERE userid = ?', undef, $otherUserID);
- $dbh->do('DELETE FROM namedqueries_link_in_footer WHERE user_id = ?', undef,
- $otherUserID);
- if ($namedqueries_as_string) {
- $dbh->do('DELETE FROM namedquery_group_map WHERE namedquery_id IN ' .
- "($namedqueries_as_string)");
- }
- $dbh->do('DELETE FROM profile_setting WHERE user_id = ?', undef,
- $otherUserID);
- $dbh->do('DELETE FROM profiles_activity WHERE userid = ? OR who = ?', undef,
- ($otherUserID, $otherUserID));
- $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID);
- $dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef,
- $otherUserID);
- $dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef,
- ($otherUserID, $otherUserID));
-
- # Deletions in referred tables which need LogActivityEntry.
- my $buglist = $dbh->selectcol_arrayref('SELECT bug_id FROM cc WHERE who = ?',
- undef, $otherUserID);
- $dbh->do('DELETE FROM cc WHERE who = ?', undef, $otherUserID);
- foreach my $bug_id (@$buglist) {
- LogActivityEntry($bug_id, 'cc', $otherUser->login, '', $userid,
- $timestamp);
- $sth_set_bug_timestamp->execute($timestamp, $bug_id);
- $updatedbugs{$bug_id} = 1;
- }
-
- # Even more complex deletions in referred tables.
- my $id;
-
- # 1) Series
- my $sth_seriesid = $dbh->prepare(
- 'SELECT series_id FROM series WHERE creator = ?');
- my $sth_deleteSeries = $dbh->prepare(
- 'DELETE FROM series WHERE series_id = ?');
- my $sth_deleteSeriesData = $dbh->prepare(
- 'DELETE FROM series_data WHERE series_id = ?');
-
- $sth_seriesid->execute($otherUserID);
- while ($id = $sth_seriesid->fetchrow_array()) {
- $sth_deleteSeriesData->execute($id);
- $sth_deleteSeries->execute($id);
+}
+elsif ($action eq 'delete') {
+ check_token_data($token, 'delete_user');
+ my $otherUser = check_user($otherUserID, $otherUserLogin);
+ $otherUserID = $otherUser->id;
+
+ # Cache for user accounts.
+ my %usercache = (0 => new Bugzilla::User());
+ my %updatedbugs;
+
+ # Lock tables during the check+removal session.
+ # XXX: if there was some change on these tables after the deletion
+ # confirmation checks, we may do something here we haven't warned
+ # about.
+ $dbh->bz_start_transaction();
+
+ Bugzilla->params->{'allowuserdeletion'}
+ || ThrowUserError('users_deletion_disabled');
+ $editusers
+ || ThrowUserError('auth_failure',
+ {group => "editusers", action => "delete", object => "users"});
+ @{$otherUser->product_responsibilities()}
+ && ThrowUserError('user_has_responsibility');
+
+ Bugzilla->logout_user($otherUser);
+
+ # Get the named query list so we can delete namedquery_group_map entries.
+ my $namedqueries_as_string = join(
+ ', ',
+ @{
+ $dbh->selectcol_arrayref('SELECT id FROM namedqueries WHERE userid = ?',
+ undef, $otherUserID)
}
-
- # 2) Whines
- my $sth_whineidFromEvents = $dbh->prepare(
- 'SELECT id FROM whine_events WHERE owner_userid = ?');
- my $sth_deleteWhineEvent = $dbh->prepare(
- 'DELETE FROM whine_events WHERE id = ?');
- my $sth_deleteWhineQuery = $dbh->prepare(
- 'DELETE FROM whine_queries WHERE eventid = ?');
- my $sth_deleteWhineSchedule = $dbh->prepare(
- 'DELETE FROM whine_schedules WHERE eventid = ?');
-
- $dbh->do('DELETE FROM whine_schedules WHERE mailto = ? AND mailto_type = ?',
- undef, ($otherUserID, MAILTO_USER));
-
- $sth_whineidFromEvents->execute($otherUserID);
- while ($id = $sth_whineidFromEvents->fetchrow_array()) {
- $sth_deleteWhineQuery->execute($id);
- $sth_deleteWhineSchedule->execute($id);
- $sth_deleteWhineEvent->execute($id);
+ );
+
+ # Get the timestamp for LogActivityEntry.
+ my $timestamp = $dbh->selectrow_array('SELECT NOW()');
+
+ # When we update a bug_activity entry, we update the bug timestamp, too.
+ my $sth_set_bug_timestamp
+ = $dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
+
+ # Flags
+ my $flag_ids
+ = $dbh->selectcol_arrayref('SELECT id FROM flags WHERE requestee_id = ?',
+ undef, $otherUserID);
+
+ my $flags = Bugzilla::Flag->new_from_list($flag_ids);
+
+ $dbh->do(
+ 'UPDATE flags SET requestee_id = NULL, modification_date = ?
+ WHERE requestee_id = ?', undef, ($timestamp, $otherUserID)
+ );
+
+ # We want to remove the requestee but leave the requester alone,
+ # so we have to log these changes manually.
+ my %bugs;
+ push(@{$bugs{$_->bug_id}->{$_->attach_id || 0}}, $_) foreach @$flags;
+ foreach my $bug_id (keys %bugs) {
+ foreach my $attach_id (keys %{$bugs{$bug_id}}) {
+ my @old_summaries = Bugzilla::Flag->snapshot($bugs{$bug_id}->{$attach_id});
+ $_->_set_requestee() foreach @{$bugs{$bug_id}->{$attach_id}};
+ my @new_summaries = Bugzilla::Flag->snapshot($bugs{$bug_id}->{$attach_id});
+ my ($removed, $added)
+ = Bugzilla::Flag->update_activity(\@old_summaries, \@new_summaries);
+ LogActivityEntry($bug_id, 'flagtypes.name', $removed, $added, $userid,
+ $timestamp, undef, $attach_id);
}
-
- # 3) Bugs
- # 3.1) fall back to the default assignee
- $buglist = $dbh->selectall_arrayref(
- 'SELECT bug_id, initialowner
+ $sth_set_bug_timestamp->execute($timestamp, $bug_id);
+ $updatedbugs{$bug_id} = 1;
+ }
+
+ # Simple deletions in referred tables.
+ $dbh->do('DELETE FROM email_setting WHERE user_id = ?', undef, $otherUserID);
+ $dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $otherUserID);
+ $dbh->do('DELETE FROM namedqueries WHERE userid = ?', undef, $otherUserID);
+ $dbh->do('DELETE FROM namedqueries_link_in_footer WHERE user_id = ?',
+ undef, $otherUserID);
+ if ($namedqueries_as_string) {
+ $dbh->do('DELETE FROM namedquery_group_map WHERE namedquery_id IN '
+ . "($namedqueries_as_string)");
+ }
+ $dbh->do('DELETE FROM profile_setting WHERE user_id = ?', undef, $otherUserID);
+ $dbh->do('DELETE FROM profiles_activity WHERE userid = ? OR who = ?',
+ undef, ($otherUserID, $otherUserID));
+ $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID);
+ $dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef, $otherUserID);
+ $dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?',
+ undef, ($otherUserID, $otherUserID));
+
+ # Deletions in referred tables which need LogActivityEntry.
+ my $buglist = $dbh->selectcol_arrayref('SELECT bug_id FROM cc WHERE who = ?',
+ undef, $otherUserID);
+ $dbh->do('DELETE FROM cc WHERE who = ?', undef, $otherUserID);
+ foreach my $bug_id (@$buglist) {
+ LogActivityEntry($bug_id, 'cc', $otherUser->login, '', $userid, $timestamp);
+ $sth_set_bug_timestamp->execute($timestamp, $bug_id);
+ $updatedbugs{$bug_id} = 1;
+ }
+
+ # Even more complex deletions in referred tables.
+ my $id;
+
+ # 1) Series
+ my $sth_seriesid
+ = $dbh->prepare('SELECT series_id FROM series WHERE creator = ?');
+ my $sth_deleteSeries = $dbh->prepare('DELETE FROM series WHERE series_id = ?');
+ my $sth_deleteSeriesData
+ = $dbh->prepare('DELETE FROM series_data WHERE series_id = ?');
+
+ $sth_seriesid->execute($otherUserID);
+ while ($id = $sth_seriesid->fetchrow_array()) {
+ $sth_deleteSeriesData->execute($id);
+ $sth_deleteSeries->execute($id);
+ }
+
+ # 2) Whines
+ my $sth_whineidFromEvents
+ = $dbh->prepare('SELECT id FROM whine_events WHERE owner_userid = ?');
+ my $sth_deleteWhineEvent
+ = $dbh->prepare('DELETE FROM whine_events WHERE id = ?');
+ my $sth_deleteWhineQuery
+ = $dbh->prepare('DELETE FROM whine_queries WHERE eventid = ?');
+ my $sth_deleteWhineSchedule
+ = $dbh->prepare('DELETE FROM whine_schedules WHERE eventid = ?');
+
+ $dbh->do('DELETE FROM whine_schedules WHERE mailto = ? AND mailto_type = ?',
+ undef, ($otherUserID, MAILTO_USER));
+
+ $sth_whineidFromEvents->execute($otherUserID);
+ while ($id = $sth_whineidFromEvents->fetchrow_array()) {
+ $sth_deleteWhineQuery->execute($id);
+ $sth_deleteWhineSchedule->execute($id);
+ $sth_deleteWhineEvent->execute($id);
+ }
+
+ # 3) Bugs
+ # 3.1) fall back to the default assignee
+ $buglist = $dbh->selectall_arrayref(
+ 'SELECT bug_id, initialowner
FROM bugs
INNER JOIN components ON components.id = bugs.component_id
- WHERE assigned_to = ?', undef, $otherUserID);
-
- my $sth_updateAssignee = $dbh->prepare(
- 'UPDATE bugs SET assigned_to = ?, delta_ts = ? WHERE bug_id = ?');
-
- foreach my $bug (@$buglist) {
- my ($bug_id, $default_assignee_id) = @$bug;
- $sth_updateAssignee->execute($default_assignee_id,
- $timestamp, $bug_id);
- $updatedbugs{$bug_id} = 1;
- $default_assignee_id ||= 0;
- $usercache{$default_assignee_id} ||=
- new Bugzilla::User($default_assignee_id);
- LogActivityEntry($bug_id, 'assigned_to', $otherUser->login,
- $usercache{$default_assignee_id}->login,
- $userid, $timestamp);
- }
-
- # 3.2) fall back to the default QA contact
- $buglist = $dbh->selectall_arrayref(
- 'SELECT bug_id, initialqacontact
+ WHERE assigned_to = ?', undef, $otherUserID
+ );
+
+ my $sth_updateAssignee = $dbh->prepare(
+ 'UPDATE bugs SET assigned_to = ?, delta_ts = ? WHERE bug_id = ?');
+
+ foreach my $bug (@$buglist) {
+ my ($bug_id, $default_assignee_id) = @$bug;
+ $sth_updateAssignee->execute($default_assignee_id, $timestamp, $bug_id);
+ $updatedbugs{$bug_id} = 1;
+ $default_assignee_id ||= 0;
+ $usercache{$default_assignee_id} ||= new Bugzilla::User($default_assignee_id);
+ LogActivityEntry($bug_id, 'assigned_to', $otherUser->login,
+ $usercache{$default_assignee_id}->login,
+ $userid, $timestamp);
+ }
+
+ # 3.2) fall back to the default QA contact
+ $buglist = $dbh->selectall_arrayref(
+ 'SELECT bug_id, initialqacontact
FROM bugs
INNER JOIN components ON components.id = bugs.component_id
- WHERE qa_contact = ?', undef, $otherUserID);
-
- my $sth_updateQAcontact = $dbh->prepare(
- 'UPDATE bugs SET qa_contact = ?, delta_ts = ? WHERE bug_id = ?');
-
- foreach my $bug (@$buglist) {
- my ($bug_id, $default_qa_contact_id) = @$bug;
- $sth_updateQAcontact->execute($default_qa_contact_id,
- $timestamp, $bug_id);
- $updatedbugs{$bug_id} = 1;
- $default_qa_contact_id ||= 0;
- $usercache{$default_qa_contact_id} ||=
- new Bugzilla::User($default_qa_contact_id);
- LogActivityEntry($bug_id, 'qa_contact', $otherUser->login,
- $usercache{$default_qa_contact_id}->login,
- $userid, $timestamp);
- }
-
- # Finally, remove the user account itself.
- $dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $otherUserID);
-
- $dbh->bz_commit_transaction();
- delete_token($token);
-
- # It's complex to determine which items now need to be flushed from
- # memcached. As user deletion is expected to be a rare event, we just
- # flush the entire cache when a user is deleted.
- Bugzilla->memcached->clear_all();
-
- $vars->{'message'} = 'account_deleted';
- $vars->{'otheruser'}{'login'} = $otherUser->login;
- $vars->{'restrictablegroups'} = $user->bless_groups();
- $template->process('admin/users/search.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
-
- # Send mail about what we've done to bugs.
- # The deleted user is not notified of the changes.
- foreach (keys(%updatedbugs)) {
- Bugzilla::BugMail::Send($_, {'changer' => $user} );
- }
+ WHERE qa_contact = ?', undef, $otherUserID
+ );
+
+ my $sth_updateQAcontact = $dbh->prepare(
+ 'UPDATE bugs SET qa_contact = ?, delta_ts = ? WHERE bug_id = ?');
+
+ foreach my $bug (@$buglist) {
+ my ($bug_id, $default_qa_contact_id) = @$bug;
+ $sth_updateQAcontact->execute($default_qa_contact_id, $timestamp, $bug_id);
+ $updatedbugs{$bug_id} = 1;
+ $default_qa_contact_id ||= 0;
+ $usercache{$default_qa_contact_id}
+ ||= new Bugzilla::User($default_qa_contact_id);
+ LogActivityEntry($bug_id, 'qa_contact', $otherUser->login,
+ $usercache{$default_qa_contact_id}->login,
+ $userid, $timestamp);
+ }
+
+ # Finally, remove the user account itself.
+ $dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $otherUserID);
+
+ $dbh->bz_commit_transaction();
+ delete_token($token);
+
+ # It's complex to determine which items now need to be flushed from
+ # memcached. As user deletion is expected to be a rare event, we just
+ # flush the entire cache when a user is deleted.
+ Bugzilla->memcached->clear_all();
+
+ $vars->{'message'} = 'account_deleted';
+ $vars->{'otheruser'}{'login'} = $otherUser->login;
+ $vars->{'restrictablegroups'} = $user->bless_groups();
+ $template->process('admin/users/search.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+
+ # Send mail about what we've done to bugs.
+ # The deleted user is not notified of the changes.
+ foreach (keys(%updatedbugs)) {
+ Bugzilla::BugMail::Send($_, {'changer' => $user});
+ }
###########################################################################
-} elsif ($action eq 'activity') {
- my $otherUser = check_user($otherUserID, $otherUserLogin);
+}
+elsif ($action eq 'activity') {
+ my $otherUser = check_user($otherUserID, $otherUserLogin);
- $vars->{'profile_changes'} = $dbh->selectall_arrayref(
- "SELECT profiles.login_name AS who, " .
- $dbh->sql_date_format('profiles_activity.profiles_when') . " AS activity_when,
+ $vars->{'profile_changes'} = $dbh->selectall_arrayref(
+ "SELECT profiles.login_name AS who, "
+ . $dbh->sql_date_format('profiles_activity.profiles_when')
+ . " AS activity_when,
fielddefs.name AS what,
profiles_activity.oldvalue AS removed,
profiles_activity.newvalue AS added
@@ -606,18 +638,18 @@ if ($action eq 'search') {
INNER JOIN profiles ON profiles_activity.who = profiles.userid
INNER JOIN fielddefs ON fielddefs.id = profiles_activity.fieldid
WHERE profiles_activity.userid = ?
- ORDER BY profiles_activity.profiles_when",
- {'Slice' => {}},
- $otherUser->id);
+ ORDER BY profiles_activity.profiles_when", {'Slice' => {}}, $otherUser->id
+ );
- $vars->{'otheruser'} = $otherUser;
+ $vars->{'otheruser'} = $otherUser;
- $template->process("account/profile-activity.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $template->process("account/profile-activity.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
###########################################################################
-} else {
- ThrowUserError('unknown_action', {action => $action});
+}
+else {
+ ThrowUserError('unknown_action', {action => $action});
}
exit;
@@ -629,52 +661,54 @@ exit;
# Try to build a user object using its ID, else its login name, and throw
# an error if the user does not exist.
sub check_user {
- my ($otherUserID, $otherUserLogin) = @_;
-
- my $otherUser;
- my $vars = {};
-
- if ($otherUserID) {
- $otherUser = Bugzilla::User->new($otherUserID);
- $vars->{'user_id'} = $otherUserID;
- }
- elsif ($otherUserLogin) {
- $otherUser = new Bugzilla::User({ name => $otherUserLogin });
- $vars->{'user_login'} = $otherUserLogin;
- }
- ($otherUser && $otherUser->id) || ThrowUserError('invalid_user', $vars);
-
- return $otherUser;
+ my ($otherUserID, $otherUserLogin) = @_;
+
+ my $otherUser;
+ my $vars = {};
+
+ if ($otherUserID) {
+ $otherUser = Bugzilla::User->new($otherUserID);
+ $vars->{'user_id'} = $otherUserID;
+ }
+ elsif ($otherUserLogin) {
+ $otherUser = new Bugzilla::User({name => $otherUserLogin});
+ $vars->{'user_login'} = $otherUserLogin;
+ }
+ ($otherUser && $otherUser->id) || ThrowUserError('invalid_user', $vars);
+
+ return $otherUser;
}
# Copy incoming list selection values from CGI params to template variables.
sub mirrorListSelectionValues {
- my $cgi = Bugzilla->cgi;
- if (defined($cgi->param('matchtype'))) {
- foreach ('matchvalue', 'matchstr', 'matchtype',
- 'grouprestrict', 'groupid', 'is_enabled')
- {
- $vars->{'listselectionvalues'}{$_} = $cgi->param($_);
- }
+ my $cgi = Bugzilla->cgi;
+ if (defined($cgi->param('matchtype'))) {
+ foreach (
+ 'matchvalue', 'matchstr', 'matchtype',
+ 'grouprestrict', 'groupid', 'is_enabled'
+ )
+ {
+ $vars->{'listselectionvalues'}{$_} = $cgi->param($_);
}
+ }
}
# Retrieve user data for the user editing form. User creation and user
# editing code rely on this to call derive_groups().
sub userDataToVars {
- my $otheruserid = shift;
- my $otheruser = new Bugzilla::User($otheruserid);
- my $query;
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
+ my $otheruserid = shift;
+ my $otheruser = new Bugzilla::User($otheruserid);
+ my $query;
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
- my $grouplist = $otheruser->groups_as_string;
+ my $grouplist = $otheruser->groups_as_string;
- $vars->{'otheruser'} = $otheruser;
- $vars->{'groups'} = $user->bless_groups();
+ $vars->{'otheruser'} = $otheruser;
+ $vars->{'groups'} = $user->bless_groups();
- $vars->{'permissions'} = $dbh->selectall_hashref(
- qq{SELECT id,
+ $vars->{'permissions'} = $dbh->selectall_hashref(
+ qq{SELECT id,
COUNT(directmember.group_id) AS directmember,
COUNT(regexpmember.group_id) AS regexpmember,
(CASE WHEN (groups.id IN ($grouplist)
@@ -700,38 +734,40 @@ sub userDataToVars {
AND directbless.isbless = 1
AND directbless.grant_type = ?
} . $dbh->sql_group_by('id'),
- 'id', undef,
- ($otheruserid, GRANT_DIRECT,
- $otheruserid, GRANT_REGEXP,
- $otheruserid, GRANT_DIRECT));
-
- # Find indirect bless permission.
- $query = qq{SELECT groups.id
+ 'id', undef,
+ (
+ $otheruserid, GRANT_DIRECT, $otheruserid, GRANT_REGEXP,
+ $otheruserid, GRANT_DIRECT
+ )
+ );
+
+ # Find indirect bless permission.
+ $query = qq{SELECT groups.id
FROM groups, group_group_map AS ggm
WHERE groups.id = ggm.grantor_id
AND ggm.member_id IN ($grouplist)
AND ggm.grant_type = ?
} . $dbh->sql_group_by('id');
- foreach (@{$dbh->selectall_arrayref($query, undef,
- (GROUP_BLESS))}) {
- # Merge indirect bless permissions into permission variable.
- $vars->{'permissions'}{${$_}[0]}{'indirectbless'} = 1;
- }
+ foreach (@{$dbh->selectall_arrayref($query, undef, (GROUP_BLESS))}) {
+
+ # Merge indirect bless permissions into permission variable.
+ $vars->{'permissions'}{${$_}[0]}{'indirectbless'} = 1;
+ }
}
sub edit_processing {
- my $otherUser = shift;
- my $user = Bugzilla->user;
- my $template = Bugzilla->template;
+ my $otherUser = shift;
+ my $user = Bugzilla->user;
+ my $template = Bugzilla->template;
- $user->in_group('editusers') || $user->can_see_user($otherUser)
- || ThrowUserError('auth_failure', {reason => "not_visible",
- action => "modify",
- object => "user"});
+ $user->in_group('editusers')
+ || $user->can_see_user($otherUser)
+ || ThrowUserError('auth_failure',
+ {reason => "not_visible", action => "modify", object => "user"});
- userDataToVars($otherUser->id);
- $vars->{'token'} = issue_session_token('edit_user');
+ userDataToVars($otherUser->id);
+ $vars->{'token'} = issue_session_token('edit_user');
- $template->process('admin/users/edit.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ $template->process('admin/users/edit.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
diff --git a/editvalues.cgi b/editvalues.cgi
index 75ebee0fc..59e4d639f 100755
--- a/editvalues.cgi
+++ b/editvalues.cgi
@@ -25,12 +25,12 @@ use Bugzilla::Field::Choice;
###############
sub display_field_values {
- my $vars = shift;
- my $template = Bugzilla->template;
- $vars->{'values'} = $vars->{'field'}->legal_values;
- $template->process("admin/fieldvalues/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my $vars = shift;
+ my $template = Bugzilla->template;
+ $vars->{'values'} = $vars->{'field'}->legal_values;
+ $template->process("admin/fieldvalues/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
######################################################################
@@ -43,7 +43,7 @@ my $user = Bugzilla->login(LOGIN_REQUIRED);
my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
# Replace this entry by separate entries in templates when
# the documentation about legal values becomes bigger.
@@ -52,33 +52,31 @@ $vars->{'doc_section'} = 'administering/field-values.html';
print $cgi->header();
$user->in_group('admin')
- || ThrowUserError('auth_failure', {group => "admin",
- action => "edit",
- object => "field_values"});
+ || ThrowUserError('auth_failure',
+ {group => "admin", action => "edit", object => "field_values"});
#
# often-used variables
#
-my $action = trim($cgi->param('action') || '');
+my $action = trim($cgi->param('action') || '');
my $token = $cgi->param('token');
#
# field = '' -> Show nice list of fields
#
if (!$cgi->param('field')) {
- my @field_list =
- @{ Bugzilla->fields({ is_select => 1, is_abnormal => 0 }) };
+ my @field_list = @{Bugzilla->fields({is_select => 1, is_abnormal => 0})};
- $vars->{'fields'} = \@field_list;
- $template->process("admin/fieldvalues/select-field.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $vars->{'fields'} = \@field_list;
+ $template->process("admin/fieldvalues/select-field.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
# At this point, the field must be defined.
my $field = Bugzilla::Field->check($cgi->param('field'));
if (!$field->is_select || $field->is_abnormal) {
- ThrowUserError('fieldname_invalid', { field => $field });
+ ThrowUserError('fieldname_invalid', {field => $field});
}
$vars->{'field'} = $field;
@@ -92,30 +90,30 @@ display_field_values($vars) unless $action;
# (next action will be 'new')
#
if ($action eq 'add') {
- $vars->{'token'} = issue_session_token('add_field_value');
- $template->process("admin/fieldvalues/create.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $vars->{'token'} = issue_session_token('add_field_value');
+ $template->process("admin/fieldvalues/create.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
#
# action='new' -> add field value entered in the 'action=add' screen
#
if ($action eq 'new') {
- check_token_data($token, 'add_field_value');
+ check_token_data($token, 'add_field_value');
- my $created_value = Bugzilla::Field::Choice->type($field)->create({
- value => scalar $cgi->param('value'),
- sortkey => scalar $cgi->param('sortkey'),
- is_open => scalar $cgi->param('is_open'),
- visibility_value_id => scalar $cgi->param('visibility_value_id'),
- });
+ my $created_value = Bugzilla::Field::Choice->type($field)->create({
+ value => scalar $cgi->param('value'),
+ sortkey => scalar $cgi->param('sortkey'),
+ is_open => scalar $cgi->param('is_open'),
+ visibility_value_id => scalar $cgi->param('visibility_value_id'),
+ });
- delete_token($token);
+ delete_token($token);
- $vars->{'message'} = 'field_value_created';
- $vars->{'value'} = $created_value;
- display_field_values($vars);
+ $vars->{'message'} = 'field_value_created';
+ $vars->{'value'} = $created_value;
+ display_field_values($vars);
}
# After this, we always have a value
@@ -127,16 +125,17 @@ $vars->{'value'} = $value;
# (next action would be 'delete')
#
if ($action eq 'del') {
- # If the value cannot be deleted, throw an error.
- if ($value->is_static) {
- ThrowUserError('fieldvalue_not_deletable', $vars);
- }
- $vars->{'token'} = issue_session_token('delete_field_value');
- $template->process("admin/fieldvalues/confirm-delete.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ # If the value cannot be deleted, throw an error.
+ if ($value->is_static) {
+ ThrowUserError('fieldvalue_not_deletable', $vars);
+ }
+ $vars->{'token'} = issue_session_token('delete_field_value');
- exit;
+ $template->process("admin/fieldvalues/confirm-delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ exit;
}
@@ -144,12 +143,12 @@ if ($action eq 'del') {
# action='delete' -> really delete the field value
#
if ($action eq 'delete') {
- check_token_data($token, 'delete_field_value');
- $value->remove_from_db();
- delete_token($token);
- $vars->{'message'} = 'field_value_deleted';
- $vars->{'no_edit_link'} = 1;
- display_field_values($vars);
+ check_token_data($token, 'delete_field_value');
+ $value->remove_from_db();
+ delete_token($token);
+ $vars->{'message'} = 'field_value_deleted';
+ $vars->{'no_edit_link'} = 1;
+ display_field_values($vars);
}
@@ -158,11 +157,11 @@ if ($action eq 'delete') {
# (next action would be 'update')
#
if ($action eq 'edit') {
- $vars->{'token'} = issue_session_token('edit_field_value');
- $template->process("admin/fieldvalues/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $vars->{'token'} = issue_session_token('edit_field_value');
+ $template->process("admin/fieldvalues/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
- exit;
+ exit;
}
@@ -170,21 +169,21 @@ if ($action eq 'edit') {
# action='update' -> update the field value
#
if ($action eq 'update') {
- check_token_data($token, 'edit_field_value');
- $vars->{'value_old'} = $value->name;
- my %params = (
- name => scalar $cgi->param('value_new'),
- sortkey => scalar $cgi->param('sortkey'),
- visibility_value => scalar $cgi->param('visibility_value_id'),
- );
- if ($cgi->should_set('is_active')) {
- $params{is_active} = $cgi->param('is_active');
- }
- $value->set_all(\%params);
- $vars->{'changes'} = $value->update();
- delete_token($token);
- $vars->{'message'} = 'field_value_updated';
- display_field_values($vars);
+ check_token_data($token, 'edit_field_value');
+ $vars->{'value_old'} = $value->name;
+ my %params = (
+ name => scalar $cgi->param('value_new'),
+ sortkey => scalar $cgi->param('sortkey'),
+ visibility_value => scalar $cgi->param('visibility_value_id'),
+ );
+ if ($cgi->should_set('is_active')) {
+ $params{is_active} = $cgi->param('is_active');
+ }
+ $value->set_all(\%params);
+ $vars->{'changes'} = $value->update();
+ delete_token($token);
+ $vars->{'message'} = 'field_value_updated';
+ display_field_values($vars);
}
# No valid action found
diff --git a/editversions.cgi b/editversions.cgi
index 1d4e17d41..23de88071 100755
--- a/editversions.cgi
+++ b/editversions.cgi
@@ -19,10 +19,11 @@ use Bugzilla::Error;
use Bugzilla::Version;
use Bugzilla::Token;
-my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
+
# There is only one section about versions in the documentation,
# so all actions point to the same page.
$vars->{'doc_section'} = 'administering/categorization.html#versions';
@@ -37,9 +38,8 @@ print $cgi->header();
$user->in_group('editcomponents')
|| scalar(@{$user->get_products_by_permission('editcomponents')})
- || ThrowUserError("auth_failure", {group => "editcomponents",
- action => "edit",
- object => "versions"});
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "edit", object => "versions"});
#
# often used variables
@@ -48,26 +48,27 @@ my $product_name = trim($cgi->param('product') || '');
my $version_name = trim($cgi->param('version') || '');
my $action = trim($cgi->param('action') || '');
my $showbugcounts = (defined $cgi->param('showbugcounts'));
-my $token = $cgi->param('token');
-my $isactive = $cgi->param('isactive');
+my $token = $cgi->param('token');
+my $isactive = $cgi->param('isactive');
#
# product = '' -> Show nice list of products
#
unless ($product_name) {
- my $selectable_products = $user->get_selectable_products;
- # If the user has editcomponents privs for some products only,
- # we have to restrict the list of products to display.
- unless ($user->in_group('editcomponents')) {
- $selectable_products = $user->get_products_by_permission('editcomponents');
- }
- $vars->{'products'} = $selectable_products;
- $vars->{'showbugcounts'} = $showbugcounts;
-
- $template->process("admin/versions/select-product.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my $selectable_products = $user->get_selectable_products;
+
+ # If the user has editcomponents privs for some products only,
+ # we have to restrict the list of products to display.
+ unless ($user->in_group('editcomponents')) {
+ $selectable_products = $user->get_products_by_permission('editcomponents');
+ }
+ $vars->{'products'} = $selectable_products;
+ $vars->{'showbugcounts'} = $showbugcounts;
+
+ $template->process("admin/versions/select-product.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
my $product = $user->check_can_admin_product($product_name);
@@ -77,12 +78,12 @@ my $product = $user->check_can_admin_product($product_name);
#
unless ($action) {
- $vars->{'showbugcounts'} = $showbugcounts;
- $vars->{'product'} = $product;
- $template->process("admin/versions/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $vars->{'showbugcounts'} = $showbugcounts;
+ $vars->{'product'} = $product;
+ $template->process("admin/versions/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
- exit;
+ exit;
}
#
@@ -92,12 +93,12 @@ unless ($action) {
#
if ($action eq 'add') {
- $vars->{'token'} = issue_session_token('add_version');
- $vars->{'product'} = $product;
- $template->process("admin/versions/create.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $vars->{'token'} = issue_session_token('add_version');
+ $vars->{'product'} = $product;
+ $template->process("admin/versions/create.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
- exit;
+ exit;
}
#
@@ -105,18 +106,18 @@ if ($action eq 'add') {
#
if ($action eq 'new') {
- check_token_data($token, 'add_version');
- my $version = Bugzilla::Version->create(
- { value => $version_name, product => $product });
- delete_token($token);
-
- $vars->{'message'} = 'version_created';
- $vars->{'version'} = $version;
- $vars->{'product'} = $product;
- $template->process("admin/versions/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
-
- exit;
+ check_token_data($token, 'add_version');
+ my $version
+ = Bugzilla::Version->create({value => $version_name, product => $product});
+ delete_token($token);
+
+ $vars->{'message'} = 'version_created';
+ $vars->{'version'} = $version;
+ $vars->{'product'} = $product;
+ $template->process("admin/versions/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ exit;
}
#
@@ -126,15 +127,15 @@ if ($action eq 'new') {
#
if ($action eq 'del') {
- my $version = Bugzilla::Version->check({ product => $product,
- name => $version_name });
- $vars->{'version'} = $version;
- $vars->{'product'} = $product;
- $vars->{'token'} = issue_session_token('delete_version');
- $template->process("admin/versions/confirm-delete.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
-
- exit;
+ my $version
+ = Bugzilla::Version->check({product => $product, name => $version_name});
+ $vars->{'version'} = $version;
+ $vars->{'product'} = $product;
+ $vars->{'token'} = issue_session_token('delete_version');
+ $template->process("admin/versions/confirm-delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ exit;
}
#
@@ -142,21 +143,21 @@ if ($action eq 'del') {
#
if ($action eq 'delete') {
- check_token_data($token, 'delete_version');
- my $version = Bugzilla::Version->check({ product => $product,
- name => $version_name });
- $version->remove_from_db;
- delete_token($token);
+ check_token_data($token, 'delete_version');
+ my $version
+ = Bugzilla::Version->check({product => $product, name => $version_name});
+ $version->remove_from_db;
+ delete_token($token);
- $vars->{'message'} = 'version_deleted';
- $vars->{'version'} = $version;
- $vars->{'product'} = $product;
- $vars->{'no_edit_version_link'} = 1;
+ $vars->{'message'} = 'version_deleted';
+ $vars->{'version'} = $version;
+ $vars->{'product'} = $product;
+ $vars->{'no_edit_version_link'} = 1;
- $template->process("admin/versions/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $template->process("admin/versions/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
- exit;
+ exit;
}
#
@@ -166,16 +167,16 @@ if ($action eq 'delete') {
#
if ($action eq 'edit') {
- my $version = Bugzilla::Version->check({ product => $product,
- name => $version_name });
- $vars->{'version'} = $version;
- $vars->{'product'} = $product;
- $vars->{'token'} = issue_session_token('edit_version');
+ my $version
+ = Bugzilla::Version->check({product => $product, name => $version_name});
+ $vars->{'version'} = $version;
+ $vars->{'product'} = $product;
+ $vars->{'token'} = issue_session_token('edit_version');
- $template->process("admin/versions/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $template->process("admin/versions/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
- exit;
+ exit;
}
#
@@ -183,30 +184,27 @@ if ($action eq 'edit') {
#
if ($action eq 'update') {
- check_token_data($token, 'edit_version');
- my $version_old_name = trim($cgi->param('versionold') || '');
- my $version = Bugzilla::Version->check({ product => $product,
- name => $version_old_name });
-
- $dbh->bz_start_transaction();
-
- $version->set_all({
- value => $version_name,
- isactive => $isactive
- });
- my $changes = $version->update();
-
- $dbh->bz_commit_transaction();
- delete_token($token);
-
- $vars->{'message'} = 'version_updated';
- $vars->{'version'} = $version;
- $vars->{'product'} = $product;
- $vars->{'changes'} = $changes;
- $template->process("admin/versions/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
-
- exit;
+ check_token_data($token, 'edit_version');
+ my $version_old_name = trim($cgi->param('versionold') || '');
+ my $version
+ = Bugzilla::Version->check({product => $product, name => $version_old_name});
+
+ $dbh->bz_start_transaction();
+
+ $version->set_all({value => $version_name, isactive => $isactive});
+ my $changes = $version->update();
+
+ $dbh->bz_commit_transaction();
+ delete_token($token);
+
+ $vars->{'message'} = 'version_updated';
+ $vars->{'version'} = $version;
+ $vars->{'product'} = $product;
+ $vars->{'changes'} = $changes;
+ $template->process("admin/versions/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ exit;
}
# No valid action found
diff --git a/editwhines.cgi b/editwhines.cgi
index b11c44949..c677273bd 100755
--- a/editwhines.cgi
+++ b/editwhines.cgi
@@ -41,9 +41,9 @@ my $template = Bugzilla->template;
my $vars = {};
my $dbh = Bugzilla->dbh;
-my $userid = $user->id;
-my $token = $cgi->param('token');
-my $sth; # database statement handle
+my $userid = $user->id;
+my $token = $cgi->param('token');
+my $sth; # database statement handle
# $events is a hash ref of Bugzilla::Whine objects keyed by event id,
# that stores the active user's events.
@@ -66,9 +66,8 @@ my $events = get_events($userid);
# First see if this user may use whines
$user->in_group('bz_canusewhines')
- || ThrowUserError("auth_failure", {group => "bz_canusewhines",
- action => "schedule",
- object => "reports"});
+ || ThrowUserError("auth_failure",
+ {group => "bz_canusewhines", action => "schedule", object => "reports"});
# May this user send mail to other users?
my $can_mail_others = $user->in_group('bz_canusewhineatothers');
@@ -77,240 +76,245 @@ my $can_mail_others = $user->in_group('bz_canusewhineatothers');
# removed, then what was altered.
if ($cgi->param('update')) {
- check_token_data($token, 'edit_whine');
-
- if ($cgi->param("add_event")) {
- # we create a new event
- $sth = $dbh->prepare("INSERT INTO whine_events " .
- "(owner_userid) " .
- "VALUES (?)");
- $sth->execute($userid);
- }
- else {
- for my $eventid (keys %{$events}) {
- # delete an entire event
- if ($cgi->param("remove_event_$eventid")) {
- # We need to make sure these belong to the same user,
- # otherwise we could simply delete whatever matched that ID.
- #
- # schedules
- my $schedules = Bugzilla::Whine::Schedule->match({ eventid => $eventid });
- $sth = $dbh->prepare("DELETE FROM whine_schedules "
- . "WHERE id=?");
- foreach my $schedule (@$schedules) {
- $sth->execute($schedule->id);
- }
-
- # queries
- $sth = $dbh->prepare("SELECT whine_queries.id " .
- "FROM whine_queries " .
- "LEFT JOIN whine_events " .
- "ON whine_events.id = " .
- "whine_queries.eventid " .
- "WHERE whine_events.id = ? " .
- "AND whine_events.owner_userid = ?");
- $sth->execute($eventid, $userid);
- my @ids = @{$sth->fetchall_arrayref};
- $sth = $dbh->prepare("DELETE FROM whine_queries " .
- "WHERE id=?");
- for (@ids) {
- my $delete_id = $_->[0];
- $sth->execute($delete_id);
- }
-
- # events
- $sth = $dbh->prepare("DELETE FROM whine_events " .
- "WHERE id=? AND owner_userid=?");
- $sth->execute($eventid, $userid);
- }
- else {
- # check the subject, body and mailifnobugs for changes
- my $subject = ($cgi->param("event_${eventid}_subject") or '');
- my $body = ($cgi->param("event_${eventid}_body") or '');
- my $mailifnobugs = $cgi->param("event_${eventid}_mailifnobugs") ? 1 : 0;
-
- trick_taint($subject) if $subject;
- trick_taint($body) if $body;
-
- if ( ($subject ne $events->{$eventid}->subject)
- || ($mailifnobugs != $events->{$eventid}->mail_if_no_bugs)
- || ($body ne $events->{$eventid}->body) ) {
-
- $sth = $dbh->prepare("UPDATE whine_events " .
- "SET subject=?, body=?, mailifnobugs=? " .
- "WHERE id=?");
- $sth->execute($subject, $body, $mailifnobugs, $eventid);
- }
-
- # add a schedule
- if ($cgi->param("add_schedule_$eventid")) {
- # the schedule table must be locked before altering
- $sth = $dbh->prepare("INSERT INTO whine_schedules " .
- "(eventid, mailto_type, mailto, " .
- "run_day, run_time) " .
- "VALUES (?, ?, ?, 'Sun', 2)");
- $sth->execute($eventid, MAILTO_USER, $userid);
- }
- # add a query
- elsif ($cgi->param("add_query_$eventid")) {
- $sth = $dbh->prepare("INSERT INTO whine_queries "
- . "(eventid) "
- . "VALUES (?)");
- $sth->execute($eventid);
- }
- }
+ check_token_data($token, 'edit_whine');
+
+ if ($cgi->param("add_event")) {
+
+ # we create a new event
+ $sth = $dbh->prepare(
+ "INSERT INTO whine_events " . "(owner_userid) " . "VALUES (?)");
+ $sth->execute($userid);
+ }
+ else {
+ for my $eventid (keys %{$events}) {
+
+ # delete an entire event
+ if ($cgi->param("remove_event_$eventid")) {
+
+ # We need to make sure these belong to the same user,
+ # otherwise we could simply delete whatever matched that ID.
+ #
+ # schedules
+ my $schedules = Bugzilla::Whine::Schedule->match({eventid => $eventid});
+ $sth = $dbh->prepare("DELETE FROM whine_schedules " . "WHERE id=?");
+ foreach my $schedule (@$schedules) {
+ $sth->execute($schedule->id);
+ }
+
+ # queries
+ $sth
+ = $dbh->prepare("SELECT whine_queries.id "
+ . "FROM whine_queries "
+ . "LEFT JOIN whine_events "
+ . "ON whine_events.id = "
+ . "whine_queries.eventid "
+ . "WHERE whine_events.id = ? "
+ . "AND whine_events.owner_userid = ?");
+ $sth->execute($eventid, $userid);
+ my @ids = @{$sth->fetchall_arrayref};
+ $sth = $dbh->prepare("DELETE FROM whine_queries " . "WHERE id=?");
+ for (@ids) {
+ my $delete_id = $_->[0];
+ $sth->execute($delete_id);
+ }
- # now check all of the schedules and queries to see if they need
- # to be altered or deleted
+ # events
+ $sth = $dbh->prepare(
+ "DELETE FROM whine_events " . "WHERE id=? AND owner_userid=?");
+ $sth->execute($eventid, $userid);
+ }
+ else {
+ # check the subject, body and mailifnobugs for changes
+ my $subject = ($cgi->param("event_${eventid}_subject") or '');
+ my $body = ($cgi->param("event_${eventid}_body") or '');
+ my $mailifnobugs = $cgi->param("event_${eventid}_mailifnobugs") ? 1 : 0;
+
+ trick_taint($subject) if $subject;
+ trick_taint($body) if $body;
+
+ if ( ($subject ne $events->{$eventid}->subject)
+ || ($mailifnobugs != $events->{$eventid}->mail_if_no_bugs)
+ || ($body ne $events->{$eventid}->body))
+ {
+
+ $sth
+ = $dbh->prepare("UPDATE whine_events "
+ . "SET subject=?, body=?, mailifnobugs=? "
+ . "WHERE id=?");
+ $sth->execute($subject, $body, $mailifnobugs, $eventid);
+ }
- # Check schedules for changes
- my $schedules = Bugzilla::Whine::Schedule->match({ eventid => $eventid });
- my @scheduleids = ();
- foreach my $schedule (@$schedules) {
- push @scheduleids, $schedule->id;
- }
+ # add a schedule
+ if ($cgi->param("add_schedule_$eventid")) {
- # we need to double-check all of the user IDs in mailto to make
- # sure they exist
- my $arglist = {}; # args for match_field
- for my $sid (@scheduleids) {
- if ($cgi->param("mailto_type_$sid") == MAILTO_USER) {
- $arglist->{"mailto_$sid"} = {
- 'type' => 'single',
- };
- }
- }
- if (scalar %{$arglist}) {
- Bugzilla::User::match_field($arglist);
- }
+ # the schedule table must be locked before altering
+ $sth
+ = $dbh->prepare("INSERT INTO whine_schedules "
+ . "(eventid, mailto_type, mailto, "
+ . "run_day, run_time) "
+ . "VALUES (?, ?, ?, 'Sun', 2)");
+ $sth->execute($eventid, MAILTO_USER, $userid);
+ }
- for my $sid (@scheduleids) {
- if ($cgi->param("remove_schedule_$sid")) {
- # having the assignee id in here is a security failsafe
- $sth = $dbh->prepare("SELECT whine_schedules.id " .
- "FROM whine_schedules " .
- "LEFT JOIN whine_events " .
- "ON whine_events.id = " .
- "whine_schedules.eventid " .
- "WHERE whine_events.owner_userid=? " .
- "AND whine_schedules.id =?");
- $sth->execute($userid, $sid);
-
- my @ids = @{$sth->fetchall_arrayref};
- for (@ids) {
- $sth = $dbh->prepare("DELETE FROM whine_schedules " .
- "WHERE id=?");
- $sth->execute($_->[0]);
- }
- }
- else {
- my $o_day = $cgi->param("orig_day_$sid") || '';
- my $day = $cgi->param("day_$sid") || '';
- my $o_time = $cgi->param("orig_time_$sid") || 0;
- my $time = $cgi->param("time_$sid") || 0;
- my $o_mailto = $cgi->param("orig_mailto_$sid") || '';
- my $mailto = $cgi->param("mailto_$sid") || '';
- my $o_mailto_type = $cgi->param("orig_mailto_type_$sid") || 0;
- my $mailto_type = $cgi->param("mailto_type_$sid") || 0;
-
- my $mailto_id = $userid;
-
- # get an id for the mailto address
- if ($can_mail_others && $mailto) {
- if ($mailto_type == MAILTO_USER) {
- $mailto_id = login_to_id($mailto);
- }
- elsif ($mailto_type == MAILTO_GROUP) {
- # The group name is used in a placeholder.
- trick_taint($mailto);
- $mailto_id = Bugzilla::Group::ValidateGroupName($mailto, ($user))
- || ThrowUserError('invalid_group_name', { name => $mailto });
- }
- else {
- # bad value, so it will just mail to the whine
- # owner. $mailto_id was already set above.
- $mailto_type = MAILTO_USER;
- }
- }
-
- detaint_natural($mailto_type);
-
- if ( ($o_day ne $day) ||
- ($o_time ne $time) ||
- ($o_mailto ne $mailto) ||
- ($o_mailto_type != $mailto_type) ){
-
- trick_taint($day);
- trick_taint($time);
-
- # the schedule table must be locked
- $sth = $dbh->prepare("UPDATE whine_schedules " .
- "SET run_day=?, run_time=?, " .
- "mailto_type=?, mailto=?, " .
- "run_next=NULL " .
- "WHERE id=?");
- $sth->execute($day, $time, $mailto_type,
- $mailto_id, $sid);
- }
- }
+ # add a query
+ elsif ($cgi->param("add_query_$eventid")) {
+ $sth
+ = $dbh->prepare("INSERT INTO whine_queries " . "(eventid) " . "VALUES (?)");
+ $sth->execute($eventid);
+ }
+ }
+
+ # now check all of the schedules and queries to see if they need
+ # to be altered or deleted
+
+ # Check schedules for changes
+ my $schedules = Bugzilla::Whine::Schedule->match({eventid => $eventid});
+ my @scheduleids = ();
+ foreach my $schedule (@$schedules) {
+ push @scheduleids, $schedule->id;
+ }
+
+ # we need to double-check all of the user IDs in mailto to make
+ # sure they exist
+ my $arglist = {}; # args for match_field
+ for my $sid (@scheduleids) {
+ if ($cgi->param("mailto_type_$sid") == MAILTO_USER) {
+ $arglist->{"mailto_$sid"} = {'type' => 'single',};
+ }
+ }
+ if (scalar %{$arglist}) {
+ Bugzilla::User::match_field($arglist);
+ }
+
+ for my $sid (@scheduleids) {
+ if ($cgi->param("remove_schedule_$sid")) {
+
+ # having the assignee id in here is a security failsafe
+ $sth
+ = $dbh->prepare("SELECT whine_schedules.id "
+ . "FROM whine_schedules "
+ . "LEFT JOIN whine_events "
+ . "ON whine_events.id = "
+ . "whine_schedules.eventid "
+ . "WHERE whine_events.owner_userid=? "
+ . "AND whine_schedules.id =?");
+ $sth->execute($userid, $sid);
+
+ my @ids = @{$sth->fetchall_arrayref};
+ for (@ids) {
+ $sth = $dbh->prepare("DELETE FROM whine_schedules " . "WHERE id=?");
+ $sth->execute($_->[0]);
+ }
+ }
+ else {
+ my $o_day = $cgi->param("orig_day_$sid") || '';
+ my $day = $cgi->param("day_$sid") || '';
+ my $o_time = $cgi->param("orig_time_$sid") || 0;
+ my $time = $cgi->param("time_$sid") || 0;
+ my $o_mailto = $cgi->param("orig_mailto_$sid") || '';
+ my $mailto = $cgi->param("mailto_$sid") || '';
+ my $o_mailto_type = $cgi->param("orig_mailto_type_$sid") || 0;
+ my $mailto_type = $cgi->param("mailto_type_$sid") || 0;
+
+ my $mailto_id = $userid;
+
+ # get an id for the mailto address
+ if ($can_mail_others && $mailto) {
+ if ($mailto_type == MAILTO_USER) {
+ $mailto_id = login_to_id($mailto);
}
+ elsif ($mailto_type == MAILTO_GROUP) {
- # Check queries for changes
- my $queries = Bugzilla::Whine::Query->match({ eventid => $eventid });
- for my $query (@$queries) {
- my $qid = $query->id;
- if ($cgi->param("remove_query_$qid")) {
-
- $sth = $dbh->prepare("SELECT whine_queries.id " .
- "FROM whine_queries " .
- "LEFT JOIN whine_events " .
- "ON whine_events.id = " .
- "whine_queries.eventid " .
- "WHERE whine_events.owner_userid=? " .
- "AND whine_queries.id =?");
- $sth->execute($userid, $qid);
-
- for (@{$sth->fetchall_arrayref}) {
- $sth = $dbh->prepare("DELETE FROM whine_queries " .
- "WHERE id=?");
- $sth->execute($_->[0]);
- }
- }
- else {
- my $o_sort = $cgi->param("orig_query_sort_$qid") || 0;
- my $sort = $cgi->param("query_sort_$qid") || 0;
- my $o_queryname = $cgi->param("orig_query_name_$qid") || '';
- my $queryname = $cgi->param("query_name_$qid") || '';
- my $o_title = $cgi->param("orig_query_title_$qid") || '';
- my $title = $cgi->param("query_title_$qid") || '';
- my $o_onemailperbug =
- $cgi->param("orig_query_onemailperbug_$qid") || 0;
- my $onemailperbug =
- $cgi->param("query_onemailperbug_$qid") ? 1 : 0;
-
- if ( ($o_sort != $sort) ||
- ($o_queryname ne $queryname) ||
- ($o_onemailperbug != $onemailperbug) ||
- ($o_title ne $title) ){
-
- detaint_natural($sort);
- trick_taint($queryname);
- trick_taint($title);
-
- $sth = $dbh->prepare("UPDATE whine_queries " .
- "SET sortkey=?, " .
- "query_name=?, " .
- "title=?, " .
- "onemailperbug=? " .
- "WHERE id=?");
- $sth->execute($sort, $queryname, $title,
- $onemailperbug, $qid);
- }
- }
+ # The group name is used in a placeholder.
+ trick_taint($mailto);
+ $mailto_id = Bugzilla::Group::ValidateGroupName($mailto, ($user))
+ || ThrowUserError('invalid_group_name', {name => $mailto});
}
+ else {
+ # bad value, so it will just mail to the whine
+ # owner. $mailto_id was already set above.
+ $mailto_type = MAILTO_USER;
+ }
+ }
+
+ detaint_natural($mailto_type);
+
+ if ( ($o_day ne $day)
+ || ($o_time ne $time)
+ || ($o_mailto ne $mailto)
+ || ($o_mailto_type != $mailto_type))
+ {
+
+ trick_taint($day);
+ trick_taint($time);
+
+ # the schedule table must be locked
+ $sth
+ = $dbh->prepare("UPDATE whine_schedules "
+ . "SET run_day=?, run_time=?, "
+ . "mailto_type=?, mailto=?, "
+ . "run_next=NULL "
+ . "WHERE id=?");
+ $sth->execute($day, $time, $mailto_type, $mailto_id, $sid);
+ }
+ }
+ }
+
+ # Check queries for changes
+ my $queries = Bugzilla::Whine::Query->match({eventid => $eventid});
+ for my $query (@$queries) {
+ my $qid = $query->id;
+ if ($cgi->param("remove_query_$qid")) {
+
+ $sth
+ = $dbh->prepare("SELECT whine_queries.id "
+ . "FROM whine_queries "
+ . "LEFT JOIN whine_events "
+ . "ON whine_events.id = "
+ . "whine_queries.eventid "
+ . "WHERE whine_events.owner_userid=? "
+ . "AND whine_queries.id =?");
+ $sth->execute($userid, $qid);
+
+ for (@{$sth->fetchall_arrayref}) {
+ $sth = $dbh->prepare("DELETE FROM whine_queries " . "WHERE id=?");
+ $sth->execute($_->[0]);
+ }
}
+ else {
+ my $o_sort = $cgi->param("orig_query_sort_$qid") || 0;
+ my $sort = $cgi->param("query_sort_$qid") || 0;
+ my $o_queryname = $cgi->param("orig_query_name_$qid") || '';
+ my $queryname = $cgi->param("query_name_$qid") || '';
+ my $o_title = $cgi->param("orig_query_title_$qid") || '';
+ my $title = $cgi->param("query_title_$qid") || '';
+ my $o_onemailperbug = $cgi->param("orig_query_onemailperbug_$qid") || 0;
+ my $onemailperbug = $cgi->param("query_onemailperbug_$qid") ? 1 : 0;
+
+ if ( ($o_sort != $sort)
+ || ($o_queryname ne $queryname)
+ || ($o_onemailperbug != $onemailperbug)
+ || ($o_title ne $title))
+ {
+
+ detaint_natural($sort);
+ trick_taint($queryname);
+ trick_taint($title);
+
+ $sth
+ = $dbh->prepare("UPDATE whine_queries "
+ . "SET sortkey=?, "
+ . "query_name=?, "
+ . "title=?, "
+ . "onemailperbug=? "
+ . "WHERE id=?");
+ $sth->execute($sort, $queryname, $title, $onemailperbug, $qid);
+ }
+ }
+ }
}
- delete_token($token);
+ }
+ delete_token($token);
}
$vars->{'mail_others'} = $can_mail_others;
@@ -336,44 +340,43 @@ $events = get_events($userid);
#
# build the whine list by event id
for my $event_id (keys %{$events}) {
- $events->{$event_id}->{'schedule'} = [];
- $events->{$event_id}->{'queries'} = [];
-
- # schedules
- my $schedules = Bugzilla::Whine::Schedule->match({ eventid => $event_id });
- foreach my $schedule (@$schedules) {
- my $mailto_type = $schedule->mailto_is_group ? MAILTO_GROUP
- : MAILTO_USER;
- my $mailto = '';
- if ($mailto_type == MAILTO_USER) {
- $mailto = $schedule->mailto->login;
- }
- elsif ($mailto_type == MAILTO_GROUP) {
- $mailto = $schedule->mailto->name;
- }
-
- push @{$events->{$event_id}->{'schedule'}},
- {
- 'day' => $schedule->run_day,
- 'time' => $schedule->run_time,
- 'mailto_type' => $mailto_type,
- 'mailto' => $mailto,
- 'id' => $schedule->id,
- };
+ $events->{$event_id}->{'schedule'} = [];
+ $events->{$event_id}->{'queries'} = [];
+
+ # schedules
+ my $schedules = Bugzilla::Whine::Schedule->match({eventid => $event_id});
+ foreach my $schedule (@$schedules) {
+ my $mailto_type = $schedule->mailto_is_group ? MAILTO_GROUP : MAILTO_USER;
+ my $mailto = '';
+ if ($mailto_type == MAILTO_USER) {
+ $mailto = $schedule->mailto->login;
}
-
- # queries
- my $queries = Bugzilla::Whine::Query->match({ eventid => $event_id });
- for my $query (@$queries) {
- push @{$events->{$event_id}->{'queries'}},
- {
- 'name' => $query->name,
- 'title' => $query->title,
- 'sort' => $query->sortkey,
- 'id' => $query->id,
- 'onemailperbug' => $query->one_email_per_bug,
- };
+ elsif ($mailto_type == MAILTO_GROUP) {
+ $mailto = $schedule->mailto->name;
}
+
+ push @{$events->{$event_id}->{'schedule'}},
+ {
+ 'day' => $schedule->run_day,
+ 'time' => $schedule->run_time,
+ 'mailto_type' => $mailto_type,
+ 'mailto' => $mailto,
+ 'id' => $schedule->id,
+ };
+ }
+
+ # queries
+ my $queries = Bugzilla::Whine::Query->match({eventid => $event_id});
+ for my $query (@$queries) {
+ push @{$events->{$event_id}->{'queries'}},
+ {
+ 'name' => $query->name,
+ 'title' => $query->title,
+ 'sort' => $query->sortkey,
+ 'id' => $query->id,
+ 'onemailperbug' => $query->one_email_per_bug,
+ };
+ }
}
$vars->{'events'} = $events;
@@ -384,10 +387,11 @@ $sth->execute($userid);
$vars->{'available_queries'} = [];
while (my ($query) = $sth->fetchrow_array) {
- push @{$vars->{'available_queries'}}, $query;
+ push @{$vars->{'available_queries'}}, $query;
}
$vars->{'token'} = issue_session_token('edit_whine');
-$vars->{'local_timezone'} = Bugzilla->local_timezone->short_name_for_datetime(DateTime->now());
+$vars->{'local_timezone'}
+ = Bugzilla->local_timezone->short_name_for_datetime(DateTime->now());
$template->process("whine/schedule.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
@@ -395,9 +399,9 @@ $template->process("whine/schedule.html.tmpl", $vars)
# get_events takes a userid and returns a hash of
# Bugzilla::Whine objects keyed by event ID.
sub get_events {
- my $userid = shift;
- my $event_rows = Bugzilla::Whine->match({ owner_userid => $userid });
- my %events = map { $_->{id} => $_ } @$event_rows;
+ my $userid = shift;
+ my $event_rows = Bugzilla::Whine->match({owner_userid => $userid});
+ my %events = map { $_->{id} => $_ } @$event_rows;
- return \%events;
+ return \%events;
}
diff --git a/editworkflow.cgi b/editworkflow.cgi
index 71b14af77..81b665e42 100755
--- a/editworkflow.cgi
+++ b/editworkflow.cgi
@@ -18,123 +18,134 @@ use Bugzilla::Error;
use Bugzilla::Token;
use Bugzilla::Status;
-my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
my $user = Bugzilla->login(LOGIN_REQUIRED);
print $cgi->header();
$user->in_group('admin')
- || ThrowUserError('auth_failure', {group => 'admin',
- action => 'modify',
- object => 'workflow'});
+ || ThrowUserError('auth_failure',
+ {group => 'admin', action => 'modify', object => 'workflow'});
my $action = $cgi->param('action') || 'edit';
-my $token = $cgi->param('token');
+my $token = $cgi->param('token');
sub get_workflow {
- my $dbh = Bugzilla->dbh;
- my $workflow = $dbh->selectall_arrayref('SELECT old_status, new_status, require_comment
- FROM status_workflow');
- my %workflow;
- foreach my $row (@$workflow) {
- my ($old, $new, $type) = @$row;
- $workflow{$old || 0}{$new} = $type;
- }
- return \%workflow;
+ my $dbh = Bugzilla->dbh;
+ my $workflow = $dbh->selectall_arrayref(
+ 'SELECT old_status, new_status, require_comment
+ FROM status_workflow'
+ );
+ my %workflow;
+ foreach my $row (@$workflow) {
+ my ($old, $new, $type) = @$row;
+ $workflow{$old || 0}{$new} = $type;
+ }
+ return \%workflow;
}
sub load_template {
- my ($filename, $message) = @_;
- my $template = Bugzilla->template;
- my $vars = {};
-
- $vars->{'statuses'} = [Bugzilla::Status->get_all];
- $vars->{'workflow'} = get_workflow();
- $vars->{'token'} = issue_session_token("workflow_$filename");
- $vars->{'message'} = $message;
-
- $template->process("admin/workflow/$filename.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my ($filename, $message) = @_;
+ my $template = Bugzilla->template;
+ my $vars = {};
+
+ $vars->{'statuses'} = [Bugzilla::Status->get_all];
+ $vars->{'workflow'} = get_workflow();
+ $vars->{'token'} = issue_session_token("workflow_$filename");
+ $vars->{'message'} = $message;
+
+ $template->process("admin/workflow/$filename.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
if ($action eq 'edit') {
- load_template('edit');
+ load_template('edit');
}
elsif ($action eq 'update') {
- check_token_data($token, 'workflow_edit');
- my $statuses = [Bugzilla::Status->get_all];
- my $workflow = get_workflow();
-
- my $sth_insert = $dbh->prepare('INSERT INTO status_workflow (old_status, new_status)
- VALUES (?, ?)');
- my $sth_delete = $dbh->prepare('DELETE FROM status_workflow
- WHERE old_status = ? AND new_status = ?');
- my $sth_delnul = $dbh->prepare('DELETE FROM status_workflow
- WHERE old_status IS NULL AND new_status = ?');
-
- # Part 1: Initial bug statuses.
- foreach my $new (@$statuses) {
- if ($cgi->param('w_0_' . $new->id)) {
- $sth_insert->execute(undef, $new->id)
- unless defined $workflow->{0}->{$new->id};
- }
- else {
- $sth_delnul->execute($new->id);
- }
+ check_token_data($token, 'workflow_edit');
+ my $statuses = [Bugzilla::Status->get_all];
+ my $workflow = get_workflow();
+
+ my $sth_insert = $dbh->prepare(
+ 'INSERT INTO status_workflow (old_status, new_status)
+ VALUES (?, ?)'
+ );
+ my $sth_delete = $dbh->prepare(
+ 'DELETE FROM status_workflow
+ WHERE old_status = ? AND new_status = ?'
+ );
+ my $sth_delnul = $dbh->prepare(
+ 'DELETE FROM status_workflow
+ WHERE old_status IS NULL AND new_status = ?'
+ );
+
+ # Part 1: Initial bug statuses.
+ foreach my $new (@$statuses) {
+ if ($cgi->param('w_0_' . $new->id)) {
+ $sth_insert->execute(undef, $new->id) unless defined $workflow->{0}->{$new->id};
}
+ else {
+ $sth_delnul->execute($new->id);
+ }
+ }
- # Part 2: Bug status changes.
- foreach my $old (@$statuses) {
- foreach my $new (@$statuses) {
- next if $old->id == $new->id;
-
- # All transitions to 'duplicate_or_move_bug_status' must be valid.
- if ($cgi->param('w_' . $old->id . '_' . $new->id)
- || ($new->name eq Bugzilla->params->{'duplicate_or_move_bug_status'}))
- {
- $sth_insert->execute($old->id, $new->id)
- unless defined $workflow->{$old->id}->{$new->id};
- }
- else {
- $sth_delete->execute($old->id, $new->id);
- }
- }
+ # Part 2: Bug status changes.
+ foreach my $old (@$statuses) {
+ foreach my $new (@$statuses) {
+ next if $old->id == $new->id;
+
+ # All transitions to 'duplicate_or_move_bug_status' must be valid.
+ if ($cgi->param('w_' . $old->id . '_' . $new->id)
+ || ($new->name eq Bugzilla->params->{'duplicate_or_move_bug_status'}))
+ {
+ $sth_insert->execute($old->id, $new->id)
+ unless defined $workflow->{$old->id}->{$new->id};
+ }
+ else {
+ $sth_delete->execute($old->id, $new->id);
+ }
}
- delete_token($token);
- load_template('edit', 'workflow_updated');
+ }
+ delete_token($token);
+ load_template('edit', 'workflow_updated');
}
elsif ($action eq 'edit_comment') {
- load_template('comment');
+ load_template('comment');
}
elsif ($action eq 'update_comment') {
- check_token_data($token, 'workflow_comment');
- my $workflow = get_workflow();
-
- my $sth_update = $dbh->prepare('UPDATE status_workflow SET require_comment = ?
- WHERE old_status = ? AND new_status = ?');
- my $sth_updnul = $dbh->prepare('UPDATE status_workflow SET require_comment = ?
- WHERE old_status IS NULL AND new_status = ?');
-
- foreach my $old (keys %$workflow) {
- # Hashes cannot have undef as a key, so we use 0. But the DB
- # must store undef, for referential integrity.
- my $old_id_for_db = $old || undef;
- foreach my $new (keys %{$workflow->{$old}}) {
- my $comment_required = $cgi->param("c_${old}_$new") ? 1 : 0;
- next if ($workflow->{$old}->{$new} == $comment_required);
- if ($old_id_for_db) {
- $sth_update->execute($comment_required, $old_id_for_db, $new);
- }
- else {
- $sth_updnul->execute($comment_required, $new);
- }
- }
+ check_token_data($token, 'workflow_comment');
+ my $workflow = get_workflow();
+
+ my $sth_update = $dbh->prepare(
+ 'UPDATE status_workflow SET require_comment = ?
+ WHERE old_status = ? AND new_status = ?'
+ );
+ my $sth_updnul = $dbh->prepare(
+ 'UPDATE status_workflow SET require_comment = ?
+ WHERE old_status IS NULL AND new_status = ?'
+ );
+
+ foreach my $old (keys %$workflow) {
+
+ # Hashes cannot have undef as a key, so we use 0. But the DB
+ # must store undef, for referential integrity.
+ my $old_id_for_db = $old || undef;
+ foreach my $new (keys %{$workflow->{$old}}) {
+ my $comment_required = $cgi->param("c_${old}_$new") ? 1 : 0;
+ next if ($workflow->{$old}->{$new} == $comment_required);
+ if ($old_id_for_db) {
+ $sth_update->execute($comment_required, $old_id_for_db, $new);
+ }
+ else {
+ $sth_updnul->execute($comment_required, $new);
+ }
}
- delete_token($token);
- load_template('comment', 'workflow_updated');
+ }
+ delete_token($token);
+ load_template('comment', 'workflow_updated');
}
else {
- ThrowUserError('unknown_action', {action => $action});
+ ThrowUserError('unknown_action', {action => $action});
}
diff --git a/email_in.pl b/email_in.pl
index a936791b7..a03935830 100755
--- a/email_in.pl
+++ b/email_in.pl
@@ -14,10 +14,11 @@ use warnings;
# run from this one so that it can find its modules.
use Cwd qw(abs_path);
use File::Basename qw(dirname);
+
BEGIN {
- # Untaint the abs_path.
- my ($a) = abs_path($0) =~ /^(.*)$/;
- chdir dirname($a);
+ # Untaint the abs_path.
+ my ($a) = abs_path($0) =~ /^(.*)$/;
+ chdir dirname($a);
}
use lib qw(. lib);
@@ -57,10 +58,10 @@ use constant SIGNATURE_DELIMITER => '-- ';
# These MIME types represent a "body" of an email if they have an
# "inline" Content-Disposition (or no content disposition).
use constant BODY_TYPES => qw(
- text/plain
- text/html
- application/xhtml+xml
- multipart/alternative
+ text/plain
+ text/html
+ application/xhtml+xml
+ multipart/alternative
);
# $input_email is a global so that it can be used in die_handler.
@@ -71,239 +72,249 @@ our ($input_email, %switch);
####################
sub parse_mail {
- my ($mail_text) = @_;
- debug_print('Parsing Email');
- $input_email = Email::MIME->new($mail_text);
-
- my %fields = %{ $switch{'default'} || {} };
- Bugzilla::Hook::process('email_in_before_parse', { mail => $input_email,
- fields => \%fields });
-
- my $summary = $input_email->header('Subject');
- if ($summary =~ /\[\S+ (\d+)\](.*)/i) {
- $fields{'bug_id'} = $1;
- $summary = trim($2);
+ my ($mail_text) = @_;
+ debug_print('Parsing Email');
+ $input_email = Email::MIME->new($mail_text);
+
+ my %fields = %{$switch{'default'} || {}};
+ Bugzilla::Hook::process('email_in_before_parse',
+ {mail => $input_email, fields => \%fields});
+
+ my $summary = $input_email->header('Subject');
+ if ($summary =~ /\[\S+ (\d+)\](.*)/i) {
+ $fields{'bug_id'} = $1;
+ $summary = trim($2);
+ }
+
+ # Ignore automatic replies.
+ # XXX - Improve the way to detect such subjects in different languages.
+ my $auto_submitted = $input_email->header('Auto-Submitted') || '';
+ if ($summary =~ /out of( the)? office/i || $auto_submitted eq 'auto-replied') {
+ debug_print("Automatic reply detected: $summary");
+ exit;
+ }
+
+ my ($body, $attachments) = get_body_and_attachments($input_email);
+
+ debug_print("Body:\n" . $body, 3);
+
+ $body = remove_leading_blank_lines($body);
+ my @body_lines = split(/\r?\n/s, $body);
+
+ # If there are fields specified.
+ if ($body =~ /^\s*@/s) {
+ my $current_field;
+ while (my $line = shift @body_lines) {
+
+ # If the sig is starting, we want to keep this in the
+ # @body_lines so that we don't keep the sig as part of the
+ # comment down below.
+ if ($line eq SIGNATURE_DELIMITER) {
+ unshift(@body_lines, $line);
+ last;
+ }
+
+ # Otherwise, we stop parsing fields on the first blank line.
+ $line = trim($line);
+ last if !$line;
+ if ($line =~ /^\@(\w+)\s*(?:=|\s|$)\s*(.*)\s*/) {
+ $current_field = lc($1);
+ $fields{$current_field} = $2;
+ }
+ else {
+ $fields{$current_field} .= " $line";
+ }
}
-
- # Ignore automatic replies.
- # XXX - Improve the way to detect such subjects in different languages.
- my $auto_submitted = $input_email->header('Auto-Submitted') || '';
- if ($summary =~ /out of( the)? office/i || $auto_submitted eq 'auto-replied') {
- debug_print("Automatic reply detected: $summary");
- exit;
+ }
+
+ %fields = %{Bugzilla::Bug::map_fields(\%fields)};
+
+ my ($reporter) = Email::Address->parse($input_email->header('From'));
+ $fields{'reporter'} = $reporter->address;
+
+ # The summary line only affects us if we're doing a post_bug.
+ # We have to check it down here because there might have been
+ # a bug_id specified in the body of the email.
+ if (!$fields{'bug_id'} && !$fields{'short_desc'}) {
+ $fields{'short_desc'} = $summary;
+ }
+
+ # The Importance/X-Priority headers are only used when creating a new bug.
+ # 1) If somebody specifies a priority, use it.
+ # 2) If there is an Importance or X-Priority header, use it as
+ # something that is relative to the default priority.
+ # If the value is High or 1, increase the priority by 1.
+ # If the value is Low or 5, decrease the priority by 1.
+ # 3) Otherwise, use the default priority.
+ # Note: this will only work if the 'letsubmitterchoosepriority'
+ # parameter is enabled.
+ my $importance
+ = $input_email->header('Importance') || $input_email->header('X-Priority');
+ if (!$fields{'bug_id'} && !$fields{'priority'} && $importance) {
+ my @legal_priorities = @{get_legal_field_values('priority')};
+ my $i
+ = firstidx { $_ eq Bugzilla->params->{'defaultpriority'} } @legal_priorities;
+ if ($importance =~ /(high|[12])/i) {
+ $i-- unless $i == 0;
}
-
- my ($body, $attachments) = get_body_and_attachments($input_email);
-
- debug_print("Body:\n" . $body, 3);
-
- $body = remove_leading_blank_lines($body);
- my @body_lines = split(/\r?\n/s, $body);
-
- # If there are fields specified.
- if ($body =~ /^\s*@/s) {
- my $current_field;
- while (my $line = shift @body_lines) {
- # If the sig is starting, we want to keep this in the
- # @body_lines so that we don't keep the sig as part of the
- # comment down below.
- if ($line eq SIGNATURE_DELIMITER) {
- unshift(@body_lines, $line);
- last;
- }
- # Otherwise, we stop parsing fields on the first blank line.
- $line = trim($line);
- last if !$line;
- if ($line =~ /^\@(\w+)\s*(?:=|\s|$)\s*(.*)\s*/) {
- $current_field = lc($1);
- $fields{$current_field} = $2;
- }
- else {
- $fields{$current_field} .= " $line";
- }
- }
+ elsif ($importance =~ /(low|[45])/i) {
+ $i++ unless $i == $#legal_priorities;
}
+ $fields{'priority'} = $legal_priorities[$i];
+ }
- %fields = %{ Bugzilla::Bug::map_fields(\%fields) };
+ my $comment = '';
- my ($reporter) = Email::Address->parse($input_email->header('From'));
- $fields{'reporter'} = $reporter->address;
+ # Get the description, except the signature.
+ foreach my $line (@body_lines) {
+ last if $line eq SIGNATURE_DELIMITER;
+ $comment .= "$line\n";
+ }
+ $fields{'comment'} = $comment;
- # The summary line only affects us if we're doing a post_bug.
- # We have to check it down here because there might have been
- # a bug_id specified in the body of the email.
- if (!$fields{'bug_id'} && !$fields{'short_desc'}) {
- $fields{'short_desc'} = $summary;
- }
+ my %override = %{$switch{'override'} || {}};
+ foreach my $key (keys %override) {
+ $fields{$key} = $override{$key};
+ }
- # The Importance/X-Priority headers are only used when creating a new bug.
- # 1) If somebody specifies a priority, use it.
- # 2) If there is an Importance or X-Priority header, use it as
- # something that is relative to the default priority.
- # If the value is High or 1, increase the priority by 1.
- # If the value is Low or 5, decrease the priority by 1.
- # 3) Otherwise, use the default priority.
- # Note: this will only work if the 'letsubmitterchoosepriority'
- # parameter is enabled.
- my $importance = $input_email->header('Importance')
- || $input_email->header('X-Priority');
- if (!$fields{'bug_id'} && !$fields{'priority'} && $importance) {
- my @legal_priorities = @{get_legal_field_values('priority')};
- my $i = firstidx { $_ eq Bugzilla->params->{'defaultpriority'} } @legal_priorities;
- if ($importance =~ /(high|[12])/i) {
- $i-- unless $i == 0;
- }
- elsif ($importance =~ /(low|[45])/i) {
- $i++ unless $i == $#legal_priorities;
- }
- $fields{'priority'} = $legal_priorities[$i];
- }
+ debug_print("Parsed Fields:\n" . Dumper(\%fields), 2);
- my $comment = '';
- # Get the description, except the signature.
- foreach my $line (@body_lines) {
- last if $line eq SIGNATURE_DELIMITER;
- $comment .= "$line\n";
- }
- $fields{'comment'} = $comment;
+ debug_print("Attachments:\n" . Dumper($attachments), 3);
+ if (@$attachments) {
+ $fields{'attachments'} = $attachments;
+ }
- my %override = %{ $switch{'override'} || {} };
- foreach my $key (keys %override) {
- $fields{$key} = $override{$key};
- }
-
- debug_print("Parsed Fields:\n" . Dumper(\%fields), 2);
-
- debug_print("Attachments:\n" . Dumper($attachments), 3);
- if (@$attachments) {
- $fields{'attachments'} = $attachments;
- }
-
- return \%fields;
+ return \%fields;
}
sub check_email_fields {
- my ($fields) = @_;
-
- my ($retval, $non_conclusive_fields) =
- Bugzilla::User::match_field({
- 'assigned_to' => { 'type' => 'single' },
- 'qa_contact' => { 'type' => 'single' },
- 'cc' => { 'type' => 'multi' },
- 'newcc' => { 'type' => 'multi' }
- }, $fields, MATCH_SKIP_CONFIRM);
-
- if ($retval != USER_MATCH_SUCCESS) {
- ThrowUserError('user_match_too_many', {fields => $non_conclusive_fields});
- }
+ my ($fields) = @_;
+
+ my ($retval, $non_conclusive_fields) = Bugzilla::User::match_field(
+ {
+ 'assigned_to' => {'type' => 'single'},
+ 'qa_contact' => {'type' => 'single'},
+ 'cc' => {'type' => 'multi'},
+ 'newcc' => {'type' => 'multi'}
+ },
+ $fields,
+ MATCH_SKIP_CONFIRM
+ );
+
+ if ($retval != USER_MATCH_SUCCESS) {
+ ThrowUserError('user_match_too_many', {fields => $non_conclusive_fields});
+ }
}
sub post_bug {
- my ($fields) = @_;
- debug_print('Posting a new bug...');
+ my ($fields) = @_;
+ debug_print('Posting a new bug...');
- my $user = Bugzilla->user;
+ my $user = Bugzilla->user;
- check_email_fields($fields);
+ check_email_fields($fields);
- my $bug = Bugzilla::Bug->create($fields);
- debug_print("Created bug " . $bug->id);
- return ($bug, $bug->comments->[0]);
+ my $bug = Bugzilla::Bug->create($fields);
+ debug_print("Created bug " . $bug->id);
+ return ($bug, $bug->comments->[0]);
}
sub process_bug {
- my ($fields_in) = @_;
- my %fields = %$fields_in;
-
- my $bug_id = $fields{'bug_id'};
- $fields{'id'} = $bug_id;
- delete $fields{'bug_id'};
-
- debug_print("Updating Bug $fields{id}...");
-
- my $bug = Bugzilla::Bug->check($bug_id);
-
- if ($fields{'bug_status'}) {
- $fields{'knob'} = $fields{'bug_status'};
- }
- # If no status is given, then we only want to change the resolution.
- elsif ($fields{'resolution'}) {
- $fields{'knob'} = 'change_resolution';
- $fields{'resolution_knob_change_resolution'} = $fields{'resolution'};
- }
- if ($fields{'dup_id'}) {
- $fields{'knob'} = 'duplicate';
- }
-
- # Move @cc to @newcc as @cc is used by process_bug.cgi to remove
- # users from the CC list when @removecc is set.
- $fields{'newcc'} = delete $fields{'cc'} if $fields{'cc'};
-
- # Make it possible to remove CCs.
- if ($fields{'removecc'}) {
- $fields{'cc'} = [split(',', $fields{'removecc'})];
- $fields{'removecc'} = 1;
- }
-
- check_email_fields(\%fields);
-
- my $cgi = Bugzilla->cgi;
- foreach my $field (keys %fields) {
- $cgi->param(-name => $field, -value => $fields{$field});
- }
- $cgi->param('token', issue_hash_token([$bug->id, $bug->delta_ts]));
-
- require 'process_bug.cgi';
- debug_print("Bug processed.");
-
- my $added_comment;
- if (trim($fields{'comment'})) {
- # The "old" bug object doesn't contain the comment we just added.
- $added_comment = Bugzilla::Bug->check($bug_id)->comments->[-1];
- }
- return ($bug, $added_comment);
+ my ($fields_in) = @_;
+ my %fields = %$fields_in;
+
+ my $bug_id = $fields{'bug_id'};
+ $fields{'id'} = $bug_id;
+ delete $fields{'bug_id'};
+
+ debug_print("Updating Bug $fields{id}...");
+
+ my $bug = Bugzilla::Bug->check($bug_id);
+
+ if ($fields{'bug_status'}) {
+ $fields{'knob'} = $fields{'bug_status'};
+ }
+
+ # If no status is given, then we only want to change the resolution.
+ elsif ($fields{'resolution'}) {
+ $fields{'knob'} = 'change_resolution';
+ $fields{'resolution_knob_change_resolution'} = $fields{'resolution'};
+ }
+ if ($fields{'dup_id'}) {
+ $fields{'knob'} = 'duplicate';
+ }
+
+ # Move @cc to @newcc as @cc is used by process_bug.cgi to remove
+ # users from the CC list when @removecc is set.
+ $fields{'newcc'} = delete $fields{'cc'} if $fields{'cc'};
+
+ # Make it possible to remove CCs.
+ if ($fields{'removecc'}) {
+ $fields{'cc'} = [split(',', $fields{'removecc'})];
+ $fields{'removecc'} = 1;
+ }
+
+ check_email_fields(\%fields);
+
+ my $cgi = Bugzilla->cgi;
+ foreach my $field (keys %fields) {
+ $cgi->param(-name => $field, -value => $fields{$field});
+ }
+ $cgi->param('token', issue_hash_token([$bug->id, $bug->delta_ts]));
+
+ require 'process_bug.cgi';
+ debug_print("Bug processed.");
+
+ my $added_comment;
+ if (trim($fields{'comment'})) {
+
+ # The "old" bug object doesn't contain the comment we just added.
+ $added_comment = Bugzilla::Bug->check($bug_id)->comments->[-1];
+ }
+ return ($bug, $added_comment);
}
sub handle_attachments {
- my ($bug, $attachments, $comment) = @_;
- return if !$attachments;
- debug_print("Handling attachments...");
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- my ($update_comment, $update_bug);
- foreach my $attachment (@$attachments) {
- debug_print("Inserting Attachment: " . Dumper($attachment), 3);
- my $type = $attachment->content_type || 'application/octet-stream';
- # MUAs add stuff like "name=" to content-type that we really don't
- # want.
- $type =~ s/;.*//;
- my $obj = Bugzilla::Attachment->create({
- bug => $bug,
- description => $attachment->filename(1),
- filename => $attachment->filename(1),
- mimetype => $type,
- data => $attachment->body,
- });
- # If we added a comment, and our comment does not already have a type,
- # and this is our first attachment, then we make the comment an
- # "attachment created" comment.
- if ($comment and !$comment->type and !$update_comment) {
- $comment->set_all({ type => CMT_ATTACHMENT_CREATED,
- extra_data => $obj->id });
- $update_comment = 1;
- }
- else {
- $bug->add_comment('', { type => CMT_ATTACHMENT_CREATED,
- extra_data => $obj->id });
- $update_bug = 1;
- }
+ my ($bug, $attachments, $comment) = @_;
+ return if !$attachments;
+ debug_print("Handling attachments...");
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ my ($update_comment, $update_bug);
+ foreach my $attachment (@$attachments) {
+ debug_print("Inserting Attachment: " . Dumper($attachment), 3);
+ my $type = $attachment->content_type || 'application/octet-stream';
+
+ # MUAs add stuff like "name=" to content-type that we really don't
+ # want.
+ $type =~ s/;.*//;
+ my $obj = Bugzilla::Attachment->create({
+ bug => $bug,
+ description => $attachment->filename(1),
+ filename => $attachment->filename(1),
+ mimetype => $type,
+ data => $attachment->body,
+ });
+
+ # If we added a comment, and our comment does not already have a type,
+ # and this is our first attachment, then we make the comment an
+ # "attachment created" comment.
+ if ($comment and !$comment->type and !$update_comment) {
+ $comment->set_all({type => CMT_ATTACHMENT_CREATED, extra_data => $obj->id});
+ $update_comment = 1;
+ }
+ else {
+ $bug->add_comment('', {type => CMT_ATTACHMENT_CREATED, extra_data => $obj->id});
+ $update_bug = 1;
}
- # We only update the comments and bugs at the end of the transaction,
- # because doing so modifies bugs_fulltext, which is a non-transactional
- # table.
- $bug->update() if $update_bug;
- $comment->update() if $update_comment;
- $dbh->bz_commit_transaction();
+ }
+
+ # We only update the comments and bugs at the end of the transaction,
+ # because doing so modifies bugs_fulltext, which is a non-transactional
+ # table.
+ $bug->update() if $update_bug;
+ $comment->update() if $update_comment;
+ $dbh->bz_commit_transaction();
}
######################
@@ -311,181 +322,199 @@ sub handle_attachments {
######################
sub debug_print {
- my ($str, $level) = @_;
- $level ||= 1;
- print STDERR "$str\n" if $level <= $switch{'verbose'};
+ my ($str, $level) = @_;
+ $level ||= 1;
+ print STDERR "$str\n" if $level <= $switch{'verbose'};
}
sub get_body_and_attachments {
- my ($email) = @_;
-
- my $ct = $email->content_type || 'text/plain';
- debug_print("Splitting Body and Attachments [Type: $ct]...", 2);
-
- my ($bodies, $attachments) = split_body_and_attachments($email);
- debug_print(scalar(@$bodies) . " body part(s) and " . scalar(@$attachments)
- . " attachment part(s).");
- debug_print('Bodies: ' . Dumper($bodies), 3);
-
- # Get the first part of the email that contains a text body,
- # and make all the other pieces into attachments. (This handles
- # people or MUAs who accidentally attach text files as an "inline"
- # attachment.)
- my $body;
- while (@$bodies) {
- my $possible = shift @$bodies;
- $body = get_text_alternative($possible);
- if (defined $body) {
- unshift(@$attachments, @$bodies);
- last;
- }
+ my ($email) = @_;
+
+ my $ct = $email->content_type || 'text/plain';
+ debug_print("Splitting Body and Attachments [Type: $ct]...", 2);
+
+ my ($bodies, $attachments) = split_body_and_attachments($email);
+ debug_print(
+ scalar(@$bodies)
+ . " body part(s) and "
+ . scalar(@$attachments)
+ . " attachment part(s).");
+ debug_print('Bodies: ' . Dumper($bodies), 3);
+
+ # Get the first part of the email that contains a text body,
+ # and make all the other pieces into attachments. (This handles
+ # people or MUAs who accidentally attach text files as an "inline"
+ # attachment.)
+ my $body;
+ while (@$bodies) {
+ my $possible = shift @$bodies;
+ $body = get_text_alternative($possible);
+ if (defined $body) {
+ unshift(@$attachments, @$bodies);
+ last;
}
+ }
- if (!defined $body) {
- # Note that this only happens if the email does not contain any
- # text/plain parts. If the email has an empty text/plain part,
- # you're fine, and this message does NOT get thrown.
- ThrowUserError('email_no_body');
- }
+ if (!defined $body) {
+
+ # Note that this only happens if the email does not contain any
+ # text/plain parts. If the email has an empty text/plain part,
+ # you're fine, and this message does NOT get thrown.
+ ThrowUserError('email_no_body');
+ }
+
+ debug_print("Picked Body:\n$body", 2);
- debug_print("Picked Body:\n$body", 2);
-
- return ($body, $attachments);
+ return ($body, $attachments);
}
sub get_text_alternative {
- my ($email) = @_;
-
- my @parts = $email->parts;
- my $body;
- foreach my $part (@parts) {
- my $ct = $part->content_type || 'text/plain';
- my $charset = 'iso-8859-1';
- # The charset may be quoted.
- if ($ct =~ /charset="?([^;"]+)/) {
- $charset= $1;
- }
- debug_print("Alternative Part Content-Type: $ct", 2);
- debug_print("Alternative Part Character Encoding: $charset", 2);
- # If we find a text/plain body here, return it immediately.
- if (!$ct || $ct =~ m{^text/plain}i) {
- return _decode_body($charset, $part->body);
- }
- # If we find a text/html body, decode it, but don't return
- # it immediately, because there might be a text/plain alternative
- # later. This could be any HTML type.
- if ($ct =~ m{^application/xhtml\+xml}i or $ct =~ m{text/html}i) {
- my $parser = HTML::FormatText::WithLinks->new(
- # Put footnnote indicators after the text, not before it.
- before_link => '',
- after_link => '[%n]',
- # Convert bold and italics, use "*" for bold instead of "_".
- with_emphasis => 1,
- bold_marker => '*',
- # If the same link appears multiple times, only create
- # one footnote.
- unique_links => 1,
- # If the link text is the URL, don't create a footnote.
- skip_linked_urls => 1,
- );
- $body = _decode_body($charset, $part->body);
- $body = $parser->parse($body);
- }
+ my ($email) = @_;
+
+ my @parts = $email->parts;
+ my $body;
+ foreach my $part (@parts) {
+ my $ct = $part->content_type || 'text/plain';
+ my $charset = 'iso-8859-1';
+
+ # The charset may be quoted.
+ if ($ct =~ /charset="?([^;"]+)/) {
+ $charset = $1;
+ }
+ debug_print("Alternative Part Content-Type: $ct", 2);
+ debug_print("Alternative Part Character Encoding: $charset", 2);
+
+ # If we find a text/plain body here, return it immediately.
+ if (!$ct || $ct =~ m{^text/plain}i) {
+ return _decode_body($charset, $part->body);
+ }
+
+ # If we find a text/html body, decode it, but don't return
+ # it immediately, because there might be a text/plain alternative
+ # later. This could be any HTML type.
+ if ($ct =~ m{^application/xhtml\+xml}i or $ct =~ m{text/html}i) {
+ my $parser = HTML::FormatText::WithLinks->new(
+
+ # Put footnnote indicators after the text, not before it.
+ before_link => '',
+ after_link => '[%n]',
+
+ # Convert bold and italics, use "*" for bold instead of "_".
+ with_emphasis => 1,
+ bold_marker => '*',
+
+ # If the same link appears multiple times, only create
+ # one footnote.
+ unique_links => 1,
+
+ # If the link text is the URL, don't create a footnote.
+ skip_linked_urls => 1,
+ );
+ $body = _decode_body($charset, $part->body);
+ $body = $parser->parse($body);
}
+ }
- return $body;
+ return $body;
}
sub _decode_body {
- my ($charset, $body) = @_;
- if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($body)) {
- return Encode::decode($charset, $body);
- }
- return $body;
+ my ($charset, $body) = @_;
+ if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($body)) {
+ return Encode::decode($charset, $body);
+ }
+ return $body;
}
sub remove_leading_blank_lines {
- my ($text) = @_;
- $text =~ s/^(\s*\n)+//s;
- return $text;
+ my ($text) = @_;
+ $text =~ s/^(\s*\n)+//s;
+ return $text;
}
sub html_strip {
- my ($var) = @_;
- # Trivial HTML tag remover (this is just for error messages, really.)
- $var =~ s/<[^>]*>//g;
- # And this basically reverses the Template-Toolkit html filter.
- $var =~ s/\&amp;/\&/g;
- $var =~ s/\&lt;/</g;
- $var =~ s/\&gt;/>/g;
- $var =~ s/\&quot;/\"/g;
- $var =~ s/&#64;/@/g;
- # Also remove undesired newlines and consecutive spaces.
- $var =~ s/[\n\s]+/ /gms;
- return $var;
+ my ($var) = @_;
+
+ # Trivial HTML tag remover (this is just for error messages, really.)
+ $var =~ s/<[^>]*>//g;
+
+ # And this basically reverses the Template-Toolkit html filter.
+ $var =~ s/\&amp;/\&/g;
+ $var =~ s/\&lt;/</g;
+ $var =~ s/\&gt;/>/g;
+ $var =~ s/\&quot;/\"/g;
+ $var =~ s/&#64;/@/g;
+
+ # Also remove undesired newlines and consecutive spaces.
+ $var =~ s/[\n\s]+/ /gms;
+ return $var;
}
sub split_body_and_attachments {
- my ($email) = @_;
-
- my (@body, @attachments);
- foreach my $part ($email->parts) {
- my $ct = lc($part->content_type || 'text/plain');
- my $disposition = lc($part->header('Content-Disposition') || 'inline');
- # Remove the charset, etc. from the content-type, we don't care here.
- $ct =~ s/;.*//;
- debug_print("Part Content-Type: [$ct]", 2);
- debug_print("Part Disposition: [$disposition]", 2);
-
- if ($disposition eq 'inline' and grep($_ eq $ct, BODY_TYPES)) {
- push(@body, $part);
- next;
- }
-
- if (scalar($part->parts) == 1) {
- push(@attachments, $part);
- next;
- }
-
- # If this part has sub-parts, analyze them similarly to how we
- # did above and return the relevant pieces.
- my ($add_body, $add_attachments) = split_body_and_attachments($part);
- push(@body, @$add_body);
- push(@attachments, @$add_attachments);
+ my ($email) = @_;
+
+ my (@body, @attachments);
+ foreach my $part ($email->parts) {
+ my $ct = lc($part->content_type || 'text/plain');
+ my $disposition = lc($part->header('Content-Disposition') || 'inline');
+
+ # Remove the charset, etc. from the content-type, we don't care here.
+ $ct =~ s/;.*//;
+ debug_print("Part Content-Type: [$ct]", 2);
+ debug_print("Part Disposition: [$disposition]", 2);
+
+ if ($disposition eq 'inline' and grep($_ eq $ct, BODY_TYPES)) {
+ push(@body, $part);
+ next;
}
- return (\@body, \@attachments);
+ if (scalar($part->parts) == 1) {
+ push(@attachments, $part);
+ next;
+ }
+
+ # If this part has sub-parts, analyze them similarly to how we
+ # did above and return the relevant pieces.
+ my ($add_body, $add_attachments) = split_body_and_attachments($part);
+ push(@body, @$add_body);
+ push(@attachments, @$add_attachments);
+ }
+
+ return (\@body, \@attachments);
}
sub die_handler {
- my ($msg) = @_;
-
- # In Template-Toolkit, [% RETURN %] is implemented as a call to "die".
- # But of course, we really don't want to actually *die* just because
- # the user-error or code-error template ended. So we don't really die.
- return if blessed($msg) && $msg->isa('Template::Exception')
- && $msg->type eq 'return';
-
- # If this is inside an eval, then we should just act like...we're
- # in an eval (instead of printing the error and exiting).
- die @_ if ($^S // Bugzilla::Error::_in_eval());
-
- # We can't depend on the MTA to send an error message, so we have
- # to generate one properly.
- if ($input_email) {
- $msg =~ s/at .+ line.*$//ms;
- $msg =~ s/^Compilation failed in require.+$//ms;
- $msg = html_strip($msg);
- my $from = Bugzilla->params->{'mailfrom'};
- my $reply = reply(to => $input_email, from => $from, top_post => 1,
- body => "$msg\n");
- MessageToMTA($reply->as_string);
- }
- print STDERR "$msg\n";
- # We exit with a successful value, because we don't want the MTA
- # to *also* send a failure notice.
- exit;
+ my ($msg) = @_;
+
+ # In Template-Toolkit, [% RETURN %] is implemented as a call to "die".
+ # But of course, we really don't want to actually *die* just because
+ # the user-error or code-error template ended. So we don't really die.
+ return
+ if blessed($msg)
+ && $msg->isa('Template::Exception')
+ && $msg->type eq 'return';
+
+ # If this is inside an eval, then we should just act like...we're
+ # in an eval (instead of printing the error and exiting).
+ die @_ if ($^S // Bugzilla::Error::_in_eval());
+
+ # We can't depend on the MTA to send an error message, so we have
+ # to generate one properly.
+ if ($input_email) {
+ $msg =~ s/at .+ line.*$//ms;
+ $msg =~ s/^Compilation failed in require.+$//ms;
+ $msg = html_strip($msg);
+ my $from = Bugzilla->params->{'mailfrom'};
+ my $reply
+ = reply(to => $input_email, from => $from, top_post => 1, body => "$msg\n");
+ MessageToMTA($reply->as_string);
+ }
+ print STDERR "$msg\n";
+
+ # We exit with a successful value, because we don't want the MTA
+ # to *also* send a failure notice.
+ exit;
}
###############
@@ -502,18 +531,19 @@ pod2usage({-verbose => 0, -exitval => 1}) if $switch{'help'};
Bugzilla->usage_mode(USAGE_MODE_EMAIL);
-my @mail_lines = <STDIN>;
-my $mail_text = join("", @mail_lines);
+my @mail_lines = <STDIN>;
+my $mail_text = join("", @mail_lines);
my $mail_fields = parse_mail($mail_text);
-Bugzilla::Hook::process('email_in_after_parse', { fields => $mail_fields });
+Bugzilla::Hook::process('email_in_after_parse', {fields => $mail_fields});
my $attachments = delete $mail_fields->{'attachments'};
my $username = $mail_fields->{'reporter'};
+
# If emailsuffix is in use, we have to remove it from the email address.
if (my $suffix = Bugzilla->params->{'emailsuffix'}) {
- $username =~ s/\Q$suffix\E$//i;
+ $username =~ s/\Q$suffix\E$//i;
}
my $user = Bugzilla::User->check($username);
@@ -521,10 +551,10 @@ Bugzilla->set_user($user);
my ($bug, $comment);
if ($mail_fields->{'bug_id'}) {
- ($bug, $comment) = process_bug($mail_fields);
+ ($bug, $comment) = process_bug($mail_fields);
}
else {
- ($bug, $comment) = post_bug($mail_fields);
+ ($bug, $comment) = post_bug($mail_fields);
}
handle_attachments($bug, $attachments, $comment);
@@ -536,7 +566,7 @@ handle_attachments($bug, $attachments, $comment);
# to wait for $bug->update() to be fully used in email_in.pl first. So
# currently, process_bug.cgi does the mail sending for bugs, and this does
# any mail sending for attachments after the first one.
-Bugzilla::BugMail::Send($bug->id, { changer => Bugzilla->user });
+Bugzilla::BugMail::Send($bug->id, {changer => Bugzilla->user});
debug_print("Sent bugmail");
diff --git a/enter_bug.cgi b/enter_bug.cgi
index 0a74af830..7d290ad7c 100755
--- a/enter_bug.cgi
+++ b/enter_bug.cgi
@@ -10,8 +10,8 @@
#
# enter_bug.cgi
# -------------
-# Displays bug entry form. Bug fields are specified through popup menus,
-# drop-down lists, or text fields. Default for these values can be
+# Displays bug entry form. Bug fields are specified through popup menus,
+# drop-down lists, or text fields. Default for these values can be
# passed in as parameters to the cgi.
#
##############################################################################
@@ -41,81 +41,90 @@ my $user = Bugzilla->login(LOGIN_REQUIRED);
my $cloned_bug;
my $cloned_bug_id;
-my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
# All pages point to the same part of the documentation.
$vars->{'doc_section'} = 'using/filing.html';
my $product_name = trim($cgi->param('product') || '');
+
# Will contain the product object the bug is created in.
my $product;
if ($product_name eq '') {
- # If the user cannot enter bugs in any product, stop here.
- my @enterable_products = @{$user->get_enterable_products};
- ThrowUserError('no_products') unless scalar(@enterable_products);
- my $classification = Bugzilla->params->{'useclassification'} ?
- scalar($cgi->param('classification')) : '__all';
+ # If the user cannot enter bugs in any product, stop here.
+ my @enterable_products = @{$user->get_enterable_products};
+ ThrowUserError('no_products') unless scalar(@enterable_products);
- # Unless a real classification name is given, we sort products
- # by classification.
- my @classifications;
+ my $classification
+ = Bugzilla->params->{'useclassification'}
+ ? scalar($cgi->param('classification'))
+ : '__all';
- unless ($classification && $classification ne '__all') {
- @classifications = @{sort_products_by_classification(\@enterable_products)};
- }
+ # Unless a real classification name is given, we sort products
+ # by classification.
+ my @classifications;
- unless ($classification) {
- # We know there is at least one classification available,
- # else we would have stopped earlier.
- if (scalar(@classifications) > 1) {
- # We only need classification objects.
- $vars->{'classifications'} = [map {$_->{'object'}} @classifications];
-
- $vars->{'target'} = "enter_bug.cgi";
-
- print $cgi->header();
- $template->process("global/choose-classification.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
- # If we come here, then there is only one classification available.
- $classification = $classifications[0]->{'object'}->name;
- }
+ unless ($classification && $classification ne '__all') {
+ @classifications = @{sort_products_by_classification(\@enterable_products)};
+ }
+
+ unless ($classification) {
+
+ # We know there is at least one classification available,
+ # else we would have stopped earlier.
+ if (scalar(@classifications) > 1) {
+
+ # We only need classification objects.
+ $vars->{'classifications'} = [map { $_->{'object'} } @classifications];
+
+ $vars->{'target'} = "enter_bug.cgi";
- # Keep only enterable products which are in the specified classification.
- if ($classification ne "__all") {
- my $class = new Bugzilla::Classification({'name' => $classification});
- # If the classification doesn't exist, then there is no product in it.
- if ($class) {
- @enterable_products
- = grep {$_->classification_id == $class->id} @enterable_products;
- @classifications = ({object => $class, products => \@enterable_products});
- }
- else {
- @enterable_products = ();
- }
+ print $cgi->header();
+ $template->process("global/choose-classification.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
- if (scalar(@enterable_products) == 0) {
- ThrowUserError('no_products');
+ # If we come here, then there is only one classification available.
+ $classification = $classifications[0]->{'object'}->name;
+ }
+
+ # Keep only enterable products which are in the specified classification.
+ if ($classification ne "__all") {
+ my $class = new Bugzilla::Classification({'name' => $classification});
+
+ # If the classification doesn't exist, then there is no product in it.
+ if ($class) {
+ @enterable_products
+ = grep { $_->classification_id == $class->id } @enterable_products;
+ @classifications = ({object => $class, products => \@enterable_products});
}
- elsif (scalar(@enterable_products) > 1) {
- $vars->{'classifications'} = \@classifications;
- $vars->{'target'} = "enter_bug.cgi";
-
- print $cgi->header();
- $template->process("global/choose-product.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
- } else {
- # Only one product exists.
- $product = $enterable_products[0];
+ else {
+ @enterable_products = ();
}
+ }
+
+ if (scalar(@enterable_products) == 0) {
+ ThrowUserError('no_products');
+ }
+ elsif (scalar(@enterable_products) > 1) {
+ $vars->{'classifications'} = \@classifications;
+ $vars->{'target'} = "enter_bug.cgi";
+
+ print $cgi->header();
+ $template->process("global/choose-product.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+ else {
+ # Only one product exists.
+ $product = $enterable_products[0];
+ }
}
# We need to check and make sure that the user has permission
@@ -126,15 +135,15 @@ $product = $user->can_enter_product($product || $product_name, THROW_ERROR);
# Useful Subroutines
##############################################################################
sub formvalue {
- my ($name, $default) = (@_);
- return Bugzilla->cgi->param($name) || $default || "";
+ my ($name, $default) = (@_);
+ return Bugzilla->cgi->param($name) || $default || "";
}
##############################################################################
# End of subroutines
##############################################################################
-my $has_editbugs = $user->in_group('editbugs', $product->id);
+my $has_editbugs = $user->in_group('editbugs', $product->id);
my $has_canconfirm = $user->in_group('canconfirm', $product->id);
# If a user is trying to clone a bug
@@ -143,139 +152,143 @@ my $has_canconfirm = $user->in_group('canconfirm', $product->id);
$cloned_bug_id = $cgi->param('cloned_bug_id');
if ($cloned_bug_id) {
- $cloned_bug = Bugzilla::Bug->check($cloned_bug_id);
- $cloned_bug_id = $cloned_bug->id;
+ $cloned_bug = Bugzilla::Bug->check($cloned_bug_id);
+ $cloned_bug_id = $cloned_bug->id;
}
# If there is only one active component, choose it
my @active = grep { $_->is_active } @{$product->components};
if (scalar(@active) == 1) {
- $cgi->param('component', $active[0]->name);
+ $cgi->param('component', $active[0]->name);
}
# If there is only one active version, choose it
@active = grep { $_->is_active } @{$product->versions};
if (scalar(@active) == 1) {
- $cgi->param('version', $active[0]->name);
+ $cgi->param('version', $active[0]->name);
}
my %default;
-$vars->{'product'} = $product;
+$vars->{'product'} = $product;
-$vars->{'priority'} = get_legal_field_values('priority');
-$vars->{'bug_severity'} = get_legal_field_values('bug_severity');
-$vars->{'rep_platform'} = get_legal_field_values('rep_platform');
-$vars->{'op_sys'} = get_legal_field_values('op_sys');
+$vars->{'priority'} = get_legal_field_values('priority');
+$vars->{'bug_severity'} = get_legal_field_values('bug_severity');
+$vars->{'rep_platform'} = get_legal_field_values('rep_platform');
+$vars->{'op_sys'} = get_legal_field_values('op_sys');
-$vars->{'assigned_to'} = formvalue('assigned_to');
-$vars->{'assigned_to_disabled'} = !$has_editbugs;
-$vars->{'cc_disabled'} = 0;
+$vars->{'assigned_to'} = formvalue('assigned_to');
+$vars->{'assigned_to_disabled'} = !$has_editbugs;
+$vars->{'cc_disabled'} = 0;
-$vars->{'qa_contact'} = formvalue('qa_contact');
-$vars->{'qa_contact_disabled'} = !$has_editbugs;
+$vars->{'qa_contact'} = formvalue('qa_contact');
+$vars->{'qa_contact_disabled'} = !$has_editbugs;
-$vars->{'cloned_bug_id'} = $cloned_bug_id;
+$vars->{'cloned_bug_id'} = $cloned_bug_id;
$vars->{'token'} = issue_session_token('create_bug');
my @enter_bug_fields = grep { $_->enter_bug } Bugzilla->active_custom_fields;
foreach my $field (@enter_bug_fields) {
- my $cf_name = $field->name;
- my $cf_value = $cgi->param($cf_name);
- if (defined $cf_value) {
- if ($field->type == FIELD_TYPE_MULTI_SELECT) {
- $cf_value = [$cgi->param($cf_name)];
- }
- $default{$cf_name} = $vars->{$cf_name} = $cf_value;
+ my $cf_name = $field->name;
+ my $cf_value = $cgi->param($cf_name);
+ if (defined $cf_value) {
+ if ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ $cf_value = [$cgi->param($cf_name)];
}
+ $default{$cf_name} = $vars->{$cf_name} = $cf_value;
+ }
}
# This allows the Field visibility and value controls to work with the
# Classification and Product fields as a parent.
$default{'classification'} = $product->classification->name;
-$default{'product'} = $product->name;
+$default{'product'} = $product->name;
if ($cloned_bug_id) {
- $default{'component_'} = $cloned_bug->component;
- $default{'priority'} = $cloned_bug->priority;
- $default{'bug_severity'} = $cloned_bug->bug_severity;
- $default{'rep_platform'} = $cloned_bug->rep_platform;
- $default{'op_sys'} = $cloned_bug->op_sys;
-
- $vars->{'short_desc'} = $cloned_bug->short_desc;
- $vars->{'bug_file_loc'} = $cloned_bug->bug_file_loc;
- $vars->{'keywords'} = $cloned_bug->keywords;
- $vars->{'dependson'} = join (", ", @{$cloned_bug->dependson});
- $vars->{'blocked'} = join (", ", @{$cloned_bug->blocked});
- $vars->{'deadline'} = $cloned_bug->deadline;
- $vars->{'estimated_time'} = $cloned_bug->estimated_time;
-
- if (scalar @{$cloned_bug->cc}) {
- $vars->{'cc'} = join (", ", @{$cloned_bug->cc});
- } else {
- $vars->{'cc'} = formvalue('cc');
+ $default{'component_'} = $cloned_bug->component;
+ $default{'priority'} = $cloned_bug->priority;
+ $default{'bug_severity'} = $cloned_bug->bug_severity;
+ $default{'rep_platform'} = $cloned_bug->rep_platform;
+ $default{'op_sys'} = $cloned_bug->op_sys;
+
+ $vars->{'short_desc'} = $cloned_bug->short_desc;
+ $vars->{'bug_file_loc'} = $cloned_bug->bug_file_loc;
+ $vars->{'keywords'} = $cloned_bug->keywords;
+ $vars->{'dependson'} = join(", ", @{$cloned_bug->dependson});
+ $vars->{'blocked'} = join(", ", @{$cloned_bug->blocked});
+ $vars->{'deadline'} = $cloned_bug->deadline;
+ $vars->{'estimated_time'} = $cloned_bug->estimated_time;
+
+ if (scalar @{$cloned_bug->cc}) {
+ $vars->{'cc'} = join(", ", @{$cloned_bug->cc});
+ }
+ else {
+ $vars->{'cc'} = formvalue('cc');
+ }
+
+ foreach my $role (qw(reporter assigned_to qa_contact)) {
+ if ( defined($cloned_bug->$role)
+ && $cloned_bug->$role->id != $user->id
+ && none { $_ eq $cloned_bug->$role->login } @{$cloned_bug->cc})
+ {
+ $vars->{'cc'} = join(", ", $cloned_bug->$role->login, $vars->{'cc'});
}
+ }
- foreach my $role (qw(reporter assigned_to qa_contact)) {
- if (defined($cloned_bug->$role)
- && $cloned_bug->$role->id != $user->id
- && none { $_ eq $cloned_bug->$role->login } @{$cloned_bug->cc})
- {
- $vars->{'cc'} = join (", ", $cloned_bug->$role->login, $vars->{'cc'});
- }
- }
+ foreach my $field (@enter_bug_fields) {
+ my $field_name = $field->name;
+ $vars->{$field_name} = $cloned_bug->$field_name;
+ }
- foreach my $field (@enter_bug_fields) {
- my $field_name = $field->name;
- $vars->{$field_name} = $cloned_bug->$field_name;
- }
+ # We need to ensure that we respect the 'insider' status of
+ # the first comment, if it has one. Either way, make a note
+ # that this bug was cloned from another bug.
+ my $bug_desc = $cloned_bug->comments({order => 'oldest_to_newest'})->[0];
+ my $isprivate = $bug_desc->is_private;
- # We need to ensure that we respect the 'insider' status of
- # the first comment, if it has one. Either way, make a note
- # that this bug was cloned from another bug.
- my $bug_desc = $cloned_bug->comments({ order => 'oldest_to_newest' })->[0];
- my $isprivate = $bug_desc->is_private;
+ $vars->{'comment'} = "";
+ $vars->{'comment_is_private'} = 0;
- $vars->{'comment'} = "";
- $vars->{'comment_is_private'} = 0;
+ if (!$isprivate || $user->is_insider) {
- if (!$isprivate || $user->is_insider) {
- # We use "body" to avoid any format_comment text, which would be
- # pointless to clone.
- $vars->{'comment'} = $bug_desc->body;
- $vars->{'comment_is_private'} = $isprivate;
- }
+ # We use "body" to avoid any format_comment text, which would be
+ # pointless to clone.
+ $vars->{'comment'} = $bug_desc->body;
+ $vars->{'comment_is_private'} = $isprivate;
+ }
-} # end of cloned bug entry form
+} # end of cloned bug entry form
else {
- $default{'component_'} = formvalue('component');
- $default{'priority'} = formvalue('priority', Bugzilla->params->{'defaultpriority'});
- $default{'bug_severity'} = formvalue('bug_severity', Bugzilla->params->{'defaultseverity'});
- $default{'rep_platform'} = formvalue('rep_platform',
- Bugzilla->params->{'defaultplatform'} || detect_platform());
- $default{'op_sys'} = formvalue('op_sys',
- Bugzilla->params->{'defaultopsys'} || detect_op_sys());
-
- $vars->{'alias'} = formvalue('alias');
- $vars->{'short_desc'} = formvalue('short_desc');
- $vars->{'bug_file_loc'} = formvalue('bug_file_loc', "http://");
- $vars->{'keywords'} = formvalue('keywords');
- $vars->{'dependson'} = formvalue('dependson');
- $vars->{'blocked'} = formvalue('blocked');
- $vars->{'deadline'} = formvalue('deadline');
- $vars->{'estimated_time'} = formvalue('estimated_time');
- $vars->{'see_also'} = formvalue('see_also');
-
- $vars->{'cc'} = join(', ', $cgi->param('cc'));
-
- $vars->{'comment'} = formvalue('comment');
- $vars->{'comment_is_private'} = formvalue('comment_is_private');
-
-} # end of normal/bookmarked entry form
+ $default{'component_'} = formvalue('component');
+ $default{'priority'}
+ = formvalue('priority', Bugzilla->params->{'defaultpriority'});
+ $default{'bug_severity'}
+ = formvalue('bug_severity', Bugzilla->params->{'defaultseverity'});
+ $default{'rep_platform'} = formvalue('rep_platform',
+ Bugzilla->params->{'defaultplatform'} || detect_platform());
+ $default{'op_sys'}
+ = formvalue('op_sys', Bugzilla->params->{'defaultopsys'} || detect_op_sys());
+
+ $vars->{'alias'} = formvalue('alias');
+ $vars->{'short_desc'} = formvalue('short_desc');
+ $vars->{'bug_file_loc'} = formvalue('bug_file_loc', "http://");
+ $vars->{'keywords'} = formvalue('keywords');
+ $vars->{'dependson'} = formvalue('dependson');
+ $vars->{'blocked'} = formvalue('blocked');
+ $vars->{'deadline'} = formvalue('deadline');
+ $vars->{'estimated_time'} = formvalue('estimated_time');
+ $vars->{'see_also'} = formvalue('see_also');
+
+ $vars->{'cc'} = join(', ', $cgi->param('cc'));
+
+ $vars->{'comment'} = formvalue('comment');
+ $vars->{'comment_is_private'} = formvalue('comment_is_private');
+
+} # end of normal/bookmarked entry form
# IF this is a cloned bug,
@@ -295,31 +308,35 @@ $vars->{'version'} = $product->versions;
my $version_cookie = $cgi->cookie("VERSION-" . $product->name);
-if ( ($cloned_bug_id) &&
- ($product->name eq $cloned_bug->product ) ) {
- $default{'version'} = $cloned_bug->version;
-} elsif (formvalue('version')) {
- $default{'version'} = formvalue('version');
-} elsif (defined $version_cookie
- and grep { $_->name eq $version_cookie } @{ $vars->{'version'} })
+if (($cloned_bug_id) && ($product->name eq $cloned_bug->product)) {
+ $default{'version'} = $cloned_bug->version;
+}
+elsif (formvalue('version')) {
+ $default{'version'} = formvalue('version');
+}
+elsif (defined $version_cookie
+ and grep { $_->name eq $version_cookie } @{$vars->{'version'}})
{
- $default{'version'} = $version_cookie;
-} else {
- $default{'version'} = $vars->{'version'}->[$#{$vars->{'version'}}]->name;
+ $default{'version'} = $version_cookie;
+}
+else {
+ $default{'version'} = $vars->{'version'}->[$#{$vars->{'version'}}]->name;
}
# Get list of milestones.
-if ( Bugzilla->params->{'usetargetmilestone'} ) {
- $vars->{'target_milestone'} = $product->milestones;
- if (formvalue('target_milestone')) {
- $default{'target_milestone'} = formvalue('target_milestone');
- } else {
- $default{'target_milestone'} = $product->default_milestone;
- }
+if (Bugzilla->params->{'usetargetmilestone'}) {
+ $vars->{'target_milestone'} = $product->milestones;
+ if (formvalue('target_milestone')) {
+ $default{'target_milestone'} = formvalue('target_milestone');
+ }
+ else {
+ $default{'target_milestone'} = $product->default_milestone;
+ }
}
# Construct the list of allowable statuses.
-my @statuses = @{ Bugzilla::Bug->new_bug_statuses($product) };
+my @statuses = @{Bugzilla::Bug->new_bug_statuses($product)};
+
# Exclude closed states from the UI, even if the workflow allows them.
# The back-end code will still accept them, though.
# XXX We should remove this when the UI accepts closed statuses and update
@@ -336,28 +353,32 @@ $vars->{'bug_status'} = \@statuses;
my $picked_status = formvalue('bug_status');
if ($picked_status and grep($_->name eq $picked_status, @statuses)) {
- $default{'bug_status'} = formvalue('bug_status');
-} else {
- $default{'bug_status'} = Bugzilla::Bug->default_bug_status(@statuses);
+ $default{'bug_status'} = formvalue('bug_status');
+}
+else {
+ $default{'bug_status'} = Bugzilla::Bug->default_bug_status(@statuses);
}
my @groups = $cgi->param('groups');
if ($cloned_bug) {
- my @clone_groups = map { $_->name } @{ $cloned_bug->groups_in };
- # It doesn't matter if there are duplicate names, since all we check
- # for in the template is whether or not the group is set.
- push(@groups, @clone_groups);
+ my @clone_groups = map { $_->name } @{$cloned_bug->groups_in};
+
+ # It doesn't matter if there are duplicate names, since all we check
+ # for in the template is whether or not the group is set.
+ push(@groups, @clone_groups);
}
$default{'groups'} = \@groups;
-Bugzilla::Hook::process('enter_bug_entrydefaultvars', { vars => $vars });
+Bugzilla::Hook::process('enter_bug_entrydefaultvars', {vars => $vars});
$vars->{'default'} = \%default;
-my $format = $template->get_format("bug/create/create",
- scalar $cgi->param('format'),
- scalar $cgi->param('ctype'));
+my $format = $template->get_format(
+ "bug/create/create",
+ scalar $cgi->param('format'),
+ scalar $cgi->param('ctype')
+);
print $cgi->header($format->{'ctype'});
$template->process($format->{'template'}, $vars)
- || ThrowTemplateError($template->error());
+ || ThrowTemplateError($template->error());
diff --git a/extensions/BmpConvert/Config.pm b/extensions/BmpConvert/Config.pm
index 4984f19a9..ed2df6197 100644
--- a/extensions/BmpConvert/Config.pm
+++ b/extensions/BmpConvert/Config.pm
@@ -12,12 +12,7 @@ use strict;
use warnings;
use constant NAME => 'BmpConvert';
-use constant REQUIRED_MODULES => [
- {
- package => 'PerlMagick',
- module => 'Image::Magick',
- version => 0,
- },
-];
+use constant REQUIRED_MODULES =>
+ [{package => 'PerlMagick', module => 'Image::Magick', version => 0,},];
__PACKAGE__->NAME;
diff --git a/extensions/BmpConvert/Extension.pm b/extensions/BmpConvert/Extension.pm
index b8201f106..84a2c81dc 100644
--- a/extensions/BmpConvert/Extension.pm
+++ b/extensions/BmpConvert/Extension.pm
@@ -18,29 +18,30 @@ use Image::Magick;
our $VERSION = '1.0';
sub attachment_process_data {
- my ($self, $args) = @_;
- return unless $args->{attributes}->{mimetype} eq 'image/bmp';
-
- my $data = ${$args->{data}};
- my $img = Image::Magick->new(magick => 'bmp');
-
- # $data is a filehandle.
- if (ref $data) {
- $img->Read(file => \*$data);
- $img->set(magick => 'png');
- $img->Write(file => \*$data);
- }
- # $data is a blob.
- else {
- $img->BlobToImage($data);
- $img->set(magick => 'png');
- $data = $img->ImageToBlob();
- }
- undef $img;
-
- ${$args->{data}} = $data;
- $args->{attributes}->{mimetype} = 'image/png';
- $args->{attributes}->{filename} =~ s/^(.+)\.bmp$/$1.png/i;
+ my ($self, $args) = @_;
+ return unless $args->{attributes}->{mimetype} eq 'image/bmp';
+
+ my $data = ${$args->{data}};
+ my $img = Image::Magick->new(magick => 'bmp');
+
+ # $data is a filehandle.
+ if (ref $data) {
+ $img->Read(file => \*$data);
+ $img->set(magick => 'png');
+ $img->Write(file => \*$data);
+ }
+
+ # $data is a blob.
+ else {
+ $img->BlobToImage($data);
+ $img->set(magick => 'png');
+ $data = $img->ImageToBlob();
+ }
+ undef $img;
+
+ ${$args->{data}} = $data;
+ $args->{attributes}->{mimetype} = 'image/png';
+ $args->{attributes}->{filename} =~ s/^(.+)\.bmp$/$1.png/i;
}
- __PACKAGE__->NAME;
+__PACKAGE__->NAME;
diff --git a/extensions/Example/Config.pm b/extensions/Example/Config.pm
index e7782ef6c..696da2de9 100644
--- a/extensions/Example/Config.pm
+++ b/extensions/Example/Config.pm
@@ -12,21 +12,16 @@ use strict;
use warnings;
use constant NAME => 'Example';
-use constant REQUIRED_MODULES => [
- {
- package => 'Data-Dumper',
- module => 'Data::Dumper',
- version => 0,
- },
-];
+use constant REQUIRED_MODULES =>
+ [{package => 'Data-Dumper', module => 'Data::Dumper', version => 0,},];
use constant OPTIONAL_MODULES => [
- {
- package => 'Acme',
- module => 'Acme',
- version => 1.11,
- feature => ['example_acme'],
- },
+ {
+ package => 'Acme',
+ module => 'Acme',
+ version => 1.11,
+ feature => ['example_acme'],
+ },
];
__PACKAGE__->NAME;
diff --git a/extensions/Example/Extension.pm b/extensions/Example/Extension.pm
index dbc84df72..1ecb3e692 100644
--- a/extensions/Example/Extension.pm
+++ b/extensions/Example/Extension.pm
@@ -35,357 +35,374 @@ use constant REL_EXAMPLE => -127;
our $VERSION = '1.0';
sub user_can_administer {
- my ($self, $args) = @_;
- my $can_administer = $args->{can_administer};
+ my ($self, $args) = @_;
+ my $can_administer = $args->{can_administer};
- # If you add an option to the admin pages (e.g. by using the Hooks in
- # template/en/default/admin/admin.html.tmpl), you may want to allow
- # users in another group view admin.cgi
- #if (Bugzilla->user->in_group('other_group')) {
- # $$can_administer = 1;
- #}
+ # If you add an option to the admin pages (e.g. by using the Hooks in
+ # template/en/default/admin/admin.html.tmpl), you may want to allow
+ # users in another group view admin.cgi
+ #if (Bugzilla->user->in_group('other_group')) {
+ # $$can_administer = 1;
+ #}
}
sub admin_editusers_action {
- my ($self, $args) = @_;
- my ($vars, $action, $user) = @$args{qw(vars action user)};
- my $template = Bugzilla->template;
-
- if ($action eq 'my_action') {
- # Allow to restrict the search to any group the user is allowed to bless.
- $vars->{'restrictablegroups'} = $user->bless_groups();
- $template->process('admin/users/search.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
+ my ($self, $args) = @_;
+ my ($vars, $action, $user) = @$args{qw(vars action user)};
+ my $template = Bugzilla->template;
+
+ if ($action eq 'my_action') {
+
+ # Allow to restrict the search to any group the user is allowed to bless.
+ $vars->{'restrictablegroups'} = $user->bless_groups();
+ $template->process('admin/users/search.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
}
sub attachment_process_data {
- my ($self, $args) = @_;
- my $type = $args->{attributes}->{mimetype};
- my $filename = $args->{attributes}->{filename};
-
- # Make sure images have the correct extension.
- # Uncomment the two lines below to make this check effective.
- if ($type =~ /^image\/(\w+)$/) {
- my $format = $1;
- if ($filename =~ /^(.+)(:?\.[^\.]+)$/) {
- my $name = $1;
- #$args->{attributes}->{filename} = "${name}.$format";
- }
- else {
- # The file has no extension. We append it.
- #$args->{attributes}->{filename} .= ".$format";
- }
+ my ($self, $args) = @_;
+ my $type = $args->{attributes}->{mimetype};
+ my $filename = $args->{attributes}->{filename};
+
+ # Make sure images have the correct extension.
+ # Uncomment the two lines below to make this check effective.
+ if ($type =~ /^image\/(\w+)$/) {
+ my $format = $1;
+ if ($filename =~ /^(.+)(:?\.[^\.]+)$/) {
+ my $name = $1;
+
+ #$args->{attributes}->{filename} = "${name}.$format";
+ }
+ else {
+ # The file has no extension. We append it.
+ #$args->{attributes}->{filename} .= ".$format";
}
+ }
}
sub auth_login_methods {
- my ($self, $args) = @_;
- my $modules = $args->{modules};
- if (exists $modules->{Example}) {
- $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Login.pm';
- }
+ my ($self, $args) = @_;
+ my $modules = $args->{modules};
+ if (exists $modules->{Example}) {
+ $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Login.pm';
+ }
}
sub auth_verify_methods {
- my ($self, $args) = @_;
- my $modules = $args->{modules};
- if (exists $modules->{Example}) {
- $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Verify.pm';
- }
+ my ($self, $args) = @_;
+ my $modules = $args->{modules};
+ if (exists $modules->{Example}) {
+ $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Verify.pm';
+ }
}
sub bug_check_can_change_field {
- my ($self, $args) = @_;
-
- my ($bug, $field, $new_value, $old_value, $priv_results)
- = @$args{qw(bug field new_value old_value priv_results)};
-
- my $user = Bugzilla->user;
-
- # Disallow a bug from being reopened if currently closed unless user
- # is in 'admin' group
- if ($field eq 'bug_status' && $bug->product_obj->name eq 'Example') {
- if (!is_open_state($old_value) && is_open_state($new_value)
- && !$user->in_group('admin'))
- {
- push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED);
- return;
- }
- }
+ my ($self, $args) = @_;
- # Disallow a bug's keywords from being edited unless user is the
- # reporter of the bug
- if ($field eq 'keywords' && $bug->product_obj->name eq 'Example'
- && $user->login ne $bug->reporter->login)
- {
- push(@$priv_results, PRIVILEGES_REQUIRED_REPORTER);
- return;
- }
+ my ($bug, $field, $new_value, $old_value, $priv_results)
+ = @$args{qw(bug field new_value old_value priv_results)};
+
+ my $user = Bugzilla->user;
- # Allow updating of priority even if user cannot normally edit the bug
- # and they are in group 'engineering'
- if ($field eq 'priority' && $bug->product_obj->name eq 'Example'
- && $user->in_group('engineering'))
+ # Disallow a bug from being reopened if currently closed unless user
+ # is in 'admin' group
+ if ($field eq 'bug_status' && $bug->product_obj->name eq 'Example') {
+ if (!is_open_state($old_value)
+ && is_open_state($new_value)
+ && !$user->in_group('admin'))
{
- push(@$priv_results, PRIVILEGES_REQUIRED_NONE);
- return;
+ push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED);
+ return;
}
+ }
+
+ # Disallow a bug's keywords from being edited unless user is the
+ # reporter of the bug
+ if ( $field eq 'keywords'
+ && $bug->product_obj->name eq 'Example'
+ && $user->login ne $bug->reporter->login)
+ {
+ push(@$priv_results, PRIVILEGES_REQUIRED_REPORTER);
+ return;
+ }
+
+ # Allow updating of priority even if user cannot normally edit the bug
+ # and they are in group 'engineering'
+ if ( $field eq 'priority'
+ && $bug->product_obj->name eq 'Example'
+ && $user->in_group('engineering'))
+ {
+ push(@$priv_results, PRIVILEGES_REQUIRED_NONE);
+ return;
+ }
}
sub bug_columns {
- my ($self, $args) = @_;
- my $columns = $args->{'columns'};
- push (@$columns, "delta_ts AS example")
+ my ($self, $args) = @_;
+ my $columns = $args->{'columns'};
+ push(@$columns, "delta_ts AS example");
}
sub bug_end_of_create {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
+
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my $bug = $args->{'bug'};
+ my $timestamp = $args->{'timestamp'};
+
+ my $bug_id = $bug->id;
- # This code doesn't actually *do* anything, it's just here to show you
- # how to use this hook.
- my $bug = $args->{'bug'};
- my $timestamp = $args->{'timestamp'};
-
- my $bug_id = $bug->id;
- # Uncomment this line to see a line in your webserver's error log whenever
- # you file a bug.
- # warn "Bug $bug_id has been filed!";
+ # Uncomment this line to see a line in your webserver's error log whenever
+ # you file a bug.
+ # warn "Bug $bug_id has been filed!";
}
sub bug_end_of_create_validators {
- my ($self, $args) = @_;
-
- # This code doesn't actually *do* anything, it's just here to show you
- # how to use this hook.
- my $bug_params = $args->{'params'};
-
- # Uncomment this line below to see a line in your webserver's error log
- # containing all validated bug field values every time you file a bug.
- # warn Dumper($bug_params);
-
- # This would remove all ccs from the bug, preventing ANY ccs from being
- # added on bug creation.
- # $bug_params->{cc} = [];
-}
+ my ($self, $args) = @_;
-sub bug_start_of_update {
- my ($self, $args) = @_;
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my $bug_params = $args->{'params'};
- # This code doesn't actually *do* anything, it's just here to show you
- # how to use this hook.
- my ($bug, $old_bug, $timestamp, $changes) =
- @$args{qw(bug old_bug timestamp changes)};
+ # Uncomment this line below to see a line in your webserver's error log
+ # containing all validated bug field values every time you file a bug.
+ # warn Dumper($bug_params);
- foreach my $field (keys %$changes) {
- my $used_to_be = $changes->{$field}->[0];
- my $now_it_is = $changes->{$field}->[1];
- }
+ # This would remove all ccs from the bug, preventing ANY ccs from being
+ # added on bug creation.
+ # $bug_params->{cc} = [];
+}
- my $old_summary = $old_bug->short_desc;
-
- my $status_message;
- if (my $status_change = $changes->{'bug_status'}) {
- my $old_status = new Bugzilla::Status({ name => $status_change->[0] });
- my $new_status = new Bugzilla::Status({ name => $status_change->[1] });
- if ($new_status->is_open && !$old_status->is_open) {
- $status_message = "Bug re-opened!";
- }
- if (!$new_status->is_open && $old_status->is_open) {
- $status_message = "Bug closed!";
- }
+sub bug_start_of_update {
+ my ($self, $args) = @_;
+
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my ($bug, $old_bug, $timestamp, $changes)
+ = @$args{qw(bug old_bug timestamp changes)};
+
+ foreach my $field (keys %$changes) {
+ my $used_to_be = $changes->{$field}->[0];
+ my $now_it_is = $changes->{$field}->[1];
+ }
+
+ my $old_summary = $old_bug->short_desc;
+
+ my $status_message;
+ if (my $status_change = $changes->{'bug_status'}) {
+ my $old_status = new Bugzilla::Status({name => $status_change->[0]});
+ my $new_status = new Bugzilla::Status({name => $status_change->[1]});
+ if ($new_status->is_open && !$old_status->is_open) {
+ $status_message = "Bug re-opened!";
+ }
+ if (!$new_status->is_open && $old_status->is_open) {
+ $status_message = "Bug closed!";
}
+ }
+
+ my $bug_id = $bug->id;
+ my $num_changes = scalar keys %$changes;
+ my $result = "There were $num_changes changes to fields on bug $bug_id"
+ . " at $timestamp.";
- my $bug_id = $bug->id;
- my $num_changes = scalar keys %$changes;
- my $result = "There were $num_changes changes to fields on bug $bug_id"
- . " at $timestamp.";
- # Uncomment this line to see $result in your webserver's error log whenever
- # you update a bug.
- # warn $result;
+ # Uncomment this line to see $result in your webserver's error log whenever
+ # you update a bug.
+ # warn $result;
}
sub bug_end_of_update {
- my ($self, $args) = @_;
-
- # This code doesn't actually *do* anything, it's just here to show you
- # how to use this hook.
- my ($bug, $old_bug, $timestamp, $changes) =
- @$args{qw(bug old_bug timestamp changes)};
-
- foreach my $field (keys %$changes) {
- my $used_to_be = $changes->{$field}->[0];
- my $now_it_is = $changes->{$field}->[1];
+ my ($self, $args) = @_;
+
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my ($bug, $old_bug, $timestamp, $changes)
+ = @$args{qw(bug old_bug timestamp changes)};
+
+ foreach my $field (keys %$changes) {
+ my $used_to_be = $changes->{$field}->[0];
+ my $now_it_is = $changes->{$field}->[1];
+ }
+
+ my $old_summary = $old_bug->short_desc;
+
+ my $status_message;
+ if (my $status_change = $changes->{'bug_status'}) {
+ my $old_status = new Bugzilla::Status({name => $status_change->[0]});
+ my $new_status = new Bugzilla::Status({name => $status_change->[1]});
+ if ($new_status->is_open && !$old_status->is_open) {
+ $status_message = "Bug re-opened!";
}
-
- my $old_summary = $old_bug->short_desc;
-
- my $status_message;
- if (my $status_change = $changes->{'bug_status'}) {
- my $old_status = new Bugzilla::Status({ name => $status_change->[0] });
- my $new_status = new Bugzilla::Status({ name => $status_change->[1] });
- if ($new_status->is_open && !$old_status->is_open) {
- $status_message = "Bug re-opened!";
- }
- if (!$new_status->is_open && $old_status->is_open) {
- $status_message = "Bug closed!";
- }
+ if (!$new_status->is_open && $old_status->is_open) {
+ $status_message = "Bug closed!";
}
-
- my $bug_id = $bug->id;
- my $num_changes = scalar keys %$changes;
- my $result = "There were $num_changes changes to fields on bug $bug_id"
- . " at $timestamp.";
- # Uncomment this line to see $result in your webserver's error log whenever
- # you update a bug.
- # warn $result;
+ }
+
+ my $bug_id = $bug->id;
+ my $num_changes = scalar keys %$changes;
+ my $result = "There were $num_changes changes to fields on bug $bug_id"
+ . " at $timestamp.";
+
+ # Uncomment this line to see $result in your webserver's error log whenever
+ # you update a bug.
+ # warn $result;
}
sub bug_fields {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $fields = $args->{'fields'};
- push (@$fields, "example")
+ my $fields = $args->{'fields'};
+ push(@$fields, "example");
}
sub bug_format_comment {
- my ($self, $args) = @_;
-
- # This replaces every occurrence of the word "foo" with the word
- # "bar"
-
- my $regexes = $args->{'regexes'};
- push(@$regexes, { match => qr/\bfoo\b/, replace => 'bar' });
-
- # And this links every occurrence of the word "bar" to example.com,
- # but it won't affect "foo"s that have already been turned into "bar"
- # above (because each regex is run in order, and later regexes don't modify
- # earlier matches, due to some cleverness in Bugzilla's internals).
- #
- # For example, the phrase "foo bar" would become:
- # bar <a href="http://example.com/bar">bar</a>
- my $bar_match = qr/\b(bar)\b/;
- push(@$regexes, { match => $bar_match, replace => \&_replace_bar });
+ my ($self, $args) = @_;
+
+ # This replaces every occurrence of the word "foo" with the word
+ # "bar"
+
+ my $regexes = $args->{'regexes'};
+ push(@$regexes, {match => qr/\bfoo\b/, replace => 'bar'});
+
+ # And this links every occurrence of the word "bar" to example.com,
+ # but it won't affect "foo"s that have already been turned into "bar"
+ # above (because each regex is run in order, and later regexes don't modify
+ # earlier matches, due to some cleverness in Bugzilla's internals).
+ #
+ # For example, the phrase "foo bar" would become:
+ # bar <a href="http://example.com/bar">bar</a>
+ my $bar_match = qr/\b(bar)\b/;
+ push(@$regexes, {match => $bar_match, replace => \&_replace_bar});
}
# Used by bug_format_comment--see its code for an explanation.
sub _replace_bar {
- my $args = shift;
- # $match is the first parentheses match in the $bar_match regex
- # in bug-format_comment.pl. We get up to 10 regex matches as
- # arguments to this function.
- my $match = $args->{matches}->[0];
- # Remember, you have to HTML-escape any data that you are returning!
- $match = html_quote($match);
- return qq{<a href="http://example.com/">$match</a>};
-};
+ my $args = shift;
+
+ # $match is the first parentheses match in the $bar_match regex
+ # in bug-format_comment.pl. We get up to 10 regex matches as
+ # arguments to this function.
+ my $match = $args->{matches}->[0];
+
+ # Remember, you have to HTML-escape any data that you are returning!
+ $match = html_quote($match);
+ return qq{<a href="http://example.com/">$match</a>};
+}
sub buglist_columns {
- my ($self, $args) = @_;
-
- my $columns = $args->{'columns'};
- $columns->{'example'} = { 'name' => 'bugs.delta_ts' , 'title' => 'Example' };
- $columns->{'product_desc'} = { 'name' => 'prod_desc.description',
- 'title' => 'Product Description' };
+ my ($self, $args) = @_;
+
+ my $columns = $args->{'columns'};
+ $columns->{'example'} = {'name' => 'bugs.delta_ts', 'title' => 'Example'};
+ $columns->{'product_desc'}
+ = {'name' => 'prod_desc.description', 'title' => 'Product Description'};
}
sub buglist_column_joins {
- my ($self, $args) = @_;
- my $joins = $args->{'column_joins'};
+ my ($self, $args) = @_;
+ my $joins = $args->{'column_joins'};
- # This column is added using the "buglist_columns" hook
- $joins->{'product_desc'} = {
- from => 'product_id',
- to => 'id',
- table => 'products',
- as => 'prod_desc',
- join => 'INNER',
- };
+ # This column is added using the "buglist_columns" hook
+ $joins->{'product_desc'} = {
+ from => 'product_id',
+ to => 'id',
+ table => 'products',
+ as => 'prod_desc',
+ join => 'INNER',
+ };
}
sub search_operator_field_override {
- my ($self, $args) = @_;
-
- my $operators = $args->{'operators'};
+ my ($self, $args) = @_;
- my $original = $operators->{component}->{_non_changed};
- $operators->{component} = {
- _non_changed => sub { _component_nonchanged($original, @_) }
- };
+ my $operators = $args->{'operators'};
+
+ my $original = $operators->{component}->{_non_changed};
+ $operators->{component} = {
+ _non_changed => sub { _component_nonchanged($original, @_) }
+ };
}
sub _component_nonchanged {
- my $original = shift;
- my ($invocant, $args) = @_;
+ my $original = shift;
+ my ($invocant, $args) = @_;
+
+ $invocant->$original($args);
- $invocant->$original($args);
- # Actually, it does not change anything in the result,
- # just an example.
- $args->{term} = $args->{term} . " OR 1=2";
+ # Actually, it does not change anything in the result,
+ # just an example.
+ $args->{term} = $args->{term} . " OR 1=2";
}
sub bugmail_recipients {
- my ($self, $args) = @_;
- my $recipients = $args->{recipients};
- my $bug = $args->{bug};
-
- my $user =
- new Bugzilla::User({ name => Bugzilla->params->{'maintainer'} });
-
- if ($bug->id == 1) {
- # Uncomment the line below to add the maintainer to the recipients
- # list of every bugmail from bug 1 as though that the maintainer
- # were on the CC list.
- #$recipients->{$user->id}->{+REL_CC} = 1;
-
- # And this line adds the maintainer as though they had the
- # "REL_EXAMPLE" relationship from the bugmail_relationships hook below.
- #$recipients->{$user->id}->{+REL_EXAMPLE} = 1;
- }
+ my ($self, $args) = @_;
+ my $recipients = $args->{recipients};
+ my $bug = $args->{bug};
+
+ my $user = new Bugzilla::User({name => Bugzilla->params->{'maintainer'}});
+
+ if ($bug->id == 1) {
+
+ # Uncomment the line below to add the maintainer to the recipients
+ # list of every bugmail from bug 1 as though that the maintainer
+ # were on the CC list.
+ #$recipients->{$user->id}->{+REL_CC} = 1;
+
+ # And this line adds the maintainer as though they had the
+ # "REL_EXAMPLE" relationship from the bugmail_relationships hook below.
+ #$recipients->{$user->id}->{+REL_EXAMPLE} = 1;
+ }
}
sub bugmail_relationships {
- my ($self, $args) = @_;
- my $relationships = $args->{relationships};
- $relationships->{+REL_EXAMPLE} = 'Example';
+ my ($self, $args) = @_;
+ my $relationships = $args->{relationships};
+ $relationships->{+REL_EXAMPLE} = 'Example';
}
sub cgi_headers {
- my ($self, $args) = @_;
- my $headers = $args->{'headers'};
+ my ($self, $args) = @_;
+ my $headers = $args->{'headers'};
- $headers->{'-x_test_header'} = "Test header from Example extension";
+ $headers->{'-x_test_header'} = "Test header from Example extension";
}
sub config_add_panels {
- my ($self, $args) = @_;
-
- my $modules = $args->{panel_modules};
- $modules->{Example} = "Bugzilla::Extension::Example::Config";
+ my ($self, $args) = @_;
+
+ my $modules = $args->{panel_modules};
+ $modules->{Example} = "Bugzilla::Extension::Example::Config";
}
sub config_modify_panels {
- my ($self, $args) = @_;
-
- my $panels = $args->{panels};
-
- # Add the "Example" auth methods.
- my $auth_params = $panels->{'auth'}->{params};
- my ($info_class) = grep($_->{name} eq 'user_info_class', @$auth_params);
- my ($verify_class) = grep($_->{name} eq 'user_verify_class', @$auth_params);
+ my ($self, $args) = @_;
+
+ my $panels = $args->{panels};
- push(@{ $info_class->{choices} }, 'CGI,Example');
- push(@{ $verify_class->{choices} }, 'Example');
+ # Add the "Example" auth methods.
+ my $auth_params = $panels->{'auth'}->{params};
+ my ($info_class) = grep($_->{name} eq 'user_info_class', @$auth_params);
+ my ($verify_class) = grep($_->{name} eq 'user_verify_class', @$auth_params);
- push(@$auth_params, { name => 'param_example',
- type => 't',
- default => 0,
- checker => \&check_numeric });
+ push(@{$info_class->{choices}}, 'CGI,Example');
+ push(@{$verify_class->{choices}}, 'Example');
+
+ push(
+ @$auth_params,
+ {
+ name => 'param_example',
+ type => 't',
+ default => 0,
+ checker => \&check_numeric
+ }
+ );
}
sub db_schema_abstract_schema {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
+
# $args->{'schema'}->{'example_table'} = {
# FIELDS => [
# id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
@@ -404,659 +421,695 @@ sub db_schema_abstract_schema {
}
sub email_in_before_parse {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $subject = $args->{mail}->header('Subject');
- # Correctly extract the bug ID from email subjects of the form [Bug comp/NNN].
- if ($subject =~ /\[.*(\d+)\].*/) {
- $args->{fields}->{bug_id} = $1;
- }
+ my $subject = $args->{mail}->header('Subject');
+
+ # Correctly extract the bug ID from email subjects of the form [Bug comp/NNN].
+ if ($subject =~ /\[.*(\d+)\].*/) {
+ $args->{fields}->{bug_id} = $1;
+ }
}
sub email_in_after_parse {
- my ($self, $args) = @_;
- my $reporter = $args->{fields}->{reporter};
- my $dbh = Bugzilla->dbh;
-
- # No other check needed if this is a valid regular user.
- return if login_to_id($reporter);
-
- # The reporter is not a regular user. We create an account for them,
- # but they can only comment on existing bugs.
- # This is useful for people who reply by email to bugmails received
- # in mailing-lists.
- if ($args->{fields}->{bug_id}) {
- # WARNING: we return now to skip the remaining code below.
- # You must understand that removing this line would make the code
- # below effective! Do it only if you are OK with the behavior
- # described here.
- return;
-
- Bugzilla::User->create({ login_name => $reporter, cryptpassword => '*' });
-
- # For security reasons, delete all fields unrelated to comments.
- foreach my $field (keys %{$args->{fields}}) {
- next if $field =~ /^(?:bug_id|comment|reporter)$/;
- delete $args->{fields}->{$field};
- }
- }
- else {
- ThrowUserError('invalid_username', { name => $reporter });
+ my ($self, $args) = @_;
+ my $reporter = $args->{fields}->{reporter};
+ my $dbh = Bugzilla->dbh;
+
+ # No other check needed if this is a valid regular user.
+ return if login_to_id($reporter);
+
+ # The reporter is not a regular user. We create an account for them,
+ # but they can only comment on existing bugs.
+ # This is useful for people who reply by email to bugmails received
+ # in mailing-lists.
+ if ($args->{fields}->{bug_id}) {
+
+ # WARNING: we return now to skip the remaining code below.
+ # You must understand that removing this line would make the code
+ # below effective! Do it only if you are OK with the behavior
+ # described here.
+ return;
+
+ Bugzilla::User->create({login_name => $reporter, cryptpassword => '*'});
+
+ # For security reasons, delete all fields unrelated to comments.
+ foreach my $field (keys %{$args->{fields}}) {
+ next if $field =~ /^(?:bug_id|comment|reporter)$/;
+ delete $args->{fields}->{$field};
}
+ }
+ else {
+ ThrowUserError('invalid_username', {name => $reporter});
+ }
}
sub enter_bug_entrydefaultvars {
- my ($self, $args) = @_;
-
- my $vars = $args->{vars};
- $vars->{'example'} = 1;
+ my ($self, $args) = @_;
+
+ my $vars = $args->{vars};
+ $vars->{'example'} = 1;
}
sub error_catch {
- my ($self, $args) = @_;
- # Customize the error message displayed when someone tries to access
- # page.cgi with an invalid page ID, and keep track of this attempt
- # in the web server log.
- return unless Bugzilla->error_mode == ERROR_MODE_WEBPAGE;
- return unless $args->{error} eq 'bad_page_cgi_id';
-
- my $page_id = $args->{vars}->{page_id};
- my $login = Bugzilla->user->identity || "Someone";
- warn "$login attempted to access page.cgi with id = $page_id";
-
- my $page = $args->{message};
- my $new_error_msg = "Ah ah, you tried to access $page_id? Good try!";
- $new_error_msg = html_quote($new_error_msg);
- # There are better tools to parse an HTML page, but it's just an example.
- # Since Perl 5.16, we can no longer write "class" inside look-behind
- # assertions, because "ss" is also seen as the german ß character, which
- # makes Perl 5.16 complain. The right fix is to use the /aa modifier,
- # but it's only understood since Perl 5.14. So the workaround is to write
- # "clas[s]" instead of "class". Stupid and ugly hack, but it works with
- # all Perl versions.
- $$page =~ s/(?<=<td id="error_msg" clas[s]="throw_error">).*(?=<\/td>)/$new_error_msg/si;
+ my ($self, $args) = @_;
+
+ # Customize the error message displayed when someone tries to access
+ # page.cgi with an invalid page ID, and keep track of this attempt
+ # in the web server log.
+ return unless Bugzilla->error_mode == ERROR_MODE_WEBPAGE;
+ return unless $args->{error} eq 'bad_page_cgi_id';
+
+ my $page_id = $args->{vars}->{page_id};
+ my $login = Bugzilla->user->identity || "Someone";
+ warn "$login attempted to access page.cgi with id = $page_id";
+
+ my $page = $args->{message};
+ my $new_error_msg = "Ah ah, you tried to access $page_id? Good try!";
+ $new_error_msg = html_quote($new_error_msg);
+
+ # There are better tools to parse an HTML page, but it's just an example.
+ # Since Perl 5.16, we can no longer write "class" inside look-behind
+ # assertions, because "ss" is also seen as the german ß character, which
+ # makes Perl 5.16 complain. The right fix is to use the /aa modifier,
+ # but it's only understood since Perl 5.14. So the workaround is to write
+ # "clas[s]" instead of "class". Stupid and ugly hack, but it works with
+ # all Perl versions.
+ $$page
+ =~ s/(?<=<td id="error_msg" clas[s]="throw_error">).*(?=<\/td>)/$new_error_msg/si;
}
sub flag_end_of_update {
- my ($self, $args) = @_;
-
- # This code doesn't actually *do* anything, it's just here to show you
- # how to use this hook.
- my $flag_params = $args;
- my ($object, $timestamp, $old_flags, $new_flags) =
- @$flag_params{qw(object timestamp old_flags new_flags)};
- my ($removed, $added) = diff_arrays($old_flags, $new_flags);
- my ($granted, $denied) = (0, 0);
- foreach my $new_flag (@$added) {
- $granted++ if $new_flag =~ /\+$/;
- $denied++ if $new_flag =~ /-$/;
- }
- my $bug_id = $object->isa('Bugzilla::Bug') ? $object->id
- : $object->bug_id;
- my $result = "$granted flags were granted and $denied flags were denied"
- . " on bug $bug_id at $timestamp.";
- # Uncomment this line to see $result in your webserver's error log whenever
- # you update flags.
- # warn $result;
+ my ($self, $args) = @_;
+
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my $flag_params = $args;
+ my ($object, $timestamp, $old_flags, $new_flags)
+ = @$flag_params{qw(object timestamp old_flags new_flags)};
+ my ($removed, $added) = diff_arrays($old_flags, $new_flags);
+ my ($granted, $denied) = (0, 0);
+ foreach my $new_flag (@$added) {
+ $granted++ if $new_flag =~ /\+$/;
+ $denied++ if $new_flag =~ /-$/;
+ }
+ my $bug_id = $object->isa('Bugzilla::Bug') ? $object->id : $object->bug_id;
+ my $result = "$granted flags were granted and $denied flags were denied"
+ . " on bug $bug_id at $timestamp.";
+
+ # Uncomment this line to see $result in your webserver's error log whenever
+ # you update flags.
+ # warn $result;
}
sub group_before_delete {
- my ($self, $args) = @_;
- # This code doesn't actually *do* anything, it's just here to show you
- # how to use this hook.
+ my ($self, $args) = @_;
+
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+
+ my $group = $args->{'group'};
+ my $group_id = $group->id;
- my $group = $args->{'group'};
- my $group_id = $group->id;
- # Uncomment this line to see a line in your webserver's error log whenever
- # you file a bug.
- # warn "Group $group_id is about to be deleted!";
+ # Uncomment this line to see a line in your webserver's error log whenever
+ # you file a bug.
+ # warn "Group $group_id is about to be deleted!";
}
sub group_end_of_create {
- my ($self, $args) = @_;
- # This code doesn't actually *do* anything, it's just here to show you
- # how to use this hook.
- my $group = $args->{'group'};
+ my ($self, $args) = @_;
- my $group_id = $group->id;
- # Uncomment this line to see a line in your webserver's error log whenever
- # you create a new group.
- #warn "Group $group_id has been created!";
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my $group = $args->{'group'};
+
+ my $group_id = $group->id;
+
+ # Uncomment this line to see a line in your webserver's error log whenever
+ # you create a new group.
+ #warn "Group $group_id has been created!";
}
sub group_end_of_update {
- my ($self, $args) = @_;
- # This code doesn't actually *do* anything, it's just here to show you
- # how to use this hook.
+ my ($self, $args) = @_;
- my ($group, $changes) = @$args{qw(group changes)};
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
- foreach my $field (keys %$changes) {
- my $used_to_be = $changes->{$field}->[0];
- my $now_it_is = $changes->{$field}->[1];
- }
+ my ($group, $changes) = @$args{qw(group changes)};
+
+ foreach my $field (keys %$changes) {
+ my $used_to_be = $changes->{$field}->[0];
+ my $now_it_is = $changes->{$field}->[1];
+ }
- my $group_id = $group->id;
- my $num_changes = scalar keys %$changes;
- my $result =
- "There were $num_changes changes to fields on group $group_id.";
- # Uncomment this line to see $result in your webserver's error log whenever
- # you update a group.
- #warn $result;
+ my $group_id = $group->id;
+ my $num_changes = scalar keys %$changes;
+ my $result = "There were $num_changes changes to fields on group $group_id.";
+
+ # Uncomment this line to see $result in your webserver's error log whenever
+ # you update a group.
+ #warn $result;
}
sub install_before_final_checks {
- my ($self, $args) = @_;
- print "Install-before_final_checks hook\n" unless $args->{silent};
-
- # Add a new user setting like this:
- #
- # add_setting('product_chooser', # setting name
- # ['pretty', 'full', 'small'], # options
- # 'pretty'); # default
- #
- # To add descriptions for the setting and choices, add extra values to
- # the hash defined in global/setting-descs.none.tmpl. Do this in a hook:
- # hook/global/setting-descs-settings.none.tmpl .
+ my ($self, $args) = @_;
+ print "Install-before_final_checks hook\n" unless $args->{silent};
+
+ # Add a new user setting like this:
+ #
+ # add_setting('product_chooser', # setting name
+ # ['pretty', 'full', 'small'], # options
+ # 'pretty'); # default
+ #
+ # To add descriptions for the setting and choices, add extra values to
+ # the hash defined in global/setting-descs.none.tmpl. Do this in a hook:
+ # hook/global/setting-descs-settings.none.tmpl .
}
sub install_filesystem {
- my ($self, $args) = @_;
- my $create_dirs = $args->{'create_dirs'};
- my $recurse_dirs = $args->{'recurse_dirs'};
- my $htaccess = $args->{'htaccess'};
-
- # Create a new directory in datadir specifically for this extension.
- # The directory will need to allow files to be created by the extension
- # code as well as allow the webserver to server content from it.
- # my $data_path = bz_locations->{'datadir'} . "/" . __PACKAGE__->NAME;
- # $create_dirs->{$data_path} = Bugzilla::Install::Filesystem::DIR_CGI_WRITE;
-
- # Update the permissions of any files and directories that currently reside
- # in the extension's directory.
- # $recurse_dirs->{$data_path} = {
- # files => Bugzilla::Install::Filesystem::CGI_READ,
- # dirs => Bugzilla::Install::Filesystem::DIR_CGI_WRITE
- # };
-
- # Create a htaccess file that allows specific content to be served from the
- # extension's directory.
- # $htaccess->{"$data_path/.htaccess"} = {
- # perms => Bugzilla::Install::Filesystem::WS_SERVE,
- # contents => Bugzilla::Install::Filesystem::HT_DEFAULT_DENY
- # };
+ my ($self, $args) = @_;
+ my $create_dirs = $args->{'create_dirs'};
+ my $recurse_dirs = $args->{'recurse_dirs'};
+ my $htaccess = $args->{'htaccess'};
+
+ # Create a new directory in datadir specifically for this extension.
+ # The directory will need to allow files to be created by the extension
+ # code as well as allow the webserver to server content from it.
+ # my $data_path = bz_locations->{'datadir'} . "/" . __PACKAGE__->NAME;
+ # $create_dirs->{$data_path} = Bugzilla::Install::Filesystem::DIR_CGI_WRITE;
+
+ # Update the permissions of any files and directories that currently reside
+ # in the extension's directory.
+ # $recurse_dirs->{$data_path} = {
+ # files => Bugzilla::Install::Filesystem::CGI_READ,
+ # dirs => Bugzilla::Install::Filesystem::DIR_CGI_WRITE
+ # };
+
+ # Create a htaccess file that allows specific content to be served from the
+ # extension's directory.
+ # $htaccess->{"$data_path/.htaccess"} = {
+ # perms => Bugzilla::Install::Filesystem::WS_SERVE,
+ # contents => Bugzilla::Install::Filesystem::HT_DEFAULT_DENY
+ # };
}
sub install_update_db {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
+
# $dbh->bz_add_column('example', 'new_column',
# {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
# $dbh->bz_add_index('example', 'example_new_column_idx', [qw(value)]);
}
sub install_update_db_fielddefs {
- my $dbh = Bugzilla->dbh;
-# $dbh->bz_add_column('fielddefs', 'example_column',
+ my $dbh = Bugzilla->dbh;
+
+# $dbh->bz_add_column('fielddefs', 'example_column',
# {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => ''});
}
sub job_map {
- my ($self, $args) = @_;
-
- my $job_map = $args->{job_map};
-
- # This adds the named class (an instance of TheSchwartz::Worker) as a
- # handler for when a job is added with the name "some_task".
- $job_map->{'some_task'} = 'Bugzilla::Extension::Example::Job::SomeClass';
-
- # Schedule a job like this:
- # my $queue = Bugzilla->job_queue();
- # $queue->insert('some_task', { some_parameter => $some_variable });
+ my ($self, $args) = @_;
+
+ my $job_map = $args->{job_map};
+
+ # This adds the named class (an instance of TheSchwartz::Worker) as a
+ # handler for when a job is added with the name "some_task".
+ $job_map->{'some_task'} = 'Bugzilla::Extension::Example::Job::SomeClass';
+
+ # Schedule a job like this:
+ # my $queue = Bugzilla->job_queue();
+ # $queue->insert('some_task', { some_parameter => $some_variable });
}
sub mailer_before_send {
- my ($self, $args) = @_;
-
- my $email = $args->{email};
- # If you add a header to an email, it's best to start it with
- # 'X-Bugzilla-<Extension>' so that you don't conflict with
- # other extensions.
- $email->header_set('X-Bugzilla-Example-Header', 'Example');
+ my ($self, $args) = @_;
+
+ my $email = $args->{email};
+
+ # If you add a header to an email, it's best to start it with
+ # 'X-Bugzilla-<Extension>' so that you don't conflict with
+ # other extensions.
+ $email->header_set('X-Bugzilla-Example-Header', 'Example');
}
sub object_before_create {
- my ($self, $args) = @_;
-
- my $class = $args->{'class'};
- my $object_params = $args->{'params'};
-
- # Note that this is a made-up class, for this example.
- if ($class->isa('Bugzilla::ExampleObject')) {
- warn "About to create an ExampleObject!";
- warn "Got the following parameters: "
- . join(', ', keys(%$object_params));
- }
+ my ($self, $args) = @_;
+
+ my $class = $args->{'class'};
+ my $object_params = $args->{'params'};
+
+ # Note that this is a made-up class, for this example.
+ if ($class->isa('Bugzilla::ExampleObject')) {
+ warn "About to create an ExampleObject!";
+ warn "Got the following parameters: " . join(', ', keys(%$object_params));
+ }
}
sub object_before_delete {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $object = $args->{'object'};
+ my $object = $args->{'object'};
- # Note that this is a made-up class, for this example.
- if ($object->isa('Bugzilla::ExampleObject')) {
- my $id = $object->id;
- warn "An object with id $id is about to be deleted!";
- }
+ # Note that this is a made-up class, for this example.
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ my $id = $object->id;
+ warn "An object with id $id is about to be deleted!";
+ }
}
sub object_before_set {
- my ($self, $args) = @_;
-
- my ($object, $field, $value) = @$args{qw(object field value)};
-
- # Note that this is a made-up class, for this example.
- if ($object->isa('Bugzilla::ExampleObject')) {
- warn "The field $field is changing from " . $object->{$field}
- . " to $value!";
- }
+ my ($self, $args) = @_;
+
+ my ($object, $field, $value) = @$args{qw(object field value)};
+
+ # Note that this is a made-up class, for this example.
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ warn "The field $field is changing from " . $object->{$field} . " to $value!";
+ }
}
sub object_columns {
- my ($self, $args) = @_;
- my ($class, $columns) = @$args{qw(class columns)};
+ my ($self, $args) = @_;
+ my ($class, $columns) = @$args{qw(class columns)};
- if ($class->isa('Bugzilla::ExampleObject')) {
- push(@$columns, 'example');
- }
+ if ($class->isa('Bugzilla::ExampleObject')) {
+ push(@$columns, 'example');
+ }
}
sub object_end_of_create {
- my ($self, $args) = @_;
-
- my $class = $args->{'class'};
- my $object = $args->{'object'};
+ my ($self, $args) = @_;
+
+ my $class = $args->{'class'};
+ my $object = $args->{'object'};
- warn "Created a new $class object!";
+ warn "Created a new $class object!";
}
sub object_end_of_create_validators {
- my ($self, $args) = @_;
-
- my $class = $args->{'class'};
- my $object_params = $args->{'params'};
-
- # Note that this is a made-up class, for this example.
- if ($class->isa('Bugzilla::ExampleObject')) {
- # Always set example_field to 1, even if the validators said otherwise.
- $object_params->{example_field} = 1;
- }
-
+ my ($self, $args) = @_;
+
+ my $class = $args->{'class'};
+ my $object_params = $args->{'params'};
+
+ # Note that this is a made-up class, for this example.
+ if ($class->isa('Bugzilla::ExampleObject')) {
+
+ # Always set example_field to 1, even if the validators said otherwise.
+ $object_params->{example_field} = 1;
+ }
+
}
sub object_end_of_set {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my ($object, $field) = @$args{qw(object field)};
+ my ($object, $field) = @$args{qw(object field)};
- # Note that this is a made-up class, for this example.
- if ($object->isa('Bugzilla::ExampleObject')) {
- warn "The field $field has changed to " . $object->{$field};
- }
+ # Note that this is a made-up class, for this example.
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ warn "The field $field has changed to " . $object->{$field};
+ }
}
sub object_end_of_set_all {
- my ($self, $args) = @_;
-
- my $object = $args->{'object'};
- my $object_params = $args->{'params'};
-
- # Note that this is a made-up class, for this example.
- if ($object->isa('Bugzilla::ExampleObject')) {
- if ($object_params->{example_field} == 1) {
- $object->{example_field} = 1;
- }
+ my ($self, $args) = @_;
+
+ my $object = $args->{'object'};
+ my $object_params = $args->{'params'};
+
+ # Note that this is a made-up class, for this example.
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ if ($object_params->{example_field} == 1) {
+ $object->{example_field} = 1;
}
-
+ }
+
}
sub object_end_of_update {
- my ($self, $args) = @_;
-
- my ($object, $old_object, $changes) =
- @$args{qw(object old_object changes)};
-
- # Note that this is a made-up class, for this example.
- if ($object->isa('Bugzilla::ExampleObject')) {
- if (defined $changes->{'name'}) {
- my ($old, $new) = @{ $changes->{'name'} };
- print "The name field changed from $old to $new!";
- }
+ my ($self, $args) = @_;
+
+ my ($object, $old_object, $changes) = @$args{qw(object old_object changes)};
+
+ # Note that this is a made-up class, for this example.
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ if (defined $changes->{'name'}) {
+ my ($old, $new) = @{$changes->{'name'}};
+ print "The name field changed from $old to $new!";
}
+ }
}
sub object_update_columns {
- my ($self, $args) = @_;
- my ($object, $columns) = @$args{qw(object columns)};
+ my ($self, $args) = @_;
+ my ($object, $columns) = @$args{qw(object columns)};
- if ($object->isa('Bugzilla::ExampleObject')) {
- push(@$columns, 'example');
- }
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ push(@$columns, 'example');
+ }
}
sub object_validators {
- my ($self, $args) = @_;
- my ($class, $validators) = @$args{qw(class validators)};
-
- if ($class->isa('Bugzilla::Bug')) {
- # This is an example of adding a new validator.
- # See the _check_example subroutine below.
- $validators->{example} = \&_check_example;
-
- # This is an example of overriding an existing validator.
- # See the check_short_desc validator below.
- my $original = $validators->{short_desc};
- $validators->{short_desc} = sub { _check_short_desc($original, @_) };
- }
+ my ($self, $args) = @_;
+ my ($class, $validators) = @$args{qw(class validators)};
+
+ if ($class->isa('Bugzilla::Bug')) {
+
+ # This is an example of adding a new validator.
+ # See the _check_example subroutine below.
+ $validators->{example} = \&_check_example;
+
+ # This is an example of overriding an existing validator.
+ # See the check_short_desc validator below.
+ my $original = $validators->{short_desc};
+ $validators->{short_desc} = sub { _check_short_desc($original, @_) };
+ }
}
sub _check_example {
- my ($invocant, $value, $field) = @_;
- warn "I was called to validate the value of $field.";
- warn "The value of $field that I was passed in is: $value";
+ my ($invocant, $value, $field) = @_;
+ warn "I was called to validate the value of $field.";
+ warn "The value of $field that I was passed in is: $value";
- # Make the value always be 1.
- my $fixed_value = 1;
- return $fixed_value;
+ # Make the value always be 1.
+ my $fixed_value = 1;
+ return $fixed_value;
}
sub _check_short_desc {
- my $original = shift;
- my $invocant = shift;
- my $value = $invocant->$original(@_);
- if ($value !~ /example/i) {
- # Use this line to make Bugzilla throw an error every time
- # you try to file a bug or update a bug without the word "example"
- # in the summary.
- if (0) {
- ThrowUserError('example_short_desc_invalid');
- }
+ my $original = shift;
+ my $invocant = shift;
+ my $value = $invocant->$original(@_);
+ if ($value !~ /example/i) {
+
+ # Use this line to make Bugzilla throw an error every time
+ # you try to file a bug or update a bug without the word "example"
+ # in the summary.
+ if (0) {
+ ThrowUserError('example_short_desc_invalid');
}
- return $value;
+ }
+ return $value;
}
sub page_before_template {
- my ($self, $args) = @_;
-
- my ($vars, $page) = @$args{qw(vars page_id)};
-
- # You can see this hook in action by loading page.cgi?id=example.html
- if ($page eq 'example.html') {
- $vars->{cgi_variables} = { Bugzilla->cgi->Vars };
- }
+ my ($self, $args) = @_;
+
+ my ($vars, $page) = @$args{qw(vars page_id)};
+
+ # You can see this hook in action by loading page.cgi?id=example.html
+ if ($page eq 'example.html') {
+ $vars->{cgi_variables} = {Bugzilla->cgi->Vars};
+ }
}
sub path_info_whitelist {
- my ($self, $args) = @_;
- my $whitelist = $args->{whitelist};
- push(@$whitelist, "page.cgi");
+ my ($self, $args) = @_;
+ my $whitelist = $args->{whitelist};
+ push(@$whitelist, "page.cgi");
}
sub post_bug_after_creation {
- my ($self, $args) = @_;
-
- my $vars = $args->{vars};
- $vars->{'example'} = 1;
+ my ($self, $args) = @_;
+
+ my $vars = $args->{vars};
+ $vars->{'example'} = 1;
}
sub product_confirm_delete {
- my ($self, $args) = @_;
-
- my $vars = $args->{vars};
- $vars->{'example'} = 1;
+ my ($self, $args) = @_;
+
+ my $vars = $args->{vars};
+ $vars->{'example'} = 1;
}
sub product_end_of_create {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $product = $args->{product};
+ my $product = $args->{product};
- # For this example, any lines of code that actually make changes to your
- # database have been commented out.
+ # For this example, any lines of code that actually make changes to your
+ # database have been commented out.
- # This section will take a group that exists in your installation
- # (possible called test_group) and automatically makes the new
- # product hidden to only members of the group. Just remove
- # the restriction if you want the new product to be public.
+ # This section will take a group that exists in your installation
+ # (possible called test_group) and automatically makes the new
+ # product hidden to only members of the group. Just remove
+ # the restriction if you want the new product to be public.
- my $example_group = new Bugzilla::Group({ name => 'example_group' });
+ my $example_group = new Bugzilla::Group({name => 'example_group'});
+
+ if ($example_group) {
+ $product->set_group_controls(
+ $example_group,
+ {
+ entry => 1,
+ membercontrol => CONTROLMAPMANDATORY,
+ othercontrol => CONTROLMAPMANDATORY
+ }
+ );
- if ($example_group) {
- $product->set_group_controls($example_group,
- { entry => 1,
- membercontrol => CONTROLMAPMANDATORY,
- othercontrol => CONTROLMAPMANDATORY });
# $product->update();
- }
+ }
- # This section will automatically add a default component
- # to the new product called 'No Component'.
+ # This section will automatically add a default component
+ # to the new product called 'No Component'.
- my $default_assignee = new Bugzilla::User(
- { name => Bugzilla->params->{maintainer} });
+ my $default_assignee
+ = new Bugzilla::User({name => Bugzilla->params->{maintainer}});
+
+ if ($default_assignee) {
- if ($default_assignee) {
# Bugzilla::Component->create(
# { name => 'No Component',
# product => $product,
-# description => 'Select this component if one does not ' .
+# description => 'Select this component if one does not ' .
# 'exist in the current list of components',
# initialowner => $default_assignee });
- }
+ }
}
sub quicksearch_map {
- my ($self, $args) = @_;
- my $map = $args->{'map'};
+ my ($self, $args) = @_;
+ my $map = $args->{'map'};
- # This demonstrates adding a shorter alias for a long custom field name.
- $map->{'impact'} = $map->{'cf_long_field_name_for_impact_field'};
+ # This demonstrates adding a shorter alias for a long custom field name.
+ $map->{'impact'} = $map->{'cf_long_field_name_for_impact_field'};
}
sub sanitycheck_check {
- my ($self, $args) = @_;
-
- my $dbh = Bugzilla->dbh;
- my $sth;
-
- my $status = $args->{'status'};
-
- # Check that all users are Australian
- $status->('example_check_au_user');
-
- $sth = $dbh->prepare("SELECT userid, login_name
+ my ($self, $args) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ my $sth;
+
+ my $status = $args->{'status'};
+
+ # Check that all users are Australian
+ $status->('example_check_au_user');
+
+ $sth = $dbh->prepare(
+ "SELECT userid, login_name
FROM profiles
- WHERE login_name NOT LIKE '%.au'");
- $sth->execute;
-
- my $seen_nonau = 0;
- while (my ($userid, $login, $numgroups) = $sth->fetchrow_array) {
- $status->('example_check_au_user_alert',
- { userid => $userid, login => $login },
- 'alert');
- $seen_nonau = 1;
- }
-
- $status->('example_check_au_user_prompt') if $seen_nonau;
+ WHERE login_name NOT LIKE '%.au'"
+ );
+ $sth->execute;
+
+ my $seen_nonau = 0;
+ while (my ($userid, $login, $numgroups) = $sth->fetchrow_array) {
+ $status->(
+ 'example_check_au_user_alert', {userid => $userid, login => $login}, 'alert'
+ );
+ $seen_nonau = 1;
+ }
+
+ $status->('example_check_au_user_prompt') if $seen_nonau;
}
sub sanitycheck_repair {
- my ($self, $args) = @_;
-
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
-
- my $status = $args->{'status'};
-
- if ($cgi->param('example_repair_au_user')) {
- $status->('example_repair_au_user_start');
-
- #$dbh->do("UPDATE profiles
- # SET login_name = CONCAT(login_name, '.au')
- # WHERE login_name NOT LIKE '%.au'");
-
- $status->('example_repair_au_user_end');
- }
+ my ($self, $args) = @_;
+
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+
+ my $status = $args->{'status'};
+
+ if ($cgi->param('example_repair_au_user')) {
+ $status->('example_repair_au_user_start');
+
+ #$dbh->do("UPDATE profiles
+ # SET login_name = CONCAT(login_name, '.au')
+ # WHERE login_name NOT LIKE '%.au'");
+
+ $status->('example_repair_au_user_end');
+ }
}
sub template_before_create {
- my ($self, $args) = @_;
-
- my $config = $args->{'config'};
- # This will be accessible as "example_global_variable" in every
- # template in Bugzilla. See Bugzilla/Template.pm's create() function
- # for more things that you can set.
- $config->{VARIABLES}->{example_global_variable} = sub { return 'value' };
+ my ($self, $args) = @_;
+
+ my $config = $args->{'config'};
+
+ # This will be accessible as "example_global_variable" in every
+ # template in Bugzilla. See Bugzilla/Template.pm's create() function
+ # for more things that you can set.
+ $config->{VARIABLES}->{example_global_variable} = sub { return 'value' };
}
-sub template_before_process {
- my ($self, $args) = @_;
-
- my ($vars, $file, $context) = @$args{qw(vars file context)};
+sub template_after_create {
+ my ($self, $args) = @_;
+ my $context = $args->{template}->context;
- if ($file eq 'bug/edit.html.tmpl') {
- $vars->{'viewing_the_bug_form'} = 1;
+ # define a pluck method on template toolkit lists.
+ $context->define_vmethod(
+ list => pluck => sub {
+ my ($list, $field) = @_;
+ return [map { $_->$field } @$list];
}
+ );
+}
+
+sub template_before_process {
+ my ($self, $args) = @_;
+
+ my ($vars, $file, $context) = @$args{qw(vars file context)};
+
+ if ($file eq 'bug/edit.html.tmpl') {
+ $vars->{'viewing_the_bug_form'} = 1;
+ }
}
sub user_check_account_creation {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $login = $args->{login};
- my $ip = remote_ip();
+ my $login = $args->{login};
+ my $ip = remote_ip();
- # Log all requests.
- warn "USER ACCOUNT CREATION REQUEST FOR $login ($ip)";
+ # Log all requests.
+ warn "USER ACCOUNT CREATION REQUEST FOR $login ($ip)";
- # Reject requests based on their email address.
- if ($login =~ /\@evil\.com$/) {
- ThrowUserError('account_creation_restricted');
- }
+ # Reject requests based on their email address.
+ if ($login =~ /\@evil\.com$/) {
+ ThrowUserError('account_creation_restricted');
+ }
- # Reject requests based on their IP address.
- if ($ip =~ /^192\.168\./) {
- ThrowUserError('account_creation_restricted');
- }
+ # Reject requests based on their IP address.
+ if ($ip =~ /^192\.168\./) {
+ ThrowUserError('account_creation_restricted');
+ }
}
sub user_preferences {
- my ($self, $args) = @_;
- my $tab = $args->{current_tab};
- my $save = $args->{save_changes};
- my $handled = $args->{handled};
+ my ($self, $args) = @_;
+ my $tab = $args->{current_tab};
+ my $save = $args->{save_changes};
+ my $handled = $args->{handled};
- return unless $tab eq 'my_tab';
+ return unless $tab eq 'my_tab';
- my $value = Bugzilla->input_params->{'example_pref'};
- if ($save) {
- # Validate your data and update the DB accordingly.
- $value =~ s/\s+/:/g;
- }
- $args->{'vars'}->{example_pref} = $value;
+ my $value = Bugzilla->input_params->{'example_pref'};
+ if ($save) {
+
+ # Validate your data and update the DB accordingly.
+ $value =~ s/\s+/:/g;
+ }
+ $args->{'vars'}->{example_pref} = $value;
- # Set the 'handled' scalar reference to true so that the caller
- # knows the panel name is valid and that an extension took care of it.
- $$handled = 1;
+ # Set the 'handled' scalar reference to true so that the caller
+ # knows the panel name is valid and that an extension took care of it.
+ $$handled = 1;
}
sub webservice {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $dispatch = $args->{dispatch};
- $dispatch->{Example} = "Bugzilla::Extension::Example::WebService";
+ my $dispatch = $args->{dispatch};
+ $dispatch->{Example} = "Bugzilla::Extension::Example::WebService";
}
sub webservice_error_codes {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $error_map = $args->{error_map};
- $error_map->{'example_my_error'} = 10001;
+ my $error_map = $args->{error_map};
+ $error_map->{'example_my_error'} = 10001;
}
sub webservice_status_code_map {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $status_code_map = $args->{status_code_map};
- # Uncomment this line to override the status code for the
- # error 'object_does_not_exist' to STATUS_BAD_REQUEST
- #$status_code_map->{51} = STATUS_BAD_REQUEST;
+ my $status_code_map = $args->{status_code_map};
+
+ # Uncomment this line to override the status code for the
+ # error 'object_does_not_exist' to STATUS_BAD_REQUEST
+ #$status_code_map->{51} = STATUS_BAD_REQUEST;
}
sub webservice_before_call {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- # This code doesn't actually *do* anything, it's just here to show you
- # how to use this hook.
- my $method = $args->{method};
- my $full_method = $args->{full_method};
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my $method = $args->{method};
+ my $full_method = $args->{full_method};
- # Uncomment this line to see a line in your webserver's error log whenever
- # a webservice call is made
- #warn "RPC call $full_method made by ",
- # Bugzilla->user->login || 'an anonymous user', "\n";
+ # Uncomment this line to see a line in your webserver's error log whenever
+ # a webservice call is made
+ #warn "RPC call $full_method made by ",
+ # Bugzilla->user->login || 'an anonymous user', "\n";
}
sub webservice_fix_credentials {
- my ($self, $args) = @_;
- my $rpc = $args->{'rpc'};
- my $params = $args->{'params'};
- # Allow user to pass in username=foo&password=bar
- if (exists $params->{'username'} && exists $params->{'password'}) {
- $params->{'Bugzilla_login'} = $params->{'username'};
- $params->{'Bugzilla_password'} = $params->{'password'};
- }
+ my ($self, $args) = @_;
+ my $rpc = $args->{'rpc'};
+ my $params = $args->{'params'};
+
+ # Allow user to pass in username=foo&password=bar
+ if (exists $params->{'username'} && exists $params->{'password'}) {
+ $params->{'Bugzilla_login'} = $params->{'username'};
+ $params->{'Bugzilla_password'} = $params->{'password'};
+ }
}
sub webservice_rest_request {
- my ($self, $args) = @_;
- my $rpc = $args->{'rpc'};
- my $params = $args->{'params'};
- # Internally we may have a field called 'cf_test_field' but we allow users
- # to use the shorter 'test_field' name.
- if (exists $params->{'test_field'}) {
- $params->{'test_field'} = delete $params->{'cf_test_field'};
- }
+ my ($self, $args) = @_;
+ my $rpc = $args->{'rpc'};
+ my $params = $args->{'params'};
+
+ # Internally we may have a field called 'cf_test_field' but we allow users
+ # to use the shorter 'test_field' name.
+ if (exists $params->{'test_field'}) {
+ $params->{'test_field'} = delete $params->{'cf_test_field'};
+ }
}
sub webservice_rest_resources {
- my ($self, $args) = @_;
- my $rpc = $args->{'rpc'};
- my $resources = $args->{'resources'};
- # Add a new resource that allows for /rest/example/hello
- # to call Example.hello
- $resources->{'Bugzilla::Extension::Example::WebService'} = [
- qr{^/example/hello$}, {
- GET => {
- method => 'hello',
- }
- }
- ];
+ my ($self, $args) = @_;
+ my $rpc = $args->{'rpc'};
+ my $resources = $args->{'resources'};
+
+ # Add a new resource that allows for /rest/example/hello
+ # to call Example.hello
+ $resources->{'Bugzilla::Extension::Example::WebService'}
+ = [qr{^/example/hello$}, {GET => {method => 'hello',}}];
}
sub webservice_rest_response {
- my ($self, $args) = @_;
- my $rpc = $args->{'rpc'};
- my $result = $args->{'result'};
- my $response = $args->{'response'};
- # Convert a list of bug hashes to a single bug hash if only one is
- # being returned.
- if (ref $$result eq 'HASH'
- && exists $$result->{'bugs'}
- && scalar @{ $$result->{'bugs'} } == 1)
- {
- $$result = $$result->{'bugs'}->[0];
- }
+ my ($self, $args) = @_;
+ my $rpc = $args->{'rpc'};
+ my $result = $args->{'result'};
+ my $response = $args->{'response'};
+
+ # Convert a list of bug hashes to a single bug hash if only one is
+ # being returned.
+ if ( ref $$result eq 'HASH'
+ && exists $$result->{'bugs'}
+ && scalar @{$$result->{'bugs'}} == 1)
+ {
+ $$result = $$result->{'bugs'}->[0];
+ }
}
# This must be the last line of your extension.
diff --git a/extensions/Example/lib/Auth/Login.pm b/extensions/Example/lib/Auth/Login.pm
index 15c58a881..49af42c22 100644
--- a/extensions/Example/lib/Auth/Login.pm
+++ b/extensions/Example/lib/Auth/Login.pm
@@ -17,7 +17,7 @@ use Bugzilla::Constants;
# Always returns no data.
sub get_login_info {
- return { failure => AUTH_NODATA };
+ return {failure => AUTH_NODATA};
}
1;
diff --git a/extensions/Example/lib/Auth/Verify.pm b/extensions/Example/lib/Auth/Verify.pm
index 49fd9fbb7..591c1d4f8 100644
--- a/extensions/Example/lib/Auth/Verify.pm
+++ b/extensions/Example/lib/Auth/Verify.pm
@@ -16,7 +16,7 @@ use Bugzilla::Constants;
# A verifier that always fails.
sub check_credentials {
- return { failure => AUTH_NO_SUCH_USER };
+ return {failure => AUTH_NO_SUCH_USER};
}
1;
diff --git a/extensions/Example/lib/Config.pm b/extensions/Example/lib/Config.pm
index fac0046af..360a57510 100644
--- a/extensions/Example/lib/Config.pm
+++ b/extensions/Example/lib/Config.pm
@@ -16,16 +16,11 @@ use Bugzilla::Config::Common;
our $sortkey = 5000;
sub get_param_list {
- my ($class) = @_;
+ my ($class) = @_;
- my @param_list = (
- {
- name => 'example_string',
- type => 't',
- default => 'EXAMPLE',
- },
- );
- return @param_list;
+ my @param_list
+ = ({name => 'example_string', type => 't', default => 'EXAMPLE',},);
+ return @param_list;
}
1;
diff --git a/extensions/Example/lib/WebService.pm b/extensions/Example/lib/WebService.pm
index d8a96b5f5..f461fee6f 100644
--- a/extensions/Example/lib/WebService.pm
+++ b/extensions/Example/lib/WebService.pm
@@ -14,8 +14,8 @@ use parent qw(Bugzilla::WebService);
use Bugzilla::Error;
use constant PUBLIC_METHODS => qw(
- hello
- throw_an_error
+ hello
+ throw_an_error
);
# This can be called as Example.hello() from the WebService.
diff --git a/extensions/Example/template/en/default/setup/strings.txt.pl b/extensions/Example/template/en/default/setup/strings.txt.pl
index 98550547e..e2fc52452 100644
--- a/extensions/Example/template/en/default/setup/strings.txt.pl
+++ b/extensions/Example/template/en/default/setup/strings.txt.pl
@@ -5,8 +5,6 @@
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
-%strings = (
- feature_example_acme => 'Example Extension: Acme Feature',
-);
+%strings = (feature_example_acme => 'Example Extension: Acme Feature',);
1;
diff --git a/extensions/Gentoo/Extension.pm b/extensions/Gentoo/Extension.pm
index 936026678..97402e88d 100644
--- a/extensions/Gentoo/Extension.pm
+++ b/extensions/Gentoo/Extension.pm
@@ -11,62 +11,58 @@ use POSIX qw(uname);
our $VERSION = '1.0';
sub install_filesystem {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $dirs = $args->{'create_dirs'};
- my $files = $args->{'files'};
- my $recurse_dirs = $args->{'recurse_dirs'};
- my $htaccess = $args->{'htaccess'};
+ my $dirs = $args->{'create_dirs'};
+ my $files = $args->{'files'};
+ my $recurse_dirs = $args->{'recurse_dirs'};
+ my $htaccess = $args->{'htaccess'};
- my $datadir = bz_locations()->{'datadir'};
+ my $datadir = bz_locations()->{'datadir'};
- $dirs->{"${datadir}/cached"} = { perms => 0750 };
+ $dirs->{"${datadir}/cached"} = {perms => 0750};
- $files->{"zzz.txt"} = { perms => 0644 };
- $files->{"robots-ssl.txt"} = { perms => 0644 };
- $files->{"bots.html"} = { perms => 0644 };
- $files->{"favicon.ico"} = { perms => 0644 };
- $files->{"runstats.sh"} = { perms => 0700 };
- $files->{"recompile.sh"} = { perms => 0700 };
+ $files->{"zzz.txt"} = {perms => 0644};
+ $files->{"robots-ssl.txt"} = {perms => 0644};
+ $files->{"bots.html"} = {perms => 0644};
+ $files->{"favicon.ico"} = {perms => 0644};
+ $files->{"runstats.sh"} = {perms => 0700};
+ $files->{"recompile.sh"} = {perms => 0700};
- $recurse_dirs->{"$datadir/cached"} = {
- files => 0640, dirs => 0750
- };
+ $recurse_dirs->{"$datadir/cached"} = {files => 0640, dirs => 0750};
- $recurse_dirs->{"images/ranks"} = {
- files => 0640, dirs => 0750
- };
+ $recurse_dirs->{"images/ranks"} = {files => 0640, dirs => 0750};
- $htaccess->{"$datadir/cached/.htaccess"} = {
- perms => 0640, contents => <<EOT
+ $htaccess->{"$datadir/cached/.htaccess"} = {
+ perms => 0640,
+ contents => <<EOT
# Allow access to the cached stuff
Allow from all
EOT
- };
+ };
}
sub template_before_create {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $config = $args->{config};
- my $constants = $config->{CONSTANTS};
+ my $config = $args->{config};
+ my $constants = $config->{CONSTANTS};
- my %nodemap = (
- 'yellowbishop' => 'bugs-web1',
- 'yellowleg' => 'bugs-web2'
- );
+ my %nodemap = ('yellowbishop' => 'bugs-web1', 'yellowleg' => 'bugs-web2');
- my $hostname = (uname())[1];
- $constants->{GENTOO_NODE} = $nodemap{$hostname} ? $nodemap{$hostname} : "[$hostname]";
- $constants->{GENTOO_APPEND_VERSION} = "+";
+ my $hostname = (uname())[1];
+ $constants->{GENTOO_NODE}
+ = $nodemap{$hostname} ? $nodemap{$hostname} : "[$hostname]";
+ $constants->{GENTOO_APPEND_VERSION} = "+";
}
sub user_check_account_creation {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $login = $args->{login};
+ my $login = $args->{login};
- ThrowUserError('restricted_email_address', {addr => $login}) if $login =~ m/.+\@gentoo\.org$/;
+ ThrowUserError('restricted_email_address', {addr => $login})
+ if $login =~ m/.+\@gentoo\.org$/;
}
__PACKAGE__->NAME;
diff --git a/extensions/InlineHistory/Extension.pm b/extensions/InlineHistory/Extension.pm
index c88ff74ed..87d0674ca 100644
--- a/extensions/InlineHistory/Extension.pm
+++ b/extensions/InlineHistory/Extension.pm
@@ -19,200 +19,207 @@ our $VERSION = '1.6';
use constant MAXIMUM_ACTIVITY_COUNT => 500;
# don't show really long values
-use constant MAXIMUM_VALUE_LENGTH => 256;
+use constant MAXIMUM_VALUE_LENGTH => 256;
sub template_before_create {
- my ($self, $args) = @_;
- $args->{config}->{FILTERS}->{ih_short_value} = sub {
- my ($str) = @_;
- return length($str) <= MAXIMUM_VALUE_LENGTH
- ? $str
- : substr($str, 0, MAXIMUM_VALUE_LENGTH - 3) . '...';
- };
+ my ($self, $args) = @_;
+ $args->{config}->{FILTERS}->{ih_short_value} = sub {
+ my ($str) = @_;
+ return
+ length($str) <= MAXIMUM_VALUE_LENGTH
+ ? $str
+ : substr($str, 0, MAXIMUM_VALUE_LENGTH - 3) . '...';
+ };
}
sub template_before_process {
- my ($self, $args) = @_;
- my $file = $args->{'file'};
- my $vars = $args->{'vars'};
-
- return if $file ne 'bug/edit.html.tmpl';
-
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
- return unless $user->id && $user->settings->{'inline_history'}->{'value'} eq 'on';
-
- # note: bug/edit.html.tmpl doesn't support multiple bugs
- my $bug = exists $vars->{'bugs'} ? $vars->{'bugs'}[0] : $vars->{'bug'};
- my $bug_id = $bug->id;
-
- # build bug activity
- my ($activity) = $bug->can('get_activity')
- ? $bug->get_activity()
- : Bugzilla::Bug::GetBugActivity($bug_id);
- $activity = _add_duplicates($bug_id, $activity);
-
- if (scalar @$activity > MAXIMUM_ACTIVITY_COUNT) {
- $activity = [];
- $vars->{'ih_activity'} = 0;
- $vars->{'ih_activity_max'} = 1;
- return;
- }
-
- # prime caches with objects already loaded
- my %user_cache;
- foreach my $comment (@{$bug->comments}) {
- $user_cache{$comment->{author}->login} = $comment->{author};
- }
-
- my %attachment_cache;
- foreach my $attachment (@{$bug->attachments}) {
- $attachment_cache{$attachment->id} = $attachment;
- }
-
- # build a list of bugs we need to check visibility of, so we can check with a single query
- my %visible_bug_ids;
-
- # augment and tweak
- foreach my $operation (@$activity) {
- # make operation.who an object
- $user_cache{$operation->{who}} ||= Bugzilla::User->new({ name => $operation->{who} });
- $operation->{who} = $user_cache{$operation->{who}};
-
- for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) {
- my $change = $operation->{changes}->[$i];
-
- # make an attachment object
- if ($change->{attachid}) {
- $change->{attach} = $attachment_cache{$change->{attachid}};
- }
-
- # empty resolutions are displayed as --- by default
- # make it explicit here to enable correct display of the change
- if ($change->{fieldname} eq 'resolution') {
- $change->{removed} = '---' if $change->{removed} eq '';
- $change->{added} = '---' if $change->{added} eq '';
- }
-
- # make boolean fields true/false instead of 1/0
- my ($table, $field) = ('bugs', $change->{fieldname});
- if ($field =~ /^([^\.]+)\.(.+)$/) {
- ($table, $field) = ($1, $2);
- }
- my $column = $dbh->bz_column_info($table, $field);
- if ($column && $column->{TYPE} eq 'BOOLEAN') {
- $change->{removed} = '';
- $change->{added} = $change->{added} ? 'true' : 'false';
- }
-
- my $field_obj;
- if ($change->{fieldname} =~ /^cf_/) {
- $field_obj = Bugzilla::Field->new({ name => $change->{fieldname}, cache => 1 });
- }
-
- # identify buglist changes
- if ($change->{fieldname} eq 'blocked' ||
- $change->{fieldname} eq 'dependson' ||
- $change->{fieldname} eq 'dupe' ||
- ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID)
- ) {
- $change->{buglist} = 1;
- foreach my $what (qw(removed added)) {
- my @buglist = split(/[\s,]+/, $change->{$what});
- foreach my $id (@buglist) {
- if ($id && $id =~ /^\d+$/) {
- $visible_bug_ids{$id} = 1;
- }
- }
- }
+ my ($self, $args) = @_;
+ my $file = $args->{'file'};
+ my $vars = $args->{'vars'};
+
+ return if $file ne 'bug/edit.html.tmpl';
+
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+ return
+ unless $user->id && $user->settings->{'inline_history'}->{'value'} eq 'on';
+
+ # note: bug/edit.html.tmpl doesn't support multiple bugs
+ my $bug = exists $vars->{'bugs'} ? $vars->{'bugs'}[0] : $vars->{'bug'};
+ my $bug_id = $bug->id;
+
+ # build bug activity
+ my ($activity)
+ = $bug->can('get_activity')
+ ? $bug->get_activity()
+ : Bugzilla::Bug::GetBugActivity($bug_id);
+ $activity = _add_duplicates($bug_id, $activity);
+
+ if (scalar @$activity > MAXIMUM_ACTIVITY_COUNT) {
+ $activity = [];
+ $vars->{'ih_activity'} = 0;
+ $vars->{'ih_activity_max'} = 1;
+ return;
+ }
+
+ # prime caches with objects already loaded
+ my %user_cache;
+ foreach my $comment (@{$bug->comments}) {
+ $user_cache{$comment->{author}->login} = $comment->{author};
+ }
+
+ my %attachment_cache;
+ foreach my $attachment (@{$bug->attachments}) {
+ $attachment_cache{$attachment->id} = $attachment;
+ }
+
+# build a list of bugs we need to check visibility of, so we can check with a single query
+ my %visible_bug_ids;
+
+ # augment and tweak
+ foreach my $operation (@$activity) {
+
+ # make operation.who an object
+ $user_cache{$operation->{who}}
+ ||= Bugzilla::User->new({name => $operation->{who}});
+ $operation->{who} = $user_cache{$operation->{who}};
+
+ for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) {
+ my $change = $operation->{changes}->[$i];
+
+ # make an attachment object
+ if ($change->{attachid}) {
+ $change->{attach} = $attachment_cache{$change->{attachid}};
+ }
+
+ # empty resolutions are displayed as --- by default
+ # make it explicit here to enable correct display of the change
+ if ($change->{fieldname} eq 'resolution') {
+ $change->{removed} = '---' if $change->{removed} eq '';
+ $change->{added} = '---' if $change->{added} eq '';
+ }
+
+ # make boolean fields true/false instead of 1/0
+ my ($table, $field) = ('bugs', $change->{fieldname});
+ if ($field =~ /^([^\.]+)\.(.+)$/) {
+ ($table, $field) = ($1, $2);
+ }
+ my $column = $dbh->bz_column_info($table, $field);
+ if ($column && $column->{TYPE} eq 'BOOLEAN') {
+ $change->{removed} = '';
+ $change->{added} = $change->{added} ? 'true' : 'false';
+ }
+
+ my $field_obj;
+ if ($change->{fieldname} =~ /^cf_/) {
+ $field_obj = Bugzilla::Field->new({name => $change->{fieldname}, cache => 1});
+ }
+
+ # identify buglist changes
+ if ( $change->{fieldname} eq 'blocked'
+ || $change->{fieldname} eq 'dependson'
+ || $change->{fieldname} eq 'dupe'
+ || ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID))
+ {
+ $change->{buglist} = 1;
+ foreach my $what (qw(removed added)) {
+ my @buglist = split(/[\s,]+/, $change->{$what});
+ foreach my $id (@buglist) {
+ if ($id && $id =~ /^\d+$/) {
+ $visible_bug_ids{$id} = 1;
}
+ }
+ }
+ }
+
+ # split multiple flag changes (must be processed last)
+ if ($change->{fieldname} eq 'flagtypes.name') {
+ my @added = split(/, /, $change->{added});
+ my @removed = split(/, /, $change->{removed});
+ next if scalar(@added) <= 1 && scalar(@removed) <= 1;
+
+ # remove current change
+ splice(@{$operation->{changes}}, $i, 1);
+
+ # restructure into added/removed for each flag
+ my %flags;
+ foreach my $added (@added) {
+ my ($value, $name) = $added =~ /^((.+).)$/;
+ $flags{$name}{added} = $value;
+ $flags{$name}{removed} |= '';
+ }
+ foreach my $removed (@removed) {
+ my ($value, $name) = $removed =~ /^((.+).)$/;
+ $flags{$name}{added} |= '';
+ $flags{$name}{removed} = $value;
+ }
- # split multiple flag changes (must be processed last)
- if ($change->{fieldname} eq 'flagtypes.name') {
- my @added = split(/, /, $change->{added});
- my @removed = split(/, /, $change->{removed});
- next if scalar(@added) <= 1 && scalar(@removed) <= 1;
- # remove current change
- splice(@{$operation->{changes}}, $i, 1);
- # restructure into added/removed for each flag
- my %flags;
- foreach my $added (@added) {
- my ($value, $name) = $added =~ /^((.+).)$/;
- $flags{$name}{added} = $value;
- $flags{$name}{removed} |= '';
- }
- foreach my $removed (@removed) {
- my ($value, $name) = $removed =~ /^((.+).)$/;
- $flags{$name}{added} |= '';
- $flags{$name}{removed} = $value;
- }
- # clone current change, modify and insert
- foreach my $flag (sort keys %flags) {
- my $flag_change = {};
- foreach my $key (keys %$change) {
- $flag_change->{$key} = $change->{$key};
- }
- $flag_change->{removed} = $flags{$flag}{removed};
- $flag_change->{added} = $flags{$flag}{added};
- splice(@{$operation->{changes}}, $i, 0, $flag_change);
- }
- $i--;
- }
+ # clone current change, modify and insert
+ foreach my $flag (sort keys %flags) {
+ my $flag_change = {};
+ foreach my $key (keys %$change) {
+ $flag_change->{$key} = $change->{$key};
+ }
+ $flag_change->{removed} = $flags{$flag}{removed};
+ $flag_change->{added} = $flags{$flag}{added};
+ splice(@{$operation->{changes}}, $i, 0, $flag_change);
}
+ $i--;
+ }
}
+ }
- $user->visible_bugs([keys %visible_bug_ids]);
+ $user->visible_bugs([keys %visible_bug_ids]);
- $vars->{'ih_activity'} = $activity;
+ $vars->{'ih_activity'} = $activity;
}
sub _add_duplicates {
- # insert 'is a dupe of this bug' comment to allow js to display
- # as activity
- my ($bug_id, $activity) = @_;
+ # insert 'is a dupe of this bug' comment to allow js to display
+ # as activity
- # we're ignoring pre-bugzilla 3.0 ".. has been marked as a duplicate .."
- # comments because searching each comment's text is expensive. these
- # legacy comments will not be visible at all in the bug's comment/activity
- # stream. bug 928786 deals with migrating those comments to be stored as
- # CMT_HAS_DUPE instead.
+ my ($bug_id, $activity) = @_;
- my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare("
- SELECT profiles.login_name, " .
- $dbh->sql_date_format('bug_when', '%Y.%m.%d %H:%i:%s') . ",
+ # we're ignoring pre-bugzilla 3.0 ".. has been marked as a duplicate .."
+ # comments because searching each comment's text is expensive. these
+ # legacy comments will not be visible at all in the bug's comment/activity
+ # stream. bug 928786 deals with migrating those comments to be stored as
+ # CMT_HAS_DUPE instead.
+
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare("
+ SELECT profiles.login_name, "
+ . $dbh->sql_date_format('bug_when', '%Y.%m.%d %H:%i:%s') . ",
extra_data
FROM longdescs
INNER JOIN profiles ON profiles.userid = longdescs.who
WHERE bug_id = ? AND type = ?
ORDER BY bug_when
");
- $sth->execute($bug_id, CMT_HAS_DUPE);
-
- while (my($who, $when, $dupe_id) = $sth->fetchrow_array) {
- my $entry = {
- 'when' => $when,
- 'who' => $who,
- 'changes' => [
- {
- 'removed' => '',
- 'added' => $dupe_id,
- 'attachid' => undef,
- 'fieldname' => 'dupe',
- 'dupe' => 1,
- }
- ],
- };
- push @$activity, $entry;
- }
+ $sth->execute($bug_id, CMT_HAS_DUPE);
+
+ while (my ($who, $when, $dupe_id) = $sth->fetchrow_array) {
+ my $entry = {
+ 'when' => $when,
+ 'who' => $who,
+ 'changes' => [{
+ 'removed' => '',
+ 'added' => $dupe_id,
+ 'attachid' => undef,
+ 'fieldname' => 'dupe',
+ 'dupe' => 1,
+ }],
+ };
+ push @$activity, $entry;
+ }
- return [ sort { $a->{when} cmp $b->{when} } @$activity ];
+ return [sort { $a->{when} cmp $b->{when} } @$activity];
}
sub install_before_final_checks {
- my ($self, $args) = @_;
- add_setting('inline_history', ['on', 'off'], 'off');
+ my ($self, $args) = @_;
+ add_setting('inline_history', ['on', 'off'], 'off');
}
__PACKAGE__->NAME;
diff --git a/extensions/MoreBugUrl/Config.pm b/extensions/MoreBugUrl/Config.pm
index e0eac5f8a..f8175bb4c 100644
--- a/extensions/MoreBugUrl/Config.pm
+++ b/extensions/MoreBugUrl/Config.pm
@@ -13,10 +13,8 @@ use warnings;
use constant NAME => 'MoreBugUrl';
-use constant REQUIRED_MODULES => [
-];
+use constant REQUIRED_MODULES => [];
-use constant OPTIONAL_MODULES => [
-];
+use constant OPTIONAL_MODULES => [];
__PACKAGE__->NAME;
diff --git a/extensions/MoreBugUrl/Extension.pm b/extensions/MoreBugUrl/Extension.pm
index 0a4223e19..3158de294 100644
--- a/extensions/MoreBugUrl/Extension.pm
+++ b/extensions/MoreBugUrl/Extension.pm
@@ -14,41 +14,43 @@ use warnings;
use parent qw(Bugzilla::Extension);
use constant MORE_SUB_CLASSES => qw(
- Bugzilla::Extension::MoreBugUrl::BitBucket
- Bugzilla::Extension::MoreBugUrl::ReviewBoard
- Bugzilla::Extension::MoreBugUrl::Rietveld
- Bugzilla::Extension::MoreBugUrl::RT
- Bugzilla::Extension::MoreBugUrl::GetSatisfaction
- Bugzilla::Extension::MoreBugUrl::PHP
- Bugzilla::Extension::MoreBugUrl::Redmine
- Bugzilla::Extension::MoreBugUrl::Savane
- Bugzilla::Extension::MoreBugUrl::Phabricator
+ Bugzilla::Extension::MoreBugUrl::BitBucket
+ Bugzilla::Extension::MoreBugUrl::ReviewBoard
+ Bugzilla::Extension::MoreBugUrl::Rietveld
+ Bugzilla::Extension::MoreBugUrl::RT
+ Bugzilla::Extension::MoreBugUrl::GetSatisfaction
+ Bugzilla::Extension::MoreBugUrl::PHP
+ Bugzilla::Extension::MoreBugUrl::Redmine
+ Bugzilla::Extension::MoreBugUrl::Savane
+ Bugzilla::Extension::MoreBugUrl::Phabricator
);
# We need to update bug_see_also table because both
# Rietveld and ReviewBoard were originally under Bugzilla/BugUrl/.
sub install_update_db {
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- my $should_rename = $dbh->selectrow_array(
- q{SELECT 1 FROM bug_see_also
+ my $should_rename = $dbh->selectrow_array(q{SELECT 1 FROM bug_see_also
WHERE class IN ('Bugzilla::BugUrl::Rietveld',
- 'Bugzilla::BugUrl::ReviewBoard')});
-
- if ($should_rename) {
- my $sth = $dbh->prepare('UPDATE bug_see_also SET class = ?
- WHERE class = ?');
- $sth->execute('Bugzilla::Extension::MoreBugUrl::ReviewBoard',
- 'Bugzilla::BugUrl::ReviewBoard');
-
- $sth->execute('Bugzilla::Extension::MoreBugUrl::Rietveld',
- 'Bugzilla::BugUrl::Rietveld');
- }
+ 'Bugzilla::BugUrl::ReviewBoard')}
+ );
+
+ if ($should_rename) {
+ my $sth = $dbh->prepare(
+ 'UPDATE bug_see_also SET class = ?
+ WHERE class = ?'
+ );
+ $sth->execute('Bugzilla::Extension::MoreBugUrl::ReviewBoard',
+ 'Bugzilla::BugUrl::ReviewBoard');
+
+ $sth->execute('Bugzilla::Extension::MoreBugUrl::Rietveld',
+ 'Bugzilla::BugUrl::Rietveld');
+ }
}
sub bug_url_sub_classes {
- my ($self, $args) = @_;
- push @{ $args->{sub_classes} }, MORE_SUB_CLASSES;
+ my ($self, $args) = @_;
+ push @{$args->{sub_classes}}, MORE_SUB_CLASSES;
}
__PACKAGE__->NAME;
diff --git a/extensions/MoreBugUrl/lib/BitBucket.pm b/extensions/MoreBugUrl/lib/BitBucket.pm
index dcc85992d..683f9aeb3 100644
--- a/extensions/MoreBugUrl/lib/BitBucket.pm
+++ b/extensions/MoreBugUrl/lib/BitBucket.pm
@@ -18,23 +18,23 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
+ my ($class, $uri) = @_;
- # BitBucket issues have the form of
- # bitbucket.org/user/project/issue/1234
- return (lc($uri->authority) eq "bitbucket.org"
- && $uri->path =~ m|[^/]+/[^/]+/issue/\d+|i) ? 1 : 0;
+ # BitBucket issues have the form of
+ # bitbucket.org/user/project/issue/1234
+ return (lc($uri->authority) eq "bitbucket.org"
+ && $uri->path =~ m|[^/]+/[^/]+/issue/\d+|i) ? 1 : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- my ($path) = $uri->path =~ m|([^/]+/[^/]+/issue/\d+)|i;
- $uri = new URI("https://bitbucket.org/$path");
+ my ($path) = $uri->path =~ m|([^/]+/[^/]+/issue/\d+)|i;
+ $uri = new URI("https://bitbucket.org/$path");
- return $uri;
+ return $uri;
}
1;
diff --git a/extensions/MoreBugUrl/lib/GetSatisfaction.pm b/extensions/MoreBugUrl/lib/GetSatisfaction.pm
index 74951735b..34c86b65f 100644
--- a/extensions/MoreBugUrl/lib/GetSatisfaction.pm
+++ b/extensions/MoreBugUrl/lib/GetSatisfaction.pm
@@ -18,24 +18,24 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
+ my ($class, $uri) = @_;
- # GetSatisfaction URLs only have one form:
- # http(s)://getsatisfaction.com/PROJECT_NAME/topics/TOPIC_NAME
- return (lc($uri->authority) eq 'getsatisfaction.com'
- and $uri->path =~ m|^/[^/]+/topics/[^/]+$|) ? 1 : 0;
+ # GetSatisfaction URLs only have one form:
+ # http(s)://getsatisfaction.com/PROJECT_NAME/topics/TOPIC_NAME
+ return (lc($uri->authority) eq 'getsatisfaction.com'
+ and $uri->path =~ m|^/[^/]+/topics/[^/]+$|) ? 1 : 0;
}
sub _check_value {
- my ($class, $uri) = @_;
+ my ($class, $uri) = @_;
- $uri = $class->SUPER::_check_value($uri);
+ $uri = $class->SUPER::_check_value($uri);
- # GetSatisfaction HTTP URLs redirect to HTTPS, so just use the HTTPS
- # scheme.
- $uri->scheme('https');
+ # GetSatisfaction HTTP URLs redirect to HTTPS, so just use the HTTPS
+ # scheme.
+ $uri->scheme('https');
- return $uri;
+ return $uri;
}
1;
diff --git a/extensions/MoreBugUrl/lib/PHP.pm b/extensions/MoreBugUrl/lib/PHP.pm
index 6f201d7b1..9419bcd14 100644
--- a/extensions/MoreBugUrl/lib/PHP.pm
+++ b/extensions/MoreBugUrl/lib/PHP.pm
@@ -18,27 +18,27 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
+ my ($class, $uri) = @_;
- # PHP Bug URLs have only one form:
- # https://bugs.php.net/bug.php?id=1234
- return (lc($uri->authority) eq 'bugs.php.net'
- and $uri->path =~ m|/bug\.php$|
- and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0;
+ # PHP Bug URLs have only one form:
+ # https://bugs.php.net/bug.php?id=1234
+ return (lc($uri->authority) eq 'bugs.php.net'
+ and $uri->path =~ m|/bug\.php$|
+ and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # PHP Bug URLs redirect to HTTPS, so just use the HTTPS scheme.
- $uri->scheme('https');
+ # PHP Bug URLs redirect to HTTPS, so just use the HTTPS scheme.
+ $uri->scheme('https');
- # And remove any # part if there is one.
- $uri->fragment(undef);
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
- return $uri;
+ return $uri;
}
1;
diff --git a/extensions/MoreBugUrl/lib/Phabricator.pm b/extensions/MoreBugUrl/lib/Phabricator.pm
index 818d9af8f..0822663ad 100644
--- a/extensions/MoreBugUrl/lib/Phabricator.pm
+++ b/extensions/MoreBugUrl/lib/Phabricator.pm
@@ -18,24 +18,25 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
- return ($uri->path =~ m|^/T\d+$|) ? 1 : 0;
+ my ($class, $uri) = @_;
+ return ($uri->path =~ m|^/T\d+$|) ? 1 : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # Phabricator URLs have only one form:
- # http://example.com/T111
+ # Phabricator URLs have only one form:
+ # http://example.com/T111
- # Make sure there are no query parameters.
- $uri->query(undef);
- # And remove any # part if there is one.
- $uri->fragment(undef);
+ # Make sure there are no query parameters.
+ $uri->query(undef);
- return $uri;
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
}
1;
diff --git a/extensions/MoreBugUrl/lib/RT.pm b/extensions/MoreBugUrl/lib/RT.pm
index acb90cf39..54dc2dd4c 100644
--- a/extensions/MoreBugUrl/lib/RT.pm
+++ b/extensions/MoreBugUrl/lib/RT.pm
@@ -18,24 +18,25 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
-
- # RT URLs can look like various things:
- # http://example.com/rt/Ticket/Display.html?id=1234
- # https://example.com/Public/Bug/Display.html?id=1234
- return ($uri->path =~ m|/Display\.html$|
- and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0;
+ my ($class, $uri) = @_;
+
+ # RT URLs can look like various things:
+ # http://example.com/rt/Ticket/Display.html?id=1234
+ # https://example.com/Public/Bug/Display.html?id=1234
+ return ($uri->path =~ m|/Display\.html$| and $uri->query_param('id') =~ /^\d+$/)
+ ? 1
+ : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # And remove any # part if there is one.
- $uri->fragment(undef);
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
- return $uri;
+ return $uri;
}
1;
diff --git a/extensions/MoreBugUrl/lib/Redmine.pm b/extensions/MoreBugUrl/lib/Redmine.pm
index 57a071239..712dab197 100644
--- a/extensions/MoreBugUrl/lib/Redmine.pm
+++ b/extensions/MoreBugUrl/lib/Redmine.pm
@@ -18,24 +18,25 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
- return ($uri->path =~ m|/issues/\d+$|) ? 1 : 0;
+ my ($class, $uri) = @_;
+ return ($uri->path =~ m|/issues/\d+$|) ? 1 : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # Redmine URLs have only one form:
- # http://demo.redmine.com/issues/111
+ # Redmine URLs have only one form:
+ # http://demo.redmine.com/issues/111
- # Make sure there are no query parameters.
- $uri->query(undef);
- # And remove any # part if there is one.
- $uri->fragment(undef);
+ # Make sure there are no query parameters.
+ $uri->query(undef);
- return $uri;
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
}
1;
diff --git a/extensions/MoreBugUrl/lib/ReviewBoard.pm b/extensions/MoreBugUrl/lib/ReviewBoard.pm
index af5ff0684..48cbecf11 100644
--- a/extensions/MoreBugUrl/lib/ReviewBoard.pm
+++ b/extensions/MoreBugUrl/lib/ReviewBoard.pm
@@ -18,29 +18,30 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
- return ($uri->path =~ m|/r/\d+/?$|) ? 1 : 0;
+ my ($class, $uri) = @_;
+ return ($uri->path =~ m|/r/\d+/?$|) ? 1 : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # Review Board URLs have only one form (the trailing slash is optional):
- # http://reviews.reviewboard.org/r/111/
+ # Review Board URLs have only one form (the trailing slash is optional):
+ # http://reviews.reviewboard.org/r/111/
- # Make sure there are no query parameters.
- $uri->query(undef);
- # And remove any # part if there is one.
- $uri->fragment(undef);
+ # Make sure there are no query parameters.
+ $uri->query(undef);
- # make sure the trailing slash is present
- if ($uri->path !~ m|/$|) {
- $uri->path($uri->path . '/');
- }
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
- return $uri;
+ # make sure the trailing slash is present
+ if ($uri->path !~ m|/$|) {
+ $uri->path($uri->path . '/');
+ }
+
+ return $uri;
}
1;
diff --git a/extensions/MoreBugUrl/lib/Rietveld.pm b/extensions/MoreBugUrl/lib/Rietveld.pm
index a4bf08492..55a18b0b2 100644
--- a/extensions/MoreBugUrl/lib/Rietveld.pm
+++ b/extensions/MoreBugUrl/lib/Rietveld.pm
@@ -18,32 +18,34 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
- return ($uri->authority =~ /\.appspot\.com$/i
- and $uri->path =~ m#^/\d+(?:/|/show)?$#) ? 1 : 0;
+ my ($class, $uri) = @_;
+ return ($uri->authority =~ /\.appspot\.com$/i
+ and $uri->path =~ m#^/\d+(?:/|/show)?$#) ? 1 : 0;
}
sub _check_value {
- my ($class, $uri) = @_;
-
- $uri = $class->SUPER::_check_value($uri);
-
- # Rietveld URLs have three forms:
- # http(s)://example.appspot.com/1234
- # http(s)://example.appspot.com/1234/
- # http(s)://example.appspot.com/1234/show
- if ($uri->path =~ m#^/(\d+)(?:/|/show)$#) {
- # This is the shortest standard URL form for Rietveld issues,
- # and so we reduce all URLs to this.
- $uri->path('/' . $1);
- }
-
- # Make sure there are no query parameters.
- $uri->query(undef);
- # And remove any # part if there is one.
- $uri->fragment(undef);
-
- return $uri;
+ my ($class, $uri) = @_;
+
+ $uri = $class->SUPER::_check_value($uri);
+
+ # Rietveld URLs have three forms:
+ # http(s)://example.appspot.com/1234
+ # http(s)://example.appspot.com/1234/
+ # http(s)://example.appspot.com/1234/show
+ if ($uri->path =~ m#^/(\d+)(?:/|/show)$#) {
+
+ # This is the shortest standard URL form for Rietveld issues,
+ # and so we reduce all URLs to this.
+ $uri->path('/' . $1);
+ }
+
+ # Make sure there are no query parameters.
+ $uri->query(undef);
+
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
}
1;
diff --git a/extensions/MoreBugUrl/lib/Savane.pm b/extensions/MoreBugUrl/lib/Savane.pm
index 5b35bbf7d..717878b33 100644
--- a/extensions/MoreBugUrl/lib/Savane.pm
+++ b/extensions/MoreBugUrl/lib/Savane.pm
@@ -18,24 +18,27 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
- my ($class, $uri) = @_;
- # Savane URLs look like the following (the index.php is optional):
- # https://savannah.gnu.org/bugs/index.php?107657
- # https://savannah.gnu.org/patch/index.php?107657
- # https://savannah.gnu.org/support/index.php?107657
- # https://savannah.gnu.org/task/index.php?107657
- return ($uri->as_string =~ m|/(bugs\|patch\|support\|task)/(index\.php)?\?\d+$|) ? 1 : 0;
+ my ($class, $uri) = @_;
+
+ # Savane URLs look like the following (the index.php is optional):
+ # https://savannah.gnu.org/bugs/index.php?107657
+ # https://savannah.gnu.org/patch/index.php?107657
+ # https://savannah.gnu.org/support/index.php?107657
+ # https://savannah.gnu.org/task/index.php?107657
+ return ($uri->as_string =~ m|/(bugs\|patch\|support\|task)/(index\.php)?\?\d+$|)
+ ? 1
+ : 0;
}
sub _check_value {
- my $class = shift;
+ my $class = shift;
- my $uri = $class->SUPER::_check_value(@_);
+ my $uri = $class->SUPER::_check_value(@_);
- # And remove any # part if there is one.
- $uri->fragment(undef);
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
- return $uri;
+ return $uri;
}
1;
diff --git a/extensions/OldBugMove/Extension.pm b/extensions/OldBugMove/Extension.pm
index 375707fd2..70dc5ad30 100644
--- a/extensions/OldBugMove/Extension.pm
+++ b/extensions/OldBugMove/Extension.pm
@@ -29,172 +29,172 @@ use constant VERSION => BUGZILLA_VERSION;
use constant CMT_MOVED_TO => 4;
sub install_update_db {
- my $reso_type = Bugzilla::Field::Choice->type('resolution');
- my $moved_reso = $reso_type->new({ name => 'MOVED' });
- # We make the MOVED resolution inactive, so that it doesn't show up
- # as a valid drop-down option.
- if ($moved_reso) {
- $moved_reso->set_is_active(0);
- $moved_reso->update();
- }
- else {
- print "Creating the MOVED resolution...\n";
- $reso_type->create(
- { value => 'MOVED', sortkey => '30000', isactive => 0 });
- }
+ my $reso_type = Bugzilla::Field::Choice->type('resolution');
+ my $moved_reso = $reso_type->new({name => 'MOVED'});
+
+ # We make the MOVED resolution inactive, so that it doesn't show up
+ # as a valid drop-down option.
+ if ($moved_reso) {
+ $moved_reso->set_is_active(0);
+ $moved_reso->update();
+ }
+ else {
+ print "Creating the MOVED resolution...\n";
+ $reso_type->create({value => 'MOVED', sortkey => '30000', isactive => 0});
+ }
}
sub config_add_panels {
- my ($self, $args) = @_;
- my $modules = $args->{'panel_modules'};
- $modules->{'OldBugMove'} = 'Bugzilla::Extension::OldBugMove::Params';
+ my ($self, $args) = @_;
+ my $modules = $args->{'panel_modules'};
+ $modules->{'OldBugMove'} = 'Bugzilla::Extension::OldBugMove::Params';
}
sub template_before_create {
- my ($self, $args) = @_;
- my $config = $args->{config};
+ my ($self, $args) = @_;
+ my $config = $args->{config};
- my $constants = $config->{CONSTANTS};
- $constants->{CMT_MOVED_TO} = CMT_MOVED_TO;
+ my $constants = $config->{CONSTANTS};
+ $constants->{CMT_MOVED_TO} = CMT_MOVED_TO;
- my $vars = $config->{VARIABLES};
- $vars->{oldbugmove_user_is_mover} = \&_user_is_mover;
+ my $vars = $config->{VARIABLES};
+ $vars->{oldbugmove_user_is_mover} = \&_user_is_mover;
}
sub object_before_delete {
- my ($self, $args) = @_;
- my $object = $args->{'object'};
- if ($object->isa('Bugzilla::Field::Choice::resolution')) {
- if ($object->name eq 'MOVED') {
- ThrowUserError('oldbugmove_no_delete_moved');
- }
+ my ($self, $args) = @_;
+ my $object = $args->{'object'};
+ if ($object->isa('Bugzilla::Field::Choice::resolution')) {
+ if ($object->name eq 'MOVED') {
+ ThrowUserError('oldbugmove_no_delete_moved');
}
+ }
}
sub object_before_set {
- my ($self, $args) = @_;
- my ($object, $field) = @$args{qw(object field)};
- if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) {
- # Store the old value so that end_of_set can check it.
- $object->{'_oldbugmove_old_resolution'} = $object->resolution;
- }
+ my ($self, $args) = @_;
+ my ($object, $field) = @$args{qw(object field)};
+ if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) {
+
+ # Store the old value so that end_of_set can check it.
+ $object->{'_oldbugmove_old_resolution'} = $object->resolution;
+ }
}
sub object_end_of_set {
- my ($self, $args) = @_;
- my ($object, $field) = @$args{qw(object field)};
- if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) {
- my $old_value = delete $object->{'_oldbugmove_old_resolution'};
- return if $old_value eq $object->resolution;
- if ($object->resolution eq 'MOVED') {
- $object->add_comment('', { type => CMT_MOVED_TO,
- extra_data => Bugzilla->user->login });
- }
+ my ($self, $args) = @_;
+ my ($object, $field) = @$args{qw(object field)};
+ if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) {
+ my $old_value = delete $object->{'_oldbugmove_old_resolution'};
+ return if $old_value eq $object->resolution;
+ if ($object->resolution eq 'MOVED') {
+ $object->add_comment('',
+ {type => CMT_MOVED_TO, extra_data => Bugzilla->user->login});
}
+ }
}
sub object_end_of_set_all {
- my ($self, $args) = @_;
- my $object = $args->{'object'};
+ my ($self, $args) = @_;
+ my $object = $args->{'object'};
- if ($object->isa('Bugzilla::Bug') and Bugzilla->input_params->{'oldbugmove'}) {
- my $new_status = Bugzilla->params->{'duplicate_or_move_bug_status'};
- $object->set_bug_status($new_status, { resolution => 'MOVED' });
- }
+ if ($object->isa('Bugzilla::Bug') and Bugzilla->input_params->{'oldbugmove'}) {
+ my $new_status = Bugzilla->params->{'duplicate_or_move_bug_status'};
+ $object->set_bug_status($new_status, {resolution => 'MOVED'});
+ }
}
sub object_validators {
- my ($self, $args) = @_;
- my ($class, $validators) = @$args{qw(class validators)};
- if ($class->isa('Bugzilla::Comment')) {
- my $extra_data_validator = $validators->{extra_data};
- $validators->{extra_data} =
- sub { _check_comment_extra_data($extra_data_validator, @_) };
- }
- elsif ($class->isa('Bugzilla::Bug')) {
- my $reso_validator = $validators->{resolution};
- $validators->{resolution} =
- sub { _check_bug_resolution($reso_validator, @_) };
- }
+ my ($self, $args) = @_;
+ my ($class, $validators) = @$args{qw(class validators)};
+ if ($class->isa('Bugzilla::Comment')) {
+ my $extra_data_validator = $validators->{extra_data};
+ $validators->{extra_data}
+ = sub { _check_comment_extra_data($extra_data_validator, @_) };
+ }
+ elsif ($class->isa('Bugzilla::Bug')) {
+ my $reso_validator = $validators->{resolution};
+ $validators->{resolution} = sub { _check_bug_resolution($reso_validator, @_) };
+ }
}
sub _check_bug_resolution {
- my $original_validator = shift;
- my ($invocant, $resolution) = @_;
-
- if ($resolution eq 'MOVED' && $invocant->resolution ne 'MOVED'
- && !Bugzilla->input_params->{'oldbugmove'})
- {
- # MOVED has a special meaning and can only be used when
- # really moving bugs to another installation.
- ThrowUserError('oldbugmove_no_manual_move');
- }
-
- return $original_validator->(@_);
+ my $original_validator = shift;
+ my ($invocant, $resolution) = @_;
+
+ if ( $resolution eq 'MOVED'
+ && $invocant->resolution ne 'MOVED'
+ && !Bugzilla->input_params->{'oldbugmove'})
+ {
+ # MOVED has a special meaning and can only be used when
+ # really moving bugs to another installation.
+ ThrowUserError('oldbugmove_no_manual_move');
+ }
+
+ return $original_validator->(@_);
}
sub _check_comment_extra_data {
- my $original_validator = shift;
- my ($invocant, $extra_data, undef, $params) = @_;
- my $type = blessed($invocant) ? $invocant->type : $params->{type};
-
- if ($type == CMT_MOVED_TO) {
- return Bugzilla::User->check($extra_data)->login;
- }
- return $original_validator->(@_);
+ my $original_validator = shift;
+ my ($invocant, $extra_data, undef, $params) = @_;
+ my $type = blessed($invocant) ? $invocant->type : $params->{type};
+
+ if ($type == CMT_MOVED_TO) {
+ return Bugzilla::User->check($extra_data)->login;
+ }
+ return $original_validator->(@_);
}
sub bug_end_of_update {
- my ($self, $args) = @_;
- my ($bug, $old_bug, $changes) = @$args{qw(bug old_bug changes)};
- if (defined $changes->{'resolution'}
- and $changes->{'resolution'}->[1] eq 'MOVED')
- {
- $self->_move_bug($bug, $old_bug);
- }
+ my ($self, $args) = @_;
+ my ($bug, $old_bug, $changes) = @$args{qw(bug old_bug changes)};
+ if (defined $changes->{'resolution'}
+ and $changes->{'resolution'}->[1] eq 'MOVED')
+ {
+ $self->_move_bug($bug, $old_bug);
+ }
}
sub _move_bug {
- my ($self, $bug, $old_bug) = @_;
-
- my $dbh = Bugzilla->dbh;
- my $template = Bugzilla->template;
-
- _user_is_mover(Bugzilla->user)
- or ThrowUserError("auth_failure", { action => 'move',
- object => 'bugs' });
-
- # Don't export the new status and resolution. We want the current
- # ones.
- local $Storable::forgive_me = 1;
- my $export_me = dclone($bug);
- $export_me->{bug_status} = $old_bug->bug_status;
- delete $export_me->{status};
- $export_me->{resolution} = $old_bug->resolution;
-
- # Prepare and send all data about these bugs to the new database
- my $to = Bugzilla->params->{'move-to-address'};
- $to =~ s/@/\@/;
- my $from = Bugzilla->params->{'mailfrom'};
- $from =~ s/@/\@/;
- my $msg = "To: $to\n";
- $msg .= "From: Bugzilla <" . $from . ">\n";
- $msg .= "Subject: Moving bug " . $bug->id . "\n\n";
- my @fieldlist = (Bugzilla::Bug->fields, 'group', 'long_desc',
- 'attachment', 'attachmentdata');
- my %displayfields = map { $_ => 1 } @fieldlist;
- my $vars = { bugs => [$export_me], displayfields => \%displayfields };
- $template->process("bug/show.xml.tmpl", $vars, \$msg)
- || ThrowTemplateError($template->error());
- $msg .= "\n";
- MessageToMTA($msg);
+ my ($self, $bug, $old_bug) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ my $template = Bugzilla->template;
+
+ _user_is_mover(Bugzilla->user)
+ or ThrowUserError("auth_failure", {action => 'move', object => 'bugs'});
+
+ # Don't export the new status and resolution. We want the current
+ # ones.
+ local $Storable::forgive_me = 1;
+ my $export_me = dclone($bug);
+ $export_me->{bug_status} = $old_bug->bug_status;
+ delete $export_me->{status};
+ $export_me->{resolution} = $old_bug->resolution;
+
+ # Prepare and send all data about these bugs to the new database
+ my $to = Bugzilla->params->{'move-to-address'};
+ $to =~ s/@/\@/;
+ my $from = Bugzilla->params->{'mailfrom'};
+ $from =~ s/@/\@/;
+ my $msg = "To: $to\n";
+ $msg .= "From: Bugzilla <" . $from . ">\n";
+ $msg .= "Subject: Moving bug " . $bug->id . "\n\n";
+ my @fieldlist = (Bugzilla::Bug->fields, 'group', 'long_desc', 'attachment',
+ 'attachmentdata');
+ my %displayfields = map { $_ => 1 } @fieldlist;
+ my $vars = {bugs => [$export_me], displayfields => \%displayfields};
+ $template->process("bug/show.xml.tmpl", $vars, \$msg)
+ || ThrowTemplateError($template->error());
+ $msg .= "\n";
+ MessageToMTA($msg);
}
sub _user_is_mover {
- my $user = shift;
+ my $user = shift;
- my @movers = map { trim($_) } split(',', Bugzilla->params->{'movers'});
- return ($user->id and grep($_ eq $user->login, @movers)) ? 1 : 0;
+ my @movers = map { trim($_) } split(',', Bugzilla->params->{'movers'});
+ return ($user->id and grep($_ eq $user->login, @movers)) ? 1 : 0;
}
__PACKAGE__->NAME;
diff --git a/extensions/OldBugMove/lib/Params.pm b/extensions/OldBugMove/lib/Params.pm
index 05e3ed277..e1bab048b 100644
--- a/extensions/OldBugMove/lib/Params.pm
+++ b/extensions/OldBugMove/lib/Params.pm
@@ -16,23 +16,11 @@ use Bugzilla::Config::Common;
our $sortkey = 700;
use constant get_param_list => (
- {
- name => 'move-to-url',
- type => 't',
- default => ''
- },
-
- {
- name => 'move-to-address',
- type => 't',
- default => 'bugzilla-import'
- },
-
- {
- name => 'movers',
- type => 't',
- default => ''
- },
+ {name => 'move-to-url', type => 't', default => ''},
+
+ {name => 'move-to-address', type => 't', default => 'bugzilla-import'},
+
+ {name => 'movers', type => 't', default => ''},
);
1;
diff --git a/extensions/SecureMail/Config.pm b/extensions/SecureMail/Config.pm
index f1975c1c1..69074573f 100644
--- a/extensions/SecureMail/Config.pm
+++ b/extensions/SecureMail/Config.pm
@@ -25,25 +25,19 @@ use strict;
use constant NAME => 'SecureMail';
use constant REQUIRED_MODULES => [
- {
- package => 'Crypt-OpenPGP',
- module => 'Crypt::OpenPGP',
- # 1.02 added the ability for new() to take KeyRing objects for the
- # PubRing argument.
- version => '1.02',
- # 1.04 hangs - https://rt.cpan.org/Public/Bug/Display.html?id=68018
- # blacklist => [ '1.04' ],
- },
- {
- package => 'Crypt-SMIME',
- module => 'Crypt::SMIME',
- version => 0,
- },
- {
- package => 'HTML-Tree',
- module => 'HTML::Tree',
- version => 0,
- }
+ {
+ package => 'Crypt-OpenPGP',
+ module => 'Crypt::OpenPGP',
+
+ # 1.02 added the ability for new() to take KeyRing objects for the
+ # PubRing argument.
+ version => '1.02',
+
+ # 1.04 hangs - https://rt.cpan.org/Public/Bug/Display.html?id=68018
+ # blacklist => [ '1.04' ],
+ },
+ {package => 'Crypt-SMIME', module => 'Crypt::SMIME', version => 0,},
+ {package => 'HTML-Tree', module => 'HTML::Tree', version => 0,}
];
__PACKAGE__->NAME;
diff --git a/extensions/SecureMail/Extension.pm b/extensions/SecureMail/Extension.pm
index 492ce0cb6..e1d7468f4 100644
--- a/extensions/SecureMail/Extension.pm
+++ b/extensions/SecureMail/Extension.pm
@@ -53,12 +53,12 @@ use constant SECURE_ALL => 2;
# public_key text in the 'profiles' table - stores public key
##############################################################################
sub install_update_db {
- my ($self, $args) = @_;
+ my ($self, $args) = @_;
- my $dbh = Bugzilla->dbh;
- $dbh->bz_add_column('groups', 'secure_mail',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
- $dbh->bz_add_column('profiles', 'public_key', { TYPE => 'LONGTEXT' });
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_add_column('groups', 'secure_mail',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_add_column('profiles', 'public_key', {TYPE => 'LONGTEXT'});
}
##############################################################################
@@ -66,516 +66,530 @@ sub install_update_db {
##############################################################################
BEGIN {
- *Bugzilla::Group::secure_mail = \&_group_secure_mail;
- *Bugzilla::User::public_key = \&_user_public_key;
+ *Bugzilla::Group::secure_mail = \&_group_secure_mail;
+ *Bugzilla::User::public_key = \&_user_public_key;
}
sub _group_secure_mail { return $_[0]->{'secure_mail'}; }
# We want to lazy-load the public_key.
sub _user_public_key {
- my $self = shift;
- if (!exists $self->{public_key}) {
- ($self->{public_key}) = Bugzilla->dbh->selectrow_array(
- "SELECT public_key FROM profiles WHERE userid = ?",
- undef,
- $self->id
- );
- }
- return $self->{public_key};
+ my $self = shift;
+ if (!exists $self->{public_key}) {
+ ($self->{public_key})
+ = Bugzilla->dbh->selectrow_array(
+ "SELECT public_key FROM profiles WHERE userid = ?",
+ undef, $self->id);
+ }
+ return $self->{public_key};
}
# Make sure generic functions know about the additional fields in the user
# and group objects.
sub object_columns {
- my ($self, $args) = @_;
- my $class = $args->{'class'};
- my $columns = $args->{'columns'};
+ my ($self, $args) = @_;
+ my $class = $args->{'class'};
+ my $columns = $args->{'columns'};
- if ($class->isa('Bugzilla::Group')) {
- push(@$columns, 'secure_mail');
- }
+ if ($class->isa('Bugzilla::Group')) {
+ push(@$columns, 'secure_mail');
+ }
}
# Plug appropriate validators so we can check the validity of the two
# fields created by this extension, when new values are submitted.
sub object_validators {
- my ($self, $args) = @_;
- my %args = %{ $args };
- my ($invocant, $validators) = @args{qw(class validators)};
+ my ($self, $args) = @_;
+ my %args = %{$args};
+ my ($invocant, $validators) = @args{qw(class validators)};
+
+ if ($invocant->isa('Bugzilla::Group')) {
+ $validators->{'secure_mail'} = \&Bugzilla::Object::check_boolean;
+ }
+ elsif ($invocant->isa('Bugzilla::User')) {
+ $validators->{'public_key'} = sub {
+ my ($self, $value) = @_;
+ $value = trim($value) || '';
+
+ return $value if $value eq '';
+
+ if ($value =~ /PUBLIC KEY/) {
+
+ # PGP keys must be ASCII-armoured.
+ if (!Crypt::OpenPGP::Armour->unarmour($value)) {
+ ThrowUserError('securemail_invalid_key',
+ {errstr => Crypt::OpenPGP::Armour->errstr});
+ }
+ }
+ elsif ($value =~ /BEGIN CERTIFICATE/) {
- if ($invocant->isa('Bugzilla::Group')) {
- $validators->{'secure_mail'} = \&Bugzilla::Object::check_boolean;
- }
- elsif ($invocant->isa('Bugzilla::User')) {
- $validators->{'public_key'} = sub {
- my ($self, $value) = @_;
- $value = trim($value) || '';
-
- return $value if $value eq '';
-
- if ($value =~ /PUBLIC KEY/) {
- # PGP keys must be ASCII-armoured.
- if (!Crypt::OpenPGP::Armour->unarmour($value)) {
- ThrowUserError('securemail_invalid_key',
- { errstr => Crypt::OpenPGP::Armour->errstr });
- }
- }
- elsif ($value =~ /BEGIN CERTIFICATE/) {
- # S/MIME Keys must be in PEM format (Base64-encoded X.509)
- #
- # Crypt::SMIME seems not to like tainted values - it claims
- # they aren't scalars!
- trick_taint($value);
-
- my $smime = Crypt::SMIME->new();
- eval {
- $smime->setPublicKey([$value]);
- };
- if ($@) {
- ThrowUserError('securemail_invalid_key',
- { errstr => $@ });
- }
- }
- else {
- ThrowUserError('securemail_invalid_key');
- }
-
- return $value;
- };
- }
+ # S/MIME Keys must be in PEM format (Base64-encoded X.509)
+ #
+ # Crypt::SMIME seems not to like tainted values - it claims
+ # they aren't scalars!
+ trick_taint($value);
+
+ my $smime = Crypt::SMIME->new();
+ eval { $smime->setPublicKey([$value]); };
+ if ($@) {
+ ThrowUserError('securemail_invalid_key', {errstr => $@});
+ }
+ }
+ else {
+ ThrowUserError('securemail_invalid_key');
+ }
+
+ return $value;
+ };
+ }
}
# When creating a 'group' object, set up the secure_mail field appropriately.
sub object_before_create {
- my ($self, $args) = @_;
- my $class = $args->{'class'};
- my $params = $args->{'params'};
+ my ($self, $args) = @_;
+ my $class = $args->{'class'};
+ my $params = $args->{'params'};
- if ($class->isa('Bugzilla::Group')) {
- $params->{secure_mail} = Bugzilla->cgi->param('secure_mail');
- }
+ if ($class->isa('Bugzilla::Group')) {
+ $params->{secure_mail} = Bugzilla->cgi->param('secure_mail');
+ }
}
# On update, make sure the updating process knows about our new columns.
sub object_update_columns {
- my ($self, $args) = @_;
- my $object = $args->{'object'};
- my $columns = $args->{'columns'};
+ my ($self, $args) = @_;
+ my $object = $args->{'object'};
+ my $columns = $args->{'columns'};
- if ($object->isa('Bugzilla::Group')) {
- # This seems like a convenient moment to extract this value...
- $object->set('secure_mail', Bugzilla->cgi->param('secure_mail'));
+ if ($object->isa('Bugzilla::Group')) {
- push(@$columns, 'secure_mail');
- }
- elsif ($object->isa('Bugzilla::User')) {
- push(@$columns, 'public_key');
- }
+ # This seems like a convenient moment to extract this value...
+ $object->set('secure_mail', Bugzilla->cgi->param('secure_mail'));
+
+ push(@$columns, 'secure_mail');
+ }
+ elsif ($object->isa('Bugzilla::User')) {
+ push(@$columns, 'public_key');
+ }
}
# Handle the setting and changing of the public key.
sub user_preferences {
- my ($self, $args) = @_;
- my $tab = $args->{'current_tab'};
- my $save = $args->{'save_changes'};
- my $handled = $args->{'handled'};
- my $vars = $args->{'vars'};
- my $params = Bugzilla->input_params;
-
- return unless $tab eq 'securemail';
-
- # Create a new user object so we don't mess with the main one, as we
- # don't know where it's been...
- my $user = new Bugzilla::User(Bugzilla->user->id);
-
- if ($save) {
- $user->set('public_key', $params->{'public_key'});
- $user->update();
-
- # Send user a test email
- if ($user->public_key) {
- _send_test_email($user);
- $vars->{'test_email_sent'} = 1;
- }
+ my ($self, $args) = @_;
+ my $tab = $args->{'current_tab'};
+ my $save = $args->{'save_changes'};
+ my $handled = $args->{'handled'};
+ my $vars = $args->{'vars'};
+ my $params = Bugzilla->input_params;
+
+ return unless $tab eq 'securemail';
+
+ # Create a new user object so we don't mess with the main one, as we
+ # don't know where it's been...
+ my $user = new Bugzilla::User(Bugzilla->user->id);
+
+ if ($save) {
+ $user->set('public_key', $params->{'public_key'});
+ $user->update();
+
+ # Send user a test email
+ if ($user->public_key) {
+ _send_test_email($user);
+ $vars->{'test_email_sent'} = 1;
}
+ }
- $vars->{'public_key'} = $user->public_key;
+ $vars->{'public_key'} = $user->public_key;
- # Set the 'handled' scalar reference to true so that the caller
- # knows the panel name is valid and that an extension took care of it.
- $$handled = 1;
+ # Set the 'handled' scalar reference to true so that the caller
+ # knows the panel name is valid and that an extension took care of it.
+ $$handled = 1;
}
sub template_before_process {
- my ($self, $args) = @_;
- my $file = $args->{'file'};
- my $vars = $args->{'vars'};
-
- # Bug dependency emails contain the subject of the dependent bug
- # right before the diffs when a status has gone from open/closed
- # or closed/open. We need to sanitize the subject of change.blocker
- # similar to how we do referenced bugs
- return unless
- $file eq 'email/bugmail.html.tmpl'
- || $file eq 'email/bugmail.txt.tmpl';
-
- if (defined $vars->{diffs}) {
- foreach my $change (@{ $vars->{diffs} }) {
- next if !defined $change->{blocker};
- if (grep($_->secure_mail, @{ $change->{blocker}->groups_in })) {
- $change->{blocker}->{short_desc} = "(Secure bug)";
- }
- }
+ my ($self, $args) = @_;
+ my $file = $args->{'file'};
+ my $vars = $args->{'vars'};
+
+ # Bug dependency emails contain the subject of the dependent bug
+ # right before the diffs when a status has gone from open/closed
+ # or closed/open. We need to sanitize the subject of change.blocker
+ # similar to how we do referenced bugs
+ return
+ unless $file eq 'email/bugmail.html.tmpl'
+ || $file eq 'email/bugmail.txt.tmpl';
+
+ if (defined $vars->{diffs}) {
+ foreach my $change (@{$vars->{diffs}}) {
+ next if !defined $change->{blocker};
+ if (grep($_->secure_mail, @{$change->{blocker}->groups_in})) {
+ $change->{blocker}->{short_desc} = "(Secure bug)";
+ }
}
+ }
}
sub _send_test_email {
- my ($user) = @_;
- my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+ my ($user) = @_;
+ my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
- my $vars = {
- to_user => $user->email,
- };
+ my $vars = {to_user => $user->email,};
- my $msg = "";
- $template->process("account/email/securemail-test.txt.tmpl", $vars, \$msg)
- || ThrowTemplateError($template->error());
+ my $msg = "";
+ $template->process("account/email/securemail-test.txt.tmpl", $vars, \$msg)
+ || ThrowTemplateError($template->error());
- MessageToMTA($msg);
+ MessageToMTA($msg);
}
##############################################################################
# Encrypting the email
##############################################################################
sub mailer_before_send {
- my ($self, $args) = @_;
-
- my $email = $args->{'email'};
- my $body = $email->body;
-
- # Decide whether to make secure.
- # This is a bit of a hack; it would be nice if it were more clear
- # what sort a particular email is.
- my $is_bugmail = $email->header('X-Bugzilla-Status') ||
- $email->header('X-Bugzilla-Type') eq 'request';
- my $is_passwordmail = !$is_bugmail && ($body =~ /cfmpw.*cxlpw/s);
- my $is_test_email = $email->header('X-Bugzilla-Type') =~ /securemail-test/ ? 1 : 0;
- my $is_whine_email = $email->header('X-Bugzilla-Type') eq 'whine' ? 1 : 0;
- my $encrypt_header = $email->header('X-Bugzilla-Encrypt') ? 1 : 0;
-
- if ($is_bugmail
- || $is_passwordmail
- || $is_test_email
- || $is_whine_email
- || $encrypt_header
- ) {
- # Convert the email's To address into a User object
- my $login = $email->header('To');
- my $emailsuffix = Bugzilla->params->{'emailsuffix'};
- $login =~ s/$emailsuffix$//;
- my $user = new Bugzilla::User({ name => $login });
-
- # Default to secure. (Of course, this means if this extension has a
- # bug, lots of people are going to get bugmail falsely claiming their
- # bugs are secure and they need to add a key...)
- my $make_secure = SECURE_ALL;
-
- if ($is_bugmail) {
- # This is also a bit of a hack, but there's no header with the
- # bug ID in. So we take the first number in the subject.
- my ($bug_id) = ($email->header('Subject') =~ /\[\D+(\d+)\]/);
- my $bug = new Bugzilla::Bug($bug_id);
- if (!_should_secure_bug($bug)) {
- $make_secure = SECURE_NONE;
- }
- # If the insider group has securemail enabled..
- my $insider_group = Bugzilla::Group->new({ name => Bugzilla->params->{'insidergroup'} });
- if ($insider_group
- && $insider_group->secure_mail
- && $make_secure == SECURE_NONE)
- {
- my $comment_is_private = Bugzilla->dbh->selectcol_arrayref(
- "SELECT isprivate FROM longdescs WHERE bug_id=? ORDER BY bug_when",
- undef, $bug_id);
- # Encrypt if there are private comments on an otherwise public bug
- while ($body =~ /[\r\n]--- Comment #(\d+)/g) {
- my $comment_number = $1;
- if ($comment_number && $comment_is_private->[$comment_number]) {
- $make_secure = SECURE_BODY;
- last;
- }
- }
- # Encrypt if updating a private attachment without a comment
- if ($email->header('X-Bugzilla-Changed-Fields')
- && $email->header('X-Bugzilla-Changed-Fields') =~ /Attachment #(\d+)/)
- {
- my $attachment = Bugzilla::Attachment->new($1);
- if ($attachment && $attachment->isprivate) {
- $make_secure = SECURE_BODY;
- }
- }
- }
- }
- elsif ($is_passwordmail) {
- # Mail is made unsecure only if the user does not have a public
- # key and is not in any security groups. So specifying a public
- # key OR being in a security group means the mail is kept secure
- # (but, as noted above, the check is the other way around because
- # we default to secure).
- if ($user &&
- !$user->public_key &&
- !grep($_->secure_mail, @{ $user->groups }))
- {
- $make_secure = SECURE_NONE;
- }
- }
- elsif ($is_whine_email) {
- # When a whine email has one or more secure bugs in the body, then
- # encrypt the entire email body. Subject can be left alone as it
- # comes from the whine settings.
- $make_secure = _should_secure_whine($email) ? SECURE_BODY : SECURE_NONE;
- }
- elsif ($encrypt_header) {
- # Templates or code may set the X-Bugzilla-Encrypt header to
- # trigger encryption of emails. Remove that header from the email.
- $email->header_set('X-Bugzilla-Encrypt');
+ my ($self, $args) = @_;
+
+ my $email = $args->{'email'};
+ my $body = $email->body;
+
+ # Decide whether to make secure.
+ # This is a bit of a hack; it would be nice if it were more clear
+ # what sort a particular email is.
+ my $is_bugmail = $email->header('X-Bugzilla-Status')
+ || $email->header('X-Bugzilla-Type') eq 'request';
+ my $is_passwordmail = !$is_bugmail && ($body =~ /cfmpw.*cxlpw/s);
+ my $is_test_email
+ = $email->header('X-Bugzilla-Type') =~ /securemail-test/ ? 1 : 0;
+ my $is_whine_email = $email->header('X-Bugzilla-Type') eq 'whine' ? 1 : 0;
+ my $encrypt_header = $email->header('X-Bugzilla-Encrypt') ? 1 : 0;
+
+ if ( $is_bugmail
+ || $is_passwordmail
+ || $is_test_email
+ || $is_whine_email
+ || $encrypt_header)
+ {
+ # Convert the email's To address into a User object
+ my $login = $email->header('To');
+ my $emailsuffix = Bugzilla->params->{'emailsuffix'};
+ $login =~ s/$emailsuffix$//;
+ my $user = new Bugzilla::User({name => $login});
+
+ # Default to secure. (Of course, this means if this extension has a
+ # bug, lots of people are going to get bugmail falsely claiming their
+ # bugs are secure and they need to add a key...)
+ my $make_secure = SECURE_ALL;
+
+ if ($is_bugmail) {
+
+ # This is also a bit of a hack, but there's no header with the
+ # bug ID in. So we take the first number in the subject.
+ my ($bug_id) = ($email->header('Subject') =~ /\[\D+(\d+)\]/);
+ my $bug = new Bugzilla::Bug($bug_id);
+ if (!_should_secure_bug($bug)) {
+ $make_secure = SECURE_NONE;
+ }
+
+ # If the insider group has securemail enabled..
+ my $insider_group
+ = Bugzilla::Group->new({name => Bugzilla->params->{'insidergroup'}});
+ if ( $insider_group
+ && $insider_group->secure_mail
+ && $make_secure == SECURE_NONE)
+ {
+ my $comment_is_private
+ = Bugzilla->dbh->selectcol_arrayref(
+ "SELECT isprivate FROM longdescs WHERE bug_id=? ORDER BY bug_when",
+ undef, $bug_id);
+
+ # Encrypt if there are private comments on an otherwise public bug
+ while ($body =~ /[\r\n]--- Comment #(\d+)/g) {
+ my $comment_number = $1;
+ if ($comment_number && $comment_is_private->[$comment_number]) {
+ $make_secure = SECURE_BODY;
+ last;
+ }
}
- # If finding the user fails for some reason, but we determine we
- # should be encrypting, we want to make the mail safe. An empty key
- # does that.
- my $public_key = $user ? $user->public_key : '';
-
- # Check if the new bugmail prefix should be added to the subject.
- my $add_new = ($email->header('X-Bugzilla-Type') eq 'new' &&
- $user &&
- $user->settings->{'bugmail_new_prefix'}->{'value'} eq 'on') ? 1 : 0;
-
- if ($make_secure == SECURE_NONE) {
- # Filter the bug_links in HTML email in case the bugs the links
- # point are "secured" bugs and the user may not be able to see
- # the summaries.
- _filter_bug_links($email);
- }
- else {
- _make_secure($email, $public_key, $is_bugmail && $make_secure == SECURE_ALL, $add_new);
+ # Encrypt if updating a private attachment without a comment
+ if ( $email->header('X-Bugzilla-Changed-Fields')
+ && $email->header('X-Bugzilla-Changed-Fields') =~ /Attachment #(\d+)/)
+ {
+ my $attachment = Bugzilla::Attachment->new($1);
+ if ($attachment && $attachment->isprivate) {
+ $make_secure = SECURE_BODY;
+ }
}
+ }
+ }
+ elsif ($is_passwordmail) {
+
+ # Mail is made unsecure only if the user does not have a public
+ # key and is not in any security groups. So specifying a public
+ # key OR being in a security group means the mail is kept secure
+ # (but, as noted above, the check is the other way around because
+ # we default to secure).
+ if ($user && !$user->public_key && !grep($_->secure_mail, @{$user->groups})) {
+ $make_secure = SECURE_NONE;
+ }
+ }
+ elsif ($is_whine_email) {
+
+ # When a whine email has one or more secure bugs in the body, then
+ # encrypt the entire email body. Subject can be left alone as it
+ # comes from the whine settings.
+ $make_secure = _should_secure_whine($email) ? SECURE_BODY : SECURE_NONE;
+ }
+ elsif ($encrypt_header) {
+
+ # Templates or code may set the X-Bugzilla-Encrypt header to
+ # trigger encryption of emails. Remove that header from the email.
+ $email->header_set('X-Bugzilla-Encrypt');
+ }
+
+ # If finding the user fails for some reason, but we determine we
+ # should be encrypting, we want to make the mail safe. An empty key
+ # does that.
+ my $public_key = $user ? $user->public_key : '';
+
+ # Check if the new bugmail prefix should be added to the subject.
+ my $add_new
+ = ( $email->header('X-Bugzilla-Type') eq 'new'
+ && $user
+ && $user->settings->{'bugmail_new_prefix'}->{'value'} eq 'on') ? 1 : 0;
+
+ if ($make_secure == SECURE_NONE) {
+
+ # Filter the bug_links in HTML email in case the bugs the links
+ # point are "secured" bugs and the user may not be able to see
+ # the summaries.
+ _filter_bug_links($email);
}
+ else {
+ _make_secure($email, $public_key, $is_bugmail && $make_secure == SECURE_ALL,
+ $add_new);
+ }
+ }
}
# Custom hook for bugzilla.mozilla.org (see bug 752400)
sub bugmail_referenced_bugs {
- my ($self, $args) = @_;
- # Sanitise subjects of referenced bugs.
- my $referenced_bugs = $args->{'referenced_bugs'};
- # No need to sanitise subjects if the entire email will be secured.
- return if _should_secure_bug($args->{'updated_bug'});
- # Replace the subject if required
- foreach my $ref (@$referenced_bugs) {
- if (grep($_->secure_mail, @{ $ref->{'bug'}->groups_in })) {
- $ref->{'short_desc'} = "(Secure bug)";
- }
+ my ($self, $args) = @_;
+
+ # Sanitise subjects of referenced bugs.
+ my $referenced_bugs = $args->{'referenced_bugs'};
+
+ # No need to sanitise subjects if the entire email will be secured.
+ return if _should_secure_bug($args->{'updated_bug'});
+
+ # Replace the subject if required
+ foreach my $ref (@$referenced_bugs) {
+ if (grep($_->secure_mail, @{$ref->{'bug'}->groups_in})) {
+ $ref->{'short_desc'} = "(Secure bug)";
}
+ }
}
sub _should_secure_bug {
- my ($bug) = @_;
- # If there's a problem with the bug, err on the side of caution and mark it
- # as secure.
- return
- !$bug
- || $bug->{'error'}
- || grep($_->secure_mail, @{ $bug->groups_in });
+ my ($bug) = @_;
+
+ # If there's a problem with the bug, err on the side of caution and mark it
+ # as secure.
+ return !$bug || $bug->{'error'} || grep($_->secure_mail, @{$bug->groups_in});
}
sub _should_secure_whine {
- my ($email) = @_;
- my $should_secure = 0;
- $email->walk_parts(sub {
- my $part = shift;
- my $content_type = $part->content_type;
- return if !$content_type || $content_type !~ /^text\/plain/;
- my $body = $part->body;
- my @bugids = $body =~ /Bug (\d+):/g;
- foreach my $id (@bugids) {
- $id = trim($id);
- next if !$id;
- my $bug = new Bugzilla::Bug($id);
- if ($bug && _should_secure_bug($bug)) {
- $should_secure = 1;
- last;
- }
- }
- });
- return $should_secure ? 1 : 0;
+ my ($email) = @_;
+ my $should_secure = 0;
+ $email->walk_parts(sub {
+ my $part = shift;
+ my $content_type = $part->content_type;
+ return if !$content_type || $content_type !~ /^text\/plain/;
+ my $body = $part->body;
+ my @bugids = $body =~ /Bug (\d+):/g;
+ foreach my $id (@bugids) {
+ $id = trim($id);
+ next if !$id;
+ my $bug = new Bugzilla::Bug($id);
+ if ($bug && _should_secure_bug($bug)) {
+ $should_secure = 1;
+ last;
+ }
+ }
+ });
+ return $should_secure ? 1 : 0;
}
sub _make_secure {
- my ($email, $key, $sanitise_subject, $add_new) = @_;
-
- # Add header showing this email has been secured
- $email->header_set('X-Bugzilla-Secure-Email', 'Yes');
-
- my $subject = $email->header('Subject');
- my ($bug_id) = $subject =~ /\[\D+(\d+)\]/;
-
- my $key_type = 0;
- if ($key && $key =~ /PUBLIC KEY/) {
- $key_type = 'PGP';
+ my ($email, $key, $sanitise_subject, $add_new) = @_;
+
+ # Add header showing this email has been secured
+ $email->header_set('X-Bugzilla-Secure-Email', 'Yes');
+
+ my $subject = $email->header('Subject');
+ my ($bug_id) = $subject =~ /\[\D+(\d+)\]/;
+
+ my $key_type = 0;
+ if ($key && $key =~ /PUBLIC KEY/) {
+ $key_type = 'PGP';
+ }
+ elsif ($key && $key =~ /BEGIN CERTIFICATE/) {
+ $key_type = 'S/MIME';
+ }
+
+ if ($key_type eq 'PGP') {
+ ##################
+ # PGP Encryption #
+ ##################
+
+ my $pubring = new Crypt::OpenPGP::KeyRing(Data => $key);
+ my $pgp = new Crypt::OpenPGP(PubRing => $pubring);
+
+ if (scalar $email->parts > 1) {
+ my $old_boundary = $email->{ct}{attributes}{boundary};
+ my $to_encrypt = "Content-Type: " . $email->content_type . "\n\n";
+
+ # We need to do some fix up of each part for proper encoding and then
+ # stringify all parts for encrypting. We have to retain the old
+ # boundaries as well so that the email client can reconstruct the
+ # original message properly.
+ $email->walk_parts(\&_fix_encoding);
+
+ $email->walk_parts(sub {
+ my ($part) = @_;
+ if ($sanitise_subject) {
+ _insert_subject($part, $subject);
+ }
+ return if $part->parts > 1; # Top-level
+ $to_encrypt .= "--$old_boundary\n" . $part->as_string . "\n";
+ });
+ $to_encrypt .= "--$old_boundary--";
+
+ # Now create the new properly formatted PGP parts containing the
+ # encrypted original message
+ my @new_parts = (
+ Email::MIME->create(
+ attributes =>
+ {content_type => 'application/pgp-encrypted', encoding => '7bit',},
+ body => "Version: 1\n",
+ ),
+ Email::MIME->create(
+ attributes => {
+ content_type => 'application/octet-stream',
+ filename => 'encrypted.asc',
+ disposition => 'inline',
+ encoding => '7bit',
+ },
+ body => _pgp_encrypt($pgp, $to_encrypt)
+ ),
+ );
+ $email->parts_set(\@new_parts);
+ my $new_boundary = $email->{ct}{attributes}{boundary};
+
+ # Redo the old content type header with the new boundaries
+ # and other information needed for PGP
+ $email->header_set("Content-Type",
+ "multipart/encrypted; "
+ . "protocol=\"application/pgp-encrypted\"; "
+ . "boundary=\"$new_boundary\"");
}
- elsif ($key && $key =~ /BEGIN CERTIFICATE/) {
- $key_type = 'S/MIME';
+ else {
+ _fix_encoding($email);
+ if ($sanitise_subject) {
+ _insert_subject($email, $subject);
+ }
+ $email->body_set(_pgp_encrypt($pgp, $email->body));
}
+ }
- if ($key_type eq 'PGP') {
- ##################
- # PGP Encryption #
- ##################
-
- my $pubring = new Crypt::OpenPGP::KeyRing(Data => $key);
- my $pgp = new Crypt::OpenPGP(PubRing => $pubring);
-
- if (scalar $email->parts > 1) {
- my $old_boundary = $email->{ct}{attributes}{boundary};
- my $to_encrypt = "Content-Type: " . $email->content_type . "\n\n";
-
- # We need to do some fix up of each part for proper encoding and then
- # stringify all parts for encrypting. We have to retain the old
- # boundaries as well so that the email client can reconstruct the
- # original message properly.
- $email->walk_parts(\&_fix_encoding);
-
- $email->walk_parts(sub {
- my ($part) = @_;
- if ($sanitise_subject) {
- _insert_subject($part, $subject);
- }
- return if $part->parts > 1; # Top-level
- $to_encrypt .= "--$old_boundary\n" . $part->as_string . "\n";
- });
- $to_encrypt .= "--$old_boundary--";
-
- # Now create the new properly formatted PGP parts containing the
- # encrypted original message
- my @new_parts = (
- Email::MIME->create(
- attributes => {
- content_type => 'application/pgp-encrypted',
- encoding => '7bit',
- },
- body => "Version: 1\n",
- ),
- Email::MIME->create(
- attributes => {
- content_type => 'application/octet-stream',
- filename => 'encrypted.asc',
- disposition => 'inline',
- encoding => '7bit',
- },
- body => _pgp_encrypt($pgp, $to_encrypt)
- ),
- );
- $email->parts_set(\@new_parts);
- my $new_boundary = $email->{ct}{attributes}{boundary};
- # Redo the old content type header with the new boundaries
- # and other information needed for PGP
- $email->header_set("Content-Type",
- "multipart/encrypted; " .
- "protocol=\"application/pgp-encrypted\"; " .
- "boundary=\"$new_boundary\"");
- }
- else {
- _fix_encoding($email);
- if ($sanitise_subject) {
- _insert_subject($email, $subject);
- }
- $email->body_set(_pgp_encrypt($pgp, $email->body));
- }
+ elsif ($key_type eq 'S/MIME') {
+ #####################
+ # S/MIME Encryption #
+ #####################
+
+ $email->walk_parts(\&_fix_encoding);
+
+ if ($sanitise_subject) {
+ $email->walk_parts(sub { _insert_subject($_[0], $subject) });
}
- elsif ($key_type eq 'S/MIME') {
- #####################
- # S/MIME Encryption #
- #####################
+ my $smime = Crypt::SMIME->new();
+ my $encrypted;
- $email->walk_parts(\&_fix_encoding);
+ eval {
+ $smime->setPublicKey([$key]);
+ $encrypted = $smime->encrypt($email->as_string());
+ };
- if ($sanitise_subject) {
- $email->walk_parts(sub { _insert_subject($_[0], $subject) });
- }
+ if (!$@) {
- my $smime = Crypt::SMIME->new();
- my $encrypted;
-
- eval {
- $smime->setPublicKey([$key]);
- $encrypted = $smime->encrypt($email->as_string());
- };
-
- if (!$@) {
- # We can't replace the Email::MIME object, so we have to swap
- # out its component parts.
- my $enc_obj = new Email::MIME($encrypted);
- $email->header_obj_set($enc_obj->header_obj());
- $email->parts_set([]);
- $email->body_set($enc_obj->body());
- $email->content_type_set('application/pkcs7-mime');
- $email->charset_set('UTF-8') if Bugzilla->params->{'utf8'};
- }
- else {
- $email->body_set('Error during Encryption: ' . $@);
- }
+ # We can't replace the Email::MIME object, so we have to swap
+ # out its component parts.
+ my $enc_obj = new Email::MIME($encrypted);
+ $email->header_obj_set($enc_obj->header_obj());
+ $email->parts_set([]);
+ $email->body_set($enc_obj->body());
+ $email->content_type_set('application/pkcs7-mime');
+ $email->charset_set('UTF-8') if Bugzilla->params->{'utf8'};
}
else {
- # No encryption key provided; send a generic, safe email.
- my $template = Bugzilla->template;
- my $message;
- my $vars = {
- 'urlbase' => correct_urlbase(),
- 'bug_id' => $bug_id,
- 'maintainer' => Bugzilla->params->{'maintainer'}
- };
-
- $template->process('account/email/encryption-required.txt.tmpl',
- $vars, \$message)
- || ThrowTemplateError($template->error());
-
- $email->parts_set([]);
- $email->content_type_set('text/plain');
- $email->body_set($message);
+ $email->body_set('Error during Encryption: ' . $@);
}
+ }
+ else {
+ # No encryption key provided; send a generic, safe email.
+ my $template = Bugzilla->template;
+ my $message;
+ my $vars = {
+ 'urlbase' => correct_urlbase(),
+ 'bug_id' => $bug_id,
+ 'maintainer' => Bugzilla->params->{'maintainer'}
+ };
- if ($sanitise_subject) {
- # This is designed to still work if the admin changes the word
- # 'bug' to something else. However, it could break if they change
- # the format of the subject line in another way.
- my $new = $add_new ? ' New:' : '';
- my $product = $email->header('X-Bugzilla-Product');
- my $component = $email->header('X-Bugzilla-Component');
- # Note: the $bug_id is required within the parentheses in order to keep
- # gmail's threading algorithm happy.
- $subject =~ s/($bug_id\])\s+(.*)$/$1$new (Secure bug $bug_id in $product :: $component)/;
- $email->header_set('Subject', $subject);
- }
+ $template->process('account/email/encryption-required.txt.tmpl',
+ $vars, \$message)
+ || ThrowTemplateError($template->error());
+
+ $email->parts_set([]);
+ $email->content_type_set('text/plain');
+ $email->body_set($message);
+ }
+
+ if ($sanitise_subject) {
+
+ # This is designed to still work if the admin changes the word
+ # 'bug' to something else. However, it could break if they change
+ # the format of the subject line in another way.
+ my $new = $add_new ? ' New:' : '';
+ my $product = $email->header('X-Bugzilla-Product');
+ my $component = $email->header('X-Bugzilla-Component');
+
+ # Note: the $bug_id is required within the parentheses in order to keep
+ # gmail's threading algorithm happy.
+ $subject
+ =~ s/($bug_id\])\s+(.*)$/$1$new (Secure bug $bug_id in $product :: $component)/;
+ $email->header_set('Subject', $subject);
+ }
}
sub _pgp_encrypt {
- my ($pgp, $text) = @_;
- # "@" matches every key in the public key ring, which is fine,
- # because there's only one key in our keyring.
- #
- # We use the CAST5 cipher because the Rijndael (AES) module doesn't
- # like us for some reason I don't have time to debug fully.
- # ("key must be an untainted string scalar")
- my $encrypted = $pgp->encrypt(Data => $text,
- Recipients => "@",
- Cipher => 'CAST5',
- Armour => 1);
- if (!defined $encrypted) {
- return 'Error during Encryption: ' . $pgp->errstr;
- }
- return $encrypted;
+ my ($pgp, $text) = @_;
+
+ # "@" matches every key in the public key ring, which is fine,
+ # because there's only one key in our keyring.
+ #
+ # We use the CAST5 cipher because the Rijndael (AES) module doesn't
+ # like us for some reason I don't have time to debug fully.
+ # ("key must be an untainted string scalar")
+ my $encrypted = $pgp->encrypt(
+ Data => $text,
+ Recipients => "@",
+ Cipher => 'CAST5',
+ Armour => 1
+ );
+ if (!defined $encrypted) {
+ return 'Error during Encryption: ' . $pgp->errstr;
+ }
+ return $encrypted;
}
# Insert the subject into the part's body, as the subject of the message will
@@ -583,77 +597,75 @@ sub _pgp_encrypt {
# XXX this incorrectly assumes all parts of the message are the body
# we should only alter parts who's parent is multipart/alternative
sub _insert_subject {
- my ($part, $subject) = @_;
- my $content_type = $part->content_type or return;
- if ($content_type =~ /^text\/plain/) {
- if (!is_7bit_clean($subject)) {
- $part->encoding_set('quoted-printable');
- }
- $part->body_str_set("Subject: $subject\015\012\015\012" . $part->body_str);
- }
- elsif ($content_type =~ /^text\/html/) {
- my $tree = HTML::Tree->new->parse_content($part->body_str);
- my $body = $tree->look_down(qw(_tag body));
- $body->unshift_content(['div', "Subject: $subject"], ['br']);
- _set_body_from_tree($part, $tree);
+ my ($part, $subject) = @_;
+ my $content_type = $part->content_type or return;
+ if ($content_type =~ /^text\/plain/) {
+ if (!is_7bit_clean($subject)) {
+ $part->encoding_set('quoted-printable');
}
+ $part->body_str_set("Subject: $subject\015\012\015\012" . $part->body_str);
+ }
+ elsif ($content_type =~ /^text\/html/) {
+ my $tree = HTML::Tree->new->parse_content($part->body_str);
+ my $body = $tree->look_down(qw(_tag body));
+ $body->unshift_content(['div', "Subject: $subject"], ['br']);
+ _set_body_from_tree($part, $tree);
+ }
}
sub _fix_encoding {
- my $part = shift;
-
- # don't touch the top-level part of multi-part mail
- return if $part->parts > 1;
-
- # nothing to do if the part already has a charset
- my $ct = parse_content_type($part->content_type);
- my $charset = $ct->{attributes}{charset}
- ? $ct->{attributes}{charset}
- : '';
- return unless !$charset || $charset eq 'us-ascii';
-
- if (Bugzilla->params->{utf8}) {
- $part->charset_set('UTF-8');
- my $raw = $part->body_raw;
- if (utf8::is_utf8($raw)) {
- utf8::encode($raw);
- $part->body_set($raw);
- }
+ my $part = shift;
+
+ # don't touch the top-level part of multi-part mail
+ return if $part->parts > 1;
+
+ # nothing to do if the part already has a charset
+ my $ct = parse_content_type($part->content_type);
+ my $charset = $ct->{attributes}{charset} ? $ct->{attributes}{charset} : '';
+ return unless !$charset || $charset eq 'us-ascii';
+
+ if (Bugzilla->params->{utf8}) {
+ $part->charset_set('UTF-8');
+ my $raw = $part->body_raw;
+ if (utf8::is_utf8($raw)) {
+ utf8::encode($raw);
+ $part->body_set($raw);
}
- $part->encoding_set('quoted-printable');
+ }
+ $part->encoding_set('quoted-printable');
}
sub _filter_bug_links {
- my ($email) = @_;
- $email->walk_parts(sub {
- my $part = shift;
- _fix_encoding($part);
- my $content_type = $part->content_type;
- return if !$content_type || $content_type !~ /text\/html/;
- my $tree = HTML::Tree->new->parse_content($part->body_str);
- my @links = $tree->look_down( _tag => q{a}, class => qr/bz_bug_link/ );
- my $updated = 0;
- foreach my $link (@links) {
- my $href = $link->attr('href');
- my ($bug_id) = $href =~ /\Qshow_bug.cgi?id=\E(\d+)/;
- my $bug = new Bugzilla::Bug($bug_id);
- if ($bug && _should_secure_bug($bug)) {
- $link->attr('title', '(secure bug)');
- $link->attr('class', 'bz_bug_link');
- $updated = 1;
- }
- }
- if ($updated) {
- _set_body_from_tree($part, $tree);
- }
- });
+ my ($email) = @_;
+ $email->walk_parts(sub {
+ my $part = shift;
+ _fix_encoding($part);
+ my $content_type = $part->content_type;
+ return if !$content_type || $content_type !~ /text\/html/;
+ my $tree = HTML::Tree->new->parse_content($part->body_str);
+ my @links = $tree->look_down(_tag => q{a}, class => qr/bz_bug_link/);
+ my $updated = 0;
+ foreach my $link (@links) {
+ my $href = $link->attr('href');
+ my ($bug_id) = $href =~ /\Qshow_bug.cgi?id=\E(\d+)/;
+ my $bug = new Bugzilla::Bug($bug_id);
+ if ($bug && _should_secure_bug($bug)) {
+ $link->attr('title', '(secure bug)');
+ $link->attr('class', 'bz_bug_link');
+ $updated = 1;
+ }
+ }
+ if ($updated) {
+ _set_body_from_tree($part, $tree);
+ }
+ });
}
sub _set_body_from_tree {
- my ($part, $tree) = @_;
- $part->body_set($tree->as_HTML);
- $part->charset_set('UTF-8') if Bugzilla->params->{'utf8'};
- $part->encoding_set('quoted-printable');
+ my ($part, $tree) = @_;
+ $part->body_set($tree->as_HTML);
+ $part->charset_set('UTF-8') if Bugzilla->params->{'utf8'};
+ $part->encoding_set('quoted-printable');
}
__PACKAGE__->NAME;
diff --git a/extensions/SecureMail/disabled b/extensions/SecureMail/disabled
deleted file mode 100644
index e69de29bb..000000000
--- a/extensions/SecureMail/disabled
+++ /dev/null
diff --git a/extensions/TypeSniffer/Config.pm b/extensions/TypeSniffer/Config.pm
index 6ad03b362..72ae2bfce 100644
--- a/extensions/TypeSniffer/Config.pm
+++ b/extensions/TypeSniffer/Config.pm
@@ -25,16 +25,8 @@ use strict;
use constant NAME => 'TypeSniffer';
use constant REQUIRED_MODULES => [
- {
- package => 'File-MimeInfo',
- module => 'File::MimeInfo::Magic',
- version => '0'
- },
- {
- package => 'IO-stringy',
- module => 'IO::Scalar',
- version => '0'
- },
+ {package => 'File-MimeInfo', module => 'File::MimeInfo::Magic', version => '0'},
+ {package => 'IO-stringy', module => 'IO::Scalar', version => '0'},
];
-__PACKAGE__->NAME; \ No newline at end of file
+__PACKAGE__->NAME;
diff --git a/extensions/TypeSniffer/Extension.pm b/extensions/TypeSniffer/Extension.pm
index b788b5426..ed8eadca1 100644
--- a/extensions/TypeSniffer/Extension.pm
+++ b/extensions/TypeSniffer/Extension.pm
@@ -29,47 +29,48 @@ use IO::Scalar;
our $VERSION = '0.02';
################################################################################
# This extension uses magic to guess MIME types for data where the browser has
-# told us it's application/octet-stream (probably because there's no file
+# told us it's application/octet-stream (probably because there's no file
# extension, or it's a text type with a non-.txt file extension).
################################################################################
sub attachment_process_data {
- my ($self, $args) = @_;
- my $attributes = $args->{'attributes'};
- my $params = Bugzilla->input_params;
-
- # If we have autodetected application/octet-stream from the Content-Type
- # header, let's have a better go using a sniffer.
- if ($params->{'contenttypemethod'} eq 'autodetect' &&
- $attributes->{'mimetype'} eq 'application/octet-stream')
- {
- # data attribute can be either scalar data or filehandle
- # bugzilla.org/docs/3.6/en/html/api/Bugzilla/Attachment.html#create
- my $fh = $attributes->{'data'};
- if (!ref($fh)) {
- my $data = $attributes->{'data'};
- $fh = new IO::Scalar \$data;
- }
- else {
- # CGI.pm sends us an Fh that isn't actually an IO::Handle, but
- # has a method for getting an actual handle out of it.
- if (!$fh->isa('IO::Handle')) {
- $fh = $fh->handle;
- # ->handle returns an literal IO::Handle, even though the
- # underlying object is a file. So we rebless it to be a proper
- # IO::File object so that we can call ->seek on it and so on.
- # Just in case CGI.pm fixes this some day, we check ->isa first.
- if (!$fh->isa('IO::File')) {
- bless $fh, 'IO::File';
- }
- }
- }
-
- my $mimetype = mimetype($fh);
- $fh->seek(0, 0);
- if ($mimetype) {
- $attributes->{'mimetype'} = $mimetype;
+ my ($self, $args) = @_;
+ my $attributes = $args->{'attributes'};
+ my $params = Bugzilla->input_params;
+
+ # If we have autodetected application/octet-stream from the Content-Type
+ # header, let's have a better go using a sniffer.
+ if ( $params->{'contenttypemethod'} eq 'autodetect'
+ && $attributes->{'mimetype'} eq 'application/octet-stream')
+ {
+ # data attribute can be either scalar data or filehandle
+ # bugzilla.org/docs/3.6/en/html/api/Bugzilla/Attachment.html#create
+ my $fh = $attributes->{'data'};
+ if (!ref($fh)) {
+ my $data = $attributes->{'data'};
+ $fh = new IO::Scalar \$data;
+ }
+ else {
+ # CGI.pm sends us an Fh that isn't actually an IO::Handle, but
+ # has a method for getting an actual handle out of it.
+ if (!$fh->isa('IO::Handle')) {
+ $fh = $fh->handle;
+
+ # ->handle returns an literal IO::Handle, even though the
+ # underlying object is a file. So we rebless it to be a proper
+ # IO::File object so that we can call ->seek on it and so on.
+ # Just in case CGI.pm fixes this some day, we check ->isa first.
+ if (!$fh->isa('IO::File')) {
+ bless $fh, 'IO::File';
}
+ }
+ }
+
+ my $mimetype = mimetype($fh);
+ $fh->seek(0, 0);
+ if ($mimetype) {
+ $attributes->{'mimetype'} = $mimetype;
}
+ }
}
__PACKAGE__->NAME;
diff --git a/extensions/Voting/Config.pm b/extensions/Voting/Config.pm
index 97c44933e..d72caa3cc 100644
--- a/extensions/Voting/Config.pm
+++ b/extensions/Voting/Config.pm
@@ -13,10 +13,8 @@ use warnings;
use constant NAME => 'Voting';
-use constant REQUIRED_MODULES => [
-];
+use constant REQUIRED_MODULES => [];
-use constant OPTIONAL_MODULES => [
-];
+use constant OPTIONAL_MODULES => [];
__PACKAGE__->NAME;
diff --git a/extensions/Voting/Extension.pm b/extensions/Voting/Extension.pm
index b125933ce..a46162ed2 100644
--- a/extensions/Voting/Extension.pm
+++ b/extensions/Voting/Extension.pm
@@ -25,82 +25,84 @@ use Bugzilla::Token;
use List::Util qw(min sum);
-use constant VERSION => BUGZILLA_VERSION;
+use constant VERSION => BUGZILLA_VERSION;
use constant DEFAULT_VOTES_PER_BUG => 1;
+
# These came from Bugzilla itself, so they maintain the old numbers
# they had before.
use constant CMT_POPULAR_VOTES => 3;
-use constant REL_VOTER => 4;
+use constant REL_VOTER => 4;
################
# Installation #
################
BEGIN {
- *Bugzilla::Bug::votes = \&votes;
+ *Bugzilla::Bug::votes = \&votes;
}
sub votes {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- return $self->{votes} if exists $self->{votes};
+ return $self->{votes} if exists $self->{votes};
- $self->{votes} = $dbh->selectrow_array('SELECT votes FROM bugs WHERE bug_id = ?',
- undef, $self->id);
- return $self->{votes};
+ $self->{votes}
+ = $dbh->selectrow_array('SELECT votes FROM bugs WHERE bug_id = ?',
+ undef, $self->id);
+ return $self->{votes};
}
sub db_schema_abstract_schema {
- my ($self, $args) = @_;
- $args->{'schema'}->{'votes'} = {
- FIELDS => [
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- vote_count => {TYPE => 'INT2', NOTNULL => 1},
- ],
- INDEXES => [
- votes_who_idx => ['who'],
- votes_bug_id_idx => ['bug_id'],
- ],
- };
+ my ($self, $args) = @_;
+ $args->{'schema'}->{'votes'} = {
+ FIELDS => [
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ vote_count => {TYPE => 'INT2', NOTNULL => 1},
+ ],
+ INDEXES => [votes_who_idx => ['who'], votes_bug_id_idx => ['bug_id'],],
+ };
}
sub install_update_db {
- my $dbh = Bugzilla->dbh;
- # Note that before Bugzilla 4.0, voting was a built-in part of Bugzilla,
- # so updates to the columns for old versions of Bugzilla happen in
- # Bugzilla::Install::DB, and can't safely be moved to this extension.
-
- my $field = new Bugzilla::Field({ name => 'votes' });
- if (!$field) {
- Bugzilla::Field->create(
- { name => 'votes', description => 'Votes', buglist => 1 });
- }
-
- $dbh->bz_add_column('products', 'votesperuser',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
- $dbh->bz_add_column('products', 'maxvotesperbug',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => DEFAULT_VOTES_PER_BUG});
- $dbh->bz_add_column('products', 'votestoconfirm',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
-
- $dbh->bz_add_column('bugs', 'votes',
- {TYPE => 'INT3', NOTNULL => 1, DEFAULT => 0});
- $dbh->bz_add_index('bugs', 'bugs_votes_idx', ['votes']);
-
- # maxvotesperbug used to default to 10,000, which isn't very sensible.
- my $per_bug = $dbh->bz_column_info('products', 'maxvotesperbug');
- if ($per_bug->{DEFAULT} != DEFAULT_VOTES_PER_BUG) {
- $dbh->bz_alter_column('products', 'maxvotesperbug',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => DEFAULT_VOTES_PER_BUG});
- }
+ my $dbh = Bugzilla->dbh;
+
+ # Note that before Bugzilla 4.0, voting was a built-in part of Bugzilla,
+ # so updates to the columns for old versions of Bugzilla happen in
+ # Bugzilla::Install::DB, and can't safely be moved to this extension.
+
+ my $field = new Bugzilla::Field({name => 'votes'});
+ if (!$field) {
+ Bugzilla::Field->create(
+ {name => 'votes', description => 'Votes', buglist => 1});
+ }
+
+ $dbh->bz_add_column('products', 'votesperuser',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_add_column('products', 'maxvotesperbug',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => DEFAULT_VOTES_PER_BUG});
+ $dbh->bz_add_column('products', 'votestoconfirm',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+
+ $dbh->bz_add_column('bugs', 'votes',
+ {TYPE => 'INT3', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_add_index('bugs', 'bugs_votes_idx', ['votes']);
+
+ # maxvotesperbug used to default to 10,000, which isn't very sensible.
+ my $per_bug = $dbh->bz_column_info('products', 'maxvotesperbug');
+ if ($per_bug->{DEFAULT} != DEFAULT_VOTES_PER_BUG) {
+ $dbh->bz_alter_column('products', 'maxvotesperbug',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => DEFAULT_VOTES_PER_BUG});
+ }
}
###########
@@ -108,93 +110,98 @@ sub install_update_db {
###########
sub object_columns {
- my ($self, $args) = @_;
- my ($class, $columns) = @$args{qw(class columns)};
- if ($class->isa('Bugzilla::Bug')) {
- push(@$columns, 'votes');
- }
- elsif ($class->isa('Bugzilla::Product')) {
- push(@$columns, qw(votesperuser maxvotesperbug votestoconfirm));
- }
+ my ($self, $args) = @_;
+ my ($class, $columns) = @$args{qw(class columns)};
+ if ($class->isa('Bugzilla::Bug')) {
+ push(@$columns, 'votes');
+ }
+ elsif ($class->isa('Bugzilla::Product')) {
+ push(@$columns, qw(votesperuser maxvotesperbug votestoconfirm));
+ }
}
sub bug_fields {
- my ($self, $args) = @_;
- my $fields = $args->{fields};
- push(@$fields, 'votes');
+ my ($self, $args) = @_;
+ my $fields = $args->{fields};
+ push(@$fields, 'votes');
}
sub object_update_columns {
- my ($self, $args) = @_;
- my ($object, $columns) = @$args{qw(object columns)};
- if ($object->isa('Bugzilla::Product')) {
- push(@$columns, qw(votesperuser maxvotesperbug votestoconfirm));
- }
+ my ($self, $args) = @_;
+ my ($object, $columns) = @$args{qw(object columns)};
+ if ($object->isa('Bugzilla::Product')) {
+ push(@$columns, qw(votesperuser maxvotesperbug votestoconfirm));
+ }
}
sub object_validators {
- my ($self, $args) = @_;
- my ($class, $validators) = @$args{qw(class validators)};
- if ($class->isa('Bugzilla::Product')) {
- $validators->{'votesperuser'} = \&_check_votesperuser;
- $validators->{'maxvotesperbug'} = \&_check_maxvotesperbug;
- $validators->{'votestoconfirm'} = \&_check_votestoconfirm;
- }
+ my ($self, $args) = @_;
+ my ($class, $validators) = @$args{qw(class validators)};
+ if ($class->isa('Bugzilla::Product')) {
+ $validators->{'votesperuser'} = \&_check_votesperuser;
+ $validators->{'maxvotesperbug'} = \&_check_maxvotesperbug;
+ $validators->{'votestoconfirm'} = \&_check_votestoconfirm;
+ }
}
sub object_before_create {
- my ($self, $args) = @_;
- my ($class, $params) = @$args{qw(class params)};
- if ($class->isa('Bugzilla::Bug')) {
- # Don't ever allow people to directly specify "votes" into the bugs
- # table.
- delete $params->{votes};
- }
- elsif ($class->isa('Bugzilla::Product')) {
- my $input = Bugzilla->input_params;
- $params->{votesperuser} = $input->{'votesperuser'};
- $params->{maxvotesperbug} = $input->{'maxvotesperbug'};
- $params->{votestoconfirm} = $input->{'votestoconfirm'};
- }
+ my ($self, $args) = @_;
+ my ($class, $params) = @$args{qw(class params)};
+ if ($class->isa('Bugzilla::Bug')) {
+
+ # Don't ever allow people to directly specify "votes" into the bugs
+ # table.
+ delete $params->{votes};
+ }
+ elsif ($class->isa('Bugzilla::Product')) {
+ my $input = Bugzilla->input_params;
+ $params->{votesperuser} = $input->{'votesperuser'};
+ $params->{maxvotesperbug} = $input->{'maxvotesperbug'};
+ $params->{votestoconfirm} = $input->{'votestoconfirm'};
+ }
}
sub object_end_of_set_all {
- my ($self, $args) = @_;
- my ($object) = $args->{object};
- if ($object->isa('Bugzilla::Product')) {
- my $input = Bugzilla->input_params;
- $object->set('votesperuser', $input->{'votesperuser'});
- $object->set('maxvotesperbug', $input->{'maxvotesperbug'});
- $object->set('votestoconfirm', $input->{'votestoconfirm'});
- }
+ my ($self, $args) = @_;
+ my ($object) = $args->{object};
+ if ($object->isa('Bugzilla::Product')) {
+ my $input = Bugzilla->input_params;
+ $object->set('votesperuser', $input->{'votesperuser'});
+ $object->set('maxvotesperbug', $input->{'maxvotesperbug'});
+ $object->set('votestoconfirm', $input->{'votestoconfirm'});
+ }
}
sub object_end_of_update {
- my ($self, $args) = @_;
- my ($object, $changes) = @$args{qw(object changes)};
- if ( $object->isa('Bugzilla::Product')
- and ($changes->{maxvotesperbug} or $changes->{votesperuser}
- or $changes->{votestoconfirm}) )
- {
- _modify_bug_votes($object, $changes);
- }
+ my ($self, $args) = @_;
+ my ($object, $changes) = @$args{qw(object changes)};
+ if (
+ $object->isa('Bugzilla::Product')
+ and ($changes->{maxvotesperbug}
+ or $changes->{votesperuser}
+ or $changes->{votestoconfirm})
+ )
+ {
+ _modify_bug_votes($object, $changes);
+ }
}
sub bug_end_of_update {
- my ($self, $args) = @_;
- my ($bug, $changes) = @$args{qw(bug changes)};
-
- if ($changes->{'product'}) {
- my @msgs;
- # If some votes have been removed, RemoveVotes() returns
- # a list of messages to send to voters.
- @msgs = _remove_votes($bug->id, 0, 'votes_bug_moved');
- _confirm_if_vote_confirmed($bug);
-
- foreach my $msg (@msgs) {
- MessageToMTA($msg);
- }
+ my ($self, $args) = @_;
+ my ($bug, $changes) = @$args{qw(bug changes)};
+
+ if ($changes->{'product'}) {
+ my @msgs;
+
+ # If some votes have been removed, RemoveVotes() returns
+ # a list of messages to send to voters.
+ @msgs = _remove_votes($bug->id, 0, 'votes_bug_moved');
+ _confirm_if_vote_confirmed($bug);
+
+ foreach my $msg (@msgs) {
+ MessageToMTA($msg);
}
+ }
}
#############
@@ -202,27 +209,28 @@ sub bug_end_of_update {
#############
sub template_before_create {
- my ($self, $args) = @_;
- my $config = $args->{config};
- my $constants = $config->{CONSTANTS};
- $constants->{REL_VOTER} = REL_VOTER;
- $constants->{CMT_POPULAR_VOTES} = CMT_POPULAR_VOTES;
- $constants->{DEFAULT_VOTES_PER_BUG} = DEFAULT_VOTES_PER_BUG;
+ my ($self, $args) = @_;
+ my $config = $args->{config};
+ my $constants = $config->{CONSTANTS};
+ $constants->{REL_VOTER} = REL_VOTER;
+ $constants->{CMT_POPULAR_VOTES} = CMT_POPULAR_VOTES;
+ $constants->{DEFAULT_VOTES_PER_BUG} = DEFAULT_VOTES_PER_BUG;
}
sub template_before_process {
- my ($self, $args) = @_;
- my ($vars, $file) = @$args{qw(vars file)};
- if ($file eq 'admin/users/confirm-delete.html.tmpl') {
- my $who = $vars->{otheruser};
- my $votes = Bugzilla->dbh->selectrow_array(
- 'SELECT COUNT(*) FROM votes WHERE who = ?', undef, $who->id);
- if ($votes) {
- $vars->{other_safe} = 1;
- $vars->{votes} = $votes;
- }
- }
+ my ($self, $args) = @_;
+ my ($vars, $file) = @$args{qw(vars file)};
+ if ($file eq 'admin/users/confirm-delete.html.tmpl') {
+ my $who = $vars->{otheruser};
+ my $votes
+ = Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM votes WHERE who = ?',
+ undef, $who->id);
+ if ($votes) {
+ $vars->{other_safe} = 1;
+ $vars->{votes} = $votes;
+ }
+ }
}
###########
@@ -230,19 +238,19 @@ sub template_before_process {
###########
sub bugmail_recipients {
- my ($self, $args) = @_;
- my ($bug, $recipients) = @$args{qw(bug recipients)};
- my $dbh = Bugzilla->dbh;
+ my ($self, $args) = @_;
+ my ($bug, $recipients) = @$args{qw(bug recipients)};
+ my $dbh = Bugzilla->dbh;
- my $voters = $dbh->selectcol_arrayref(
- "SELECT who FROM votes WHERE bug_id = ?", undef, $bug->id);
- $recipients->{$_}->{+REL_VOTER} = 1 foreach (@$voters);
+ my $voters = $dbh->selectcol_arrayref("SELECT who FROM votes WHERE bug_id = ?",
+ undef, $bug->id);
+ $recipients->{$_}->{+REL_VOTER} = 1 foreach (@$voters);
}
sub bugmail_relationships {
- my ($self, $args) = @_;
- my $relationships = $args->{relationships};
- $relationships->{+REL_VOTER} = 'Voter';
+ my ($self, $args) = @_;
+ my $relationships = $args->{relationships};
+ $relationships->{+REL_VOTER} = 'Voter';
}
###############
@@ -250,59 +258,59 @@ sub bugmail_relationships {
###############
sub sanitycheck_check {
- my ($self, $args) = @_;
- my $status = $args->{status};
-
- # Vote Cache
- $status->('voting_count_start');
- my $dbh = Bugzilla->dbh;
- my %cached_counts = @{ $dbh->selectcol_arrayref(
- 'SELECT bug_id, votes FROM bugs', {Columns=>[1,2]}) };
-
- my %real_counts = @{ $dbh->selectcol_arrayref(
- 'SELECT bug_id, SUM(vote_count) FROM votes '
- . $dbh->sql_group_by('bug_id'), {Columns=>[1,2]}) };
-
- my $needs_rebuild;
- foreach my $id (keys %cached_counts) {
- my $cached_count = $cached_counts{$id};
- my $real_count = $real_counts{$id} || 0;
- if ($cached_count < 0) {
- $status->('voting_count_alert', { id => $id }, 'alert');
- }
- elsif ($cached_count != $real_count) {
- $status->('voting_cache_alert', { id => $id }, 'alert');
- $needs_rebuild = 1;
- }
- }
-
- $status->('voting_cache_rebuild_fix') if $needs_rebuild;
+ my ($self, $args) = @_;
+ my $status = $args->{status};
+
+ # Vote Cache
+ $status->('voting_count_start');
+ my $dbh = Bugzilla->dbh;
+ my %cached_counts = @{$dbh->selectcol_arrayref('SELECT bug_id, votes FROM bugs',
+ {Columns => [1, 2]})};
+
+ my %real_counts = @{
+ $dbh->selectcol_arrayref(
+ 'SELECT bug_id, SUM(vote_count) FROM votes ' . $dbh->sql_group_by('bug_id'),
+ {Columns => [1, 2]})
+ };
+
+ my $needs_rebuild;
+ foreach my $id (keys %cached_counts) {
+ my $cached_count = $cached_counts{$id};
+ my $real_count = $real_counts{$id} || 0;
+ if ($cached_count < 0) {
+ $status->('voting_count_alert', {id => $id}, 'alert');
+ }
+ elsif ($cached_count != $real_count) {
+ $status->('voting_cache_alert', {id => $id}, 'alert');
+ $needs_rebuild = 1;
+ }
+ }
+
+ $status->('voting_cache_rebuild_fix') if $needs_rebuild;
}
sub sanitycheck_repair {
- my ($self, $args) = @_;
- my $status = $args->{status};
- my $input = Bugzilla->input_params;
- my $dbh = Bugzilla->dbh;
-
- return if !$input->{rebuild_vote_cache};
-
- $status->('voting_cache_rebuild_start');
- $dbh->bz_start_transaction();
- $dbh->do('UPDATE bugs SET votes = 0');
-
- my $sth = $dbh->prepare(
- 'SELECT bug_id, SUM(vote_count) FROM votes '
- . $dbh->sql_group_by('bug_id'));
- $sth->execute();
-
- my $sth_update = $dbh->prepare(
- 'UPDATE bugs SET votes = ? WHERE bug_id = ?');
- while (my ($id, $count) = $sth->fetchrow_array) {
- $sth_update->execute($count, $id);
- }
- $dbh->bz_commit_transaction();
- $status->('voting_cache_rebuild_end');
+ my ($self, $args) = @_;
+ my $status = $args->{status};
+ my $input = Bugzilla->input_params;
+ my $dbh = Bugzilla->dbh;
+
+ return if !$input->{rebuild_vote_cache};
+
+ $status->('voting_cache_rebuild_start');
+ $dbh->bz_start_transaction();
+ $dbh->do('UPDATE bugs SET votes = 0');
+
+ my $sth = $dbh->prepare(
+ 'SELECT bug_id, SUM(vote_count) FROM votes ' . $dbh->sql_group_by('bug_id'));
+ $sth->execute();
+
+ my $sth_update = $dbh->prepare('UPDATE bugs SET votes = ? WHERE bug_id = ?');
+ while (my ($id, $count) = $sth->fetchrow_array) {
+ $sth_update->execute($count, $id);
+ }
+ $dbh->bz_commit_transaction();
+ $status->('voting_cache_rebuild_end');
}
@@ -311,35 +319,36 @@ sub sanitycheck_repair {
##############
sub _check_votesperuser {
- return _check_votes(0, @_);
+ return _check_votes(0, @_);
}
sub _check_maxvotesperbug {
- return _check_votes(DEFAULT_VOTES_PER_BUG, @_);
+ return _check_votes(DEFAULT_VOTES_PER_BUG, @_);
}
sub _check_votestoconfirm {
- return _check_votes(0, @_);
+ return _check_votes(0, @_);
}
# This subroutine is only used internally by other _check_votes_* validators.
sub _check_votes {
- my ($default, $invocant, $votes, $field) = @_;
-
- detaint_natural($votes) if defined $votes;
- # On product creation, if the number of votes is not a valid integer,
- # we silently fall back to the given default value.
- # If the product already exists and the change is illegal, we complain.
- if (!defined $votes) {
- if (ref $invocant) {
- ThrowUserError('voting_product_illegal_votes',
- { field => $field, votes => $_[2] });
- }
- else {
- $votes = $default;
- }
+ my ($default, $invocant, $votes, $field) = @_;
+
+ detaint_natural($votes) if defined $votes;
+
+ # On product creation, if the number of votes is not a valid integer,
+ # we silently fall back to the given default value.
+ # If the product already exists and the change is illegal, we complain.
+ if (!defined $votes) {
+ if (ref $invocant) {
+ ThrowUserError('voting_product_illegal_votes',
+ {field => $field, votes => $_[2]});
+ }
+ else {
+ $votes = $default;
}
- return $votes;
+ }
+ return $votes;
}
#########
@@ -347,290 +356,321 @@ sub _check_votes {
#########
sub page_before_template {
- my ($self, $args) = @_;
- my $page = $args->{page_id};
- my $vars = $args->{vars};
-
- if ($page =~ m{^voting/bug\.}) {
- _page_bug($vars);
- }
- elsif ($page =~ m{^voting/user\.}) {
- _page_user($vars);
- }
+ my ($self, $args) = @_;
+ my $page = $args->{page_id};
+ my $vars = $args->{vars};
+
+ if ($page =~ m{^voting/bug\.}) {
+ _page_bug($vars);
+ }
+ elsif ($page =~ m{^voting/user\.}) {
+ _page_user($vars);
+ }
}
sub _page_bug {
- my ($vars) = @_;
- my $dbh = Bugzilla->dbh;
- my $input = Bugzilla->input_params;
+ my ($vars) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $input = Bugzilla->input_params;
- my $bug_id = $input->{bug_id};
- my $bug = Bugzilla::Bug->check($bug_id);
+ my $bug_id = $input->{bug_id};
+ my $bug = Bugzilla::Bug->check($bug_id);
- $vars->{'bug'} = $bug;
- $vars->{'users'} =
- $dbh->selectall_arrayref('SELECT profiles.login_name,
+ $vars->{'bug'} = $bug;
+ $vars->{'users'} = $dbh->selectall_arrayref(
+ 'SELECT profiles.login_name,
profiles.userid AS id,
votes.vote_count
FROM votes
INNER JOIN profiles
ON profiles.userid = votes.who
- WHERE votes.bug_id = ?',
- {Slice=>{}}, $bug->id);
+ WHERE votes.bug_id = ?', {Slice => {}}, $bug->id
+ );
}
sub _page_user {
- my ($vars) = @_;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
- my $input = Bugzilla->input_params;
+ my ($vars) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ my $input = Bugzilla->input_params;
- my $action = $input->{action};
- if ($action and $action eq 'vote') {
- _update_votes($vars);
- }
+ my $action = $input->{action};
+ if ($action and $action eq 'vote') {
+ _update_votes($vars);
+ }
- # If a bug_id is given, and we're editing, we'll add it to the votes list.
-
- my $bug_id = $input->{bug_id};
- my $bug = Bugzilla::Bug->check({ id => $bug_id, cache => 1 }) if $bug_id;
- my $who_id = $input->{user_id} || $user->id;
+ # If a bug_id is given, and we're editing, we'll add it to the votes list.
- # Logged-out users must specify a user_id.
- Bugzilla->login(LOGIN_REQUIRED) if !$who_id;
+ my $bug_id = $input->{bug_id};
+ my $bug = Bugzilla::Bug->check({id => $bug_id, cache => 1}) if $bug_id;
+ my $who_id = $input->{user_id} || $user->id;
- my $who = Bugzilla::User->check({ id => $who_id });
+ # Logged-out users must specify a user_id.
+ Bugzilla->login(LOGIN_REQUIRED) if !$who_id;
- my $canedit = $user->id == $who->id;
+ my $who = Bugzilla::User->check({id => $who_id});
- $dbh->bz_start_transaction();
+ my $canedit = $user->id == $who->id;
- if ($canedit && $bug) {
- # Make sure there is an entry for this bug
- # in the vote table, just so that things display right.
- my $has_votes = $dbh->selectrow_array('SELECT vote_count FROM votes
- WHERE bug_id = ? AND who = ?',
- undef, ($bug->id, $who->id));
- if (!$has_votes) {
- $dbh->do('INSERT INTO votes (who, bug_id, vote_count)
- VALUES (?, ?, 0)', undef, ($who->id, $bug->id));
- }
+ $dbh->bz_start_transaction();
+
+ if ($canedit && $bug) {
+
+ # Make sure there is an entry for this bug
+ # in the vote table, just so that things display right.
+ my $has_votes = $dbh->selectrow_array(
+ 'SELECT vote_count FROM votes
+ WHERE bug_id = ? AND who = ?', undef,
+ ($bug->id, $who->id)
+ );
+ if (!$has_votes) {
+ $dbh->do(
+ 'INSERT INTO votes (who, bug_id, vote_count)
+ VALUES (?, ?, 0)', undef, ($who->id, $bug->id)
+ );
}
+ }
- my (@products, @all_bug_ids);
- # Read the votes data for this user for each product.
- foreach my $product (@{ $user->get_selectable_products }) {
- next unless ($product->{votesperuser} > 0);
+ my (@products, @all_bug_ids);
- my $vote_list =
- $dbh->selectall_arrayref('SELECT votes.bug_id, votes.vote_count
+ # Read the votes data for this user for each product.
+ foreach my $product (@{$user->get_selectable_products}) {
+ next unless ($product->{votesperuser} > 0);
+
+ my $vote_list = $dbh->selectall_arrayref(
+ 'SELECT votes.bug_id, votes.vote_count
FROM votes
INNER JOIN bugs
ON votes.bug_id = bugs.bug_id
WHERE votes.who = ?
- AND bugs.product_id = ?',
- undef, ($who->id, $product->id));
-
- my %votes = map { $_->[0] => $_->[1] } @$vote_list;
- my @bug_ids = sort keys %votes;
- # Exclude bugs that the user can no longer see.
- @bug_ids = @{ $user->visible_bugs(\@bug_ids) };
- next unless scalar @bug_ids;
-
- push(@all_bug_ids, @bug_ids);
- my @bugs = @{ Bugzilla::Bug->new_from_list(\@bug_ids) };
- $_->{count} = $votes{$_->id} foreach @bugs;
- # We include votes from bugs that the user can no longer see.
- my $total = sum(values %votes) || 0;
-
- my $onevoteonly = 0;
- $onevoteonly = 1 if (min($product->{votesperuser},
- $product->{maxvotesperbug}) == 1);
-
- push(@products, { name => $product->name,
- bugs => \@bugs,
- bug_ids => \@bug_ids,
- onevoteonly => $onevoteonly,
- total => $total,
- maxvotes => $product->{votesperuser},
- maxperbug => $product->{maxvotesperbug} });
- }
-
- if ($canedit && $bug) {
- $dbh->do('DELETE FROM votes WHERE vote_count = 0 AND who = ?',
- undef, $who->id);
- }
- $dbh->bz_commit_transaction();
-
- $vars->{'canedit'} = $canedit;
- $vars->{'voting_user'} = { "login" => $who->name };
- $vars->{'products'} = \@products;
- $vars->{'this_bug'} = $bug;
- $vars->{'all_bug_ids'} = \@all_bug_ids;
+ AND bugs.product_id = ?', undef,
+ ($who->id, $product->id)
+ );
+
+ my %votes = map { $_->[0] => $_->[1] } @$vote_list;
+ my @bug_ids = sort keys %votes;
+
+ # Exclude bugs that the user can no longer see.
+ @bug_ids = @{$user->visible_bugs(\@bug_ids)};
+ next unless scalar @bug_ids;
+
+ push(@all_bug_ids, @bug_ids);
+ my @bugs = @{Bugzilla::Bug->new_from_list(\@bug_ids)};
+ $_->{count} = $votes{$_->id} foreach @bugs;
+
+ # We include votes from bugs that the user can no longer see.
+ my $total = sum(values %votes) || 0;
+
+ my $onevoteonly = 0;
+ $onevoteonly = 1
+ if (min($product->{votesperuser}, $product->{maxvotesperbug}) == 1);
+
+ push(
+ @products,
+ {
+ name => $product->name,
+ bugs => \@bugs,
+ bug_ids => \@bug_ids,
+ onevoteonly => $onevoteonly,
+ total => $total,
+ maxvotes => $product->{votesperuser},
+ maxperbug => $product->{maxvotesperbug}
+ }
+ );
+ }
+
+ if ($canedit && $bug) {
+ $dbh->do('DELETE FROM votes WHERE vote_count = 0 AND who = ?', undef, $who->id);
+ }
+ $dbh->bz_commit_transaction();
+
+ $vars->{'canedit'} = $canedit;
+ $vars->{'voting_user'} = {"login" => $who->name};
+ $vars->{'products'} = \@products;
+ $vars->{'this_bug'} = $bug;
+ $vars->{'all_bug_ids'} = \@all_bug_ids;
}
sub _update_votes {
- my ($vars) = @_;
-
- ############################################################################
- # Begin Data/Security Validation
- ############################################################################
-
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
- my $template = Bugzilla->template;
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- my $input = Bugzilla->input_params;
-
- # Build a list of bug IDs for which votes have been submitted. Votes
- # are submitted in form fields in which the field names are the bug
- # IDs and the field values are the number of votes.
-
- my @buglist = grep {/^\d+$/} keys %$input;
- my (%bugs, %votes);
-
- # If no bugs are in the buglist, let's make sure the user gets notified
- # that their votes will get nuked if they continue.
- if (scalar(@buglist) == 0) {
- if (!defined $cgi->param('delete_all_votes')) {
- print $cgi->header();
- $template->process("voting/delete-all.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
- elsif ($cgi->param('delete_all_votes') == 0) {
- print $cgi->redirect("page.cgi?id=voting/user.html");
- exit;
+ my ($vars) = @_;
+
+ ############################################################################
+ # Begin Data/Security Validation
+ ############################################################################
+
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+ my $template = Bugzilla->template;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $input = Bugzilla->input_params;
+
+ # Build a list of bug IDs for which votes have been submitted. Votes
+ # are submitted in form fields in which the field names are the bug
+ # IDs and the field values are the number of votes.
+
+ my @buglist = grep {/^\d+$/} keys %$input;
+ my (%bugs, %votes);
+
+ # If no bugs are in the buglist, let's make sure the user gets notified
+ # that their votes will get nuked if they continue.
+ if (scalar(@buglist) == 0) {
+ if (!defined $cgi->param('delete_all_votes')) {
+ print $cgi->header();
+ $template->process("voting/delete-all.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+ elsif ($cgi->param('delete_all_votes') == 0) {
+ print $cgi->redirect("page.cgi?id=voting/user.html");
+ exit;
+ }
+ }
+ else {
+ $user->visible_bugs(\@buglist);
+ my $bugs_obj = Bugzilla::Bug->new_from_list(\@buglist);
+ $bugs{$_->id} = $_ foreach @$bugs_obj;
+ }
+
+ # Call check_is_visible() on each bug to make sure it is an existing bug
+ # that the user is authorized to access, and make sure the number of votes
+ # submitted is also an integer.
+ foreach my $id (@buglist) {
+ my $bug = $bugs{$id}
+ or ThrowUserError('bug_id_does_not_exist', {bug_id => $id});
+ $bug->check_is_visible;
+ $id = $bug->id;
+ $votes{$id} = $input->{$id};
+ detaint_natural($votes{$id}) || ThrowUserError("voting_must_be_nonnegative");
+ }
+
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['vote']);
+
+ ############################################################################
+ # End Data/Security Validation
+ ############################################################################
+ my $who = $user->id;
+
+ # If the user is voting for bugs, make sure they aren't overstuffing
+ # the ballot box.
+ if (scalar @buglist) {
+ my (%prodcount, %products);
+ foreach my $bug_id (keys %bugs) {
+ my $bug = $bugs{$bug_id};
+ my $prod = $bug->product;
+ $products{$prod} ||= $bug->product_obj;
+ $prodcount{$prod} ||= 0;
+ $prodcount{$prod} += $votes{$bug_id};
+
+ # Make sure we haven't broken the votes-per-bug limit
+ ($votes{$bug_id} <= $products{$prod}->{maxvotesperbug}) || ThrowUserError(
+ "voting_too_many_votes_for_bug",
+ {
+ max => $products{$prod}->{maxvotesperbug},
+ product => $prod,
+ votes => $votes{$bug_id}
}
- }
- else {
- $user->visible_bugs(\@buglist);
- my $bugs_obj = Bugzilla::Bug->new_from_list(\@buglist);
- $bugs{$_->id} = $_ foreach @$bugs_obj;
+ );
}
- # Call check_is_visible() on each bug to make sure it is an existing bug
- # that the user is authorized to access, and make sure the number of votes
- # submitted is also an integer.
- foreach my $id (@buglist) {
- my $bug = $bugs{$id}
- or ThrowUserError('bug_id_does_not_exist', { bug_id => $id });
- $bug->check_is_visible;
- $id = $bug->id;
- $votes{$id} = $input->{$id};
- detaint_natural($votes{$id})
- || ThrowUserError("voting_must_be_nonnegative");
- }
-
- my $token = $cgi->param('token');
- check_hash_token($token, ['vote']);
-
- ############################################################################
- # End Data/Security Validation
- ############################################################################
- my $who = $user->id;
-
- # If the user is voting for bugs, make sure they aren't overstuffing
- # the ballot box.
- if (scalar @buglist) {
- my (%prodcount, %products);
- foreach my $bug_id (keys %bugs) {
- my $bug = $bugs{$bug_id};
- my $prod = $bug->product;
- $products{$prod} ||= $bug->product_obj;
- $prodcount{$prod} ||= 0;
- $prodcount{$prod} += $votes{$bug_id};
-
- # Make sure we haven't broken the votes-per-bug limit
- ($votes{$bug_id} <= $products{$prod}->{maxvotesperbug})
- || ThrowUserError("voting_too_many_votes_for_bug",
- {max => $products{$prod}->{maxvotesperbug},
- product => $prod,
- votes => $votes{$bug_id}});
- }
-
- # Make sure we haven't broken the votes-per-product limit
- foreach my $prod (keys(%prodcount)) {
- ($prodcount{$prod} <= $products{$prod}->{votesperuser})
- || ThrowUserError("voting_too_many_votes_for_product",
- {max => $products{$prod}->{votesperuser},
- product => $prod,
- votes => $prodcount{$prod}});
+ # Make sure we haven't broken the votes-per-product limit
+ foreach my $prod (keys(%prodcount)) {
+ ($prodcount{$prod} <= $products{$prod}->{votesperuser}) || ThrowUserError(
+ "voting_too_many_votes_for_product",
+ {
+ max => $products{$prod}->{votesperuser},
+ product => $prod,
+ votes => $prodcount{$prod}
}
+ );
}
+ }
- # Update the user's votes in the database.
- $dbh->bz_start_transaction();
+ # Update the user's votes in the database.
+ $dbh->bz_start_transaction();
- my $old_list = $dbh->selectall_arrayref('SELECT bug_id, vote_count FROM votes
- WHERE who = ?', undef, $who);
+ my $old_list = $dbh->selectall_arrayref(
+ 'SELECT bug_id, vote_count FROM votes
+ WHERE who = ?', undef, $who
+ );
- my %old_votes = map { $_->[0] => $_->[1] } @$old_list;
+ my %old_votes = map { $_->[0] => $_->[1] } @$old_list;
- my $sth_insertVotes = $dbh->prepare('INSERT INTO votes (who, bug_id, vote_count)
- VALUES (?, ?, ?)');
- my $sth_updateVotes = $dbh->prepare('UPDATE votes SET vote_count = ?
- WHERE bug_id = ? AND who = ?');
+ my $sth_insertVotes = $dbh->prepare(
+ 'INSERT INTO votes (who, bug_id, vote_count)
+ VALUES (?, ?, ?)'
+ );
+ my $sth_updateVotes = $dbh->prepare(
+ 'UPDATE votes SET vote_count = ?
+ WHERE bug_id = ? AND who = ?'
+ );
- my %affected = map { $_ => 1 } (@buglist, keys %old_votes);
- my @deleted_votes;
-
- foreach my $id (keys %affected) {
- if (!$votes{$id}) {
- push(@deleted_votes, $id);
- next;
- }
- if ($votes{$id} == ($old_votes{$id} || 0)) {
- delete $affected{$id};
- next;
- }
- # We use 'defined' in case 0 was accidentally stored in the DB.
- if (defined $old_votes{$id}) {
- $sth_updateVotes->execute($votes{$id}, $id, $who);
- }
- else {
- $sth_insertVotes->execute($who, $id, $votes{$id});
- }
- }
+ my %affected = map { $_ => 1 } (@buglist, keys %old_votes);
+ my @deleted_votes;
- if (@deleted_votes) {
- $dbh->do('DELETE FROM votes WHERE who = ? AND ' .
- $dbh->sql_in('bug_id', \@deleted_votes), undef, $who);
+ foreach my $id (keys %affected) {
+ if (!$votes{$id}) {
+ push(@deleted_votes, $id);
+ next;
}
-
- # Update the cached values in the bugs table
- my @updated_bugs = ();
-
- my $sth_getVotes = $dbh->prepare("SELECT SUM(vote_count) FROM votes
- WHERE bug_id = ?");
-
- $sth_updateVotes = $dbh->prepare('UPDATE bugs SET votes = ? WHERE bug_id = ?');
-
- foreach my $id (keys %affected) {
- $sth_getVotes->execute($id);
- my $v = $sth_getVotes->fetchrow_array || 0;
- $sth_updateVotes->execute($v, $id);
- $bugs{$id}->{votes} = $v if $bugs{$id};
- my $confirmed = _confirm_if_vote_confirmed($bugs{$id} || $id);
- push (@updated_bugs, $id) if $confirmed;
+ if ($votes{$id} == ($old_votes{$id} || 0)) {
+ delete $affected{$id};
+ next;
}
- $dbh->bz_commit_transaction();
-
- print $cgi->header() if scalar @updated_bugs;
- $vars->{'type'} = "votes";
- $vars->{'title_tag'} = 'change_votes';
- foreach my $bug_id (@updated_bugs) {
- $vars->{'id'} = $bug_id;
- $vars->{'sent_bugmail'} =
- Bugzilla::BugMail::Send($bug_id, { 'changer' => $user });
-
- $template->process("bug/process/results.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- # Set header_done to 1 only after the first bug.
- $vars->{'header_done'} = 1;
+ # We use 'defined' in case 0 was accidentally stored in the DB.
+ if (defined $old_votes{$id}) {
+ $sth_updateVotes->execute($votes{$id}, $id, $who);
}
- $vars->{'message'} = 'votes_recorded';
+ else {
+ $sth_insertVotes->execute($who, $id, $votes{$id});
+ }
+ }
+
+ if (@deleted_votes) {
+ $dbh->do(
+ 'DELETE FROM votes WHERE who = ? AND '
+ . $dbh->sql_in('bug_id', \@deleted_votes),
+ undef, $who
+ );
+ }
+
+ # Update the cached values in the bugs table
+ my @updated_bugs = ();
+
+ my $sth_getVotes = $dbh->prepare(
+ "SELECT SUM(vote_count) FROM votes
+ WHERE bug_id = ?"
+ );
+
+ $sth_updateVotes = $dbh->prepare('UPDATE bugs SET votes = ? WHERE bug_id = ?');
+
+ foreach my $id (keys %affected) {
+ $sth_getVotes->execute($id);
+ my $v = $sth_getVotes->fetchrow_array || 0;
+ $sth_updateVotes->execute($v, $id);
+ $bugs{$id}->{votes} = $v if $bugs{$id};
+ my $confirmed = _confirm_if_vote_confirmed($bugs{$id} || $id);
+ push(@updated_bugs, $id) if $confirmed;
+ }
+
+ $dbh->bz_commit_transaction();
+
+ print $cgi->header() if scalar @updated_bugs;
+ $vars->{'type'} = "votes";
+ $vars->{'title_tag'} = 'change_votes';
+ foreach my $bug_id (@updated_bugs) {
+ $vars->{'id'} = $bug_id;
+ $vars->{'sent_bugmail'}
+ = Bugzilla::BugMail::Send($bug_id, {'changer' => $user});
+
+ $template->process("bug/process/results.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+
+ # Set header_done to 1 only after the first bug.
+ $vars->{'header_done'} = 1;
+ }
+ $vars->{'message'} = 'votes_recorded';
}
######################
@@ -638,233 +678,245 @@ sub _update_votes {
######################
sub _modify_bug_votes {
- my ($product, $changes) = @_;
- my $dbh = Bugzilla->dbh;
- my @msgs;
+ my ($product, $changes) = @_;
+ my $dbh = Bugzilla->dbh;
+ my @msgs;
- # 1. too many votes for a single user on a single bug.
- my @toomanyvotes_list;
- if ($product->{maxvotesperbug} < $product->{votesperuser}) {
- my $votes = $dbh->selectall_arrayref(
- 'SELECT votes.who, votes.bug_id
+ # 1. too many votes for a single user on a single bug.
+ my @toomanyvotes_list;
+ if ($product->{maxvotesperbug} < $product->{votesperuser}) {
+ my $votes = $dbh->selectall_arrayref(
+ 'SELECT votes.who, votes.bug_id
FROM votes
INNER JOIN bugs ON bugs.bug_id = votes.bug_id
WHERE bugs.product_id = ?
- AND votes.vote_count > ?',
- undef, ($product->id, $product->{maxvotesperbug}));
-
- foreach my $vote (@$votes) {
- my ($who, $id) = (@$vote);
- # If some votes are removed, _remove_votes() returns a list
- # of messages to send to voters.
- push(@msgs, _remove_votes($id, $who, 'votes_too_many_per_bug'));
- my $name = Bugzilla::User->new($who)->login;
-
- push(@toomanyvotes_list, {id => $id, name => $name});
- }
+ AND votes.vote_count > ?', undef,
+ ($product->id, $product->{maxvotesperbug})
+ );
+
+ foreach my $vote (@$votes) {
+ my ($who, $id) = (@$vote);
+
+ # If some votes are removed, _remove_votes() returns a list
+ # of messages to send to voters.
+ push(@msgs, _remove_votes($id, $who, 'votes_too_many_per_bug'));
+ my $name = Bugzilla::User->new($who)->login;
+
+ push(@toomanyvotes_list, {id => $id, name => $name});
}
+ }
- $changes->{'_too_many_votes'} = \@toomanyvotes_list;
+ $changes->{'_too_many_votes'} = \@toomanyvotes_list;
- # 2. too many total votes for a single user.
- # This part doesn't work in the general case because _remove_votes
- # doesn't enforce votesperuser (except per-bug when it's less
- # than maxvotesperbug). See _remove_votes().
+ # 2. too many total votes for a single user.
+ # This part doesn't work in the general case because _remove_votes
+ # doesn't enforce votesperuser (except per-bug when it's less
+ # than maxvotesperbug). See _remove_votes().
- my $votes = $dbh->selectall_arrayref(
- 'SELECT votes.who, votes.vote_count
+ my $votes = $dbh->selectall_arrayref(
+ 'SELECT votes.who, votes.vote_count
FROM votes
INNER JOIN bugs ON bugs.bug_id = votes.bug_id
- WHERE bugs.product_id = ?',
- undef, $product->id);
+ WHERE bugs.product_id = ?', undef, $product->id
+ );
- my %counts;
- foreach my $vote (@$votes) {
- my ($who, $count) = @$vote;
- if (!defined $counts{$who}) {
- $counts{$who} = $count;
- } else {
- $counts{$who} += $count;
- }
+ my %counts;
+ foreach my $vote (@$votes) {
+ my ($who, $count) = @$vote;
+ if (!defined $counts{$who}) {
+ $counts{$who} = $count;
+ }
+ else {
+ $counts{$who} += $count;
}
+ }
- my @toomanytotalvotes_list;
- foreach my $who (keys(%counts)) {
- if ($counts{$who} > $product->{votesperuser}) {
- my $bug_ids = $dbh->selectcol_arrayref(
- 'SELECT votes.bug_id
+ my @toomanytotalvotes_list;
+ foreach my $who (keys(%counts)) {
+ if ($counts{$who} > $product->{votesperuser}) {
+ my $bug_ids = $dbh->selectcol_arrayref(
+ 'SELECT votes.bug_id
FROM votes
INNER JOIN bugs ON bugs.bug_id = votes.bug_id
WHERE bugs.product_id = ?
- AND votes.who = ?',
- undef, $product->id, $who);
-
- my $name = Bugzilla::User->new($who)->login;
- foreach my $bug_id (@$bug_ids) {
- # _remove_votes returns a list of messages to send
- # in case some voters had too many votes.
- push(@msgs, _remove_votes($bug_id, $who,
- 'votes_too_many_per_user'));
-
- push(@toomanytotalvotes_list, {id => $bug_id, name => $name});
- }
- }
- }
-
- $changes->{'_too_many_total_votes'} = \@toomanytotalvotes_list;
-
- # 3. enough votes to confirm
- my $bug_list = $dbh->selectcol_arrayref(
- 'SELECT bug_id FROM bugs
- WHERE product_id = ? AND bug_status = ? AND votes >= ?',
- undef, ($product->id, 'UNCONFIRMED', $product->{votestoconfirm}));
-
- my @updated_bugs;
- foreach my $bug_id (@$bug_list) {
- my $confirmed = _confirm_if_vote_confirmed($bug_id);
- push (@updated_bugs, $bug_id) if $confirmed;
- }
- $changes->{'_confirmed_bugs'} = \@updated_bugs;
-
- # Now that changes are done, we can send emails to voters.
- foreach my $msg (@msgs) {
- MessageToMTA($msg);
- }
- # And send out emails about changed bugs
- foreach my $bug_id (@updated_bugs) {
- my $sent_bugmail = Bugzilla::BugMail::Send(
- $bug_id, { changer => Bugzilla->user });
- $changes->{'_confirmed_bugs_sent_bugmail'}->{$bug_id} = $sent_bugmail;
- }
+ AND votes.who = ?', undef, $product->id, $who
+ );
+
+ my $name = Bugzilla::User->new($who)->login;
+ foreach my $bug_id (@$bug_ids) {
+
+ # _remove_votes returns a list of messages to send
+ # in case some voters had too many votes.
+ push(@msgs, _remove_votes($bug_id, $who, 'votes_too_many_per_user'));
+
+ push(@toomanytotalvotes_list, {id => $bug_id, name => $name});
+ }
+ }
+ }
+
+ $changes->{'_too_many_total_votes'} = \@toomanytotalvotes_list;
+
+ # 3. enough votes to confirm
+ my $bug_list = $dbh->selectcol_arrayref(
+ 'SELECT bug_id FROM bugs
+ WHERE product_id = ? AND bug_status = ? AND votes >= ?', undef,
+ ($product->id, 'UNCONFIRMED', $product->{votestoconfirm})
+ );
+
+ my @updated_bugs;
+ foreach my $bug_id (@$bug_list) {
+ my $confirmed = _confirm_if_vote_confirmed($bug_id);
+ push(@updated_bugs, $bug_id) if $confirmed;
+ }
+ $changes->{'_confirmed_bugs'} = \@updated_bugs;
+
+ # Now that changes are done, we can send emails to voters.
+ foreach my $msg (@msgs) {
+ MessageToMTA($msg);
+ }
+
+ # And send out emails about changed bugs
+ foreach my $bug_id (@updated_bugs) {
+ my $sent_bugmail
+ = Bugzilla::BugMail::Send($bug_id, {changer => Bugzilla->user});
+ $changes->{'_confirmed_bugs_sent_bugmail'}->{$bug_id} = $sent_bugmail;
+ }
}
# If a bug is moved to a product which allows less votes per bug
# compared to the previous product, extra votes need to be removed.
sub _remove_votes {
- my ($id, $who, $reason) = (@_);
- my $dbh = Bugzilla->dbh;
-
- my $whopart = ($who) ? " AND votes.who = $who" : "";
-
- my $sth = $dbh->prepare("SELECT profiles.login_name, " .
- "profiles.userid, votes.vote_count, " .
- "products.votesperuser, products.maxvotesperbug " .
- "FROM profiles " .
- "LEFT JOIN votes ON profiles.userid = votes.who " .
- "LEFT JOIN bugs ON votes.bug_id = bugs.bug_id " .
- "LEFT JOIN products ON products.id = bugs.product_id " .
- "WHERE votes.bug_id = ? " . $whopart);
- $sth->execute($id);
- my @list;
- while (my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = $sth->fetchrow_array()) {
- push(@list, [$name, $userid, $oldvotes, $votesperuser, $maxvotesperbug]);
- }
-
- # @messages stores all emails which have to be sent, if any.
- # This array is passed to the caller which will send these emails itself.
- my @messages = ();
-
- if (scalar(@list)) {
- foreach my $ref (@list) {
- my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = (@$ref);
-
- $maxvotesperbug = min($votesperuser, $maxvotesperbug);
-
- # If this product allows voting and the user's votes are in
- # the acceptable range, then don't do anything.
- next if $votesperuser && $oldvotes <= $maxvotesperbug;
-
- # If the user has more votes on this bug than this product
- # allows, then reduce the number of votes so it fits
- my $newvotes = $maxvotesperbug;
-
- my $removedvotes = $oldvotes - $newvotes;
-
- if ($newvotes) {
- $dbh->do("UPDATE votes SET vote_count = ? " .
- "WHERE bug_id = ? AND who = ?",
- undef, ($newvotes, $id, $userid));
- } else {
- $dbh->do("DELETE FROM votes WHERE bug_id = ? AND who = ?",
- undef, ($id, $userid));
- }
-
- # Notice that we did not make sure that the user fit within the $votesperuser
- # range. This is considered to be an acceptable alternative to losing votes
- # during product moves. Then next time the user attempts to change their votes,
- # they will be forced to fit within the $votesperuser limit.
-
- # Now lets send the e-mail to alert the user to the fact that their votes have
- # been reduced or removed.
- my $vars = {
- 'to' => $name . Bugzilla->params->{'emailsuffix'},
- 'bugid' => $id,
- 'reason' => $reason,
-
- 'votesremoved' => $removedvotes,
- 'votesold' => $oldvotes,
- 'votesnew' => $newvotes,
- };
-
- my $voter = new Bugzilla::User($userid);
- my $template = Bugzilla->template_inner($voter->setting('lang'));
-
- my $msg;
- $template->process("voting/votes-removed.txt.tmpl", $vars, \$msg);
- push(@messages, $msg);
- }
-
- my $votes = $dbh->selectrow_array("SELECT SUM(vote_count) " .
- "FROM votes WHERE bug_id = ?",
- undef, $id) || 0;
- $dbh->do("UPDATE bugs SET votes = ? WHERE bug_id = ?",
- undef, ($votes, $id));
- }
- # Now return the array containing emails to be sent.
- return @messages;
+ my ($id, $who, $reason) = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ my $whopart = ($who) ? " AND votes.who = $who" : "";
+
+ my $sth
+ = $dbh->prepare("SELECT profiles.login_name, "
+ . "profiles.userid, votes.vote_count, "
+ . "products.votesperuser, products.maxvotesperbug "
+ . "FROM profiles "
+ . "LEFT JOIN votes ON profiles.userid = votes.who "
+ . "LEFT JOIN bugs ON votes.bug_id = bugs.bug_id "
+ . "LEFT JOIN products ON products.id = bugs.product_id "
+ . "WHERE votes.bug_id = ? "
+ . $whopart);
+ $sth->execute($id);
+ my @list;
+ while (my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug)
+ = $sth->fetchrow_array())
+ {
+ push(@list, [$name, $userid, $oldvotes, $votesperuser, $maxvotesperbug]);
+ }
+
+ # @messages stores all emails which have to be sent, if any.
+ # This array is passed to the caller which will send these emails itself.
+ my @messages = ();
+
+ if (scalar(@list)) {
+ foreach my $ref (@list) {
+ my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = (@$ref);
+
+ $maxvotesperbug = min($votesperuser, $maxvotesperbug);
+
+ # If this product allows voting and the user's votes are in
+ # the acceptable range, then don't do anything.
+ next if $votesperuser && $oldvotes <= $maxvotesperbug;
+
+ # If the user has more votes on this bug than this product
+ # allows, then reduce the number of votes so it fits
+ my $newvotes = $maxvotesperbug;
+
+ my $removedvotes = $oldvotes - $newvotes;
+
+ if ($newvotes) {
+ $dbh->do("UPDATE votes SET vote_count = ? " . "WHERE bug_id = ? AND who = ?",
+ undef, ($newvotes, $id, $userid));
+ }
+ else {
+ $dbh->do("DELETE FROM votes WHERE bug_id = ? AND who = ?",
+ undef, ($id, $userid));
+ }
+
+ # Notice that we did not make sure that the user fit within the $votesperuser
+ # range. This is considered to be an acceptable alternative to losing votes
+ # during product moves. Then next time the user attempts to change their votes,
+ # they will be forced to fit within the $votesperuser limit.
+
+ # Now lets send the e-mail to alert the user to the fact that their votes have
+ # been reduced or removed.
+ my $vars = {
+ 'to' => $name . Bugzilla->params->{'emailsuffix'},
+ 'bugid' => $id,
+ 'reason' => $reason,
+
+ 'votesremoved' => $removedvotes,
+ 'votesold' => $oldvotes,
+ 'votesnew' => $newvotes,
+ };
+
+ my $voter = new Bugzilla::User($userid);
+ my $template = Bugzilla->template_inner($voter->setting('lang'));
+
+ my $msg;
+ $template->process("voting/votes-removed.txt.tmpl", $vars, \$msg);
+ push(@messages, $msg);
+ }
+
+ my $votes
+ = $dbh->selectrow_array(
+ "SELECT SUM(vote_count) " . "FROM votes WHERE bug_id = ?",
+ undef, $id)
+ || 0;
+ $dbh->do("UPDATE bugs SET votes = ? WHERE bug_id = ?", undef, ($votes, $id));
+ }
+
+ # Now return the array containing emails to be sent.
+ return @messages;
}
# If a user votes for a bug, or the number of votes required to
# confirm a bug has been reduced, check if the bug is now confirmed.
sub _confirm_if_vote_confirmed {
- my $id = shift;
- my $bug = ref $id ? $id : new Bugzilla::Bug({ id => $id, cache => 1 });
-
- my $ret = 0;
- if (!$bug->everconfirmed
- and $bug->product_obj->{votestoconfirm}
- and $bug->votes >= $bug->product_obj->{votestoconfirm})
- {
- $bug->add_comment('', { type => CMT_POPULAR_VOTES });
-
- if ($bug->bug_status eq 'UNCONFIRMED') {
- # Get a valid open state.
- my $new_status;
- foreach my $state (@{$bug->status->can_change_to}) {
- if ($state->is_open && $state->name ne 'UNCONFIRMED') {
- $new_status = $state->name;
- last;
- }
- }
- ThrowCodeError('voting_no_open_bug_status') unless $new_status;
-
- # We cannot call $bug->set_bug_status() here, because a user without
- # canconfirm privs should still be able to confirm a bug by
- # popular vote. We already know the new status is valid, so it's safe.
- $bug->{bug_status} = $new_status;
- $bug->{everconfirmed} = 1;
- delete $bug->{'status'}; # Contains the status object.
- }
- else {
- # If the bug is in a closed state, only set everconfirmed to 1.
- # Do not call $bug->_set_everconfirmed(), for the same reason as above.
- $bug->{everconfirmed} = 1;
+ my $id = shift;
+ my $bug = ref $id ? $id : new Bugzilla::Bug({id => $id, cache => 1});
+
+ my $ret = 0;
+ if ( !$bug->everconfirmed
+ and $bug->product_obj->{votestoconfirm}
+ and $bug->votes >= $bug->product_obj->{votestoconfirm})
+ {
+ $bug->add_comment('', {type => CMT_POPULAR_VOTES});
+
+ if ($bug->bug_status eq 'UNCONFIRMED') {
+
+ # Get a valid open state.
+ my $new_status;
+ foreach my $state (@{$bug->status->can_change_to}) {
+ if ($state->is_open && $state->name ne 'UNCONFIRMED') {
+ $new_status = $state->name;
+ last;
}
- $bug->update();
+ }
+ ThrowCodeError('voting_no_open_bug_status') unless $new_status;
- $ret = 1;
+ # We cannot call $bug->set_bug_status() here, because a user without
+ # canconfirm privs should still be able to confirm a bug by
+ # popular vote. We already know the new status is valid, so it's safe.
+ $bug->{bug_status} = $new_status;
+ $bug->{everconfirmed} = 1;
+ delete $bug->{'status'}; # Contains the status object.
}
- return $ret;
+ else {
+ # If the bug is in a closed state, only set everconfirmed to 1.
+ # Do not call $bug->_set_everconfirmed(), for the same reason as above.
+ $bug->{everconfirmed} = 1;
+ }
+ $bug->update();
+
+ $ret = 1;
+ }
+ return $ret;
}
diff --git a/extensions/create.pl b/extensions/create.pl
index 7c8693e28..f2aa3f933 100755
--- a/extensions/create.pl
+++ b/extensions/create.pl
@@ -27,42 +27,41 @@ my $base_dir = bz_locations()->{'extensionsdir'};
my $name = $ARGV[0] or ThrowUserError('extension_create_no_name');
$name = ucfirst($name);
if ($name !~ /^[A-Z]/) {
- ThrowUserError('extension_first_letter_caps', { name => $name });
+ ThrowUserError('extension_first_letter_caps', {name => $name});
}
-my $extension_dir = "$base_dir/$name";
-mkpath($extension_dir)
+my $extension_dir = "$base_dir/$name";
+mkpath($extension_dir)
|| die "$extension_dir already exists or cannot be created.\n";
my $lcname = lc($name);
foreach my $path (qw(lib docs/en/rst web template/en/default/hook),
- "template/en/default/$lcname")
+ "template/en/default/$lcname")
{
- mkpath("$extension_dir/$path") || die "$extension_dir/$path: $!";
+ mkpath("$extension_dir/$path") || die "$extension_dir/$path: $!";
}
-my $template = Bugzilla->template;
-my $vars = { name => $name, path => $extension_dir };
+my $template = Bugzilla->template;
+my $vars = {name => $name, path => $extension_dir};
my %create_files = (
- 'config.pm.tmpl' => 'Config.pm',
- 'extension.pm.tmpl' => 'Extension.pm',
- 'util.pm.tmpl' => 'lib/Util.pm',
- 'web-readme.txt.tmpl' => 'web/README',
- 'hook-readme.txt.tmpl' => 'template/en/default/hook/README',
- 'name-readme.txt.tmpl' => "template/en/default/$lcname/README",
- 'index-admin.rst.tmpl' => "docs/en/rst/index-admin.rst",
- 'index-user.rst.tmpl' => "docs/en/rst/index-user.rst",
+ 'config.pm.tmpl' => 'Config.pm',
+ 'extension.pm.tmpl' => 'Extension.pm',
+ 'util.pm.tmpl' => 'lib/Util.pm',
+ 'web-readme.txt.tmpl' => 'web/README',
+ 'hook-readme.txt.tmpl' => 'template/en/default/hook/README',
+ 'name-readme.txt.tmpl' => "template/en/default/$lcname/README",
+ 'index-admin.rst.tmpl' => "docs/en/rst/index-admin.rst",
+ 'index-user.rst.tmpl' => "docs/en/rst/index-user.rst",
);
foreach my $template_file (keys %create_files) {
- my $target = $create_files{$template_file};
- my $output;
- $template->process("extensions/$template_file", $vars, \$output)
- or ThrowTemplateError($template->error());
- open(my $fh, '>', "$extension_dir/$target")
- or die "extension_dir/$target: $!";
- print $fh $output;
- close($fh);
+ my $target = $create_files{$template_file};
+ my $output;
+ $template->process("extensions/$template_file", $vars, \$output)
+ or ThrowTemplateError($template->error());
+ open(my $fh, '>', "$extension_dir/$target") or die "extension_dir/$target: $!";
+ print $fh $output;
+ close($fh);
}
say get_text('extension_created', $vars);
diff --git a/importxml.pl b/importxml.pl
index 12831e0b9..a65f2db92 100755
--- a/importxml.pl
+++ b/importxml.pl
@@ -38,17 +38,21 @@ use warnings;
#####################################################################
use File::Basename qw(dirname);
+
# MTAs may call this script from any directory, but it should always
# run from this one so that it can find its modules.
BEGIN {
- require File::Basename;
- my $dir = $0; $dir =~ /(.*)/; $dir = $1; # trick taint
- chdir(File::Basename::dirname($dir));
+ require File::Basename;
+ my $dir = $0;
+ $dir =~ /(.*)/;
+ $dir = $1; # trick taint
+ chdir(File::Basename::dirname($dir));
}
use lib qw(. lib);
-# Data dumber is used for debugging, I got tired of copying it back in
-# and then removing it.
+
+# Data dumber is used for debugging, I got tired of copying it back in
+# and then removing it.
#use Data::Dumper;
@@ -76,22 +80,22 @@ use Getopt::Long;
use Pod::Usage;
use XML::Twig;
-my $debug = 0;
-my $mail = '';
-my $attach_path = '';
-my $help = 0;
-my $bug_page = 'show_bug.cgi?id=';
-my $default_product_name = '';
+my $debug = 0;
+my $mail = '';
+my $attach_path = '';
+my $help = 0;
+my $bug_page = 'show_bug.cgi?id=';
+my $default_product_name = '';
my $default_component_name = '';
my $result = GetOptions(
- "verbose|debug+" => \$debug,
- "mail|sendmail!" => \$mail,
- "attach_path=s" => \$attach_path,
- "help|?" => \$help,
- "bug_page=s" => \$bug_page,
- "product=s" => \$default_product_name,
- "component=s" => \$default_component_name,
+ "verbose|debug+" => \$debug,
+ "mail|sendmail!" => \$mail,
+ "attach_path=s" => \$attach_path,
+ "help|?" => \$help,
+ "bug_page=s" => \$bug_page,
+ "product=s" => \$default_product_name,
+ "component=s" => \$default_component_name,
);
pod2usage(0) if $help;
@@ -104,8 +108,8 @@ our @logs;
our @attachments;
our $bugtotal;
my $xml;
-my $dbh = Bugzilla->dbh;
-my $params = Bugzilla->params;
+my $dbh = Bugzilla->dbh;
+my $params = Bugzilla->params;
my ($timestamp) = $dbh->selectrow_array("SELECT NOW()");
###############################################################################
@@ -113,166 +117,168 @@ my ($timestamp) = $dbh->selectrow_array("SELECT NOW()");
###############################################################################
sub MailMessage {
- return unless ($mail);
- my $subject = shift;
- my $message = shift;
- my @recipients = @_;
- my $from = $params->{"mailfrom"};
- $from =~ s/@/\@/g;
-
- foreach my $to (@recipients){
- my $header = "To: $to\n";
- $header .= "From: Bugzilla <$from>\n";
- $header .= "Subject: $subject\n\n";
- my $sendmessage = $header . $message . "\n";
- MessageToMTA($sendmessage);
- }
+ return unless ($mail);
+ my $subject = shift;
+ my $message = shift;
+ my @recipients = @_;
+ my $from = $params->{"mailfrom"};
+ $from =~ s/@/\@/g;
+
+ foreach my $to (@recipients) {
+ my $header = "To: $to\n";
+ $header .= "From: Bugzilla <$from>\n";
+ $header .= "Subject: $subject\n\n";
+ my $sendmessage = $header . $message . "\n";
+ MessageToMTA($sendmessage);
+ }
}
sub Debug {
- return unless ($debug);
- my ( $message, $level ) = (@_);
- print STDERR "OK: $message \n" if ( $level == OK_LEVEL );
- print STDERR "ERR: $message \n" if ( $level == ERR_LEVEL );
- print STDERR "$message\n"
- if ( ( $debug == $level ) && ( $level == DEBUG_LEVEL ) );
+ return unless ($debug);
+ my ($message, $level) = (@_);
+ print STDERR "OK: $message \n" if ($level == OK_LEVEL);
+ print STDERR "ERR: $message \n" if ($level == ERR_LEVEL);
+ print STDERR "$message\n" if (($debug == $level) && ($level == DEBUG_LEVEL));
}
sub Error {
- my ( $reason, $errtype, $exporter ) = @_;
- my $subject = "Bug import error: $reason";
- my $message = "Cannot import these bugs because $reason ";
- $message .= "\n\nPlease re-open the original bug.\n" if ($errtype);
- $message .= "For more info, contact " . $params->{"maintainer"} . ".\n";
- my @to = ( $params->{"maintainer"}, $exporter);
- Debug( $message, ERR_LEVEL );
- MailMessage( $subject, $message, @to );
- exit;
+ my ($reason, $errtype, $exporter) = @_;
+ my $subject = "Bug import error: $reason";
+ my $message = "Cannot import these bugs because $reason ";
+ $message .= "\n\nPlease re-open the original bug.\n" if ($errtype);
+ $message .= "For more info, contact " . $params->{"maintainer"} . ".\n";
+ my @to = ($params->{"maintainer"}, $exporter);
+ Debug($message, ERR_LEVEL);
+ MailMessage($subject, $message, @to);
+ exit;
}
# This subroutine handles flags for process_bug. It is generic in that
# it can handle both attachment flags and bug flags.
sub flag_handler {
- my (
- $name, $status, $setter_login,
- $requestee_login, $exporterid, $bugid,
- $componentid, $productid, $attachid
- )
- = @_;
-
- my $type = ($attachid) ? "attachment" : "bug";
- my $err = '';
- my $setter = new Bugzilla::User({ name => $setter_login });
- my $requestee;
- my $requestee_id;
-
- unless ($setter) {
- $err = "Invalid setter $setter_login on $type flag $name\n";
- $err .= " Dropping flag $name\n";
- return $err;
- }
- if ( !$setter->can_see_bug($bugid) ) {
- $err .= "Setter is not a member of bug group\n";
- $err .= " Dropping flag $name\n";
- return $err;
- }
- my $setter_id = $setter->id;
- if ( defined($requestee_login) ) {
- $requestee = new Bugzilla::User({ name => $requestee_login });
- if ( $requestee ) {
- if ( !$requestee->can_see_bug($bugid) ) {
- $err .= "Requestee is not a member of bug group\n";
- $err .= " Requesting from the wind\n";
- }
- else{
- $requestee_id = $requestee->id;
- }
- }
- else {
- $err = "Invalid requestee $requestee_login on $type flag $name\n";
- $err .= " Requesting from the wind.\n";
- }
-
- }
- my $flag_types;
-
- # If this is an attachment flag we need to do some dirty work to look
- # up the flagtype ID
- if ($attachid) {
- $flag_types = Bugzilla::FlagType::match(
- {
- 'target_type' => 'attachment',
- 'product_id' => $productid,
- 'component_id' => $componentid
- } );
+ my (
+ $name, $status, $setter_login,
+ $requestee_login, $exporterid, $bugid,
+ $componentid, $productid, $attachid
+ ) = @_;
+
+ my $type = ($attachid) ? "attachment" : "bug";
+ my $err = '';
+ my $setter = new Bugzilla::User({name => $setter_login});
+ my $requestee;
+ my $requestee_id;
+
+ unless ($setter) {
+ $err = "Invalid setter $setter_login on $type flag $name\n";
+ $err .= " Dropping flag $name\n";
+ return $err;
+ }
+ if (!$setter->can_see_bug($bugid)) {
+ $err .= "Setter is not a member of bug group\n";
+ $err .= " Dropping flag $name\n";
+ return $err;
+ }
+ my $setter_id = $setter->id;
+ if (defined($requestee_login)) {
+ $requestee = new Bugzilla::User({name => $requestee_login});
+ if ($requestee) {
+ if (!$requestee->can_see_bug($bugid)) {
+ $err .= "Requestee is not a member of bug group\n";
+ $err .= " Requesting from the wind\n";
+ }
+ else {
+ $requestee_id = $requestee->id;
+ }
}
else {
- my $bug = new Bugzilla::Bug($bugid);
- $flag_types = $bug->flag_types;
- }
- unless ($flag_types){
- $err = "No flag types defined for this bug\n";
- $err .= " Dropping flag $name\n";
- return $err;
+ $err = "Invalid requestee $requestee_login on $type flag $name\n";
+ $err .= " Requesting from the wind.\n";
}
- # We need to see if the imported flag is in the list of known flags
- # It is possible for two flags on the same bug have the same name
- # If this is the case, we will only match the first one.
- my $ftype;
- foreach my $f ( @{$flag_types} ) {
- if ( $f->name eq $name) {
- $ftype = $f;
- last;
- }
+ }
+ my $flag_types;
+
+ # If this is an attachment flag we need to do some dirty work to look
+ # up the flagtype ID
+ if ($attachid) {
+ $flag_types = Bugzilla::FlagType::match({
+ 'target_type' => 'attachment',
+ 'product_id' => $productid,
+ 'component_id' => $componentid
+ });
+ }
+ else {
+ my $bug = new Bugzilla::Bug($bugid);
+ $flag_types = $bug->flag_types;
+ }
+ unless ($flag_types) {
+ $err = "No flag types defined for this bug\n";
+ $err .= " Dropping flag $name\n";
+ return $err;
+ }
+
+ # We need to see if the imported flag is in the list of known flags
+ # It is possible for two flags on the same bug have the same name
+ # If this is the case, we will only match the first one.
+ my $ftype;
+ foreach my $f (@{$flag_types}) {
+ if ($f->name eq $name) {
+ $ftype = $f;
+ last;
}
+ }
- if ($ftype) { # We found the flag in the list
- my $grant_group = $ftype->grant_group;
- if (( $status eq '+' || $status eq '-' )
- && $grant_group && !$setter->in_group_id($grant_group->id)) {
- $err = "Setter $setter_login on $type flag $name ";
- $err .= "is not in the Grant Group\n";
- $err .= " Dropping flag $name\n";
- return $err;
- }
- my $request_group = $ftype->request_group;
- if ($request_group
- && $status eq '?' && !$setter->in_group_id($request_group->id)) {
- $err = "Setter $setter_login on $type flag $name ";
- $err .= "is not in the Request Group\n";
- $err .= " Dropping flag $name\n";
- return $err;
- }
+ if ($ftype) { # We found the flag in the list
+ my $grant_group = $ftype->grant_group;
+ if ( ($status eq '+' || $status eq '-')
+ && $grant_group
+ && !$setter->in_group_id($grant_group->id))
+ {
+ $err = "Setter $setter_login on $type flag $name ";
+ $err .= "is not in the Grant Group\n";
+ $err .= " Dropping flag $name\n";
+ return $err;
+ }
+ my $request_group = $ftype->request_group;
+ if ( $request_group
+ && $status eq '?'
+ && !$setter->in_group_id($request_group->id))
+ {
+ $err = "Setter $setter_login on $type flag $name ";
+ $err .= "is not in the Request Group\n";
+ $err .= " Dropping flag $name\n";
+ return $err;
+ }
- # Take the first flag_type that matches
- unless ($ftype->is_active) {
- $err = "Flag $name is not active in this database\n";
- $err .= " Dropping flag $name\n";
- return $err;
- }
+ # Take the first flag_type that matches
+ unless ($ftype->is_active) {
+ $err = "Flag $name is not active in this database\n";
+ $err .= " Dropping flag $name\n";
+ return $err;
+ }
- $dbh->do("INSERT INTO flags
+ $dbh->do(
+ "INSERT INTO flags
(type_id, status, bug_id, attach_id, creation_date,
setter_id, requestee_id)
VALUES (?, ?, ?, ?, ?, ?, ?)", undef,
- ($ftype->id, $status, $bugid, $attachid, $timestamp,
- $setter_id, $requestee_id));
- }
- else {
- $err = "Dropping unknown $type flag: $name\n";
- return $err;
- }
+ ($ftype->id, $status, $bugid, $attachid, $timestamp, $setter_id, $requestee_id)
+ );
+ }
+ else {
+ $err = "Dropping unknown $type flag: $name\n";
return $err;
+ }
+ return $err;
}
# Converts and returns the input data as an array.
sub _to_array {
- my $value = shift;
+ my $value = shift;
- $value = [$value] if !ref($value);
- return @$value;
+ $value = [$value] if !ref($value);
+ return @$value;
}
###############################################################################
@@ -290,27 +296,28 @@ sub _to_array {
# bugs are being moved from
#
sub init() {
- my ( $twig, $bugzilla ) = @_;
- my $root = $twig->root;
- my $maintainer = $root->{'att'}->{'maintainer'};
- my $exporter = $root->{'att'}->{'exporter'};
- my $urlbase = $root->{'att'}->{'urlbase'};
- my $xmlversion = $root->{'att'}->{'version'};
-
- if ($xmlversion ne BUGZILLA_VERSION) {
- my $log = "Possible version conflict!\n";
- $log .= " XML was exported from Bugzilla version $xmlversion\n";
- $log .= " But this installation uses ";
- $log .= BUGZILLA_VERSION . "\n";
- Debug($log, OK_LEVEL);
- push(@logs, $log);
- }
- Error( "no maintainer", "REOPEN", $exporter ) unless ($maintainer);
- Error( "no exporter", "REOPEN", $exporter ) unless ($exporter);
- Error( "invalid exporter: $exporter", "REOPEN", $exporter ) if ( !login_to_id($exporter) );
- Error( "no urlbase set", "REOPEN", $exporter ) unless ($urlbase);
+ my ($twig, $bugzilla) = @_;
+ my $root = $twig->root;
+ my $maintainer = $root->{'att'}->{'maintainer'};
+ my $exporter = $root->{'att'}->{'exporter'};
+ my $urlbase = $root->{'att'}->{'urlbase'};
+ my $xmlversion = $root->{'att'}->{'version'};
+
+ if ($xmlversion ne BUGZILLA_VERSION) {
+ my $log = "Possible version conflict!\n";
+ $log .= " XML was exported from Bugzilla version $xmlversion\n";
+ $log .= " But this installation uses ";
+ $log .= BUGZILLA_VERSION . "\n";
+ Debug($log, OK_LEVEL);
+ push(@logs, $log);
+ }
+ Error("no maintainer", "REOPEN", $exporter) unless ($maintainer);
+ Error("no exporter", "REOPEN", $exporter) unless ($exporter);
+ Error("invalid exporter: $exporter", "REOPEN", $exporter)
+ if (!login_to_id($exporter));
+ Error("no urlbase set", "REOPEN", $exporter) unless ($urlbase);
}
-
+
# Parse attachments.
#
@@ -327,69 +334,74 @@ sub init() {
# The submitter_id gets filled in with $exporterid.
sub process_attachment() {
- my ( $twig, $attach ) = @_;
- Debug( "Parsing attachments", DEBUG_LEVEL );
- my %attachment;
-
- $attachment{'date'} =
- format_time( $attach->field('date'), "%Y-%m-%d %R" ) || $timestamp;
- $attachment{'desc'} = $attach->field('desc');
- $attachment{'ctype'} = $attach->field('type') || "unknown/unknown";
- $attachment{'attachid'} = $attach->field('attachid');
- $attachment{'ispatch'} = $attach->{'att'}->{'ispatch'} || 0;
- $attachment{'isobsolete'} = $attach->{'att'}->{'isobsolete'} || 0;
- $attachment{'isprivate'} = $attach->{'att'}->{'isprivate'} || 0;
- $attachment{'filename'} = $attach->field('filename') || "file";
- $attachment{'attacher'} = $attach->field('attacher');
- # Attachment data is not exported in versions 2.20 and older.
- if (defined $attach->first_child('data') &&
- defined $attach->first_child('data')->{'att'}->{'encoding'}) {
- my $encoding = $attach->first_child('data')->{'att'}->{'encoding'};
- if ($encoding =~ /base64/) {
- # decode the base64
- my $data = $attach->field('data');
- my $output = decode_base64($data);
- $attachment{'data'} = $output;
- }
- elsif ($encoding =~ /filename/) {
- # read the attachment file
- Error("attach_path is required", undef) unless ($attach_path);
-
- my $filename = $attach->field('data');
- # Remove any leading path data from the filename
- $filename =~ s/(.*\/|.*\\)//gs;
-
- my $attach_filename = $attach_path . "/" . $filename;
- open(ATTACH_FH, "<", $attach_filename) or
- Error("cannot open $attach_filename", undef);
- $attachment{'data'} = do { local $/; <ATTACH_FH> };
- close ATTACH_FH;
- }
- }
- else {
- $attachment{'data'} = $attach->field('data');
+ my ($twig, $attach) = @_;
+ Debug("Parsing attachments", DEBUG_LEVEL);
+ my %attachment;
+
+ $attachment{'date'}
+ = format_time($attach->field('date'), "%Y-%m-%d %R") || $timestamp;
+ $attachment{'desc'} = $attach->field('desc');
+ $attachment{'ctype'} = $attach->field('type') || "unknown/unknown";
+ $attachment{'attachid'} = $attach->field('attachid');
+ $attachment{'ispatch'} = $attach->{'att'}->{'ispatch'} || 0;
+ $attachment{'isobsolete'} = $attach->{'att'}->{'isobsolete'} || 0;
+ $attachment{'isprivate'} = $attach->{'att'}->{'isprivate'} || 0;
+ $attachment{'filename'} = $attach->field('filename') || "file";
+ $attachment{'attacher'} = $attach->field('attacher');
+
+ # Attachment data is not exported in versions 2.20 and older.
+ if ( defined $attach->first_child('data')
+ && defined $attach->first_child('data')->{'att'}->{'encoding'})
+ {
+ my $encoding = $attach->first_child('data')->{'att'}->{'encoding'};
+ if ($encoding =~ /base64/) {
+
+ # decode the base64
+ my $data = $attach->field('data');
+ my $output = decode_base64($data);
+ $attachment{'data'} = $output;
}
+ elsif ($encoding =~ /filename/) {
- # attachment flags
- my @aflags;
- foreach my $aflag ( $attach->children('flag') ) {
- my %aflag;
- $aflag{'name'} = $aflag->{'att'}->{'name'};
- $aflag{'status'} = $aflag->{'att'}->{'status'};
- $aflag{'setter'} = $aflag->{'att'}->{'setter'};
- $aflag{'requestee'} = $aflag->{'att'}->{'requestee'};
- push @aflags, \%aflag;
- }
- $attachment{'flags'} = \@aflags if (@aflags);
+ # read the attachment file
+ Error("attach_path is required", undef) unless ($attach_path);
- # free up the memory for use by the rest of the script
- $attach->delete;
- if ($attachment{'attachid'}) {
- push @attachments, \%attachment;
- }
- else {
- push @attachments, "err";
+ my $filename = $attach->field('data');
+
+ # Remove any leading path data from the filename
+ $filename =~ s/(.*\/|.*\\)//gs;
+
+ my $attach_filename = $attach_path . "/" . $filename;
+ open(ATTACH_FH, "<", $attach_filename)
+ or Error("cannot open $attach_filename", undef);
+ $attachment{'data'} = do { local $/; <ATTACH_FH> };
+ close ATTACH_FH;
}
+ }
+ else {
+ $attachment{'data'} = $attach->field('data');
+ }
+
+ # attachment flags
+ my @aflags;
+ foreach my $aflag ($attach->children('flag')) {
+ my %aflag;
+ $aflag{'name'} = $aflag->{'att'}->{'name'};
+ $aflag{'status'} = $aflag->{'att'}->{'status'};
+ $aflag{'setter'} = $aflag->{'att'}->{'setter'};
+ $aflag{'requestee'} = $aflag->{'att'}->{'requestee'};
+ push @aflags, \%aflag;
+ }
+ $attachment{'flags'} = \@aflags if (@aflags);
+
+ # free up the memory for use by the rest of the script
+ $attach->delete;
+ if ($attachment{'attachid'}) {
+ push @attachments, \%attachment;
+ }
+ else {
+ push @attachments, "err";
+ }
}
# This subroutine will be called once for each <bug> in the xml file.
@@ -401,849 +413,869 @@ sub process_attachment() {
# purged from memory to free it up for later bugs.
sub process_bug {
- my ( $twig, $bug ) = @_;
- my $root = $twig->root;
- my $maintainer = $root->{'att'}->{'maintainer'};
- my $exporter_login = $root->{'att'}->{'exporter'};
- my $exporter = new Bugzilla::User({ name => $exporter_login });
- my $urlbase = $root->{'att'}->{'urlbase'};
- my $url = $urlbase . $bug_page;
- trick_taint($url);
-
- # We will store output information in this variable.
- my $log = "";
- if ( defined $bug->{'att'}->{'error'} ) {
- $log .= "\nError in bug " . $bug->field('bug_id') . "\@$urlbase: ";
- $log .= $bug->{'att'}->{'error'} . "\n";
- if ( $bug->{'att'}->{'error'} =~ /NotFound/ ) {
- $log .= "$exporter_login tried to move bug " . $bug->field('bug_id');
- $log .= " here, but $urlbase reports that this bug";
- $log .= " does not exist.\n";
- }
- elsif ( $bug->{'att'}->{'error'} =~ /NotPermitted/ ) {
- $log .= "$exporter_login tried to move bug " . $bug->field('bug_id');
- $log .= " here, but $urlbase reports that $exporter_login does ";
- $log .= " not have access to that bug.\n";
- }
- return;
+ my ($twig, $bug) = @_;
+ my $root = $twig->root;
+ my $maintainer = $root->{'att'}->{'maintainer'};
+ my $exporter_login = $root->{'att'}->{'exporter'};
+ my $exporter = new Bugzilla::User({name => $exporter_login});
+ my $urlbase = $root->{'att'}->{'urlbase'};
+ my $url = $urlbase . $bug_page;
+ trick_taint($url);
+
+ # We will store output information in this variable.
+ my $log = "";
+ if (defined $bug->{'att'}->{'error'}) {
+ $log .= "\nError in bug " . $bug->field('bug_id') . "\@$urlbase: ";
+ $log .= $bug->{'att'}->{'error'} . "\n";
+ if ($bug->{'att'}->{'error'} =~ /NotFound/) {
+ $log .= "$exporter_login tried to move bug " . $bug->field('bug_id');
+ $log .= " here, but $urlbase reports that this bug";
+ $log .= " does not exist.\n";
}
- $bugtotal++;
-
- # This list contains all other bug fields that we want to process.
- # If it is not in this list it will not be included.
- my %all_fields;
- foreach my $field (
- qw(long_desc attachment flag group), Bugzilla::Bug::fields() )
- {
- $all_fields{$field} = 1;
+ elsif ($bug->{'att'}->{'error'} =~ /NotPermitted/) {
+ $log .= "$exporter_login tried to move bug " . $bug->field('bug_id');
+ $log .= " here, but $urlbase reports that $exporter_login does ";
+ $log .= " not have access to that bug.\n";
}
-
- my %bug_fields;
- my $err = "";
-
- # Loop through all the xml tags inside a <bug> and compare them to the
- # lists of fields. If they match throw them into the hash. Otherwise
- # append it to the log, which will go into the comments when we are done.
- foreach my $bugchild ( $bug->children() ) {
- Debug( "Parsing field: " . $bugchild->name, DEBUG_LEVEL );
-
- # Skip the token if one is included. We don't want it included in
- # the comments, and it is not used by the importer.
- next if $bugchild->name eq 'token';
-
- if ( defined $all_fields{ $bugchild->name } ) {
- my @values = $bug->children_text($bugchild->name);
- if (scalar @values > 1) {
- $bug_fields{$bugchild->name} = \@values;
- }
- else {
- $bug_fields{$bugchild->name} = $values[0];
- }
- }
- else {
- $err .= "Unknown bug field \"" . $bugchild->name . "\"";
- $err .= " encountered while moving bug\n";
- $err .= " <" . $bugchild->name . ">";
- if ( $bugchild->children_count > 1 ) {
- $err .= "\n";
- foreach my $subchild ( $bugchild->children() ) {
- $err .= " <" . $subchild->name . ">";
- $err .= $subchild->field;
- $err .= "</" . $subchild->name . ">\n";
- }
- }
- else {
- $err .= $bugchild->field;
- }
- $err .= "</" . $bugchild->name . ">\n";
- }
+ return;
+ }
+ $bugtotal++;
+
+ # This list contains all other bug fields that we want to process.
+ # If it is not in this list it will not be included.
+ my %all_fields;
+ foreach my $field (qw(long_desc attachment flag group), Bugzilla::Bug::fields())
+ {
+ $all_fields{$field} = 1;
+ }
+
+ my %bug_fields;
+ my $err = "";
+
+ # Loop through all the xml tags inside a <bug> and compare them to the
+ # lists of fields. If they match throw them into the hash. Otherwise
+ # append it to the log, which will go into the comments when we are done.
+ foreach my $bugchild ($bug->children()) {
+ Debug("Parsing field: " . $bugchild->name, DEBUG_LEVEL);
+
+ # Skip the token if one is included. We don't want it included in
+ # the comments, and it is not used by the importer.
+ next if $bugchild->name eq 'token';
+
+ if (defined $all_fields{$bugchild->name}) {
+ my @values = $bug->children_text($bugchild->name);
+ if (scalar @values > 1) {
+ $bug_fields{$bugchild->name} = \@values;
+ }
+ else {
+ $bug_fields{$bugchild->name} = $values[0];
+ }
}
-
- # Parse long descriptions
- my @long_descs;
- foreach my $comment ( $bug->children('long_desc') ) {
- Debug( "Parsing Long Description", DEBUG_LEVEL );
- my %long_desc = ( who => $comment->field('who'),
- bug_when => format_time($comment->field('bug_when'), '%Y-%m-%d %T'),
- isprivate => $comment->{'att'}->{'isprivate'} || 0 );
-
- # If the exporter is not in the insidergroup, keep the comment public.
- $long_desc{isprivate} = 0 unless $exporter->is_insider;
-
- my $data = $comment->field('thetext');
- if ( defined $comment->first_child('thetext')->{'att'}->{'encoding'}
- && $comment->first_child('thetext')->{'att'}->{'encoding'} =~
- /base64/ )
- {
- $data = decode_base64($data);
- }
-
- # For backwards-compatibility with Bugzillas before 3.6:
- #
- # If we leave the attachment ID in the comment it will be made a link
- # to the wrong attachment. Since the new attachment ID is unknown yet
- # let's strip it out for now. We will make a comment with the right ID
- # later
- $data =~ s/Created an attachment \(id=\d+\)/Created an attachment/g;
-
- # Same goes for bug #'s Since we don't know if the referenced bug
- # is also being moved, lets make sure they know it means a different
- # bugzilla.
- $data =~ s/([Bb]ugs?\s*\#?\s*(\d+))/$url$2/g;
-
- # Keep the original commenter if possible, else we will fall back
- # to the exporter account.
- $long_desc{whoid} = login_to_id($long_desc{who});
-
- if (!$long_desc{whoid}) {
- $data = "The original author of this comment is $long_desc{who}.\n\n" . $data;
+ else {
+ $err .= "Unknown bug field \"" . $bugchild->name . "\"";
+ $err .= " encountered while moving bug\n";
+ $err .= " <" . $bugchild->name . ">";
+ if ($bugchild->children_count > 1) {
+ $err .= "\n";
+ foreach my $subchild ($bugchild->children()) {
+ $err .= " <" . $subchild->name . ">";
+ $err .= $subchild->field;
+ $err .= "</" . $subchild->name . ">\n";
}
-
- $long_desc{'thetext'} = $data;
- push @long_descs, \%long_desc;
+ }
+ else {
+ $err .= $bugchild->field;
+ }
+ $err .= "</" . $bugchild->name . ">\n";
}
-
- my @sorted_descs = sort { $a->{'bug_when'} cmp $b->{'bug_when'} } @long_descs;
-
- my $comments = "\n\n--- Bug imported by $exporter_login ";
- $comments .= format_time(scalar localtime(time()), '%Y-%m-%d %R %Z') . " ";
- $comments .= " ---\n\n";
- $comments .= "This bug was previously known as _bug_ $bug_fields{'bug_id'} at ";
- $comments .= $url . $bug_fields{'bug_id'} . "\n";
- if ( defined $bug_fields{'dependson'} ) {
- $comments .= "This bug depended on bug(s) " .
- join(' ', _to_array($bug_fields{'dependson'})) . ".\n";
+ }
+
+ # Parse long descriptions
+ my @long_descs;
+ foreach my $comment ($bug->children('long_desc')) {
+ Debug("Parsing Long Description", DEBUG_LEVEL);
+ my %long_desc = (
+ who => $comment->field('who'),
+ bug_when => format_time($comment->field('bug_when'), '%Y-%m-%d %T'),
+ isprivate => $comment->{'att'}->{'isprivate'} || 0
+ );
+
+ # If the exporter is not in the insidergroup, keep the comment public.
+ $long_desc{isprivate} = 0 unless $exporter->is_insider;
+
+ my $data = $comment->field('thetext');
+ if (defined $comment->first_child('thetext')->{'att'}->{'encoding'}
+ && $comment->first_child('thetext')->{'att'}->{'encoding'} =~ /base64/)
+ {
+ $data = decode_base64($data);
}
- if ( defined $bug_fields{'blocked'} ) {
- $comments .= "This bug blocked bug(s) " .
- join(' ', _to_array($bug_fields{'blocked'})) . ".\n";
+
+ # For backwards-compatibility with Bugzillas before 3.6:
+ #
+ # If we leave the attachment ID in the comment it will be made a link
+ # to the wrong attachment. Since the new attachment ID is unknown yet
+ # let's strip it out for now. We will make a comment with the right ID
+ # later
+ $data =~ s/Created an attachment \(id=\d+\)/Created an attachment/g;
+
+ # Same goes for bug #'s Since we don't know if the referenced bug
+ # is also being moved, lets make sure they know it means a different
+ # bugzilla.
+ $data =~ s/([Bb]ugs?\s*\#?\s*(\d+))/$url$2/g;
+
+ # Keep the original commenter if possible, else we will fall back
+ # to the exporter account.
+ $long_desc{whoid} = login_to_id($long_desc{who});
+
+ if (!$long_desc{whoid}) {
+ $data = "The original author of this comment is $long_desc{who}.\n\n" . $data;
}
- # Now we process each of the fields in turn and make sure they contain
- # valid data. We will create two parallel arrays, one for the query
- # and one for the values. For every field we need to push an entry onto
- # each array.
- my @query = ();
- my @values = ();
-
- # Each of these fields we will check for newlines and shove onto the array
- foreach my $field (qw(status_whiteboard bug_file_loc short_desc)) {
- if ($bug_fields{$field}) {
- $bug_fields{$field} = clean_text( $bug_fields{$field} );
- push( @query, $field );
- push( @values, $bug_fields{$field} );
- }
+ $long_desc{'thetext'} = $data;
+ push @long_descs, \%long_desc;
+ }
+
+ my @sorted_descs = sort { $a->{'bug_when'} cmp $b->{'bug_when'} } @long_descs;
+
+ my $comments = "\n\n--- Bug imported by $exporter_login ";
+ $comments .= format_time(scalar localtime(time()), '%Y-%m-%d %R %Z') . " ";
+ $comments .= " ---\n\n";
+ $comments .= "This bug was previously known as _bug_ $bug_fields{'bug_id'} at ";
+ $comments .= $url . $bug_fields{'bug_id'} . "\n";
+ if (defined $bug_fields{'dependson'}) {
+ $comments .= "This bug depended on bug(s) "
+ . join(' ', _to_array($bug_fields{'dependson'})) . ".\n";
+ }
+ if (defined $bug_fields{'blocked'}) {
+ $comments .= "This bug blocked bug(s) "
+ . join(' ', _to_array($bug_fields{'blocked'})) . ".\n";
+ }
+
+ # Now we process each of the fields in turn and make sure they contain
+ # valid data. We will create two parallel arrays, one for the query
+ # and one for the values. For every field we need to push an entry onto
+ # each array.
+ my @query = ();
+ my @values = ();
+
+ # Each of these fields we will check for newlines and shove onto the array
+ foreach my $field (qw(status_whiteboard bug_file_loc short_desc)) {
+ if ($bug_fields{$field}) {
+ $bug_fields{$field} = clean_text($bug_fields{$field});
+ push(@query, $field);
+ push(@values, $bug_fields{$field});
}
+ }
- # Alias
- if ( $bug_fields{'alias'} ) {
- my ($alias) = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs
+ # Alias
+ if ($bug_fields{'alias'}) {
+ my ($alias) = $dbh->selectrow_array(
+ "SELECT COUNT(*) FROM bugs
WHERE alias = ?", undef,
- $bug_fields{'alias'} );
- if ($alias) {
- $err .= "Dropping conflicting bug alias ";
- $err .= $bug_fields{'alias'} . "\n";
- }
- else {
- $alias = $bug_fields{'alias'};
- push @query, 'alias';
- push @values, $alias;
- }
+ $bug_fields{'alias'}
+ );
+ if ($alias) {
+ $err .= "Dropping conflicting bug alias ";
+ $err .= $bug_fields{'alias'} . "\n";
}
-
- # Timestamps
- push( @query, "creation_ts" );
- push( @values,
- format_time( $bug_fields{'creation_ts'}, "%Y-%m-%d %T" )
- || $timestamp );
-
- push( @query, "delta_ts" );
- push( @values,
- format_time( $bug_fields{'delta_ts'}, "%Y-%m-%d %T" )
- || $timestamp );
-
- # Bug Access
- push( @query, "cclist_accessible" );
- push( @values, $bug_fields{'cclist_accessible'} ? 1 : 0 );
-
- push( @query, "reporter_accessible" );
- push( @values, $bug_fields{'reporter_accessible'} ? 1 : 0 );
-
- my $product = new Bugzilla::Product(
- { name => $bug_fields{'product'} || '' });
- if (!$product) {
- $err .= "Unknown Product " . $bug_fields{'product'} . "\n";
- $err .= " Using default product set at the command line.\n";
- $product = new Bugzilla::Product({ name => $default_product_name })
- or Error("an invalid default product was defined for the target"
- . " DB. " . $params->{"maintainer"} . " needs to specify "
- . "--product when calling importxml.pl", "REOPEN",
- $exporter);
+ else {
+ $alias = $bug_fields{'alias'};
+ push @query, 'alias';
+ push @values, $alias;
}
- my $component = new Bugzilla::Component({
- product => $product, name => $bug_fields{'component'} || '' });
+ }
+
+ # Timestamps
+ push(@query, "creation_ts");
+ push(@values,
+ format_time($bug_fields{'creation_ts'}, "%Y-%m-%d %T") || $timestamp);
+
+ push(@query, "delta_ts");
+ push(@values,
+ format_time($bug_fields{'delta_ts'}, "%Y-%m-%d %T") || $timestamp);
+
+ # Bug Access
+ push(@query, "cclist_accessible");
+ push(@values, $bug_fields{'cclist_accessible'} ? 1 : 0);
+
+ push(@query, "reporter_accessible");
+ push(@values, $bug_fields{'reporter_accessible'} ? 1 : 0);
+
+ my $product = new Bugzilla::Product({name => $bug_fields{'product'} || ''});
+ if (!$product) {
+ $err .= "Unknown Product " . $bug_fields{'product'} . "\n";
+ $err .= " Using default product set at the command line.\n";
+ $product = new Bugzilla::Product({name => $default_product_name})
+ or Error(
+ "an invalid default product was defined for the target" . " DB. "
+ . $params->{"maintainer"}
+ . " needs to specify "
+ . "--product when calling importxml.pl",
+ "REOPEN", $exporter
+ );
+ }
+ my $component = new Bugzilla::Component(
+ {product => $product, name => $bug_fields{'component'} || ''});
+ if (!$component) {
+ $err .= "Unknown Component " . $bug_fields{'component'} . "\n";
+ $err .= " Using default product and component set ";
+ $err .= "at the command line.\n";
+
+ $product = new Bugzilla::Product({name => $default_product_name});
+ $component = new Bugzilla::Component(
+ {name => $default_component_name, product => $product});
if (!$component) {
- $err .= "Unknown Component " . $bug_fields{'component'} . "\n";
- $err .= " Using default product and component set ";
- $err .= "at the command line.\n";
-
- $product = new Bugzilla::Product({ name => $default_product_name });
- $component = new Bugzilla::Component({
- name => $default_component_name, product => $product });
- if (!$component) {
- Error("an invalid default component was defined for the target"
- . " DB. ". $params->{"maintainer"} . " needs to specify "
- . "--component when calling importxml.pl", "REOPEN",
- $exporter);
- }
+ Error(
+ "an invalid default component was defined for the target" . " DB. "
+ . $params->{"maintainer"}
+ . " needs to specify "
+ . "--component when calling importxml.pl",
+ "REOPEN", $exporter
+ );
}
+ }
+
+ my $prod_id = $product->id;
+ my $comp_id = $component->id;
+
+ push(@query, "product_id");
+ push(@values, $prod_id);
+ push(@query, "component_id");
+ push(@values, $comp_id);
+
+ # Since there is no default version for a product, we check that the one
+ # coming over is valid. If not we will use the first one in @versions
+ # and warn them.
+ my $version = new Bugzilla::Version(
+ {product => $product, name => $bug_fields{'version'}});
+
+ push(@query, "version");
+ if ($version) {
+ push(@values, $version->name);
+ }
+ else {
+ my @versions = @{$product->versions};
+ my $v = $versions[0];
+ push(@values, $v->name);
+ $err .= "Unknown version \"";
+ $err .= (defined $bug_fields{'version'}) ? $bug_fields{'version'} : "unknown";
+ $err .= " in product " . $product->name . ". \n";
+ $err .= " Setting version to \"" . $v->name . "\".\n";
+ }
+
+ # Milestone
+ if ($params->{"usetargetmilestone"}) {
+ my $milestone;
+ if (defined $bug_fields{'target_milestone'}
+ && $bug_fields{'target_milestone'} ne "")
+ {
- my $prod_id = $product->id;
- my $comp_id = $component->id;
-
- push( @query, "product_id" );
- push( @values, $prod_id );
- push( @query, "component_id" );
- push( @values, $comp_id );
-
- # Since there is no default version for a product, we check that the one
- # coming over is valid. If not we will use the first one in @versions
- # and warn them.
- my $version = new Bugzilla::Version(
- { product => $product, name => $bug_fields{'version'} });
-
- push( @query, "version" );
- if ($version) {
- push( @values, $version->name );
+ $milestone = new Bugzilla::Milestone(
+ {product => $product, name => $bug_fields{'target_milestone'}});
+ }
+ if ($milestone) {
+ push(@values, $milestone->name);
}
else {
- my @versions = @{ $product->versions };
- my $v = $versions[0];
- push( @values, $v->name );
- $err .= "Unknown version \"";
- $err .= ( defined $bug_fields{'version'} )
- ? $bug_fields{'version'}
- : "unknown";
- $err .= " in product " . $product->name . ". \n";
- $err .= " Setting version to \"" . $v->name . "\".\n";
+ push(@values, $product->default_milestone);
+ $err .= "Unknown milestone \"";
+ $err
+ .= (defined $bug_fields{'target_milestone'})
+ ? $bug_fields{'target_milestone'}
+ : "unknown";
+ $err .= " in product " . $product->name . ". \n";
+ $err .= " Setting to default milestone for this product, ";
+ $err .= "\"" . $product->default_milestone . "\".\n";
}
-
- # Milestone
- if ( $params->{"usetargetmilestone"} ) {
- my $milestone;
- if (defined $bug_fields{'target_milestone'}
- && $bug_fields{'target_milestone'} ne "") {
-
- $milestone = new Bugzilla::Milestone(
- { product => $product, name => $bug_fields{'target_milestone'} });
- }
- if ($milestone) {
- push( @values, $milestone->name );
- }
- else {
- push( @values, $product->default_milestone );
- $err .= "Unknown milestone \"";
- $err .= ( defined $bug_fields{'target_milestone'} )
- ? $bug_fields{'target_milestone'}
- : "unknown";
- $err .= " in product " . $product->name . ". \n";
- $err .= " Setting to default milestone for this product, ";
- $err .= "\"" . $product->default_milestone . "\".\n";
- }
- push( @query, "target_milestone" );
+ push(@query, "target_milestone");
+ }
+
+ # For priority, severity, opsys and platform we check that the one being
+ # imported is valid. If it is not we use the defaults set in the parameters.
+ if (
+ defined($bug_fields{'bug_severity'})
+ && check_field(
+ 'bug_severity', scalar $bug_fields{'bug_severity'},
+ undef, ERR_LEVEL
+ )
+ )
+ {
+ push(@values, $bug_fields{'bug_severity'});
+ }
+ else {
+ push(@values, $params->{'defaultseverity'});
+ $err .= "Unknown severity ";
+ $err
+ .= (defined $bug_fields{'bug_severity'})
+ ? $bug_fields{'bug_severity'}
+ : "unknown";
+ $err .= ". Setting to default severity \"";
+ $err .= $params->{'defaultseverity'} . "\".\n";
+ }
+ push(@query, "bug_severity");
+
+ if (defined($bug_fields{'priority'})
+ && check_field('priority', scalar $bug_fields{'priority'}, undef, ERR_LEVEL))
+ {
+ push(@values, $bug_fields{'priority'});
+ }
+ else {
+ push(@values, $params->{'defaultpriority'});
+ $err .= "Unknown priority ";
+ $err .= (defined $bug_fields{'priority'}) ? $bug_fields{'priority'} : "unknown";
+ $err .= ". Setting to default priority \"";
+ $err .= $params->{'defaultpriority'} . "\".\n";
+ }
+ push(@query, "priority");
+
+ if (
+ defined($bug_fields{'rep_platform'})
+ && check_field(
+ 'rep_platform', scalar $bug_fields{'rep_platform'},
+ undef, ERR_LEVEL
+ )
+ )
+ {
+ push(@values, $bug_fields{'rep_platform'});
+ }
+ else {
+ push(@values, $params->{'defaultplatform'});
+ $err .= "Unknown platform ";
+ $err
+ .= (defined $bug_fields{'rep_platform'})
+ ? $bug_fields{'rep_platform'}
+ : "unknown";
+ $err .= ". Setting to default platform \"";
+ $err .= $params->{'defaultplatform'} . "\".\n";
+ }
+ push(@query, "rep_platform");
+
+ if (defined($bug_fields{'op_sys'})
+ && check_field('op_sys', scalar $bug_fields{'op_sys'}, undef, ERR_LEVEL))
+ {
+ push(@values, $bug_fields{'op_sys'});
+ }
+ else {
+ push(@values, $params->{'defaultopsys'});
+ $err .= "Unknown operating system ";
+ $err .= (defined $bug_fields{'op_sys'}) ? $bug_fields{'op_sys'} : "unknown";
+ $err .= ". Setting to default OS \"" . $params->{'defaultopsys'} . "\".\n";
+ }
+ push(@query, "op_sys");
+
+ # Process time fields
+ if ($params->{"timetrackinggroup"}) {
+ my $date
+ = validate_date($bug_fields{'deadline'}) ? $bug_fields{'deadline'} : undef;
+ push(@values, $date);
+ push(@query, "deadline");
+ if (defined $bug_fields{'estimated_time'}) {
+ eval { Bugzilla::Object::_validate_time($bug_fields{'estimated_time'}, "e"); };
+ if (!$@) {
+ push(@values, $bug_fields{'estimated_time'});
+ push(@query, "estimated_time");
+ }
}
-
- # For priority, severity, opsys and platform we check that the one being
- # imported is valid. If it is not we use the defaults set in the parameters.
- if (defined( $bug_fields{'bug_severity'} )
- && check_field('bug_severity', scalar $bug_fields{'bug_severity'},
- undef, ERR_LEVEL) )
- {
- push( @values, $bug_fields{'bug_severity'} );
+ if (defined $bug_fields{'remaining_time'}) {
+ eval { Bugzilla::Object::_validate_time($bug_fields{'remaining_time'}, "r"); };
+ if (!$@) {
+ push(@values, $bug_fields{'remaining_time'});
+ push(@query, "remaining_time");
+ }
+ }
+ if (defined $bug_fields{'actual_time'}) {
+ eval { Bugzilla::Object::_validate_time($bug_fields{'actual_time'}, "a"); };
+ if ($@) {
+ $bug_fields{'actual_time'} = 0.0;
+ $err .= "Invalid Actual Time. Setting to 0.0\n";
+ }
}
else {
- push( @values, $params->{'defaultseverity'} );
- $err .= "Unknown severity ";
- $err .= ( defined $bug_fields{'bug_severity'} )
- ? $bug_fields{'bug_severity'}
- : "unknown";
- $err .= ". Setting to default severity \"";
- $err .= $params->{'defaultseverity'} . "\".\n";
+ $bug_fields{'actual_time'} = 0.0;
+ $err .= "Actual time not defined. Setting to 0.0\n";
}
- push( @query, "bug_severity" );
-
- if (defined( $bug_fields{'priority'} )
- && check_field('priority', scalar $bug_fields{'priority'},
- undef, ERR_LEVEL ) )
- {
- push( @values, $bug_fields{'priority'} );
+ }
+
+ # Reporter Assignee QA Contact
+ my $exporterid = $exporter->id;
+ my $reporterid = login_to_id($bug_fields{'reporter'})
+ if $bug_fields{'reporter'};
+ push(@query, "reporter");
+ if (($bug_fields{'reporter'}) && ($reporterid)) {
+ push(@values, $reporterid);
+ }
+ else {
+ push(@values, $exporterid);
+ $err .= "The original reporter of this bug does not have\n";
+ $err .= " an account here. Reassigning to the person who moved\n";
+ $err .= " it here: $exporter_login.\n";
+ if ($bug_fields{'reporter'}) {
+ $err .= " Previous reporter was $bug_fields{'reporter'}.\n";
}
else {
- push( @values, $params->{'defaultpriority'} );
- $err .= "Unknown priority ";
- $err .= ( defined $bug_fields{'priority'} )
- ? $bug_fields{'priority'}
- : "unknown";
- $err .= ". Setting to default priority \"";
- $err .= $params->{'defaultpriority'} . "\".\n";
+ $err .= " Previous reporter is unknown.\n";
}
- push( @query, "priority" );
-
- if (defined( $bug_fields{'rep_platform'} )
- && check_field('rep_platform', scalar $bug_fields{'rep_platform'},
- undef, ERR_LEVEL ) )
- {
- push( @values, $bug_fields{'rep_platform'} );
+ }
+
+ my $changed_owner = 0;
+ my $owner;
+ push(@query, "assigned_to");
+ if ( ($bug_fields{'assigned_to'})
+ && ($owner = login_to_id($bug_fields{'assigned_to'})))
+ {
+ push(@values, $owner);
+ }
+ else {
+ push(@values, $component->default_assignee->id);
+ $changed_owner = 1;
+ $err .= "The original assignee of this bug does not have\n";
+ $err .= " an account here. Reassigning to the default assignee\n";
+ $err .= " for the component, " . $component->default_assignee->login . ".\n";
+ if ($bug_fields{'assigned_to'}) {
+ $err .= " Previous assignee was $bug_fields{'assigned_to'}.\n";
}
else {
- push( @values, $params->{'defaultplatform'} );
- $err .= "Unknown platform ";
- $err .= ( defined $bug_fields{'rep_platform'} )
- ? $bug_fields{'rep_platform'}
- : "unknown";
- $err .=". Setting to default platform \"";
- $err .= $params->{'defaultplatform'} . "\".\n";
+ $err .= " Previous assignee is unknown.\n";
}
- push( @query, "rep_platform" );
+ }
- if (defined( $bug_fields{'op_sys'} )
- && check_field('op_sys', scalar $bug_fields{'op_sys'},
- undef, ERR_LEVEL ) )
+ if ($params->{"useqacontact"}) {
+ my $qa_contact;
+ push(@query, "qa_contact");
+ if ( (defined $bug_fields{'qa_contact'})
+ && ($qa_contact = login_to_id($bug_fields{'qa_contact'})))
{
- push( @values, $bug_fields{'op_sys'} );
+ push(@values, $qa_contact);
}
else {
- push( @values, $params->{'defaultopsys'} );
- $err .= "Unknown operating system ";
- $err .= ( defined $bug_fields{'op_sys'} )
- ? $bug_fields{'op_sys'}
- : "unknown";
- $err .= ". Setting to default OS \"" . $params->{'defaultopsys'} . "\".\n";
- }
- push( @query, "op_sys" );
-
- # Process time fields
- if ( $params->{"timetrackinggroup"} ) {
- my $date = validate_date( $bug_fields{'deadline'} ) ? $bug_fields{'deadline'} : undef;
- push( @values, $date );
- push( @query, "deadline" );
- if ( defined $bug_fields{'estimated_time'} ) {
- eval {
- Bugzilla::Object::_validate_time($bug_fields{'estimated_time'}, "e");
- };
- if (!$@){
- push( @values, $bug_fields{'estimated_time'} );
- push( @query, "estimated_time" );
- }
- }
- if ( defined $bug_fields{'remaining_time'} ) {
- eval {
- Bugzilla::Object::_validate_time($bug_fields{'remaining_time'}, "r");
- };
- if (!$@){
- push( @values, $bug_fields{'remaining_time'} );
- push( @query, "remaining_time" );
- }
- }
- if ( defined $bug_fields{'actual_time'} ) {
- eval {
- Bugzilla::Object::_validate_time($bug_fields{'actual_time'}, "a");
- };
- if ($@){
- $bug_fields{'actual_time'} = 0.0;
- $err .= "Invalid Actual Time. Setting to 0.0\n";
- }
- }
- else {
- $bug_fields{'actual_time'} = 0.0;
- $err .= "Actual time not defined. Setting to 0.0\n";
- }
- }
+ push(@values,
+ $component->default_qa_contact ? $component->default_qa_contact->id : undef);
- # Reporter Assignee QA Contact
- my $exporterid = $exporter->id;
- my $reporterid = login_to_id( $bug_fields{'reporter'} )
- if $bug_fields{'reporter'};
- push( @query, "reporter" );
- if ( ( $bug_fields{'reporter'} ) && ($reporterid) ) {
- push( @values, $reporterid );
+ if ($component->default_qa_contact) {
+ $err .= "Setting qa contact to the default for this product.\n";
+ $err .= " This bug either had no qa contact or an invalid one.\n";
+ }
+ }
+ }
+
+ # Status & Resolution
+ my $valid_res
+ = check_field('resolution', scalar $bug_fields{'resolution'}, undef,
+ ERR_LEVEL);
+ my $valid_status
+ = check_field('bug_status', scalar $bug_fields{'bug_status'}, undef,
+ ERR_LEVEL);
+ my $status = $bug_fields{'bug_status'} || undef;
+ my $resolution = $bug_fields{'resolution'} || undef;
+
+ # Check everconfirmed
+ my $everconfirmed;
+ if ($product->allows_unconfirmed) {
+ $everconfirmed = $bug_fields{'everconfirmed'} || 0;
+ }
+ else {
+ $everconfirmed = 1;
+ }
+ push(@query, "everconfirmed");
+ push(@values, $everconfirmed);
+
+ # Sanity check will complain about having bugs marked duplicate but no
+ # entry in the dup table. Since we can't tell the bug ID of bugs
+ # that might not yet be in the database we have no way of populating
+ # this table. Change the resolution instead.
+ if ($valid_res && ($bug_fields{'resolution'} eq "DUPLICATE")) {
+ $resolution = "INVALID";
+ $err .= "This bug was marked DUPLICATE in the database ";
+ $err .= "it was moved from.\n Changing resolution to \"INVALID\"\n";
+ }
+
+ # If there is at least 1 initial bug status different from UNCO, use it,
+ # else use the open bug status with the lowest sortkey (different from UNCO).
+ my @bug_statuses = @{Bugzilla::Status->can_change_to()};
+ @bug_statuses = grep { $_->name ne 'UNCONFIRMED' } @bug_statuses;
+
+ my $initial_status;
+ if (scalar(@bug_statuses)) {
+ $initial_status = $bug_statuses[0]->name;
+ }
+ else {
+ @bug_statuses = Bugzilla::Status->get_all();
+
+ # Exclude UNCO and inactive bug statuses.
+ @bug_statuses
+ = grep { $_->is_active && $_->name ne 'UNCONFIRMED' } @bug_statuses;
+ my @open_statuses = grep { $_->is_open } @bug_statuses;
+ if (scalar(@open_statuses)) {
+ $initial_status = $open_statuses[0]->name;
}
else {
- push( @values, $exporterid );
- $err .= "The original reporter of this bug does not have\n";
- $err .= " an account here. Reassigning to the person who moved\n";
- $err .= " it here: $exporter_login.\n";
- if ( $bug_fields{'reporter'} ) {
- $err .= " Previous reporter was $bug_fields{'reporter'}.\n";
+ # There is NO other open bug statuses outside UNCO???
+ Error("no open bug statuses available.");
+ }
+ }
+
+ if ($status) {
+ if ($valid_status) {
+ if (is_open_state($status)) {
+ if ($resolution) {
+ $err .= "Resolution set on an open status.\n";
+ $err .= " Dropping resolution $resolution\n";
+ $resolution = undef;
}
- else {
- $err .= " Previous reporter is unknown.\n";
+ if ($changed_owner) {
+ if ($everconfirmed) {
+ $status = $initial_status;
+ }
+ else {
+ $status = "UNCONFIRMED";
+ }
+ if ($status ne $bug_fields{'bug_status'}) {
+ $err .= "Bug reassigned, setting status to \"$status\".\n";
+ $err .= " Previous status was \"";
+ $err .= $bug_fields{'bug_status'} . "\".\n";
+ }
}
- }
-
- my $changed_owner = 0;
- my $owner;
- push( @query, "assigned_to" );
- if ( ( $bug_fields{'assigned_to'} )
- && ( $owner = login_to_id( $bug_fields{'assigned_to'} )) ) {
- push( @values, $owner );
- }
- else {
- push( @values, $component->default_assignee->id );
- $changed_owner = 1;
- $err .= "The original assignee of this bug does not have\n";
- $err .= " an account here. Reassigning to the default assignee\n";
- $err .= " for the component, ". $component->default_assignee->login .".\n";
- if ( $bug_fields{'assigned_to'} ) {
- $err .= " Previous assignee was $bug_fields{'assigned_to'}.\n";
+ if ($everconfirmed) {
+ if ($status eq "UNCONFIRMED") {
+ $err .= "Bug Status was UNCONFIRMED but everconfirmed was true\n";
+ $err .= " Setting status to $initial_status\n";
+ $status = $initial_status;
+ }
}
- else {
- $err .= " Previous assignee is unknown.\n";
+ else { # $everconfirmed is false
+ if ($status ne "UNCONFIRMED") {
+ $err .= "Bug Status was $status but everconfirmed was false\n";
+ $err .= " Setting status to UNCONFIRMED\n";
+ $status = "UNCONFIRMED";
+ }
}
- }
-
- if ( $params->{"useqacontact"} ) {
- my $qa_contact;
- push( @query, "qa_contact" );
- if ( ( defined $bug_fields{'qa_contact'})
- && ( $qa_contact = login_to_id( $bug_fields{'qa_contact'} ) ) ) {
- push( @values, $qa_contact );
+ }
+ else {
+ if (!$resolution) {
+ $err .= "Missing Resolution. Setting status to ";
+ if ($everconfirmed) {
+ $status = $initial_status;
+ $err .= "$initial_status\n";
+ }
+ else {
+ $status = "UNCONFIRMED";
+ $err .= "UNCONFIRMED\n";
+ }
}
- else {
- push(@values, $component->default_qa_contact ?
- $component->default_qa_contact->id : undef);
-
- if ($component->default_qa_contact) {
- $err .= "Setting qa contact to the default for this product.\n";
- $err .= " This bug either had no qa contact or an invalid one.\n";
- }
+ elsif (!$valid_res) {
+ $err .= "Unknown resolution \"$resolution\".\n";
+ $err .= " Setting resolution to INVALID\n";
+ $resolution = "INVALID";
}
+ }
}
-
- # Status & Resolution
- my $valid_res = check_field('resolution',
- scalar $bug_fields{'resolution'},
- undef, ERR_LEVEL );
- my $valid_status = check_field('bug_status',
- scalar $bug_fields{'bug_status'},
- undef, ERR_LEVEL );
- my $status = $bug_fields{'bug_status'} || undef;
- my $resolution = $bug_fields{'resolution'} || undef;
-
- # Check everconfirmed
- my $everconfirmed;
- if ($product->allows_unconfirmed) {
- $everconfirmed = $bug_fields{'everconfirmed'} || 0;
+ else { # $valid_status is false
+ if ($everconfirmed) {
+ $status = $initial_status;
+ }
+ else {
+ $status = "UNCONFIRMED";
+ }
+ $err .= "Bug has invalid status, setting status to \"$status\".\n";
+ $err .= " Previous status was \"";
+ $err .= $bug_fields{'bug_status'} . "\".\n";
+ $resolution = undef;
+ }
+ }
+ else {
+ if ($everconfirmed) {
+ $status = $initial_status;
}
else {
- $everconfirmed = 1;
+ $status = "UNCONFIRMED";
}
- push (@query, "everconfirmed");
- push (@values, $everconfirmed);
-
- # Sanity check will complain about having bugs marked duplicate but no
- # entry in the dup table. Since we can't tell the bug ID of bugs
- # that might not yet be in the database we have no way of populating
- # this table. Change the resolution instead.
- if ( $valid_res && ( $bug_fields{'resolution'} eq "DUPLICATE" ) ) {
- $resolution = "INVALID";
- $err .= "This bug was marked DUPLICATE in the database ";
- $err .= "it was moved from.\n Changing resolution to \"INVALID\"\n";
- }
-
- # If there is at least 1 initial bug status different from UNCO, use it,
- # else use the open bug status with the lowest sortkey (different from UNCO).
- my @bug_statuses = @{Bugzilla::Status->can_change_to()};
- @bug_statuses = grep { $_->name ne 'UNCONFIRMED' } @bug_statuses;
-
- my $initial_status;
- if (scalar(@bug_statuses)) {
- $initial_status = $bug_statuses[0]->name;
+ $err .= "Bug has no status, setting status to \"$status\".\n";
+ $err .= " Previous status was unknown\n";
+ $resolution = undef;
+ }
+
+ if ($resolution) {
+ push(@query, "resolution");
+ push(@values, $resolution);
+ }
+
+ # Bug status
+ push(@query, "bug_status");
+ push(@values, $status);
+
+ # Custom fields - Multi-select fields have their own table.
+ my %multi_select_fields;
+ foreach my $field (Bugzilla->active_custom_fields) {
+ my $custom_field = $field->name;
+ my $value = $bug_fields{$custom_field};
+ next unless defined $value;
+ if ($field->type == FIELD_TYPE_FREETEXT) {
+ push(@query, $custom_field);
+ push(@values, clean_text($value));
}
- else {
- @bug_statuses = Bugzilla::Status->get_all();
- # Exclude UNCO and inactive bug statuses.
- @bug_statuses = grep { $_->is_active && $_->name ne 'UNCONFIRMED'} @bug_statuses;
- my @open_statuses = grep { $_->is_open } @bug_statuses;
- if (scalar(@open_statuses)) {
- $initial_status = $open_statuses[0]->name;
+ elsif ($field->type == FIELD_TYPE_TEXTAREA) {
+ push(@query, $custom_field);
+ push(@values, $value);
+ }
+ elsif ($field->type == FIELD_TYPE_SINGLE_SELECT) {
+ my $is_well_formed = check_field($custom_field, $value, undef, ERR_LEVEL);
+ if ($is_well_formed) {
+ push(@query, $custom_field);
+ push(@values, $value);
+ }
+ else {
+ $err .= "Skipping illegal value \"$value\" in $custom_field.\n";
+ }
+ }
+ elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ my @legal_values;
+ foreach my $item (_to_array($value)) {
+ my $is_well_formed = check_field($custom_field, $item, undef, ERR_LEVEL);
+ if ($is_well_formed) {
+ push(@legal_values, $item);
}
else {
- # There is NO other open bug statuses outside UNCO???
- Error("no open bug statuses available.");
+ $err .= "Skipping illegal value \"$item\" in $custom_field.\n";
}
+ }
+ if (scalar @legal_values) {
+ $multi_select_fields{$custom_field} = \@legal_values;
+ }
}
-
- if ($status) {
- if($valid_status){
- if (is_open_state($status)) {
- if ($resolution) {
- $err .= "Resolution set on an open status.\n";
- $err .= " Dropping resolution $resolution\n";
- $resolution = undef;
- }
- if($changed_owner){
- if($everconfirmed){
- $status = $initial_status;
- }
- else{
- $status = "UNCONFIRMED";
- }
- if ($status ne $bug_fields{'bug_status'}){
- $err .= "Bug reassigned, setting status to \"$status\".\n";
- $err .= " Previous status was \"";
- $err .= $bug_fields{'bug_status'} . "\".\n";
- }
- }
- if($everconfirmed){
- if($status eq "UNCONFIRMED"){
- $err .= "Bug Status was UNCONFIRMED but everconfirmed was true\n";
- $err .= " Setting status to $initial_status\n";
- $status = $initial_status;
- }
- }
- else{ # $everconfirmed is false
- if($status ne "UNCONFIRMED"){
- $err .= "Bug Status was $status but everconfirmed was false\n";
- $err .= " Setting status to UNCONFIRMED\n";
- $status = "UNCONFIRMED";
- }
- }
- }
- else {
- if (!$resolution) {
- $err .= "Missing Resolution. Setting status to ";
- if($everconfirmed){
- $status = $initial_status;
- $err .= "$initial_status\n";
- }
- else{
- $status = "UNCONFIRMED";
- $err .= "UNCONFIRMED\n";
- }
- }
- elsif (!$valid_res) {
- $err .= "Unknown resolution \"$resolution\".\n";
- $err .= " Setting resolution to INVALID\n";
- $resolution = "INVALID";
- }
- }
- }
- else{ # $valid_status is false
- if($everconfirmed){
- $status = $initial_status;
- }
- else{
- $status = "UNCONFIRMED";
- }
- $err .= "Bug has invalid status, setting status to \"$status\".\n";
- $err .= " Previous status was \"";
- $err .= $bug_fields{'bug_status'} . "\".\n";
- $resolution = undef;
- }
+ elsif ($field->type == FIELD_TYPE_DATETIME) {
+ eval { $value = Bugzilla::Bug->_check_datetime_field($value); };
+ if ($@) {
+ $err .= "Skipping illegal value \"$value\" in $custom_field.\n";
+ }
+ else {
+ push(@query, $custom_field);
+ push(@values, $value);
+ }
}
- else {
- if($everconfirmed){
- $status = $initial_status;
- }
- else{
- $status = "UNCONFIRMED";
- }
- $err .= "Bug has no status, setting status to \"$status\".\n";
- $err .= " Previous status was unknown\n";
- $resolution = undef;
+ elsif ($field->type == FIELD_TYPE_DATE) {
+ eval { $value = Bugzilla::Bug->_check_date_field($value); };
+ if ($@) {
+ $err .= "Skipping illegal value \"$value\" in $custom_field.\n";
+ }
+ else {
+ push(@query, $custom_field);
+ push(@values, $value);
+ }
}
-
- if ($resolution) {
- push( @query, "resolution" );
- push( @values, $resolution );
+ else {
+ $err .= "Type of custom field $custom_field is an unhandled FIELD_TYPE: "
+ . $field->type . "\n";
}
-
- # Bug status
- push( @query, "bug_status" );
- push( @values, $status );
-
- # Custom fields - Multi-select fields have their own table.
- my %multi_select_fields;
- foreach my $field (Bugzilla->active_custom_fields) {
- my $custom_field = $field->name;
- my $value = $bug_fields{$custom_field};
- next unless defined $value;
- if ($field->type == FIELD_TYPE_FREETEXT) {
- push(@query, $custom_field);
- push(@values, clean_text($value));
- } elsif ($field->type == FIELD_TYPE_TEXTAREA) {
- push(@query, $custom_field);
- push(@values, $value);
- } elsif ($field->type == FIELD_TYPE_SINGLE_SELECT) {
- my $is_well_formed = check_field($custom_field, $value, undef, ERR_LEVEL);
- if ($is_well_formed) {
- push(@query, $custom_field);
- push(@values, $value);
- } else {
- $err .= "Skipping illegal value \"$value\" in $custom_field.\n" ;
- }
- } elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
- my @legal_values;
- foreach my $item (_to_array($value)) {
- my $is_well_formed = check_field($custom_field, $item, undef, ERR_LEVEL);
- if ($is_well_formed) {
- push(@legal_values, $item);
- } else {
- $err .= "Skipping illegal value \"$item\" in $custom_field.\n" ;
- }
- }
- if (scalar @legal_values) {
- $multi_select_fields{$custom_field} = \@legal_values;
- }
- } elsif ($field->type == FIELD_TYPE_DATETIME) {
- eval { $value = Bugzilla::Bug->_check_datetime_field($value); };
- if ($@) {
- $err .= "Skipping illegal value \"$value\" in $custom_field.\n" ;
- }
- else {
- push(@query, $custom_field);
- push(@values, $value);
- }
- } elsif ($field->type == FIELD_TYPE_DATE) {
- eval { $value = Bugzilla::Bug->_check_date_field($value); };
- if ($@) {
- $err .= "Skipping illegal value \"$value\" in $custom_field.\n" ;
- }
- else {
- push(@query, $custom_field);
- push(@values, $value);
- }
- } else {
- $err .= "Type of custom field $custom_field is an unhandled FIELD_TYPE: " .
- $field->type . "\n";
+ }
+
+ # For the sake of sanitycheck.cgi we do this.
+ # Update lastdiffed if you do not want to have mail sent
+ unless ($mail) {
+ push @query, "lastdiffed";
+ push @values, $timestamp;
+ }
+
+ # INSERT the bug
+ my $query = "INSERT INTO bugs (" . join(", ", @query) . ") VALUES (";
+ $query .= '?,' foreach (@values);
+ chop($query); # Remove the last comma.
+ $query .= ")";
+
+ $dbh->do($query, undef, @values);
+ my $id = $dbh->bz_last_key('bugs', 'bug_id');
+ my $bug_obj = Bugzilla::Bug->new($id);
+
+ # We are almost certain to get some uninitialized warnings
+ # Since this is just for debugging the query, let's shut them up
+ eval {
+ no warnings 'uninitialized';
+ Debug(
+ "Bug Query: INSERT INTO bugs (\n"
+ . join(",\n", @query)
+ . "\n) VALUES (\n"
+ . join(",\n", @values),
+ DEBUG_LEVEL
+ );
+ };
+
+ # Handle CC's
+ if (defined $bug_fields{'cc'}) {
+ my %ccseen;
+ my $sth_cc = $dbh->prepare("INSERT INTO cc (bug_id, who) VALUES (?,?)");
+ foreach my $person (_to_array($bug_fields{'cc'})) {
+ next unless $person;
+ my $uid;
+ if ($uid = login_to_id($person)) {
+ if (!$ccseen{$uid}) {
+ $sth_cc->execute($id, $uid);
+ $ccseen{$uid} = 1;
}
+ }
+ else {
+ $err .= "CC member $person does not have an account here\n";
+ }
}
+ }
- # For the sake of sanitycheck.cgi we do this.
- # Update lastdiffed if you do not want to have mail sent
- unless ($mail) {
- push @query, "lastdiffed";
- push @values, $timestamp;
+ # Handle keywords
+ if (defined($bug_fields{'keywords'})) {
+ my %keywordseen;
+ my $key_sth = $dbh->prepare(
+ "INSERT INTO keywords
+ (bug_id, keywordid) VALUES (?,?)"
+ );
+ foreach my $keyword (split(/[\s,]+/, $bug_fields{'keywords'})) {
+ next unless $keyword;
+ my $keyword_obj = new Bugzilla::Keyword({name => $keyword});
+ if (!$keyword_obj) {
+ $err .= "Skipping unknown keyword: $keyword.\n";
+ next;
+ }
+ if (!$keywordseen{$keyword_obj->id}) {
+ $key_sth->execute($id, $keyword_obj->id);
+ $keywordseen{$keyword_obj->id} = 1;
+ }
}
-
- # INSERT the bug
- my $query = "INSERT INTO bugs (" . join( ", ", @query ) . ") VALUES (";
- $query .= '?,' foreach (@values);
- chop($query); # Remove the last comma.
- $query .= ")";
-
- $dbh->do( $query, undef, @values );
- my $id = $dbh->bz_last_key( 'bugs', 'bug_id' );
- my $bug_obj = Bugzilla::Bug->new($id);
-
- # We are almost certain to get some uninitialized warnings
- # Since this is just for debugging the query, let's shut them up
- eval {
- no warnings 'uninitialized';
- Debug(
- "Bug Query: INSERT INTO bugs (\n"
- . join( ",\n", @query )
- . "\n) VALUES (\n"
- . join( ",\n", @values ),
- DEBUG_LEVEL
- );
- };
-
- # Handle CC's
- if ( defined $bug_fields{'cc'} ) {
- my %ccseen;
- my $sth_cc = $dbh->prepare("INSERT INTO cc (bug_id, who) VALUES (?,?)");
- foreach my $person (_to_array($bug_fields{'cc'})) {
- next unless $person;
- my $uid;
- if ($uid = login_to_id($person)) {
- if ( !$ccseen{$uid} ) {
- $sth_cc->execute( $id, $uid );
- $ccseen{$uid} = 1;
- }
- }
- else {
- $err .= "CC member $person does not have an account here\n";
- }
- }
+ }
+
+ # Insert values of custom multi-select fields. They have already
+ # been validated.
+ foreach my $custom_field (keys %multi_select_fields) {
+ my $sth = $dbh->prepare(
+ "INSERT INTO bug_$custom_field
+ (bug_id, value) VALUES (?, ?)"
+ );
+ foreach my $value (@{$multi_select_fields{$custom_field}}) {
+ $sth->execute($id, $value);
}
-
- # Handle keywords
- if ( defined( $bug_fields{'keywords'} ) ) {
- my %keywordseen;
- my $key_sth = $dbh->prepare(
- "INSERT INTO keywords
- (bug_id, keywordid) VALUES (?,?)"
- );
- foreach my $keyword ( split( /[\s,]+/, $bug_fields{'keywords'} )) {
- next unless $keyword;
- my $keyword_obj = new Bugzilla::Keyword({name => $keyword});
- if (!$keyword_obj) {
- $err .= "Skipping unknown keyword: $keyword.\n";
- next;
- }
- if (!$keywordseen{$keyword_obj->id}) {
- $key_sth->execute($id, $keyword_obj->id);
- $keywordseen{$keyword_obj->id} = 1;
- }
- }
+ }
+
+ # Parse bug flags
+ foreach my $bflag ($bug->children('flag')) {
+ next unless (defined($bflag));
+ $err .= flag_handler(
+ $bflag->{'att'}->{'name'}, $bflag->{'att'}->{'status'},
+ $bflag->{'att'}->{'setter'}, $bflag->{'att'}->{'requestee'},
+ $exporterid, $id,
+ $comp_id, $prod_id,
+ undef
+ );
+ }
+
+ # Insert Attachments for the bug
+ foreach my $att (@attachments) {
+ if ($att eq "err") {
+ $err .= "No attachment ID specified, dropping attachment\n";
+ next;
}
- # Insert values of custom multi-select fields. They have already
- # been validated.
- foreach my $custom_field (keys %multi_select_fields) {
- my $sth = $dbh->prepare("INSERT INTO bug_$custom_field
- (bug_id, value) VALUES (?, ?)");
- foreach my $value (@{$multi_select_fields{$custom_field}}) {
- $sth->execute($id, $value);
- }
+ my $attacher;
+ if ($att->{'attacher'}) {
+ $attacher = Bugzilla::User->new({name => $att->{'attacher'}, cache => 1});
}
+ my $new_attacher = $attacher || $exporter;
- # Parse bug flags
- foreach my $bflag ( $bug->children('flag')) {
- next unless ( defined($bflag) );
- $err .= flag_handler(
- $bflag->{'att'}->{'name'}, $bflag->{'att'}->{'status'},
- $bflag->{'att'}->{'setter'}, $bflag->{'att'}->{'requestee'},
- $exporterid, $id,
- $comp_id, $prod_id,
- undef
- );
+ if ($att->{'isprivate'} && !$new_attacher->is_insider) {
+ my $who = $new_attacher->login;
+ $err .= "$who not in insidergroup and attachment marked private.\n";
+ $err .= " Marking attachment public\n";
+ $att->{'isprivate'} = 0;
}
- # Insert Attachments for the bug
- foreach my $att (@attachments) {
- if ($att eq "err"){
- $err .= "No attachment ID specified, dropping attachment\n";
- next;
- }
-
- my $attacher;
- if ($att->{'attacher'}) {
- $attacher = Bugzilla::User->new({name => $att->{'attacher'}, cache => 1});
- }
- my $new_attacher = $attacher || $exporter;
-
- if ($att->{'isprivate'} && !$new_attacher->is_insider) {
- my $who = $new_attacher->login;
- $err .= "$who not in insidergroup and attachment marked private.\n";
- $err .= " Marking attachment public\n";
- $att->{'isprivate'} = 0;
- }
-
- # We log in the user so that the attachment creator is set correctly.
- Bugzilla->set_user($new_attacher);
-
- my $attachment = Bugzilla::Attachment->create(
- { bug => $bug_obj,
- creation_ts => $att->{date},
- data => $att->{data},
- description => $att->{desc},
- filename => $att->{filename},
- ispatch => $att->{ispatch},
- isprivate => $att->{isprivate},
- isobsolete => $att->{isobsolete},
- mimetype => $att->{ctype},
- });
- my $att_id = $attachment->id;
-
- # We log out the attacher as the remaining steps are not on his behalf.
- Bugzilla->logout_request;
-
- $comments .= "Imported an attachment (id=$att_id)\n";
- if (!$attacher) {
- if ($att->{'attacher'}) {
- $err .= "The original submitter of attachment $att_id was\n ";
- $err .= $att->{'attacher'} . ", but they don't have an account here.\n";
- }
- else {
- $err .= "The original submitter of attachment $att_id is unknown.\n";
- }
- $err .= " Reassigning to the person who moved it here: $exporter_login.\n";
- }
+ # We log in the user so that the attachment creator is set correctly.
+ Bugzilla->set_user($new_attacher);
+
+ my $attachment = Bugzilla::Attachment->create({
+ bug => $bug_obj,
+ creation_ts => $att->{date},
+ data => $att->{data},
+ description => $att->{desc},
+ filename => $att->{filename},
+ ispatch => $att->{ispatch},
+ isprivate => $att->{isprivate},
+ isobsolete => $att->{isobsolete},
+ mimetype => $att->{ctype},
+ });
+ my $att_id = $attachment->id;
+
+ # We log out the attacher as the remaining steps are not on his behalf.
+ Bugzilla->logout_request;
+
+ $comments .= "Imported an attachment (id=$att_id)\n";
+ if (!$attacher) {
+ if ($att->{'attacher'}) {
+ $err .= "The original submitter of attachment $att_id was\n ";
+ $err .= $att->{'attacher'} . ", but they don't have an account here.\n";
+ }
+ else {
+ $err .= "The original submitter of attachment $att_id is unknown.\n";
+ }
+ $err .= " Reassigning to the person who moved it here: $exporter_login.\n";
+ }
- # Process attachment flags
- foreach my $aflag (@{ $att->{'flags'} }) {
- next unless defined($aflag) ;
- $err .= flag_handler(
- $aflag->{'name'}, $aflag->{'status'},
- $aflag->{'setter'}, $aflag->{'requestee'},
- $exporterid, $id,
- $comp_id, $prod_id,
- $att_id
- );
- }
+ # Process attachment flags
+ foreach my $aflag (@{$att->{'flags'}}) {
+ next unless defined($aflag);
+ $err .= flag_handler(
+ $aflag->{'name'}, $aflag->{'status'}, $aflag->{'setter'},
+ $aflag->{'requestee'}, $exporterid, $id,
+ $comp_id, $prod_id, $att_id
+ );
}
+ }
- # Clear the attachments array for the next bug
- @attachments = ();
+ # Clear the attachments array for the next bug
+ @attachments = ();
- # Insert comments and append any errors
- my $worktime = $bug_fields{'actual_time'} || 0.0;
- $worktime = 0.0 if (!$exporter->is_timetracker);
- $comments .= "\n$err\n" if $err;
+ # Insert comments and append any errors
+ my $worktime = $bug_fields{'actual_time'} || 0.0;
+ $worktime = 0.0 if (!$exporter->is_timetracker);
+ $comments .= "\n$err\n" if $err;
- my $sth_comment =
- $dbh->prepare('INSERT INTO longdescs (bug_id, who, bug_when, isprivate,
+ my $sth_comment = $dbh->prepare(
+ 'INSERT INTO longdescs (bug_id, who, bug_when, isprivate,
thetext, work_time)
- VALUES (?, ?, ?, ?, ?, ?)');
-
- foreach my $c (@sorted_descs) {
- $sth_comment->execute($id, $c->{whoid} || $exporterid, $c->{bug_when},
- $c->{isprivate}, $c->{thetext}, 0);
- }
- $sth_comment->execute($id, $exporterid, $timestamp, 0, $comments, $worktime);
- Bugzilla::Bug->new($id)->_sync_fulltext( new_bug => 1);
-
- # Add this bug to each group of which its product is a member.
- my $sth_group = $dbh->prepare("INSERT INTO bug_group_map (bug_id, group_id)
- VALUES (?, ?)");
- foreach my $group_id ( keys %{ $product->group_controls } ) {
- if ($product->group_controls->{$group_id}->{'membercontrol'} != CONTROLMAPNA
- && $product->group_controls->{$group_id}->{'othercontrol'} != CONTROLMAPNA){
- $sth_group->execute( $id, $group_id );
- }
- }
-
- $log .= "Bug ${url}$bug_fields{'bug_id'} ";
- $log .= "imported as bug $id.\n";
- $log .= $params->{"urlbase"} . "show_bug.cgi?id=$id\n\n";
- if ($err) {
- $log .= "The following problems were encountered while creating bug $id.\n";
- $log .= $err;
- $log .= "You may have to set certain fields in the new bug by hand.\n\n";
+ VALUES (?, ?, ?, ?, ?, ?)'
+ );
+
+ foreach my $c (@sorted_descs) {
+ $sth_comment->execute($id, $c->{whoid} || $exporterid,
+ $c->{bug_when}, $c->{isprivate}, $c->{thetext}, 0);
+ }
+ $sth_comment->execute($id, $exporterid, $timestamp, 0, $comments, $worktime);
+ Bugzilla::Bug->new($id)->_sync_fulltext(new_bug => 1);
+
+ # Add this bug to each group of which its product is a member.
+ my $sth_group = $dbh->prepare(
+ "INSERT INTO bug_group_map (bug_id, group_id)
+ VALUES (?, ?)"
+ );
+ foreach my $group_id (keys %{$product->group_controls}) {
+ if ( $product->group_controls->{$group_id}->{'membercontrol'} != CONTROLMAPNA
+ && $product->group_controls->{$group_id}->{'othercontrol'} != CONTROLMAPNA)
+ {
+ $sth_group->execute($id, $group_id);
}
- Debug( $log, OK_LEVEL );
- push(@logs, $log);
- Bugzilla::BugMail::Send( $id, { 'changer' => $exporter } ) if ($mail);
-
- # done with the xml data. Lets clear it from memory
- $twig->purge;
+ }
+
+ $log .= "Bug ${url}$bug_fields{'bug_id'} ";
+ $log .= "imported as bug $id.\n";
+ $log .= $params->{"urlbase"} . "show_bug.cgi?id=$id\n\n";
+ if ($err) {
+ $log .= "The following problems were encountered while creating bug $id.\n";
+ $log .= $err;
+ $log .= "You may have to set certain fields in the new bug by hand.\n\n";
+ }
+ Debug($log, OK_LEVEL);
+ push(@logs, $log);
+ Bugzilla::BugMail::Send($id, {'changer' => $exporter}) if ($mail);
+
+ # done with the xml data. Lets clear it from memory
+ $twig->purge;
}
-Debug( "Reading xml", DEBUG_LEVEL );
+Debug("Reading xml", DEBUG_LEVEL);
# Read STDIN in slurp mode. VERY dangerous, but we live on the wild side ;-)
local ($/);
@@ -1251,30 +1283,28 @@ $xml = <>;
# If there's anything except whitespace before <?xml then we guess it's a mail
# and MIME::Parser should parse it. Else don't.
-if ($xml =~ m/\S.*<\?xml/s ) {
+if ($xml =~ m/\S.*<\?xml/s) {
- # If the email was encoded (Mailer::MessageToMTA() does it when using UTF-8),
- # we have to decode it first, else the XML parsing will fail.
- my $parser = MIME::Parser->new;
- $parser->output_to_core(1);
- $parser->tmp_to_core(1);
- my $entity = $parser->parse_data($xml);
- my $bodyhandle = $entity->bodyhandle;
- $xml = $bodyhandle->as_string;
+ # If the email was encoded (Mailer::MessageToMTA() does it when using UTF-8),
+ # we have to decode it first, else the XML parsing will fail.
+ my $parser = MIME::Parser->new;
+ $parser->output_to_core(1);
+ $parser->tmp_to_core(1);
+ my $entity = $parser->parse_data($xml);
+ my $bodyhandle = $entity->bodyhandle;
+ $xml = $bodyhandle->as_string;
}
# remove everything in file before xml header
$xml =~ s/^.+(<\?xml version.+)$/$1/s;
-Debug( "Parsing tree", DEBUG_LEVEL );
+Debug("Parsing tree", DEBUG_LEVEL);
my $twig = XML::Twig->new(
- twig_handlers => {
- bug => \&process_bug,
- attachment => \&process_attachment
- },
- start_tag_handlers => { bugzilla => \&init }
+ twig_handlers => {bug => \&process_bug, attachment => \&process_attachment},
+ start_tag_handlers => {bugzilla => \&init}
);
+
# Prevent DoS using the billion laughs attack.
$twig->{NoExpand} = 1;
@@ -1286,11 +1316,11 @@ my $urlbase = $root->{'att'}->{'urlbase'};
# It is time to email the result of the import.
my $log = join("\n\n", @logs);
-$log .= "\n\nImported $bugtotal bug(s) from $urlbase,\n sent by $exporter.\n";
-my $subject = "$bugtotal Bug(s) successfully moved from $urlbase to "
- . $params->{"urlbase"};
+$log .= "\n\nImported $bugtotal bug(s) from $urlbase,\n sent by $exporter.\n";
+my $subject = "$bugtotal Bug(s) successfully moved from $urlbase to "
+ . $params->{"urlbase"};
my @to = ($exporter, $maintainer);
-MailMessage( $subject, $log, @to );
+MailMessage($subject, $log, @to);
__END__
diff --git a/index.cgi b/index.cgi
index 15d34451d..474c5c7ce 100755
--- a/index.cgi
+++ b/index.cgi
@@ -18,46 +18,52 @@ use Bugzilla::Error;
use Bugzilla::Update;
# Check whether or not the user is logged in
-my $user = Bugzilla->login(LOGIN_OPTIONAL);
-my $cgi = Bugzilla->cgi;
+my $user = Bugzilla->login(LOGIN_OPTIONAL);
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
# And log out the user if requested. We do this first so that nothing
# else accidentally relies on the current login.
if ($cgi->param('logout')) {
- Bugzilla->logout();
- $user = Bugzilla->user;
- $vars->{'message'} = "logged_out";
- # Make sure that templates or other code doesn't get confused about this.
- $cgi->delete('logout');
+ Bugzilla->logout();
+ $user = Bugzilla->user;
+ $vars->{'message'} = "logged_out";
+
+ # Make sure that templates or other code doesn't get confused about this.
+ $cgi->delete('logout');
}
# Return the appropriate HTTP response headers.
print $cgi->header();
if ($user->in_group('admin')) {
- # If 'urlbase' is not set, display the Welcome page.
- unless (Bugzilla->params->{'urlbase'}) {
- $template->process('welcome-admin.html.tmpl')
- || ThrowTemplateError($template->error());
- exit;
- }
- # Inform the administrator about new releases, if any.
- $vars->{'release'} = Bugzilla::Update::get_notifications();
+
+ # If 'urlbase' is not set, display the Welcome page.
+ unless (Bugzilla->params->{'urlbase'}) {
+ $template->process('welcome-admin.html.tmpl')
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+
+ # Inform the administrator about new releases, if any.
+ $vars->{'release'} = Bugzilla::Update::get_notifications();
}
if ($user->id) {
- my $dbh = Bugzilla->dbh;
- $vars->{assignee_count} =
- $dbh->selectrow_array("SELECT COUNT(*) FROM bugs WHERE assigned_to = ?
- AND resolution = ''", undef, $user->id);
- $vars->{reporter_count} =
- $dbh->selectrow_array("SELECT COUNT(*) FROM bugs WHERE reporter = ?
- AND resolution = ''", undef, $user->id);
- $vars->{requestee_count} =
- $dbh->selectrow_array('SELECT COUNT(DISTINCT bug_id) FROM flags
- WHERE requestee_id = ?', undef, $user->id);
+ my $dbh = Bugzilla->dbh;
+ $vars->{assignee_count} = $dbh->selectrow_array(
+ "SELECT COUNT(*) FROM bugs WHERE assigned_to = ?
+ AND resolution = ''", undef, $user->id
+ );
+ $vars->{reporter_count} = $dbh->selectrow_array(
+ "SELECT COUNT(*) FROM bugs WHERE reporter = ?
+ AND resolution = ''", undef, $user->id
+ );
+ $vars->{requestee_count} = $dbh->selectrow_array(
+ 'SELECT COUNT(DISTINCT bug_id) FROM flags
+ WHERE requestee_id = ?', undef, $user->id
+ );
}
# Generate and return the UI (HTML page) from the appropriate template.
diff --git a/install-module.pl b/install-module.pl
index 365f10bbf..972b4baca 100755
--- a/install-module.pl
+++ b/install-module.pl
@@ -32,69 +32,69 @@ use Pod::Usage;
init_console();
my @original_args = @ARGV;
-my $original_dir = cwd();
+my $original_dir = cwd();
our %switch;
GetOptions(\%switch, 'all|a', 'upgrade-all|u', 'show-config|s', 'global|g',
- 'shell', 'help|h');
+ 'shell', 'help|h');
-pod2usage({ -verbose => 1 }) if $switch{'help'};
+pod2usage({-verbose => 1}) if $switch{'help'};
if (ON_ACTIVESTATE) {
- print <<END;
+ print <<END;
You cannot run this script when using ActiveState Perl. Please follow
the instructions given by checksetup.pl to install missing Perl modules.
END
- exit;
+ exit;
}
-pod2usage({ -verbose => 0 }) if (!%switch && !@ARGV);
+pod2usage({-verbose => 0}) if (!%switch && !@ARGV);
set_cpan_config($switch{'global'});
if ($switch{'show-config'}) {
- print Dumper($CPAN::Config);
- exit;
+ print Dumper($CPAN::Config);
+ exit;
}
check_cpan_requirements($original_dir, \@original_args);
if ($switch{'shell'}) {
- CPAN::shell();
- exit;
+ CPAN::shell();
+ exit;
}
if ($switch{'all'} || $switch{'upgrade-all'}) {
- my @modules;
- if ($switch{'upgrade-all'}) {
- @modules = (@{REQUIRED_MODULES()}, @{OPTIONAL_MODULES()});
- push(@modules, DB_MODULE->{$_}->{dbd}) foreach (keys %{DB_MODULE()});
- }
- else {
- # This is the only time we need a Bugzilla-related module, so
- # we require them down here. Otherwise this script can be run from
- # any directory, even outside of Bugzilla itself.
- my $reqs = check_requirements(0);
- @modules = (@{$reqs->{missing}}, @{$reqs->{optional}});
- my $dbs = DB_MODULE;
- foreach my $db (keys %$dbs) {
- push(@modules, $dbs->{$db}->{dbd})
- if !have_vers($dbs->{$db}->{dbd}, 0);
- }
- }
- foreach my $module (@modules) {
- my $cpan_name = $module->{module};
- # --all shouldn't include mod_perl2, because it can have some complex
- # configuration, and really should be installed on its own.
- next if $cpan_name eq 'mod_perl2';
- next if $cpan_name eq 'DBD::Oracle' and !$ENV{ORACLE_HOME};
- next if $cpan_name eq 'DBD::Pg' and !bin_loc('pg_config');
- install_module($cpan_name);
+ my @modules;
+ if ($switch{'upgrade-all'}) {
+ @modules = (@{REQUIRED_MODULES()}, @{OPTIONAL_MODULES()});
+ push(@modules, DB_MODULE->{$_}->{dbd}) foreach (keys %{DB_MODULE()});
+ }
+ else {
+ # This is the only time we need a Bugzilla-related module, so
+ # we require them down here. Otherwise this script can be run from
+ # any directory, even outside of Bugzilla itself.
+ my $reqs = check_requirements(0);
+ @modules = (@{$reqs->{missing}}, @{$reqs->{optional}});
+ my $dbs = DB_MODULE;
+ foreach my $db (keys %$dbs) {
+ push(@modules, $dbs->{$db}->{dbd}) if !have_vers($dbs->{$db}->{dbd}, 0);
}
+ }
+ foreach my $module (@modules) {
+ my $cpan_name = $module->{module};
+
+ # --all shouldn't include mod_perl2, because it can have some complex
+ # configuration, and really should be installed on its own.
+ next if $cpan_name eq 'mod_perl2';
+ next if $cpan_name eq 'DBD::Oracle' and !$ENV{ORACLE_HOME};
+ next if $cpan_name eq 'DBD::Pg' and !bin_loc('pg_config');
+ install_module($cpan_name);
+ }
}
foreach my $module (@ARGV) {
- install_module($module);
+ install_module($module);
}
__END__
diff --git a/jobqueue.pl b/jobqueue.pl
index f6722467c..310cf4d26 100755
--- a/jobqueue.pl
+++ b/jobqueue.pl
@@ -12,10 +12,11 @@ use warnings;
use Cwd qw(abs_path);
use File::Basename;
+
BEGIN {
- # Untaint the abs_path.
- my ($a) = abs_path($0) =~ /^(.*)$/;
- chdir dirname($a);
+ # Untaint the abs_path.
+ my ($a) = abs_path($0) =~ /^(.*)$/;
+ chdir dirname($a);
}
use lib qw(. lib);
diff --git a/jsonrpc.cgi b/jsonrpc.cgi
index 2c93585b0..6933e26cb 100755
--- a/jsonrpc.cgi
+++ b/jsonrpc.cgi
@@ -16,10 +16,11 @@ use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::WebService::Constants;
+
BEGIN {
- if (!Bugzilla->feature('jsonrpc')) {
- ThrowUserError('feature_disabled', { feature => 'jsonrpc' });
- }
+ if (!Bugzilla->feature('jsonrpc')) {
+ ThrowUserError('feature_disabled', {feature => 'jsonrpc'});
+ }
}
use Bugzilla::WebService::Server::JSONRPC;
diff --git a/migrate.pl b/migrate.pl
index eb2a84b59..061509027 100755
--- a/migrate.pl
+++ b/migrate.pl
@@ -25,7 +25,7 @@ GetOptions(\%switch, 'help|h|?', 'from=s', 'verbose|v+', 'dry-run');
# Print the help message if that switch was selected or if --from
# wasn't specified.
if (!$switch{'from'} or $switch{'help'}) {
- pod2usage({-exitval => 1});
+ pod2usage({-exitval => 1});
}
my $migrator = Bugzilla::Migrate->load($switch{'from'});
@@ -37,13 +37,13 @@ $migrator->do_migration();
# Even if there's an error, we want to be sure that the serial values
# get reset properly.
END {
- if ($migrator and $migrator->dry_run) {
- my $dbh = Bugzilla->dbh;
- if ($dbh->bz_in_transaction) {
- $dbh->bz_rollback_transaction();
- }
- $migrator->reset_serial_values();
+ if ($migrator and $migrator->dry_run) {
+ my $dbh = Bugzilla->dbh;
+ if ($dbh->bz_in_transaction) {
+ $dbh->bz_rollback_transaction();
}
+ $migrator->reset_serial_values();
+ }
}
__END__
diff --git a/mod_perl.pl b/mod_perl.pl
index 80dff2304..642a92149 100644
--- a/mod_perl.pl
+++ b/mod_perl.pl
@@ -31,18 +31,19 @@ use lib Bugzilla::Constants::bz_locations()->{'ext_libpath'};
use Apache2::Log ();
use Apache2::ServerUtil;
use ModPerl::RegistryLoader ();
-use File::Basename ();
-use DateTime ();
+use File::Basename ();
+use DateTime ();
# This loads most of our modules.
use Bugzilla ();
+
# Loading Bugzilla.pm doesn't load this, though, and we want it preloaded.
-use Bugzilla::BugMail ();
-use Bugzilla::CGI ();
-use Bugzilla::Extension ();
+use Bugzilla::BugMail ();
+use Bugzilla::CGI ();
+use Bugzilla::Extension ();
use Bugzilla::Install::Requirements ();
-use Bugzilla::Util ();
-use Bugzilla::RNG ();
+use Bugzilla::Util ();
+use Bugzilla::RNG ();
# Make warnings go to the virtual host's log and not the main
# server log.
@@ -52,6 +53,7 @@ BEGIN { *CORE::GLOBAL::warn = \&Apache2::ServerRec::warn; }
Bugzilla::CGI->compile(qw(:cgi :push));
use Apache2::SizeLimit;
+
# This means that every httpd child will die after processing
# a CGI if it is taking up more than 45MB of RAM all by itself,
# not counting RAM it is sharing with the other httpd processes.
@@ -62,7 +64,7 @@ my $cgi_path = Bugzilla::Constants::bz_locations()->{'cgi_path'};
# Set up the configuration for the web server
my $server = Apache2::ServerUtil->server;
-my $conf = <<EOT;
+my $conf = <<EOT;
# Make sure each httpd child receives a different random seed (bug 476622).
# Bugzilla::RNG has one srand that needs to be called for
# every process, and Perl has another. (Various Perl modules still use
@@ -88,7 +90,8 @@ $Bugzilla::extension_packages = Bugzilla::Extension->load_all();
# Have ModPerl::RegistryLoader pre-compile all CGI scripts.
my $rl = new ModPerl::RegistryLoader();
-# If we try to do this in "new" it fails because it looks for a
+
+# If we try to do this in "new" it fails because it looks for a
# Bugzilla/ModPerl/ResponseHandler.pm
$rl->{package} = 'Bugzilla::ModPerl::ResponseHandler';
my $feature_files = Bugzilla::Install::Requirements::map_files_to_features();
@@ -97,16 +100,16 @@ my $feature_files = Bugzilla::Install::Requirements::map_files_to_features();
# This is important to prevent the current directory from getting into
# @INC and messing things up. (See bug 630750.)
no warnings 'redefine';
-local *lib::import = sub {};
+local *lib::import = sub { };
use warnings;
foreach my $file (glob "$cgi_path/*.cgi") {
- my $base_filename = File::Basename::basename($file);
- if (my $feature = $feature_files->{$base_filename}) {
- next if !Bugzilla->feature($feature);
- }
- Bugzilla::Util::trick_taint($file);
- $rl->handler($file, $file);
+ my $base_filename = File::Basename::basename($file);
+ if (my $feature = $feature_files->{$base_filename}) {
+ next if !Bugzilla->feature($feature);
+ }
+ Bugzilla::Util::trick_taint($file);
+ $rl->handler($file, $file);
}
package Bugzilla::ModPerl::ResponseHandler;
@@ -120,29 +123,28 @@ use Bugzilla;
use Bugzilla::Constants qw(USAGE_MODE_REST);
sub handler : method {
- my $class = shift;
-
- # $0 is broken under mod_perl before 2.0.2, so we have to set it
- # here explicitly or init_page's shutdownhtml code won't work right.
- $0 = $ENV{'SCRIPT_FILENAME'};
-
- # Prevent "use lib" from modifying @INC in the case where a .cgi file
- # is being automatically recompiled by mod_perl when Apache is
- # running. (This happens if a file changes while Apache is already
- # running.)
- no warnings 'redefine';
- local *lib::import = sub {};
- use warnings;
-
- Bugzilla::init_page();
- my $result = $class->SUPER::handler(@_);
-
- # When returning data from the REST api we must only return 200 or 304,
- # which tells Apache not to append its error html documents to the
- # response.
- return Bugzilla->usage_mode == USAGE_MODE_REST && $result != 304
- ? Apache2::Const::OK
- : $result;
+ my $class = shift;
+
+ # $0 is broken under mod_perl before 2.0.2, so we have to set it
+ # here explicitly or init_page's shutdownhtml code won't work right.
+ $0 = $ENV{'SCRIPT_FILENAME'};
+
+ # Prevent "use lib" from modifying @INC in the case where a .cgi file
+ # is being automatically recompiled by mod_perl when Apache is
+ # running. (This happens if a file changes while Apache is already
+ # running.)
+ no warnings 'redefine';
+ local *lib::import = sub { };
+ use warnings;
+
+ Bugzilla::init_page();
+ my $result = $class->SUPER::handler(@_);
+
+ # When returning data from the REST api we must only return 200 or 304,
+ # which tells Apache not to append its error html documents to the
+ # response.
+ return Bugzilla->usage_mode == USAGE_MODE_REST
+ && $result != 304 ? Apache2::Const::OK : $result;
}
@@ -155,16 +157,17 @@ use warnings;
use Apache2::Const -compile => qw(OK);
sub handler {
- my $r = shift;
+ my $r = shift;
+
+ Bugzilla::_cleanup();
- Bugzilla::_cleanup();
- # Sometimes mod_perl doesn't properly call DESTROY on all
- # the objects in pnotes()
- foreach my $key (keys %{$r->pnotes}) {
- delete $r->pnotes->{$key};
- }
+ # Sometimes mod_perl doesn't properly call DESTROY on all
+ # the objects in pnotes()
+ foreach my $key (keys %{$r->pnotes}) {
+ delete $r->pnotes->{$key};
+ }
- return Apache2::Const::OK;
+ return Apache2::Const::OK;
}
1;
diff --git a/page.cgi b/page.cgi
index b60ce8bde..930eb58df 100755
--- a/page.cgi
+++ b/page.cgi
@@ -30,16 +30,17 @@ use Bugzilla::Search::Quicksearch;
# For quicksearch.html.
sub quicksearch_field_names {
- my $fields = Bugzilla::Search::Quicksearch->FIELD_MAP;
- my %fields_reverse;
- # Put longer names before shorter names.
- my @nicknames = sort { length($b) <=> length($a) } (keys %$fields);
- foreach my $nickname (@nicknames) {
- my $db_field = $fields->{$nickname};
- $fields_reverse{$db_field} ||= [];
- push(@{ $fields_reverse{$db_field} }, $nickname);
- }
- return \%fields_reverse;
+ my $fields = Bugzilla::Search::Quicksearch->FIELD_MAP;
+ my %fields_reverse;
+
+ # Put longer names before shorter names.
+ my @nicknames = sort { length($b) <=> length($a) } (keys %$fields);
+ foreach my $nickname (@nicknames) {
+ my $db_field = $fields->{$nickname};
+ $fields_reverse{$db_field} ||= [];
+ push(@{$fields_reverse{$db_field}}, $nickname);
+ }
+ return \%fields_reverse;
}
###############
@@ -48,38 +49,40 @@ sub quicksearch_field_names {
Bugzilla->login();
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
my $id = $cgi->param('id');
if ($id) {
- # Be careful not to allow directory traversal.
- if ($id =~ /\.\./) {
- # two dots in a row is bad
- ThrowUserError("bad_page_cgi_id", { "page_id" => $id });
- }
- # Split into name and ctype.
- $id =~ /^([\w\-\/\.]+)\.(\w+)$/;
- if (!$2) {
- # if this regexp fails to match completely, something bad came in
- ThrowUserError("bad_page_cgi_id", { "page_id" => $id });
- }
-
- my %vars = (
- quicksearch_field_names => \&quicksearch_field_names,
- );
- Bugzilla::Hook::process('page_before_template',
- { page_id => $id, vars => \%vars });
-
- my $format = $template->get_format("pages/$1", undef, $2);
-
- $cgi->param('id', $id);
-
- print $cgi->header($format->{'ctype'});
-
- $template->process("$format->{'template'}", \%vars)
- || ThrowTemplateError($template->error());
+
+ # Be careful not to allow directory traversal.
+ if ($id =~ /\.\./) {
+
+ # two dots in a row is bad
+ ThrowUserError("bad_page_cgi_id", {"page_id" => $id});
+ }
+
+ # Split into name and ctype.
+ $id =~ /^([\w\-\/\.]+)\.(\w+)$/;
+ if (!$2) {
+
+ # if this regexp fails to match completely, something bad came in
+ ThrowUserError("bad_page_cgi_id", {"page_id" => $id});
+ }
+
+ my %vars = (quicksearch_field_names => \&quicksearch_field_names,);
+ Bugzilla::Hook::process('page_before_template',
+ {page_id => $id, vars => \%vars});
+
+ my $format = $template->get_format("pages/$1", undef, $2);
+
+ $cgi->param('id', $id);
+
+ print $cgi->header($format->{'ctype'});
+
+ $template->process("$format->{'template'}", \%vars)
+ || ThrowTemplateError($template->error());
}
else {
- ThrowUserError("no_page_specified");
+ ThrowUserError("no_page_specified");
}
diff --git a/post_bug.cgi b/post_bug.cgi
index ca029c01a..757db7fdf 100755
--- a/post_bug.cgi
+++ b/post_bug.cgi
@@ -28,10 +28,10 @@ use List::MoreUtils qw(uniq);
my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
######################################################################
# Main Script
@@ -39,8 +39,8 @@ my $vars = {};
# redirect to enter_bug if no field is passed.
unless ($cgi->param()) {
- print $cgi->redirect(correct_urlbase() . 'enter_bug.cgi');
- exit;
+ print $cgi->redirect(correct_urlbase() . 'enter_bug.cgi');
+ exit;
}
# Detect if the user already used the same form to submit a bug
@@ -48,40 +48,43 @@ my $token = trim($cgi->param('token'));
check_token_data($token, 'create_bug', 'index.cgi');
# do a match on the fields if applicable
-Bugzilla::User::match_field ({
- 'cc' => { 'type' => 'multi' },
- 'assigned_to' => { 'type' => 'single' },
- 'qa_contact' => { 'type' => 'single' },
+Bugzilla::User::match_field({
+ 'cc' => {'type' => 'multi'},
+ 'assigned_to' => {'type' => 'single'},
+ 'qa_contact' => {'type' => 'single'},
});
if (defined $cgi->param('maketemplate')) {
- $vars->{'url'} = $cgi->canonicalise_query('token');
- $vars->{'short_desc'} = $cgi->param('short_desc');
-
- print $cgi->header();
- $template->process("bug/create/make-template.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $vars->{'url'} = $cgi->canonicalise_query('token');
+ $vars->{'short_desc'} = $cgi->param('short_desc');
+
+ print $cgi->header();
+ $template->process("bug/create/make-template.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
# The format of the initial comment can be structured by adding fields to the
# enter_bug template and then referencing them in the comment template.
my $comment;
-my $format = $template->get_format("bug/create/comment",
- scalar($cgi->param('format')), "txt");
+my $format
+ = $template->get_format("bug/create/comment", scalar($cgi->param('format')),
+ "txt");
$template->process($format->{'template'}, $vars, \$comment)
- || ThrowTemplateError($template->error());
+ || ThrowTemplateError($template->error());
# Include custom fields editable on bug creation.
-my @custom_bug_fields = grep {$_->type != FIELD_TYPE_MULTI_SELECT && $_->enter_bug}
- Bugzilla->active_custom_fields;
+my @custom_bug_fields
+ = grep { $_->type != FIELD_TYPE_MULTI_SELECT && $_->enter_bug }
+ Bugzilla->active_custom_fields;
# Undefined custom fields are ignored to ensure they will get their default
# value (e.g. "---" for custom single select fields).
my @bug_fields = grep { defined $cgi->param($_->name) } @custom_bug_fields;
@bug_fields = map { $_->name } @bug_fields;
-push(@bug_fields, qw(
+push(
+ @bug_fields, qw(
product
component
@@ -106,23 +109,25 @@ push(@bug_fields, qw(
see_also
estimated_time
deadline
-));
+ )
+);
my %bug_params;
foreach my $field (@bug_fields) {
- $bug_params{$field} = $cgi->param($field);
+ $bug_params{$field} = $cgi->param($field);
}
foreach my $field (qw(cc groups)) {
- next if !$cgi->should_set($field);
- $bug_params{$field} = [$cgi->param($field)];
+ next if !$cgi->should_set($field);
+ $bug_params{$field} = [$cgi->param($field)];
}
$bug_params{'comment'} = $comment;
-my @multi_selects = grep {$_->type == FIELD_TYPE_MULTI_SELECT && $_->enter_bug}
- Bugzilla->active_custom_fields;
+my @multi_selects
+ = grep { $_->type == FIELD_TYPE_MULTI_SELECT && $_->enter_bug }
+ Bugzilla->active_custom_fields;
foreach my $field (@multi_selects) {
- next if !$cgi->should_set($field->name);
- $bug_params{$field->name} = [$cgi->param($field->name)];
+ next if !$cgi->should_set($field->name);
+ $bug_params{$field->name} = [$cgi->param($field->name)];
}
my $bug = Bugzilla::Bug->create(\%bug_params);
@@ -133,15 +138,18 @@ delete_token($token);
# We do this directly from the DB because $bug->creation_ts has the seconds
# formatted out of it (which should be fixed some day).
-my $timestamp = $dbh->selectrow_array(
- 'SELECT creation_ts FROM bugs WHERE bug_id = ?', undef, $id);
+my $timestamp
+ = $dbh->selectrow_array('SELECT creation_ts FROM bugs WHERE bug_id = ?',
+ undef, $id);
# Set Version cookie, but only if the user actually selected
# a version on the page.
if (defined $cgi->param('version')) {
- $cgi->send_cookie(-name => "VERSION-" . $bug->product,
- -value => $bug->version,
- -expires => "Fri, 01-Jan-2038 00:00:00 GMT");
+ $cgi->send_cookie(
+ -name => "VERSION-" . $bug->product,
+ -value => $bug->version,
+ -expires => "Fri, 01-Jan-2038 00:00:00 GMT"
+ );
}
# We don't have to check if the user can see the bug, because a user filing
@@ -149,88 +157,90 @@ if (defined $cgi->param('version')) {
# after the bug is filed.
# Add an attachment if requested.
-my $data_fh = $cgi->upload('data');
+my $data_fh = $cgi->upload('data');
my $attach_text = $cgi->param('attach_text');
if ($data_fh || $attach_text) {
- $cgi->param('isprivate', $cgi->param('comment_is_private'));
-
- # Must be called before create() as it may alter $cgi->param('ispatch').
- my $content_type = Bugzilla::Attachment::get_content_type();
- my $attachment;
-
- # If the attachment cannot be successfully added to the bug,
- # we notify the user, but we don't interrupt the bug creation process.
- my $error_mode_cache = Bugzilla->error_mode;
- Bugzilla->error_mode(ERROR_MODE_DIE);
- eval {
- $attachment = Bugzilla::Attachment->create(
- {bug => $bug,
- creation_ts => $timestamp,
- data => $attach_text || $data_fh,
- description => scalar $cgi->param('description'),
- filename => $attach_text ? "file_$id.txt" : $data_fh,
- ispatch => scalar $cgi->param('ispatch'),
- isprivate => scalar $cgi->param('isprivate'),
- mimetype => $content_type,
- });
- };
- Bugzilla->error_mode($error_mode_cache);
-
- if ($attachment) {
- # Set attachment flags.
- my ($flags, $new_flags) = Bugzilla::Flag->extract_flags_from_cgi(
- $bug, $attachment, $vars, SKIP_REQUESTEE_ON_ERROR);
- $attachment->set_flags($flags, $new_flags);
- $attachment->update($timestamp);
- my $comment = $bug->comments->[0];
- $comment->set_all({ type => CMT_ATTACHMENT_CREATED,
- extra_data => $attachment->id });
- $comment->update();
- }
- else {
- $vars->{'message'} = 'attachment_creation_failed';
- }
+ $cgi->param('isprivate', $cgi->param('comment_is_private'));
+
+ # Must be called before create() as it may alter $cgi->param('ispatch').
+ my $content_type = Bugzilla::Attachment::get_content_type();
+ my $attachment;
+
+ # If the attachment cannot be successfully added to the bug,
+ # we notify the user, but we don't interrupt the bug creation process.
+ my $error_mode_cache = Bugzilla->error_mode;
+ Bugzilla->error_mode(ERROR_MODE_DIE);
+ eval {
+ $attachment = Bugzilla::Attachment->create({
+ bug => $bug,
+ creation_ts => $timestamp,
+ data => $attach_text || $data_fh,
+ description => scalar $cgi->param('description'),
+ filename => $attach_text ? "file_$id.txt" : $data_fh,
+ ispatch => scalar $cgi->param('ispatch'),
+ isprivate => scalar $cgi->param('isprivate'),
+ mimetype => $content_type,
+ });
+ };
+ Bugzilla->error_mode($error_mode_cache);
+
+ if ($attachment) {
+
+ # Set attachment flags.
+ my ($flags, $new_flags)
+ = Bugzilla::Flag->extract_flags_from_cgi($bug, $attachment, $vars,
+ SKIP_REQUESTEE_ON_ERROR);
+ $attachment->set_flags($flags, $new_flags);
+ $attachment->update($timestamp);
+ my $comment = $bug->comments->[0];
+ $comment->set_all(
+ {type => CMT_ATTACHMENT_CREATED, extra_data => $attachment->id});
+ $comment->update();
+ }
+ else {
+ $vars->{'message'} = 'attachment_creation_failed';
+ }
}
# Set bug flags.
-my ($flags, $new_flags) = Bugzilla::Flag->extract_flags_from_cgi($bug, undef, $vars,
- SKIP_REQUESTEE_ON_ERROR);
+my ($flags, $new_flags)
+ = Bugzilla::Flag->extract_flags_from_cgi($bug, undef, $vars,
+ SKIP_REQUESTEE_ON_ERROR);
$bug->set_flags($flags, $new_flags);
$bug->update($timestamp);
-$vars->{'id'} = $id;
+$vars->{'id'} = $id;
$vars->{'bug'} = $bug;
-Bugzilla::Hook::process('post_bug_after_creation', { vars => $vars });
+Bugzilla::Hook::process('post_bug_after_creation', {vars => $vars});
-my $recipients = { changer => $user };
-my $bug_sent = Bugzilla::BugMail::Send($id, $recipients);
+my $recipients = {changer => $user};
+my $bug_sent = Bugzilla::BugMail::Send($id, $recipients);
$bug_sent->{type} = 'created';
$bug_sent->{id} = $id;
my @all_mail_results = ($bug_sent);
foreach my $dep (@{$bug->dependson || []}, @{$bug->blocked || []}) {
- my $dep_sent = Bugzilla::BugMail::Send($dep, $recipients);
- $dep_sent->{type} = 'dep';
- $dep_sent->{id} = $dep;
- push(@all_mail_results, $dep_sent);
+ my $dep_sent = Bugzilla::BugMail::Send($dep, $recipients);
+ $dep_sent->{type} = 'dep';
+ $dep_sent->{id} = $dep;
+ push(@all_mail_results, $dep_sent);
}
# Sending emails for any referenced bugs.
-foreach my $ref_bug_id (uniq @{ $bug->{see_also_changes} || [] }) {
- my $ref_sent = Bugzilla::BugMail::Send($ref_bug_id, $recipients);
- $ref_sent->{id} = $ref_bug_id;
- push(@all_mail_results, $ref_sent);
+foreach my $ref_bug_id (uniq @{$bug->{see_also_changes} || []}) {
+ my $ref_sent = Bugzilla::BugMail::Send($ref_bug_id, $recipients);
+ $ref_sent->{id} = $ref_bug_id;
+ push(@all_mail_results, $ref_sent);
}
$vars->{sentmail} = \@all_mail_results;
$format = $template->get_format("bug/create/created",
- scalar($cgi->param('created-format')),
- "html");
+ scalar($cgi->param('created-format')), "html");
print $cgi->header();
$template->process($format->{'template'}, $vars)
- || ThrowTemplateError($template->error());
+ || ThrowTemplateError($template->error());
1;
diff --git a/process_bug.cgi b/process_bug.cgi
index 0b0ecd64e..3adce62c1 100755
--- a/process_bug.cgi
+++ b/process_bug.cgi
@@ -27,10 +27,10 @@ use Storable qw(dclone);
my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
######################################################################
# Subroutines
@@ -38,18 +38,19 @@ my $vars = {};
# Tells us whether or not a field should be changed by process_bug.
sub should_set {
- # check_defined is used for fields where there's another field
- # whose name starts with "defined_" and then the field name--it's used
- # to know when we did things like empty a multi-select or deselect
- # a checkbox.
- my ($field, $check_defined) = @_;
- my $cgi = Bugzilla->cgi;
- if ( defined $cgi->param($field)
- || ($check_defined && defined $cgi->param("defined_$field")) )
- {
- return 1;
- }
- return 0;
+
+ # check_defined is used for fields where there's another field
+ # whose name starts with "defined_" and then the field name--it's used
+ # to know when we did things like empty a multi-select or deselect
+ # a checkbox.
+ my ($field, $check_defined) = @_;
+ my $cgi = Bugzilla->cgi;
+ if (defined $cgi->param($field)
+ || ($check_defined && defined $cgi->param("defined_$field")))
+ {
+ return 1;
+ }
+ return 0;
}
######################################################################
@@ -62,39 +63,40 @@ if (defined $cgi->param('id')) {
my $bug = Bugzilla::Bug->check_for_edit(scalar $cgi->param('id'));
$cgi->param('id', $bug->id);
push(@bug_objects, $bug);
-} else {
- foreach my $i ($cgi->param()) {
- if ($i =~ /^id_([1-9][0-9]*)/) {
- my $id = $1;
- push(@bug_objects, Bugzilla::Bug->check_for_edit($id));
- }
+}
+else {
+ foreach my $i ($cgi->param()) {
+ if ($i =~ /^id_([1-9][0-9]*)/) {
+ my $id = $1;
+ push(@bug_objects, Bugzilla::Bug->check_for_edit($id));
}
+ }
}
# Make sure there are bugs to process.
scalar(@bug_objects) || ThrowUserError("no_bugs_chosen", {action => 'modify'});
-my $first_bug = $bug_objects[0]; # Used when we're only updating a single bug.
+my $first_bug = $bug_objects[0]; # Used when we're only updating a single bug.
# Delete any parameter set to 'dontchange'.
if (defined $cgi->param('dontchange')) {
- foreach my $name ($cgi->param) {
- next if $name eq 'dontchange'; # But don't delete dontchange itself!
- # Skip ones we've already deleted (such as "defined_$name").
- next if !defined $cgi->param($name);
- if ($cgi->param($name) eq $cgi->param('dontchange')) {
- $cgi->delete($name);
- $cgi->delete("defined_$name");
- }
+ foreach my $name ($cgi->param) {
+ next if $name eq 'dontchange'; # But don't delete dontchange itself!
+ # Skip ones we've already deleted (such as "defined_$name").
+ next if !defined $cgi->param($name);
+ if ($cgi->param($name) eq $cgi->param('dontchange')) {
+ $cgi->delete($name);
+ $cgi->delete("defined_$name");
}
+ }
}
# do a match on the fields if applicable
Bugzilla::User::match_field({
- 'qa_contact' => { 'type' => 'single' },
- 'newcc' => { 'type' => 'multi' },
- 'masscc' => { 'type' => 'multi' },
- 'assigned_to' => { 'type' => 'single' },
+ 'qa_contact' => {'type' => 'single'},
+ 'newcc' => {'type' => 'multi'},
+ 'masscc' => {'type' => 'multi'},
+ 'assigned_to' => {'type' => 'single'},
});
print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_EMAIL;
@@ -104,48 +106,49 @@ print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_EMAIL;
my $delta_ts = $cgi->param('delta_ts') || '';
if ($delta_ts) {
- my $delta_ts_z = datetime_from($delta_ts)
- or ThrowCodeError('invalid_timestamp', { timestamp => $delta_ts });
-
- my $first_delta_tz_z = datetime_from($first_bug->delta_ts);
-
- if ($first_delta_tz_z ne $delta_ts_z) {
- ($vars->{'operations'}) = $first_bug->get_activity(undef, $delta_ts);
-
- # Always sort midair collision comments oldest to newest,
- # regardless of the user's personal preference.
- my $comments = $first_bug->comments({ order => 'oldest_to_newest',
- after => $delta_ts });
-
- # Show midair if previous changes made other than CC
- # and/or one or more comments were made
- my $do_midair = scalar @$comments ? 1 : 0;
-
- if (!$do_midair) {
- foreach my $operation (@{ $vars->{'operations'} }) {
- foreach my $change (@{ $operation->{'changes'} }) {
- if ($change->{'fieldname'} ne 'cc') {
- $do_midair = 1;
- last;
- }
- }
- last if $do_midair;
- }
+ my $delta_ts_z = datetime_from($delta_ts)
+ or ThrowCodeError('invalid_timestamp', {timestamp => $delta_ts});
+
+ my $first_delta_tz_z = datetime_from($first_bug->delta_ts);
+
+ if ($first_delta_tz_z ne $delta_ts_z) {
+ ($vars->{'operations'}) = $first_bug->get_activity(undef, $delta_ts);
+
+ # Always sort midair collision comments oldest to newest,
+ # regardless of the user's personal preference.
+ my $comments
+ = $first_bug->comments({order => 'oldest_to_newest', after => $delta_ts});
+
+ # Show midair if previous changes made other than CC
+ # and/or one or more comments were made
+ my $do_midair = scalar @$comments ? 1 : 0;
+
+ if (!$do_midair) {
+ foreach my $operation (@{$vars->{'operations'}}) {
+ foreach my $change (@{$operation->{'changes'}}) {
+ if ($change->{'fieldname'} ne 'cc') {
+ $do_midair = 1;
+ last;
+ }
}
+ last if $do_midair;
+ }
+ }
- if ($do_midair) {
- $vars->{'title_tag'} = "mid_air";
- $vars->{'comments'} = $comments;
- $vars->{'bug'} = $first_bug;
- # The token contains the old delta_ts. We need a new one.
- $cgi->param('token', issue_hash_token([$first_bug->id, $first_bug->delta_ts]));
-
- # Warn the user about the mid-air collision and ask them what to do.
- $template->process("bug/process/midair.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
+ if ($do_midair) {
+ $vars->{'title_tag'} = "mid_air";
+ $vars->{'comments'} = $comments;
+ $vars->{'bug'} = $first_bug;
+
+ # The token contains the old delta_ts. We need a new one.
+ $cgi->param('token', issue_hash_token([$first_bug->id, $first_bug->delta_ts]));
+
+ # Warn the user about the mid-air collision and ask them what to do.
+ $template->process("bug/process/midair.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
+ }
}
# We couldn't do this check earlier as we first had to validate bug IDs
@@ -154,10 +157,10 @@ if ($delta_ts) {
my $token = $cgi->param('token');
if ($cgi->param('id')) {
- check_hash_token($token, [$first_bug->id, $delta_ts || $first_bug->delta_ts]);
+ check_hash_token($token, [$first_bug->id, $delta_ts || $first_bug->delta_ts]);
}
else {
- check_token_data($token, 'buglist_mass_change', 'query.cgi');
+ check_token_data($token, 'buglist_mass_change', 'query.cgi');
}
######################################################################
@@ -168,263 +171,276 @@ $vars->{'title_tag'} = "bug_processed";
my $action;
if (defined $cgi->param('id')) {
- $action = $user->setting('post_bug_submit_action');
-
- if ($action eq 'next_bug') {
- my $bug_list_obj = $user->recent_search_for($first_bug);
- my @bug_list = $bug_list_obj ? @{$bug_list_obj->bug_list} : ();
- my $cur = firstidx { $_ eq $cgi->param('id') } @bug_list;
- if ($cur >= 0 && $cur < $#bug_list) {
- my $next_bug_id = $bug_list[$cur + 1];
- detaint_natural($next_bug_id);
- if ($next_bug_id and $user->can_see_bug($next_bug_id)) {
- # We create an object here so that $bug->send_changes can use it
- # when displaying the header.
- $vars->{'bug'} = new Bugzilla::Bug($next_bug_id);
- }
- }
- }
- # Include both action = 'same_bug' and 'nothing'.
- else {
- $vars->{'bug'} = $first_bug;
+ $action = $user->setting('post_bug_submit_action');
+
+ if ($action eq 'next_bug') {
+ my $bug_list_obj = $user->recent_search_for($first_bug);
+ my @bug_list = $bug_list_obj ? @{$bug_list_obj->bug_list} : ();
+ my $cur = firstidx { $_ eq $cgi->param('id') } @bug_list;
+ if ($cur >= 0 && $cur < $#bug_list) {
+ my $next_bug_id = $bug_list[$cur + 1];
+ detaint_natural($next_bug_id);
+ if ($next_bug_id and $user->can_see_bug($next_bug_id)) {
+
+ # We create an object here so that $bug->send_changes can use it
+ # when displaying the header.
+ $vars->{'bug'} = new Bugzilla::Bug($next_bug_id);
+ }
}
+ }
+
+ # Include both action = 'same_bug' and 'nothing'.
+ else {
+ $vars->{'bug'} = $first_bug;
+ }
}
else {
- # param('id') is not defined when changing multiple bugs at once.
- $action = 'nothing';
+ # param('id') is not defined when changing multiple bugs at once.
+ $action = 'nothing';
}
# Component, target_milestone, and version are in here just in case
# the 'product' field wasn't defined in the CGI. It doesn't hurt to set
# them twice.
my @set_fields = qw(op_sys rep_platform priority bug_severity
- component target_milestone version
- bug_file_loc status_whiteboard short_desc
- deadline remaining_time estimated_time
- work_time set_default_assignee set_default_qa_contact
- cclist_accessible reporter_accessible
- product confirm_product_change
- bug_status resolution dup_id bug_ignored);
+ component target_milestone version
+ bug_file_loc status_whiteboard short_desc
+ deadline remaining_time estimated_time
+ work_time set_default_assignee set_default_qa_contact
+ cclist_accessible reporter_accessible
+ product confirm_product_change
+ bug_status resolution dup_id bug_ignored);
push(@set_fields, 'assigned_to') if !$cgi->param('set_default_assignee');
push(@set_fields, 'qa_contact') if !$cgi->param('set_default_qa_contact');
my %field_translation = (
- bug_severity => 'severity',
- rep_platform => 'platform',
- short_desc => 'summary',
- bug_file_loc => 'url',
- set_default_assignee => 'reset_assigned_to',
- set_default_qa_contact => 'reset_qa_contact',
- confirm_product_change => 'product_change_confirmed',
+ bug_severity => 'severity',
+ rep_platform => 'platform',
+ short_desc => 'summary',
+ bug_file_loc => 'url',
+ set_default_assignee => 'reset_assigned_to',
+ set_default_qa_contact => 'reset_qa_contact',
+ confirm_product_change => 'product_change_confirmed',
);
-my %set_all_fields = ( other_bugs => \@bug_objects );
+my %set_all_fields = (other_bugs => \@bug_objects);
foreach my $field_name (@set_fields) {
- if (should_set($field_name, 1)) {
- my $param_name = $field_translation{$field_name} || $field_name;
- $set_all_fields{$param_name} = $cgi->param($field_name);
- }
+ if (should_set($field_name, 1)) {
+ my $param_name = $field_translation{$field_name} || $field_name;
+ $set_all_fields{$param_name} = $cgi->param($field_name);
+ }
}
if (should_set('keywords')) {
- my $action = $cgi->param('keywordaction') || '';
- # Backward-compatibility for Bugzilla 3.x and older.
- $action = 'remove' if $action eq 'delete';
- $action = 'set' if $action eq 'makeexact';
- $set_all_fields{keywords}->{$action} = $cgi->param('keywords');
+ my $action = $cgi->param('keywordaction') || '';
+
+ # Backward-compatibility for Bugzilla 3.x and older.
+ $action = 'remove' if $action eq 'delete';
+ $action = 'set' if $action eq 'makeexact';
+ $set_all_fields{keywords}->{$action} = $cgi->param('keywords');
}
if (should_set('comment')) {
- $set_all_fields{comment} = {
- body => scalar $cgi->param('comment'),
- is_private => scalar $cgi->param('comment_is_private'),
- };
+ $set_all_fields{comment} = {
+ body => scalar $cgi->param('comment'),
+ is_private => scalar $cgi->param('comment_is_private'),
+ };
}
if (should_set('see_also')) {
- $set_all_fields{'see_also'}->{add} =
- [split(/[\s]+/, $cgi->param('see_also'))];
+ $set_all_fields{'see_also'}->{add} = [split(/[\s]+/, $cgi->param('see_also'))];
}
if (should_set('remove_see_also')) {
- $set_all_fields{'see_also'}->{remove} = [$cgi->param('remove_see_also')];
+ $set_all_fields{'see_also'}->{remove} = [$cgi->param('remove_see_also')];
}
foreach my $dep_field (qw(dependson blocked)) {
- if (should_set($dep_field)) {
- if (my $dep_action = $cgi->param("${dep_field}_action")) {
- $set_all_fields{$dep_field}->{$dep_action} =
- [split(/[\s,]+/, $cgi->param($dep_field))];
- }
- else {
- $set_all_fields{$dep_field}->{set} = $cgi->param($dep_field);
- }
+ if (should_set($dep_field)) {
+ if (my $dep_action = $cgi->param("${dep_field}_action")) {
+ $set_all_fields{$dep_field}->{$dep_action}
+ = [split(/[\s,]+/, $cgi->param($dep_field))];
}
+ else {
+ $set_all_fields{$dep_field}->{set} = $cgi->param($dep_field);
+ }
+ }
}
+
# Formulate the CC data into two arrays of users involved in this CC change.
-if (defined $cgi->param('newcc')
- or defined $cgi->param('addselfcc')
- or defined $cgi->param('removecc')
- or defined $cgi->param('masscc'))
+if ( defined $cgi->param('newcc')
+ or defined $cgi->param('addselfcc')
+ or defined $cgi->param('removecc')
+ or defined $cgi->param('masscc'))
{
- my (@cc_add, @cc_remove);
- # If masscc is defined, then we came from buglist and need to either add or
- # remove cc's... otherwise, we came from show_bug and may need to do both.
- if (defined $cgi->param('masscc')) {
- if ($cgi->param('ccaction') eq 'add') {
- @cc_add = $cgi->param('masscc');
- } elsif ($cgi->param('ccaction') eq 'remove') {
- @cc_remove = $cgi->param('masscc');
- }
- } else {
- @cc_add = $cgi->param('newcc');
- push(@cc_add, $user) if $cgi->param('addselfcc');
-
- # We came from show_bug which uses a select box to determine what cc's
- # need to be removed...
- if ($cgi->param('removecc') && $cgi->param('cc')) {
- @cc_remove = $cgi->param('cc');
- }
+ my (@cc_add, @cc_remove);
+
+ # If masscc is defined, then we came from buglist and need to either add or
+ # remove cc's... otherwise, we came from show_bug and may need to do both.
+ if (defined $cgi->param('masscc')) {
+ if ($cgi->param('ccaction') eq 'add') {
+ @cc_add = $cgi->param('masscc');
}
+ elsif ($cgi->param('ccaction') eq 'remove') {
+ @cc_remove = $cgi->param('masscc');
+ }
+ }
+ else {
+ @cc_add = $cgi->param('newcc');
+ push(@cc_add, $user) if $cgi->param('addselfcc');
+
+ # We came from show_bug which uses a select box to determine what cc's
+ # need to be removed...
+ if ($cgi->param('removecc') && $cgi->param('cc')) {
+ @cc_remove = $cgi->param('cc');
+ }
+ }
- $set_all_fields{cc} = { add => \@cc_add, remove => \@cc_remove };
+ $set_all_fields{cc} = {add => \@cc_add, remove => \@cc_remove};
}
# Fields that can only be set on one bug at a time.
if (defined $cgi->param('id')) {
- # Since aliases are unique (like bug numbers), they can only be changed
- # for one bug at a time.
- if (defined $cgi->param('newalias') || defined $cgi->param('removealias')) {
- my @alias_add = split /[, ]+/, $cgi->param('newalias');
-
- # We came from bug_form which uses a select box to determine what
- # aliases need to be removed...
- my @alias_remove = ();
- if ($cgi->param('removealias') && $cgi->param('alias')) {
- @alias_remove = $cgi->param('alias');
- }
- $set_all_fields{alias} = { add => \@alias_add, remove => \@alias_remove };
+ # Since aliases are unique (like bug numbers), they can only be changed
+ # for one bug at a time.
+ if (defined $cgi->param('newalias') || defined $cgi->param('removealias')) {
+ my @alias_add = split /[, ]+/, $cgi->param('newalias');
+
+ # We came from bug_form which uses a select box to determine what
+ # aliases need to be removed...
+ my @alias_remove = ();
+ if ($cgi->param('removealias') && $cgi->param('alias')) {
+ @alias_remove = $cgi->param('alias');
}
+
+ $set_all_fields{alias} = {add => \@alias_add, remove => \@alias_remove};
+ }
}
my %is_private;
foreach my $field (grep(/^defined_isprivate/, $cgi->param())) {
- if ($field =~ /(\d+)$/) {
- my $comment_id = $1;
- $is_private{$comment_id} = $cgi->param("isprivate_$comment_id");
- }
+ if ($field =~ /(\d+)$/) {
+ my $comment_id = $1;
+ $is_private{$comment_id} = $cgi->param("isprivate_$comment_id");
+ }
}
$set_all_fields{comment_is_private} = \%is_private;
my @check_groups = $cgi->param('defined_groups');
-my @set_groups = $cgi->param('groups');
+my @set_groups = $cgi->param('groups');
my ($removed_groups) = diff_arrays(\@check_groups, \@set_groups);
-$set_all_fields{groups} = { add => \@set_groups, remove => $removed_groups };
+$set_all_fields{groups} = {add => \@set_groups, remove => $removed_groups};
my @custom_fields = Bugzilla->active_custom_fields;
foreach my $field (@custom_fields) {
- my $fname = $field->name;
- if (should_set($fname, 1)) {
- $set_all_fields{$fname} = [$cgi->param($fname)];
- }
+ my $fname = $field->name;
+ if (should_set($fname, 1)) {
+ $set_all_fields{$fname} = [$cgi->param($fname)];
+ }
}
# We are going to alter the list of removed groups, so we keep a copy here.
my @unchecked_groups = @$removed_groups;
foreach my $b (@bug_objects) {
- # Don't blindly ask to remove unchecked groups available in the UI.
- # A group can be already unchecked, and the user didn't try to remove it.
- # In this case, we don't want remove_group() to complain.
- my @remove_groups;
- foreach my $g (@{$b->groups_in}) {
- push(@remove_groups, $g->name) if grep { $_ eq $g->name } @unchecked_groups;
- }
- local $set_all_fields{groups}->{remove} = \@remove_groups;
- $b->set_all(\%set_all_fields);
+
+ # Don't blindly ask to remove unchecked groups available in the UI.
+ # A group can be already unchecked, and the user didn't try to remove it.
+ # In this case, we don't want remove_group() to complain.
+ my @remove_groups;
+ foreach my $g (@{$b->groups_in}) {
+ push(@remove_groups, $g->name) if grep { $_ eq $g->name } @unchecked_groups;
+ }
+ local $set_all_fields{groups}->{remove} = \@remove_groups;
+ $b->set_all(\%set_all_fields);
}
if (defined $cgi->param('id')) {
- # Flags should be set AFTER the bug has been moved into another
- # product/component. The structure of flags code doesn't currently
- # allow them to be set using set_all.
- my ($flags, $new_flags) = Bugzilla::Flag->extract_flags_from_cgi(
- $first_bug, undef, $vars);
- $first_bug->set_flags($flags, $new_flags);
-
- # Tags can only be set to one bug at once.
- if (should_set('tag')) {
- my @new_tags = grep { trim($_) } split(/,/, $cgi->param('tag'));
- my ($tags_removed, $tags_added) = diff_arrays($first_bug->tags, \@new_tags);
- $first_bug->remove_tag($_) foreach @$tags_removed;
- $first_bug->add_tag($_) foreach @$tags_added;
- }
+
+ # Flags should be set AFTER the bug has been moved into another
+ # product/component. The structure of flags code doesn't currently
+ # allow them to be set using set_all.
+ my ($flags, $new_flags)
+ = Bugzilla::Flag->extract_flags_from_cgi($first_bug, undef, $vars);
+ $first_bug->set_flags($flags, $new_flags);
+
+ # Tags can only be set to one bug at once.
+ if (should_set('tag')) {
+ my @new_tags = grep { trim($_) } split(/,/, $cgi->param('tag'));
+ my ($tags_removed, $tags_added) = diff_arrays($first_bug->tags, \@new_tags);
+ $first_bug->remove_tag($_) foreach @$tags_removed;
+ $first_bug->add_tag($_) foreach @$tags_added;
+ }
}
else {
- # Update flags on multiple bugs. The cgi params are slightly different
- # than on a single bug, so we need to call a different sub. We also
- # need to call this per bug, since we might be updating a flag in one
- # bug, but adding it to a second bug
- foreach my $b (@bug_objects) {
- my ($flags, $new_flags)
- = Bugzilla::Flag->multi_extract_flags_from_cgi($b, $vars);
- $b->set_flags($flags, $new_flags);
- }
+ # Update flags on multiple bugs. The cgi params are slightly different
+ # than on a single bug, so we need to call a different sub. We also
+ # need to call this per bug, since we might be updating a flag in one
+ # bug, but adding it to a second bug
+ foreach my $b (@bug_objects) {
+ my ($flags, $new_flags)
+ = Bugzilla::Flag->multi_extract_flags_from_cgi($b, $vars);
+ $b->set_flags($flags, $new_flags);
+ }
}
##############################
# Do Actual Database Updates #
##############################
foreach my $bug (@bug_objects) {
- my $changes = $bug->update();
-
- if ($changes->{'bug_status'}) {
- my $new_status = $changes->{'bug_status'}->[1];
- # We may have zeroed the remaining time, if we moved into a closed
- # status, so we should inform the user about that.
- if (!is_open_state($new_status) && $changes->{'remaining_time'}) {
- $vars->{'message'} = "remaining_time_zeroed"
- if $user->is_timetracker;
- }
+ my $changes = $bug->update();
+
+ if ($changes->{'bug_status'}) {
+ my $new_status = $changes->{'bug_status'}->[1];
+
+ # We may have zeroed the remaining time, if we moved into a closed
+ # status, so we should inform the user about that.
+ if (!is_open_state($new_status) && $changes->{'remaining_time'}) {
+ $vars->{'message'} = "remaining_time_zeroed" if $user->is_timetracker;
}
+ }
- $bug->send_changes($changes, $vars);
+ $bug->send_changes($changes, $vars);
}
# Delete the session token used for the mass-change.
delete_token($token) unless $cgi->param('id');
if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
- # Do nothing.
+
+ # Do nothing.
}
elsif ($action eq 'next_bug' or $action eq 'same_bug') {
- my $bug = $vars->{'bug'};
- if ($bug and $user->can_see_bug($bug)) {
- if ($action eq 'same_bug') {
- # $bug->update() does not update the internal structure of
- # the bug sufficiently to display the bug with the new values.
- # (That is, if we just passed in the old Bug object, we'd get
- # a lot of old values displayed.)
- $bug = new Bugzilla::Bug($bug->id);
- $vars->{'bug'} = $bug;
- }
- $vars->{'bugs'} = [$bug];
- if ($action eq 'next_bug') {
- $vars->{'nextbug'} = $bug->id;
- }
- # For performance reasons, preload visibility of dependencies
- # and duplicates related to this bug.
- Bugzilla::Bug->preload([$bug]);
-
- $template->process("bug/show.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ my $bug = $vars->{'bug'};
+ if ($bug and $user->can_see_bug($bug)) {
+ if ($action eq 'same_bug') {
+
+ # $bug->update() does not update the internal structure of
+ # the bug sufficiently to display the bug with the new values.
+ # (That is, if we just passed in the old Bug object, we'd get
+ # a lot of old values displayed.)
+ $bug = new Bugzilla::Bug($bug->id);
+ $vars->{'bug'} = $bug;
}
-} elsif ($action ne 'nothing') {
- ThrowCodeError("invalid_post_bug_submit_action");
+ $vars->{'bugs'} = [$bug];
+ if ($action eq 'next_bug') {
+ $vars->{'nextbug'} = $bug->id;
+ }
+
+ # For performance reasons, preload visibility of dependencies
+ # and duplicates related to this bug.
+ Bugzilla::Bug->preload([$bug]);
+
+ $template->process("bug/show.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+}
+elsif ($action ne 'nothing') {
+ ThrowCodeError("invalid_post_bug_submit_action");
}
# End the response page.
unless (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
- $template->process("bug/navigate.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- $template->process("global/footer.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $template->process("bug/navigate.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ $template->process("global/footer.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
1;
diff --git a/query.cgi b/query.cgi
index faf670d23..0c176f987 100755
--- a/query.cgi
+++ b/query.cgi
@@ -31,61 +31,62 @@ use Bugzilla::Token;
###############
sub get_product_values {
- my ($products, $field, $vars) = @_;
- my @all_values = map { @{ $_->$field } } @$products;
-
- my (@unique, %duplicates, %duplicate_count, %seen);
- foreach my $value (@all_values) {
- my $lc_name = lc($value->name);
- if ($seen{$lc_name}) {
- $duplicate_count{$seen{$lc_name}->id}++;
- $duplicates{$value->id} = $seen{$lc_name};
- next;
- }
- push(@unique, $value);
- $seen{$lc_name} = $value;
+ my ($products, $field, $vars) = @_;
+ my @all_values = map { @{$_->$field} } @$products;
+
+ my (@unique, %duplicates, %duplicate_count, %seen);
+ foreach my $value (@all_values) {
+ my $lc_name = lc($value->name);
+ if ($seen{$lc_name}) {
+ $duplicate_count{$seen{$lc_name}->id}++;
+ $duplicates{$value->id} = $seen{$lc_name};
+ next;
}
-
- $field =~ s/s$//;
- if ($field eq 'version') {
- @unique = sort { vers_cmp(lc($a->name), lc($b->name)) } @unique;
- }
- else {
- @unique = sort { lc($a->name) cmp lc($b->name) } @unique;
- }
-
- $field = 'target_milestone' if $field eq 'milestone';
- $vars->{duplicates}->{$field} = \%duplicates;
- $vars->{duplicate_count}->{$field} = \%duplicate_count;
- # "component" is a reserved word in Template Toolkit.
- $field = 'component_' if $field eq 'component';
- $vars->{$field} = \@unique;
+ push(@unique, $value);
+ $seen{$lc_name} = $value;
+ }
+
+ $field =~ s/s$//;
+ if ($field eq 'version') {
+ @unique = sort { vers_cmp(lc($a->name), lc($b->name)) } @unique;
+ }
+ else {
+ @unique = sort { lc($a->name) cmp lc($b->name) } @unique;
+ }
+
+ $field = 'target_milestone' if $field eq 'milestone';
+ $vars->{duplicates}->{$field} = \%duplicates;
+ $vars->{duplicate_count}->{$field} = \%duplicate_count;
+
+ # "component" is a reserved word in Template Toolkit.
+ $field = 'component_' if $field eq 'component';
+ $vars->{$field} = \@unique;
}
###############
# Main Script #
###############
-my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
-my $vars = {};
-my $buffer = $cgi->query_string();
+my $vars = {};
+my $buffer = $cgi->query_string();
-my $user = Bugzilla->login();
+my $user = Bugzilla->login();
my $userid = $user->id;
if ($cgi->param('nukedefaultquery')) {
- if ($userid) {
- my $token = $cgi->param('token');
- check_hash_token($token, ['nukedefaultquery']);
- my $named_queries = Bugzilla::Search::Saved->match(
- { userid => $userid, name => DEFAULT_QUERY_NAME });
- if (@$named_queries) {
- $named_queries->[0]->remove_from_db();
- }
+ if ($userid) {
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['nukedefaultquery']);
+ my $named_queries = Bugzilla::Search::Saved->match(
+ {userid => $userid, name => DEFAULT_QUERY_NAME});
+ if (@$named_queries) {
+ $named_queries->[0]->remove_from_db();
}
- $buffer = "";
+ }
+ $buffer = "";
}
# We are done with changes committed to the DB.
@@ -93,10 +94,10 @@ $dbh = Bugzilla->switch_to_shadow_db;
my $userdefaultquery;
if ($userid) {
- $userdefaultquery = $dbh->selectrow_array(
- "SELECT query FROM namedqueries " .
- "WHERE userid = ? AND name = ?",
- undef, ($userid, DEFAULT_QUERY_NAME));
+ $userdefaultquery
+ = $dbh->selectrow_array(
+ "SELECT query FROM namedqueries " . "WHERE userid = ? AND name = ?",
+ undef, ($userid, DEFAULT_QUERY_NAME));
}
local our %default;
@@ -105,74 +106,77 @@ local our %default;
# Items which are single-valued, the template should only reference [0]
# and ignore any multiple values.
sub PrefillForm {
- my ($buf) = @_;
- my $cgi = Bugzilla->cgi;
- $buf = new Bugzilla::CGI($buf);
- my $foundone = 0;
-
- # If there are old-style boolean charts in the URL (from an old saved
- # search or from an old link on the web somewhere) then convert them
- # to the new "custom search" format so that the form is populated
- # properly.
- my $any_boolean_charts = grep { /^field-?\d+/ } $buf->param();
- if ($any_boolean_charts) {
- my $search = new Bugzilla::Search(params => scalar $buf->Vars);
- $search->boolean_charts_to_custom_search($buf);
+ my ($buf) = @_;
+ my $cgi = Bugzilla->cgi;
+ $buf = new Bugzilla::CGI($buf);
+ my $foundone = 0;
+
+ # If there are old-style boolean charts in the URL (from an old saved
+ # search or from an old link on the web somewhere) then convert them
+ # to the new "custom search" format so that the form is populated
+ # properly.
+ my $any_boolean_charts = grep {/^field-?\d+/} $buf->param();
+ if ($any_boolean_charts) {
+ my $search = new Bugzilla::Search(params => scalar $buf->Vars);
+ $search->boolean_charts_to_custom_search($buf);
+ }
+
+ # Query parameters that don't represent form fields on this page.
+ my @skip = qw(format query_format list_id columnlist);
+
+ # Iterate over the URL parameters
+ foreach my $name ($buf->param()) {
+ next if grep { $_ eq $name } @skip;
+ $foundone = 1;
+ my @values = $buf->param($name);
+
+ # If the name is a single letter followed by numbers, it's part
+ # of Custom Search. We store these as an array of hashes.
+ if ($name =~ /^([[:lower:]])(\d+)$/) {
+ $default{'custom_search'}->[$2]->{$1} = $values[0];
}
- # Query parameters that don't represent form fields on this page.
- my @skip = qw(format query_format list_id columnlist);
-
- # Iterate over the URL parameters
- foreach my $name ($buf->param()) {
- next if grep { $_ eq $name } @skip;
- $foundone = 1;
- my @values = $buf->param($name);
-
- # If the name is a single letter followed by numbers, it's part
- # of Custom Search. We store these as an array of hashes.
- if ($name =~ /^([[:lower:]])(\d+)$/) {
- $default{'custom_search'}->[$2]->{$1} = $values[0];
- }
- # If the name ends in a number (which it does for the fields which
- # are part of the email searching), we use the array
- # positions to show the defaults for that number field.
- elsif ($name =~ /^(\w+)(\d)$/) {
- $default{$1}->[$2] = $values[0];
- }
- else {
- push (@{ $default{$name} }, @values);
- }
+ # If the name ends in a number (which it does for the fields which
+ # are part of the email searching), we use the array
+ # positions to show the defaults for that number field.
+ elsif ($name =~ /^(\w+)(\d)$/) {
+ $default{$1}->[$2] = $values[0];
+ }
+ else {
+ push(@{$default{$name}}, @values);
}
+ }
- return $foundone;
+ return $foundone;
}
if (!PrefillForm($buffer)) {
- # Ah-hah, there was no form stuff specified. Do it again with the
- # default query.
- if ($userdefaultquery) {
- PrefillForm($userdefaultquery);
- } else {
- PrefillForm(Bugzilla->params->{"defaultquery"});
- }
+
+ # Ah-hah, there was no form stuff specified. Do it again with the
+ # default query.
+ if ($userdefaultquery) {
+ PrefillForm($userdefaultquery);
+ }
+ else {
+ PrefillForm(Bugzilla->params->{"defaultquery"});
+ }
}
-# if using groups for entry, then we don't want people to see products they
+# if using groups for entry, then we don't want people to see products they
# don't have access to. Remove them from the list.
-my @selectable_products = sort {lc($a->name) cmp lc($b->name)}
- @{$user->get_selectable_products};
+my @selectable_products
+ = sort { lc($a->name) cmp lc($b->name) } @{$user->get_selectable_products};
Bugzilla::Product::preload(\@selectable_products);
$vars->{'product'} = \@selectable_products;
# Create the component, version and milestone lists.
foreach my $field (qw(components versions milestones)) {
- get_product_values(\@selectable_products, $field, $vars);
+ get_product_values(\@selectable_products, $field, $vars);
}
# Create data structures representing each classification
if (Bugzilla->params->{'useclassification'}) {
- $vars->{'classification'} = $user->get_selectable_classifications;
+ $vars->{'classification'} = $user->get_selectable_classifications;
}
my @chfields;
@@ -182,67 +186,78 @@ push @chfields, "[Bug creation]";
# This is what happens when you have variables whose definition depends
# on the DB schema, and then the underlying schema changes...
foreach my $val (editable_bug_fields()) {
- if ($val eq 'classification_id') {
- $val = 'classification';
- } elsif ($val eq 'product_id') {
- $val = 'product';
- } elsif ($val eq 'component_id') {
- $val = 'component';
- }
- push @chfields, $val;
+ if ($val eq 'classification_id') {
+ $val = 'classification';
+ }
+ elsif ($val eq 'product_id') {
+ $val = 'product';
+ }
+ elsif ($val eq 'component_id') {
+ $val = 'component';
+ }
+ push @chfields, $val;
}
if ($user->is_timetracker) {
- push @chfields, "work_time";
-} else {
- foreach my $tt_field (TIMETRACKING_FIELDS) {
- @chfields = grep($_ ne $tt_field, @chfields);
- }
+ push @chfields, "work_time";
+}
+else {
+ foreach my $tt_field (TIMETRACKING_FIELDS) {
+ @chfields = grep($_ ne $tt_field, @chfields);
+ }
}
@chfields = (sort(@chfields));
$vars->{'chfield'} = \@chfields;
-$vars->{'bug_status'} = Bugzilla::Field->new({name => 'bug_status'})->legal_values;
-$vars->{'rep_platform'} = Bugzilla::Field->new({name => 'rep_platform'})->legal_values;
-$vars->{'op_sys'} = Bugzilla::Field->new({name => 'op_sys'})->legal_values;
+$vars->{'bug_status'}
+ = Bugzilla::Field->new({name => 'bug_status'})->legal_values;
+$vars->{'rep_platform'}
+ = Bugzilla::Field->new({name => 'rep_platform'})->legal_values;
+$vars->{'op_sys'} = Bugzilla::Field->new({name => 'op_sys'})->legal_values;
$vars->{'priority'} = Bugzilla::Field->new({name => 'priority'})->legal_values;
-$vars->{'bug_severity'} = Bugzilla::Field->new({name => 'bug_severity'})->legal_values;
-$vars->{'resolution'} = Bugzilla::Field->new({name => 'resolution'})->legal_values;
+$vars->{'bug_severity'}
+ = Bugzilla::Field->new({name => 'bug_severity'})->legal_values;
+$vars->{'resolution'}
+ = Bugzilla::Field->new({name => 'resolution'})->legal_values;
# Boolean charts
-my @fields = @{ Bugzilla->fields({ obsolete => 0 }) };
+my @fields = @{Bugzilla->fields({obsolete => 0})};
my %exclude_fields = ();
# If we're not in the time-tracking group, exclude time-tracking fields.
if (!$user->is_timetracker) {
- foreach my $tt_field (TIMETRACKING_FIELDS) {
- $exclude_fields{$tt_field} = 1;
- }
+ foreach my $tt_field (TIMETRACKING_FIELDS) {
+ $exclude_fields{$tt_field} = 1;
+ }
}
# Exclude fields turned off by params
-my %param_controlled_fields = ('useqacontact' => 'qa_contact',
- 'usetargetmilestone' => 'target_milestone',
- 'useclassification' => 'classification',
- 'usestatuswhiteboard' => 'status_whiteboard');
+my %param_controlled_fields = (
+ 'useqacontact' => 'qa_contact',
+ 'usetargetmilestone' => 'target_milestone',
+ 'useclassification' => 'classification',
+ 'usestatuswhiteboard' => 'status_whiteboard'
+);
while (my ($param, $field) = each %param_controlled_fields) {
- $exclude_fields{$field} = 1 unless Bugzilla->params->{$param};
+ $exclude_fields{$field} = 1 unless Bugzilla->params->{$param};
}
@fields = grep(!$exclude_fields{$_->name}, @fields);
-@fields = sort {lc($a->description) cmp lc($b->description)} @fields;
-unshift(@fields, { name => "noop", description => "---" });
+@fields = sort { lc($a->description) cmp lc($b->description) } @fields;
+unshift(@fields, {name => "noop", description => "---"});
$vars->{'fields'} = \@fields;
# Named queries
if ($userid) {
- $vars->{'namedqueries'} = $dbh->selectcol_arrayref(
- "SELECT name FROM namedqueries " .
- "WHERE userid = ? AND name != ? " .
- "ORDER BY name",
- undef, ($userid, DEFAULT_QUERY_NAME));
+ $vars->{'namedqueries'} = $dbh->selectcol_arrayref(
+ "SELECT name FROM namedqueries "
+ . "WHERE userid = ? AND name != ? "
+ . "ORDER BY name",
+ undef,
+ ($userid, DEFAULT_QUERY_NAME)
+ );
}
# Sort order
@@ -250,20 +265,21 @@ my $deforder;
my @orders = ('Bug Number', 'Importance', 'Assignee', 'Last Changed');
if ($cgi->cookie('LASTORDER')) {
- $deforder = "Reuse same sort as last time";
- unshift(@orders, $deforder);
+ $deforder = "Reuse same sort as last time";
+ unshift(@orders, $deforder);
}
if ($cgi->param('order')) { $deforder = $cgi->param('order') }
$vars->{'userdefaultquery'} = $userdefaultquery;
-$vars->{'orders'} = \@orders;
+$vars->{'orders'} = \@orders;
$default{'order'} = [$deforder || 'Importance'];
-if (($cgi->param('query_format') || $cgi->param('format') || "")
- eq "create-series") {
- require Bugzilla::Chart;
- $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
+if (
+ ($cgi->param('query_format') || $cgi->param('format') || "") eq "create-series")
+{
+ require Bugzilla::Chart;
+ $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
}
$vars->{'known_name'} = $cgi->param('known_name');
@@ -273,33 +289,38 @@ $vars->{'columnlist'} = $cgi->param('columnlist');
# Add in the defaults.
$vars->{'default'} = \%default;
-$vars->{'format'} = $cgi->param('format');
+$vars->{'format'} = $cgi->param('format');
$vars->{'query_format'} = $cgi->param('query_format');
# Set default page to "specific" if none provided
if (!($cgi->param('query_format') || $cgi->param('format'))) {
- if (defined $cgi->cookie('DEFAULTFORMAT')) {
- $vars->{'format'} = $cgi->cookie('DEFAULTFORMAT');
- } else {
- $vars->{'format'} = 'specific';
- }
+ if (defined $cgi->cookie('DEFAULTFORMAT')) {
+ $vars->{'format'} = $cgi->cookie('DEFAULTFORMAT');
+ }
+ else {
+ $vars->{'format'} = 'specific';
+ }
}
# Set cookie to current format as default, but only if the format
# one that we should remember.
if (defined($vars->{'format'}) && IsValidQueryType($vars->{'format'})) {
- $cgi->send_cookie(-name => 'DEFAULTFORMAT',
- -value => $vars->{'format'},
- -expires => "Fri, 01-Jan-2038 00:00:00 GMT");
+ $cgi->send_cookie(
+ -name => 'DEFAULTFORMAT',
+ -value => $vars->{'format'},
+ -expires => "Fri, 01-Jan-2038 00:00:00 GMT"
+ );
}
# Generate and return the UI (HTML page) from the appropriate template.
# If we submit back to ourselves (for e.g. boolean charts), we need to
# preserve format information; hence query_format taking priority over
# format.
-my $format = $template->get_format("search/search",
- $vars->{'query_format'} || $vars->{'format'},
- scalar $cgi->param('ctype'));
+my $format = $template->get_format(
+ "search/search",
+ $vars->{'query_format'} || $vars->{'format'},
+ scalar $cgi->param('ctype')
+);
print $cgi->header($format->{'ctype'});
diff --git a/quips.cgi b/quips.cgi
index b2790be54..671ec1f09 100755
--- a/quips.cgi
+++ b/quips.cgi
@@ -21,120 +21,127 @@ use Bugzilla::Token;
my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
my $action = $cgi->param('action') || "";
-my $token = $cgi->param('token');
+my $token = $cgi->param('token');
if ($action eq "show") {
- # Read in the entire quip list
- my $quipsref = $dbh->selectall_arrayref(
- "SELECT quipid, userid, quip, approved FROM quips ORDER BY quipid");
-
- my $quips;
- my @quipids;
- foreach my $quipref (@$quipsref) {
- my ($quipid, $userid, $quip, $approved) = @$quipref;
- $quips->{$quipid} = {'userid' => $userid, 'quip' => $quip,
- 'approved' => $approved};
- push(@quipids, $quipid);
- }
- my $users;
- my $sth = $dbh->prepare("SELECT login_name FROM profiles WHERE userid = ?");
- foreach my $quipid (@quipids) {
- my $userid = $quips->{$quipid}{'userid'};
- if ($userid && not defined $users->{$userid}) {
- ($users->{$userid}) = $dbh->selectrow_array($sth, undef, $userid);
- }
+ # Read in the entire quip list
+ my $quipsref = $dbh->selectall_arrayref(
+ "SELECT quipid, userid, quip, approved FROM quips ORDER BY quipid");
+
+ my $quips;
+ my @quipids;
+ foreach my $quipref (@$quipsref) {
+ my ($quipid, $userid, $quip, $approved) = @$quipref;
+ $quips->{$quipid}
+ = {'userid' => $userid, 'quip' => $quip, 'approved' => $approved};
+ push(@quipids, $quipid);
+ }
+
+ my $users;
+ my $sth = $dbh->prepare("SELECT login_name FROM profiles WHERE userid = ?");
+ foreach my $quipid (@quipids) {
+ my $userid = $quips->{$quipid}{'userid'};
+ if ($userid && not defined $users->{$userid}) {
+ ($users->{$userid}) = $dbh->selectrow_array($sth, undef, $userid);
}
- $vars->{'quipids'} = \@quipids;
- $vars->{'quips'} = $quips;
- $vars->{'users'} = $users;
- $vars->{'show_quips'} = 1;
+ }
+ $vars->{'quipids'} = \@quipids;
+ $vars->{'quips'} = $quips;
+ $vars->{'users'} = $users;
+ $vars->{'show_quips'} = 1;
}
if ($action eq "add") {
- (Bugzilla->params->{'quip_list_entry_control'} eq "closed") &&
- ThrowUserError("no_new_quips");
-
- check_hash_token($token, ['create-quips']);
- # Add the quip
- my $approved = (Bugzilla->params->{'quip_list_entry_control'} eq "open")
- || $user->in_group('bz_quip_moderators') || 0;
- my $comment = $cgi->param("quip");
- $comment || ThrowUserError("need_quip");
-
- ThrowUserError("quip_too_long", { length => length($comment) })
- if length($comment) > MAX_QUIP_LENGTH;
-
- trick_taint($comment); # Used in a placeholder below
-
- $dbh->do("INSERT INTO quips (userid, quip, approved) VALUES (?, ?, ?)",
- undef, ($user->id, $comment, $approved));
-
- $vars->{'added_quip'} = $comment;
- $vars->{'message'} = 'quips_added';
+ (Bugzilla->params->{'quip_list_entry_control'} eq "closed")
+ && ThrowUserError("no_new_quips");
+
+ check_hash_token($token, ['create-quips']);
+
+ # Add the quip
+ my $approved
+ = (Bugzilla->params->{'quip_list_entry_control'} eq "open")
+ || $user->in_group('bz_quip_moderators')
+ || 0;
+ my $comment = $cgi->param("quip");
+ $comment || ThrowUserError("need_quip");
+
+ ThrowUserError("quip_too_long", {length => length($comment)})
+ if length($comment) > MAX_QUIP_LENGTH;
+
+ trick_taint($comment); # Used in a placeholder below
+
+ $dbh->do("INSERT INTO quips (userid, quip, approved) VALUES (?, ?, ?)",
+ undef, ($user->id, $comment, $approved));
+
+ $vars->{'added_quip'} = $comment;
+ $vars->{'message'} = 'quips_added';
}
if ($action eq 'approve') {
- $user->in_group('bz_quip_moderators')
- || ThrowUserError("auth_failure", {group => "bz_quip_moderators",
- action => "approve",
- object => "quips"});
-
- check_hash_token($token, ['approve-quips']);
- # Read in the entire quip list
- my $quipsref = $dbh->selectall_arrayref("SELECT quipid, approved FROM quips");
-
- my %quips;
- foreach my $quipref (@$quipsref) {
- my ($quipid, $approved) = @$quipref;
- $quips{$quipid} = $approved;
- }
-
- my @approved;
- my @unapproved;
- foreach my $quipid (keys %quips) {
- # Must check for each quipid being defined for concurrency and
- # automated usage where only one quipid might be defined.
- my $quip = $cgi->param("quipid_$quipid") ? 1 : 0;
- if(defined($cgi->param("defined_quipid_$quipid"))) {
- if($quips{$quipid} != $quip) {
- if($quip) {
- push(@approved, $quipid);
- } else {
- push(@unapproved, $quipid);
- }
- }
+ $user->in_group('bz_quip_moderators')
+ || ThrowUserError("auth_failure",
+ {group => "bz_quip_moderators", action => "approve", object => "quips"});
+
+ check_hash_token($token, ['approve-quips']);
+
+ # Read in the entire quip list
+ my $quipsref = $dbh->selectall_arrayref("SELECT quipid, approved FROM quips");
+
+ my %quips;
+ foreach my $quipref (@$quipsref) {
+ my ($quipid, $approved) = @$quipref;
+ $quips{$quipid} = $approved;
+ }
+
+ my @approved;
+ my @unapproved;
+ foreach my $quipid (keys %quips) {
+
+ # Must check for each quipid being defined for concurrency and
+ # automated usage where only one quipid might be defined.
+ my $quip = $cgi->param("quipid_$quipid") ? 1 : 0;
+ if (defined($cgi->param("defined_quipid_$quipid"))) {
+ if ($quips{$quipid} != $quip) {
+ if ($quip) {
+ push(@approved, $quipid);
+ }
+ else {
+ push(@unapproved, $quipid);
}
+ }
}
- $dbh->do("UPDATE quips SET approved = 1 WHERE quipid IN (" .
- join(",", @approved) . ")") if($#approved > -1);
- $dbh->do("UPDATE quips SET approved = 0 WHERE quipid IN (" .
- join(",", @unapproved) . ")") if($#unapproved > -1);
- $vars->{ 'approved' } = \@approved;
- $vars->{ 'unapproved' } = \@unapproved;
- $vars->{'message'} = 'quips_approved_unapproved';
+ }
+ $dbh->do(
+ "UPDATE quips SET approved = 1 WHERE quipid IN (" . join(",", @approved) . ")")
+ if ($#approved > -1);
+ $dbh->do("UPDATE quips SET approved = 0 WHERE quipid IN ("
+ . join(",", @unapproved) . ")")
+ if ($#unapproved > -1);
+ $vars->{'approved'} = \@approved;
+ $vars->{'unapproved'} = \@unapproved;
+ $vars->{'message'} = 'quips_approved_unapproved';
}
if ($action eq "delete") {
- $user->in_group('bz_quip_moderators')
- || ThrowUserError("auth_failure", {group => "bz_quip_moderators",
- action => "delete",
- object => "quips"});
- my $quipid = $cgi->param("quipid");
- detaint_natural($quipid) || ThrowUserError("need_quipid");
- check_hash_token($token, ['quips', $quipid]);
-
- ($vars->{'deleted_quip'}) = $dbh->selectrow_array(
- "SELECT quip FROM quips WHERE quipid = ?",
- undef, $quipid);
- $dbh->do("DELETE FROM quips WHERE quipid = ?", undef, $quipid);
- $vars->{'message'} = 'quips_deleted';
+ $user->in_group('bz_quip_moderators')
+ || ThrowUserError("auth_failure",
+ {group => "bz_quip_moderators", action => "delete", object => "quips"});
+ my $quipid = $cgi->param("quipid");
+ detaint_natural($quipid) || ThrowUserError("need_quipid");
+ check_hash_token($token, ['quips', $quipid]);
+
+ ($vars->{'deleted_quip'})
+ = $dbh->selectrow_array("SELECT quip FROM quips WHERE quipid = ?",
+ undef, $quipid);
+ $dbh->do("DELETE FROM quips WHERE quipid = ?", undef, $quipid);
+ $vars->{'message'} = 'quips_deleted';
}
print $cgi->header();
diff --git a/relogin.cgi b/relogin.cgi
index c4aae8d0b..2cd9bb25c 100755
--- a/relogin.cgi
+++ b/relogin.cgi
@@ -22,7 +22,7 @@ use Bugzilla::Util;
use Date::Format;
my $template = Bugzilla->template;
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $action = $cgi->param('action') || '';
@@ -30,182 +30,191 @@ my $vars = {};
my $target;
if (!$action) {
- # redirect to index.cgi if no action is defined.
- print $cgi->redirect(correct_urlbase() . 'index.cgi');
- exit;
+
+ # redirect to index.cgi if no action is defined.
+ print $cgi->redirect(correct_urlbase() . 'index.cgi');
+ exit;
}
+
# prepare-sudo: Display the sudo information & login page
elsif ($action eq 'prepare-sudo') {
- # We must have a logged-in user to do this
- # That user must be in the 'bz_sudoers' group
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- unless ($user->in_group('bz_sudoers')) {
- ThrowUserError('auth_failure', { group => 'bz_sudoers',
- action => 'begin',
- object => 'sudo_session' }
- );
- }
-
- # Do not try to start a new session if one is already in progress!
- if (defined(Bugzilla->sudoer)) {
- ThrowUserError('sudo_in_progress', { target => $user->login });
- }
-
- # Keep a temporary record of the user visiting this page
- $vars->{'token'} = issue_session_token('sudo_prepared');
-
- if ($user->authorizer->can_login) {
- my $value = generate_random_password();
- my %args;
- $args{'-secure'} = 1 if Bugzilla->params->{ssl_redirect};
-
- $cgi->send_cookie(-name => 'Bugzilla_login_request_cookie',
- -value => $value,
- -httponly => 1,
- %args);
-
- # The user ID must not be set when generating the token, because
- # that information will not be available when validating it.
- local Bugzilla->user->{userid} = 0;
- $vars->{'login_request_token'} = issue_hash_token(['login_request', $value]);
- }
-
- # Show the sudo page
- $vars->{'target_login_default'} = $cgi->param('target_login');
- $vars->{'reason_default'} = $cgi->param('reason');
- $target = 'admin/sudo.html.tmpl';
+
+ # We must have a logged-in user to do this
+ # That user must be in the 'bz_sudoers' group
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ unless ($user->in_group('bz_sudoers')) {
+ ThrowUserError('auth_failure',
+ {group => 'bz_sudoers', action => 'begin', object => 'sudo_session'});
+ }
+
+ # Do not try to start a new session if one is already in progress!
+ if (defined(Bugzilla->sudoer)) {
+ ThrowUserError('sudo_in_progress', {target => $user->login});
+ }
+
+ # Keep a temporary record of the user visiting this page
+ $vars->{'token'} = issue_session_token('sudo_prepared');
+
+ if ($user->authorizer->can_login) {
+ my $value = generate_random_password();
+ my %args;
+ $args{'-secure'} = 1 if Bugzilla->params->{ssl_redirect};
+
+ $cgi->send_cookie(
+ -name => 'Bugzilla_login_request_cookie',
+ -value => $value,
+ -httponly => 1,
+ %args
+ );
+
+ # The user ID must not be set when generating the token, because
+ # that information will not be available when validating it.
+ local Bugzilla->user->{userid} = 0;
+ $vars->{'login_request_token'} = issue_hash_token(['login_request', $value]);
+ }
+
+ # Show the sudo page
+ $vars->{'target_login_default'} = $cgi->param('target_login');
+ $vars->{'reason_default'} = $cgi->param('reason');
+ $target = 'admin/sudo.html.tmpl';
}
+
# begin-sudo: Confirm login and start sudo session
elsif ($action eq 'begin-sudo') {
- # We must be sure that the user is authenticating by providing a login
- # and password.
- # We only need to do this for authentication methods that involve Bugzilla
- # directly obtaining a login (i.e. normal CGI login), as opposed to other
- # methods (like Environment vars login).
-
- # First, record if Bugzilla_login and Bugzilla_password were provided
- my $credentials_provided;
- if (defined($cgi->param('Bugzilla_login'))
- && defined($cgi->param('Bugzilla_password')))
- {
- $credentials_provided = 1;
- }
-
- # Next, log in the user
- my $user = Bugzilla->login(LOGIN_REQUIRED);
-
- my $target_login = $cgi->param('target_login');
- my $reason = $cgi->param('reason') || '';
-
- # At this point, the user is logged in. However, if they used a method
- # where they could have provided a username/password (i.e. CGI), but they
- # did not provide a username/password, then throw an error.
- if ($user->authorizer->can_login && !$credentials_provided) {
- ThrowUserError('sudo_password_required',
- { target_login => $target_login, reason => $reason });
- }
-
- # The user must be in the 'bz_sudoers' group
- unless ($user->in_group('bz_sudoers')) {
- ThrowUserError('auth_failure', { group => 'bz_sudoers',
- action => 'begin',
- object => 'sudo_session' }
- );
- }
-
- # Do not try to start a new session if one is already in progress!
- if (defined(Bugzilla->sudoer)) {
- ThrowUserError('sudo_in_progress', { target => $user->login });
- }
-
- # Did the user actually go trough the 'sudo-prepare' action? Do some
- # checks on the token the action should have left.
- my ($token_user, $token_timestamp, $token_data) =
- Bugzilla::Token::GetTokenData($cgi->param('token'));
- unless (defined($token_user)
- && defined($token_data)
- && ($token_user == $user->id)
- && ($token_data eq 'sudo_prepared'))
- {
- ThrowUserError('sudo_preparation_required',
- { target_login => $target_login, reason => $reason });
- }
- delete_token($cgi->param('token'));
-
- # Get & verify the target user (the user who we will be impersonating)
- my $target_user = new Bugzilla::User({ name => $target_login });
- unless (defined($target_user)
- && $target_user->id
- && $user->can_see_user($target_user))
- {
- ThrowUserError('user_match_failed', { name => $target_login });
- }
- if ($target_user->in_group('bz_sudo_protect')) {
- ThrowUserError('sudo_protected', { login => $target_user->login });
- }
-
- # Calculate the session expiry time (T + 6 hours)
- my $time_string = time2str('%a, %d-%b-%Y %T %Z', time + MAX_SUDO_TOKEN_AGE, 'GMT');
-
- # For future sessions, store the unique ID of the target user
- my $token = Bugzilla::Token::_create_token($user->id, 'sudo', $target_user->id);
- my %args;
- if (Bugzilla->params->{ssl_redirect}) {
- $args{'-secure'} = 1;
- }
+ # We must be sure that the user is authenticating by providing a login
+ # and password.
+ # We only need to do this for authentication methods that involve Bugzilla
+ # directly obtaining a login (i.e. normal CGI login), as opposed to other
+ # methods (like Environment vars login).
+
+ # First, record if Bugzilla_login and Bugzilla_password were provided
+ my $credentials_provided;
+ if ( defined($cgi->param('Bugzilla_login'))
+ && defined($cgi->param('Bugzilla_password')))
+ {
+ $credentials_provided = 1;
+ }
+
+ # Next, log in the user
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ my $target_login = $cgi->param('target_login');
+ my $reason = $cgi->param('reason') || '';
+
+ # At this point, the user is logged in. However, if they used a method
+ # where they could have provided a username/password (i.e. CGI), but they
+ # did not provide a username/password, then throw an error.
+ if ($user->authorizer->can_login && !$credentials_provided) {
+ ThrowUserError('sudo_password_required',
+ {target_login => $target_login, reason => $reason});
+ }
+
+ # The user must be in the 'bz_sudoers' group
+ unless ($user->in_group('bz_sudoers')) {
+ ThrowUserError('auth_failure',
+ {group => 'bz_sudoers', action => 'begin', object => 'sudo_session'});
+ }
+
+ # Do not try to start a new session if one is already in progress!
+ if (defined(Bugzilla->sudoer)) {
+ ThrowUserError('sudo_in_progress', {target => $user->login});
+ }
+
+ # Did the user actually go trough the 'sudo-prepare' action? Do some
+ # checks on the token the action should have left.
+ my ($token_user, $token_timestamp, $token_data)
+ = Bugzilla::Token::GetTokenData($cgi->param('token'));
+ unless (defined($token_user)
+ && defined($token_data)
+ && ($token_user == $user->id)
+ && ($token_data eq 'sudo_prepared'))
+ {
+ ThrowUserError('sudo_preparation_required',
+ {target_login => $target_login, reason => $reason});
+ }
+ delete_token($cgi->param('token'));
+
+ # Get & verify the target user (the user who we will be impersonating)
+ my $target_user = new Bugzilla::User({name => $target_login});
+ unless (defined($target_user)
+ && $target_user->id
+ && $user->can_see_user($target_user))
+ {
+ ThrowUserError('user_match_failed', {name => $target_login});
+ }
+ if ($target_user->in_group('bz_sudo_protect')) {
+ ThrowUserError('sudo_protected', {login => $target_user->login});
+ }
+
+ # Calculate the session expiry time (T + 6 hours)
+ my $time_string
+ = time2str('%a, %d-%b-%Y %T %Z', time + MAX_SUDO_TOKEN_AGE, 'GMT');
+
+ # For future sessions, store the unique ID of the target user
+ my $token = Bugzilla::Token::_create_token($user->id, 'sudo', $target_user->id);
+
+ my %args;
+ if (Bugzilla->params->{ssl_redirect}) {
+ $args{'-secure'} = 1;
+ }
+
+ $cgi->send_cookie(
+ '-name' => 'sudo',
+ '-expires' => $time_string,
+ '-value' => $token,
+ '-httponly' => 1,
+ %args
+ );
+
+ # For the present, change the values of Bugzilla::user & Bugzilla::sudoer
+ Bugzilla->sudo_request($target_user, $user);
+
+ # NOTE: If you want to log the start of an sudo session, do it here.
+
+ # If we have a reason passed in, keep it under 200 characters
+ $reason = substr($reason, 0, 200);
+
+ # Go ahead and send out the message now
+ my $message;
+ my $mail_template = Bugzilla->template_inner($target_user->setting('lang'));
+ $mail_template->process('email/sudo.txt.tmpl', {reason => $reason}, \$message);
+ MessageToMTA($message);
+
+ $vars->{'message'} = 'sudo_started';
+ $vars->{'target'} = $target_user->login;
+ $target = 'global/message.html.tmpl';
+}
- $cgi->send_cookie('-name' => 'sudo',
- '-expires' => $time_string,
- '-value' => $token,
- '-httponly' => 1,
- %args);
+# end-sudo: End the current sudo session (if one is in progress)
+elsif ($action eq 'end-sudo') {
- # For the present, change the values of Bugzilla::user & Bugzilla::sudoer
- Bugzilla->sudo_request($target_user, $user);
+ # Regardless of our state, delete the sudo cookie if it exists
+ my $token = $cgi->cookie('sudo');
+ $cgi->remove_cookie('sudo');
- # NOTE: If you want to log the start of an sudo session, do it here.
+ # Are we in an sudo session?
+ Bugzilla->login(LOGIN_OPTIONAL);
+ my $sudoer = Bugzilla->sudoer;
+ if (defined($sudoer)) {
+ Bugzilla->sudo_request($sudoer, undef);
+ }
- # If we have a reason passed in, keep it under 200 characters
- $reason = substr($reason, 0, 200);
+ # Now that the session is over, remove the token from the DB.
+ delete_token($token);
- # Go ahead and send out the message now
- my $message;
- my $mail_template = Bugzilla->template_inner($target_user->setting('lang'));
- $mail_template->process('email/sudo.txt.tmpl', { reason => $reason }, \$message);
- MessageToMTA($message);
+ # NOTE: If you want to log the end of an sudo session, so it here.
- $vars->{'message'} = 'sudo_started';
- $vars->{'target'} = $target_user->login;
- $target = 'global/message.html.tmpl';
-}
-# end-sudo: End the current sudo session (if one is in progress)
-elsif ($action eq 'end-sudo') {
- # Regardless of our state, delete the sudo cookie if it exists
- my $token = $cgi->cookie('sudo');
- $cgi->remove_cookie('sudo');
-
- # Are we in an sudo session?
- Bugzilla->login(LOGIN_OPTIONAL);
- my $sudoer = Bugzilla->sudoer;
- if (defined($sudoer)) {
- Bugzilla->sudo_request($sudoer, undef);
- }
- # Now that the session is over, remove the token from the DB.
- delete_token($token);
-
- # NOTE: If you want to log the end of an sudo session, so it here.
-
- $vars->{'message'} = 'sudo_ended';
- $target = 'global/message.html.tmpl';
+ $vars->{'message'} = 'sudo_ended';
+ $target = 'global/message.html.tmpl';
}
+
# No valid action found
else {
- Bugzilla->login(LOGIN_OPTIONAL);
- ThrowUserError('unknown_action', {action => $action});
+ Bugzilla->login(LOGIN_OPTIONAL);
+ ThrowUserError('unknown_action', {action => $action});
}
# Display the template
print $cgi->header();
-$template->process($target, $vars)
- || ThrowTemplateError($template->error());
+$template->process($target, $vars) || ThrowTemplateError($template->error());
diff --git a/report.cgi b/report.cgi
index 2a8317d7a..1a616b453 100755
--- a/report.cgi
+++ b/report.cgi
@@ -23,18 +23,20 @@ use Bugzilla::Token;
use List::MoreUtils qw(uniq);
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
# Go straight back to query.cgi if we are adding a boolean chart.
if (grep(/^cmd-/, $cgi->param())) {
- my $params = $cgi->canonicalise_query("format", "ctype");
- my $location = "query.cgi?format=" . $cgi->param('query_format') .
- ($params ? "&$params" : "");
-
- print $cgi->redirect($location);
- exit;
+ my $params = $cgi->canonicalise_query("format", "ctype");
+ my $location
+ = "query.cgi?format="
+ . $cgi->param('query_format')
+ . ($params ? "&$params" : "");
+
+ print $cgi->redirect($location);
+ exit;
}
Bugzilla->login();
@@ -42,57 +44,59 @@ my $action = $cgi->param('action') || 'menu';
my $token = $cgi->param('token');
if ($action eq "menu") {
- # No need to do any searching in this case, so bail out early.
- print $cgi->header();
- $template->process("reports/menu.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+
+ # No need to do any searching in this case, so bail out early.
+ print $cgi->header();
+ $template->process("reports/menu.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
elsif ($action eq 'add') {
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- check_hash_token($token, ['save_report']);
-
- my $name = clean_text($cgi->param('name'));
- my $query = $cgi->param('query');
-
- if (my ($report) = grep{ lc($_->name) eq lc($name) } @{$user->reports}) {
- $report->set_query($query);
- $report->update;
- $vars->{'message'} = "report_updated";
- } else {
- my $report = Bugzilla::Report->create({name => $name, query => $query});
- $vars->{'message'} = "report_created";
- }
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ check_hash_token($token, ['save_report']);
+
+ my $name = clean_text($cgi->param('name'));
+ my $query = $cgi->param('query');
+
+ if (my ($report) = grep { lc($_->name) eq lc($name) } @{$user->reports}) {
+ $report->set_query($query);
+ $report->update;
+ $vars->{'message'} = "report_updated";
+ }
+ else {
+ my $report = Bugzilla::Report->create({name => $name, query => $query});
+ $vars->{'message'} = "report_created";
+ }
- $user->flush_reports_cache;
+ $user->flush_reports_cache;
- print $cgi->header();
+ print $cgi->header();
- $vars->{'reportname'} = $name;
+ $vars->{'reportname'} = $name;
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
elsif ($action eq 'del') {
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- my $report_id = $cgi->param('saved_report_id');
- check_hash_token($token, ['delete_report', $report_id]);
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $report_id = $cgi->param('saved_report_id');
+ check_hash_token($token, ['delete_report', $report_id]);
- my $report = Bugzilla::Report->check({id => $report_id});
- $report->remove_from_db();
+ my $report = Bugzilla::Report->check({id => $report_id});
+ $report->remove_from_db();
- $user->flush_reports_cache;
+ $user->flush_reports_cache;
- print $cgi->header();
+ print $cgi->header();
- $vars->{'message'} = 'report_deleted';
- $vars->{'reportname'} = $report->name;
+ $vars->{'message'} = 'report_deleted';
+ $vars->{'reportname'} = $report->name;
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
# Sanitize the URL, to make URLs shorter.
@@ -103,16 +107,15 @@ my $row_field = $cgi->param('y_axis_field') || '';
my $tbl_field = $cgi->param('z_axis_field') || '';
if (!($col_field || $row_field || $tbl_field)) {
- ThrowUserError("no_axes_defined");
+ ThrowUserError("no_axes_defined");
}
# There is no UI for these parameters anymore,
# but they are still here just in case.
-my $width = $cgi->param('width') || 1024;
+my $width = $cgi->param('width') || 1024;
my $height = $cgi->param('height') || 600;
-(detaint_natural($width) && $width > 0)
- || ThrowUserError("invalid_dimensions");
+(detaint_natural($width) && $width > 0) || ThrowUserError("invalid_dimensions");
$width <= 2000 || ThrowUserError("chart_too_large");
(detaint_natural($height) && $height > 0)
@@ -121,50 +124,52 @@ $height <= 2000 || ThrowUserError("chart_too_large");
my $formatparam = $cgi->param('format') || '';
-# These shenanigans are necessary to make sure that both vertical and
+# These shenanigans are necessary to make sure that both vertical and
# horizontal 1D tables convert to the correct dimension when you ask to
# display them as some sort of chart.
if ($formatparam eq "table") {
- if ($col_field && !$row_field) {
- # 1D *tables* should be displayed vertically (with a row_field only)
- $row_field = $col_field;
- $col_field = '';
- }
+ if ($col_field && !$row_field) {
+
+ # 1D *tables* should be displayed vertically (with a row_field only)
+ $row_field = $col_field;
+ $col_field = '';
+ }
}
else {
- if (!Bugzilla->feature('graphical_reports')) {
- ThrowUserError('feature_disabled', { feature => 'graphical_reports' });
- }
+ if (!Bugzilla->feature('graphical_reports')) {
+ ThrowUserError('feature_disabled', {feature => 'graphical_reports'});
+ }
- if ($row_field && !$col_field) {
- # 1D *charts* should be displayed horizontally (with an col_field only)
- $col_field = $row_field;
- $row_field = '';
- }
+ if ($row_field && !$col_field) {
+
+ # 1D *charts* should be displayed horizontally (with an col_field only)
+ $col_field = $row_field;
+ $row_field = '';
+ }
}
# Valid bug fields that can be reported on.
my $valid_columns = Bugzilla::Search::REPORT_COLUMNS;
# Validate the values in the axis fields or throw an error.
-!$row_field
+!$row_field
|| ($valid_columns->{$row_field} && trick_taint($row_field))
|| ThrowUserError("report_axis_invalid", {fld => "x", val => $row_field});
-!$col_field
+!$col_field
|| ($valid_columns->{$col_field} && trick_taint($col_field))
|| ThrowUserError("report_axis_invalid", {fld => "y", val => $col_field});
-!$tbl_field
+!$tbl_field
|| ($valid_columns->{$tbl_field} && trick_taint($tbl_field))
|| ThrowUserError("report_axis_invalid", {fld => "z", val => $tbl_field});
-my @axis_fields = grep { $_ } ($row_field, $col_field, $tbl_field);
+my @axis_fields = grep {$_} ($row_field, $col_field, $tbl_field);
# Clone the params, so that Bugzilla::Search can modify them
my $params = new Bugzilla::CGI($cgi);
my $search = new Bugzilla::Search(
- fields => \@axis_fields,
- params => scalar $params->Vars,
- allow_unlimited => 1,
+ fields => \@axis_fields,
+ params => scalar $params->Vars,
+ allow_unlimited => 1,
);
$::SIG{TERM} = 'DEFAULT';
@@ -173,7 +178,7 @@ $::SIG{PIPE} = 'DEFAULT';
Bugzilla->switch_to_shadow_db();
my ($results, $extra_data) = $search->data;
-# We have a hash of hashes for the data itself, and a hash to hold the
+# We have a hash of hashes for the data itself, and a hash to hold the
# row/col/table names.
my %data;
my %names;
@@ -189,62 +194,61 @@ my $tbl_isnumeric = 1;
# define which fields are multiselect
my @multi_selects = map { $_->name } @{Bugzilla->fields(
- {
- obsolete => 0,
- type => [FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_KEYWORDS]
- }
-)};
-my $col_ismultiselect = scalar grep {$col_field eq $_} @multi_selects;
-my $row_ismultiselect = scalar grep {$row_field eq $_} @multi_selects;
-my $tbl_ismultiselect = scalar grep {$tbl_field eq $_} @multi_selects;
+ {obsolete => 0, type => [FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_KEYWORDS]}
+ )
+};
+my $col_ismultiselect = scalar grep { $col_field eq $_ } @multi_selects;
+my $row_ismultiselect = scalar grep { $row_field eq $_ } @multi_selects;
+my $tbl_ismultiselect = scalar grep { $tbl_field eq $_ } @multi_selects;
foreach my $result (@$results) {
- # handle empty dimension member names
-
- my @rows = check_value($row_field, $result, $row_ismultiselect);
- my @cols = check_value($col_field, $result, $col_ismultiselect);
- my @tbls = check_value($tbl_field, $result, $tbl_ismultiselect);
-
- my %in_total_row;
- my %in_total_col;
- for my $tbl (@tbls) {
- my %in_row_total;
- for my $col (@cols) {
- for my $row (@rows) {
- $data{$tbl}{$col}{$row}++;
- $names{"row"}{$row}++;
- $row_isnumeric &&= ($row =~ /^-?\d+(\.\d+)?$/o);
- if ($formatparam eq "table") {
- if (!$in_row_total{$row}) {
- $data{$tbl}{'-total-'}{$row}++;
- $in_row_total{$row} = 1;
- }
- if (!$in_total_row{$row}) {
- $data{'-total-'}{'-total-'}{$row}++;
- $in_total_row{$row} = 1;
- }
- }
- }
- if ($formatparam eq "table") {
- $data{$tbl}{$col}{'-total-'}++;
- if (!$in_total_col{$col}) {
- $data{'-total-'}{$col}{'-total-'}++;
- $in_total_col{$col} = 1;
- }
- }
- $names{"col"}{$col}++;
- $col_isnumeric &&= ($col =~ /^-?\d+(\.\d+)?$/o);
- }
- $names{"tbl"}{$tbl}++;
- $tbl_isnumeric &&= ($tbl =~ /^-?\d+(\.\d+)?$/o);
+
+ # handle empty dimension member names
+
+ my @rows = check_value($row_field, $result, $row_ismultiselect);
+ my @cols = check_value($col_field, $result, $col_ismultiselect);
+ my @tbls = check_value($tbl_field, $result, $tbl_ismultiselect);
+
+ my %in_total_row;
+ my %in_total_col;
+ for my $tbl (@tbls) {
+ my %in_row_total;
+ for my $col (@cols) {
+ for my $row (@rows) {
+ $data{$tbl}{$col}{$row}++;
+ $names{"row"}{$row}++;
+ $row_isnumeric &&= ($row =~ /^-?\d+(\.\d+)?$/o);
if ($formatparam eq "table") {
- $data{$tbl}{'-total-'}{'-total-'}++;
+ if (!$in_row_total{$row}) {
+ $data{$tbl}{'-total-'}{$row}++;
+ $in_row_total{$row} = 1;
+ }
+ if (!$in_total_row{$row}) {
+ $data{'-total-'}{'-total-'}{$row}++;
+ $in_total_row{$row} = 1;
+ }
+ }
+ }
+ if ($formatparam eq "table") {
+ $data{$tbl}{$col}{'-total-'}++;
+ if (!$in_total_col{$col}) {
+ $data{'-total-'}{$col}{'-total-'}++;
+ $in_total_col{$col} = 1;
}
+ }
+ $names{"col"}{$col}++;
+ $col_isnumeric &&= ($col =~ /^-?\d+(\.\d+)?$/o);
}
+ $names{"tbl"}{$tbl}++;
+ $tbl_isnumeric &&= ($tbl =~ /^-?\d+(\.\d+)?$/o);
if ($formatparam eq "table") {
- $data{'-total-'}{'-total-'}{'-total-'}++;
+ $data{$tbl}{'-total-'}{'-total-'}++;
}
+ }
+ if ($formatparam eq "table") {
+ $data{'-total-'}{'-total-'}{'-total-'}++;
+ }
}
my @col_names = get_names($names{"col"}, $col_isnumeric, $col_field);
@@ -255,106 +259,112 @@ my @tbl_names = get_names($names{"tbl"}, $tbl_isnumeric, $tbl_field);
# gathered everything into the hashes and made sure we know the size of the
# data, we reformat it into an array of arrays of arrays of data.
push(@tbl_names, "-total-") if (scalar(@tbl_names) > 1);
-
+
my @image_data;
foreach my $tbl (@tbl_names) {
- my @tbl_data;
- push(@tbl_data, \@col_names);
- foreach my $row (@row_names) {
- my @col_data;
- foreach my $col (@col_names) {
- $data{$tbl}{$col}{$row} = $data{$tbl}{$col}{$row} || 0;
- push(@col_data, $data{$tbl}{$col}{$row});
- if ($tbl ne "-total-") {
- # This is a bit sneaky. We spend every loop except the last
- # building up the -total- data, and then last time round,
- # we process it as another tbl, and push() the total values
- # into the image_data array.
- $data{"-total-"}{$col}{$row} += $data{$tbl}{$col}{$row};
- }
- }
-
- push(@tbl_data, \@col_data);
+ my @tbl_data;
+ push(@tbl_data, \@col_names);
+ foreach my $row (@row_names) {
+ my @col_data;
+ foreach my $col (@col_names) {
+ $data{$tbl}{$col}{$row} = $data{$tbl}{$col}{$row} || 0;
+ push(@col_data, $data{$tbl}{$col}{$row});
+ if ($tbl ne "-total-") {
+
+ # This is a bit sneaky. We spend every loop except the last
+ # building up the -total- data, and then last time round,
+ # we process it as another tbl, and push() the total values
+ # into the image_data array.
+ $data{"-total-"}{$col}{$row} += $data{$tbl}{$col}{$row};
+ }
}
-
- unshift(@image_data, \@tbl_data);
+
+ push(@tbl_data, \@col_data);
+ }
+
+ unshift(@image_data, \@tbl_data);
}
$vars->{'col_field'} = $col_field;
$vars->{'row_field'} = $row_field;
$vars->{'tbl_field'} = $tbl_field;
-$vars->{'time'} = localtime(time());
+$vars->{'time'} = localtime(time());
-$vars->{'col_names'} = \@col_names;
-$vars->{'row_names'} = \@row_names;
-$vars->{'tbl_names'} = \@tbl_names;
+$vars->{'col_names'} = \@col_names;
+$vars->{'row_names'} = \@row_names;
+$vars->{'tbl_names'} = \@tbl_names;
$vars->{'note_multi_select'} = $row_ismultiselect || $col_ismultiselect;
# Below a certain width, we don't see any bars, so there needs to be a minimum.
if ($formatparam eq "bar") {
- my $min_width = (scalar(@col_names) || 1) * 20;
+ my $min_width = (scalar(@col_names) || 1) * 20;
- if (!$cgi->param('cumulate')) {
- $min_width *= (scalar(@row_names) || 1);
- }
+ if (!$cgi->param('cumulate')) {
+ $min_width *= (scalar(@row_names) || 1);
+ }
- $vars->{'min_width'} = $min_width;
+ $vars->{'min_width'} = $min_width;
}
-$vars->{'width'} = $width;
-$vars->{'height'} = $height;
-$vars->{'queries'} = $extra_data;
+$vars->{'width'} = $width;
+$vars->{'height'} = $height;
+$vars->{'queries'} = $extra_data;
$vars->{'saved_report_id'} = $cgi->param('saved_report_id');
-if ($cgi->param('debug')
- && Bugzilla->params->{debug_group}
- && Bugzilla->user->in_group(Bugzilla->params->{debug_group})
-) {
- $vars->{'debug'} = 1;
+if ( $cgi->param('debug')
+ && Bugzilla->params->{debug_group}
+ && Bugzilla->user->in_group(Bugzilla->params->{debug_group}))
+{
+ $vars->{'debug'} = 1;
}
if ($action eq "wrap") {
- # So which template are we using? If action is "wrap", we will be using
- # no format (it gets passed through to be the format of the actual data),
- # and either report.csv.tmpl (CSV), or report.html.tmpl (everything else).
- # report.html.tmpl produces an HTML framework for either tables of HTML
- # data, or images generated by calling report.cgi again with action as
- # "plot".
- $formatparam =~ s/[^a-zA-Z\-]//g;
- $vars->{'format'} = $formatparam;
- $formatparam = '';
-
- # We need to keep track of the defined restrictions on each of the
- # axes, because buglistbase, below, throws them away. Without this, we
- # get buglistlinks wrong if there is a restriction on an axis field.
- $vars->{'col_vals'} = get_field_restrictions($col_field);
- $vars->{'row_vals'} = get_field_restrictions($row_field);
- $vars->{'tbl_vals'} = get_field_restrictions($tbl_field);
-
- # We need a number of different variants of the base URL for different
- # URLs in the HTML.
- $vars->{'buglistbase'} = $cgi->canonicalise_query(
- "x_axis_field", "y_axis_field", "z_axis_field",
- "ctype", "format", "query_format", @axis_fields);
- $vars->{'imagebase'} = $cgi->canonicalise_query(
- $tbl_field, "action", "ctype", "format", "width", "height");
- $vars->{'switchbase'} = $cgi->canonicalise_query(
- "query_format", "action", "ctype", "format", "width", "height");
- $vars->{'data'} = \%data;
+
+ # So which template are we using? If action is "wrap", we will be using
+ # no format (it gets passed through to be the format of the actual data),
+ # and either report.csv.tmpl (CSV), or report.html.tmpl (everything else).
+ # report.html.tmpl produces an HTML framework for either tables of HTML
+ # data, or images generated by calling report.cgi again with action as
+ # "plot".
+ $formatparam =~ s/[^a-zA-Z\-]//g;
+ $vars->{'format'} = $formatparam;
+ $formatparam = '';
+
+ # We need to keep track of the defined restrictions on each of the
+ # axes, because buglistbase, below, throws them away. Without this, we
+ # get buglistlinks wrong if there is a restriction on an axis field.
+ $vars->{'col_vals'} = get_field_restrictions($col_field);
+ $vars->{'row_vals'} = get_field_restrictions($row_field);
+ $vars->{'tbl_vals'} = get_field_restrictions($tbl_field);
+
+ # We need a number of different variants of the base URL for different
+ # URLs in the HTML.
+ $vars->{'buglistbase'} = $cgi->canonicalise_query(
+ "x_axis_field", "y_axis_field", "z_axis_field", "ctype",
+ "format", "query_format", @axis_fields
+ );
+ $vars->{'imagebase'}
+ = $cgi->canonicalise_query($tbl_field, "action", "ctype", "format", "width",
+ "height");
+ $vars->{'switchbase'}
+ = $cgi->canonicalise_query("query_format", "action", "ctype", "format",
+ "width", "height");
+ $vars->{'data'} = \%data;
}
elsif ($action eq "plot") {
- # If action is "plot", we will be using a format as normal (pie, bar etc.)
- # and a ctype as normal (currently only png.)
- $vars->{'cumulate'} = $cgi->param('cumulate') ? 1 : 0;
- $vars->{'x_labels_vertical'} = $cgi->param('x_labels_vertical') ? 1 : 0;
- $vars->{'data'} = \@image_data;
+
+ # If action is "plot", we will be using a format as normal (pie, bar etc.)
+ # and a ctype as normal (currently only png.)
+ $vars->{'cumulate'} = $cgi->param('cumulate') ? 1 : 0;
+ $vars->{'x_labels_vertical'} = $cgi->param('x_labels_vertical') ? 1 : 0;
+ $vars->{'data'} = \@image_data;
}
else {
- ThrowUserError('unknown_action', {action => $action});
+ ThrowUserError('unknown_action', {action => $action});
}
my $format = $template->get_format("reports/report", $formatparam,
- scalar($cgi->param('ctype')));
+ scalar($cgi->param('ctype')));
# If we get a template or CGI error, it comes out as HTML, which isn't valid
# PNG data, and the browser just displays a "corrupt PNG" message. So, you can
@@ -367,11 +377,11 @@ print $cgi->header($format->{'ctype'});
# Problems with this CGI are often due to malformed data. Setting debug=1
# prints out both data structures.
if ($cgi->param('debug')) {
- require Data::Dumper;
- say "<pre>data hash:";
- say html_quote(Data::Dumper::Dumper(%data));
- say "\ndata array:";
- say html_quote(Data::Dumper::Dumper(@image_data)) . "\n\n</pre>";
+ require Data::Dumper;
+ say "<pre>data hash:";
+ say html_quote(Data::Dumper::Dumper(%data));
+ say "\ndata array:";
+ say html_quote(Data::Dumper::Dumper(@image_data)) . "\n\n</pre>";
}
# All formats point to the same section of the documentation.
@@ -384,65 +394,69 @@ $template->process("$format->{'template'}", $vars)
sub get_names {
- my ($names, $isnumeric, $field_name) = @_;
- my ($field, @sorted);
- # XXX - This is a hack to handle the actual_time/work_time field,
- # because it's named 'actual_time' in Search.pm but 'work_time' in Field.pm.
- $_[2] = $field_name = 'work_time' if $field_name eq 'actual_time';
-
- # _realname fields aren't real Bugzilla::Field objects, but they are a
- # valid axis, so we don't vailidate them as Bugzilla::Field objects.
- $field = Bugzilla::Field->check($field_name)
- if ($field_name && $field_name !~ /_realname$/);
-
- if ($field && $field->is_select) {
- foreach my $value (@{$field->legal_values}) {
- push(@sorted, $value->name) if $names->{$value->name};
- }
- unshift(@sorted, '---') if ($field_name eq 'resolution'
- || $field->type == FIELD_TYPE_MULTI_SELECT);
- @sorted = uniq @sorted;
- }
- elsif ($isnumeric) {
- # It's not a field we are preserving the order of, so sort it
- # numerically...
- @sorted = sort { $a <=> $b } keys %$names;
- }
- else {
- # ...or alphabetically, as appropriate.
- @sorted = sort keys %$names;
+ my ($names, $isnumeric, $field_name) = @_;
+ my ($field, @sorted);
+
+ # XXX - This is a hack to handle the actual_time/work_time field,
+ # because it's named 'actual_time' in Search.pm but 'work_time' in Field.pm.
+ $_[2] = $field_name = 'work_time' if $field_name eq 'actual_time';
+
+ # _realname fields aren't real Bugzilla::Field objects, but they are a
+ # valid axis, so we don't vailidate them as Bugzilla::Field objects.
+ $field = Bugzilla::Field->check($field_name)
+ if ($field_name && $field_name !~ /_realname$/);
+
+ if ($field && $field->is_select) {
+ foreach my $value (@{$field->legal_values}) {
+ push(@sorted, $value->name) if $names->{$value->name};
}
-
- return @sorted;
+ unshift(@sorted, '---')
+ if ($field_name eq 'resolution' || $field->type == FIELD_TYPE_MULTI_SELECT);
+ @sorted = uniq @sorted;
+ }
+ elsif ($isnumeric) {
+
+ # It's not a field we are preserving the order of, so sort it
+ # numerically...
+ @sorted = sort { $a <=> $b } keys %$names;
+ }
+ else {
+ # ...or alphabetically, as appropriate.
+ @sorted = sort keys %$names;
+ }
+
+ return @sorted;
}
sub check_value {
- my ($field, $result, $ismultiselect) = @_;
-
- my $value;
- if (!defined $field) {
- $value = '';
- }
- elsif ($field eq '') {
- $value = ' ';
- }
- else {
- $value = shift @$result;
- $value = ' ' if (!defined $value || $value eq '');
- $value = '---' if (($field eq 'resolution' || $ismultiselect ) &&
- $value eq ' ');
- }
- if ($ismultiselect) {
- # Some DB servers have a space after the comma, some others don't.
- return split(/, ?/, $value);
- } else {
- return ($value);
- }
+ my ($field, $result, $ismultiselect) = @_;
+
+ my $value;
+ if (!defined $field) {
+ $value = '';
+ }
+ elsif ($field eq '') {
+ $value = ' ';
+ }
+ else {
+ $value = shift @$result;
+ $value = ' ' if (!defined $value || $value eq '');
+ $value = '---' if (($field eq 'resolution' || $ismultiselect) && $value eq ' ');
+ }
+ if ($ismultiselect) {
+
+ # Some DB servers have a space after the comma, some others don't.
+ return split(/, ?/, $value);
+ }
+ else {
+ return ($value);
+ }
}
sub get_field_restrictions {
- my $field = shift;
- my $cgi = Bugzilla->cgi;
+ my $field = shift;
+ my $cgi = Bugzilla->cgi;
- return join('&amp;', map {url_quote($field) . '=' . url_quote($_)} $cgi->param($field));
+ return join('&amp;',
+ map { url_quote($field) . '=' . url_quote($_) } $cgi->param($field));
}
diff --git a/reports.cgi b/reports.cgi
index 89dee1c9a..860a537d8 100755
--- a/reports.cgi
+++ b/reports.cgi
@@ -23,91 +23,93 @@ use Digest::SHA qw(hmac_sha256_base64);
# If we're using bug groups for products, we should apply those restrictions
# to viewing reports, as well. Time to check the login in that case.
-my $user = Bugzilla->login();
-my $cgi = Bugzilla->cgi;
+my $user = Bugzilla->login();
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
# We use a dummy product instance with ID 0, representing all products
my $product_all = {id => 0};
bless($product_all, 'Bugzilla::Product');
if (!Bugzilla->feature('old_charts')) {
- ThrowUserError('feature_disabled', { feature => 'old_charts' });
+ ThrowUserError('feature_disabled', {feature => 'old_charts'});
}
-my $dir = bz_locations()->{'datadir'} . "/mining";
-my $graph_dir = bz_locations()->{'graphsdir'};
-my $graph_url = basename($graph_dir);
+my $dir = bz_locations()->{'datadir'} . "/mining";
+my $graph_dir = bz_locations()->{'graphsdir'};
+my $graph_url = basename($graph_dir);
my $product_id = $cgi->param('product_id');
Bugzilla->switch_to_shadow_db();
-if (! defined($product_id)) {
- # Can we do bug charts?
- (-d $dir && -d $graph_dir)
- || ThrowCodeError('chart_dir_nonexistent',
- {dir => $dir, graph_dir => $graph_dir});
+if (!defined($product_id)) {
- my %default_sel = map { $_ => 1 } BUG_STATE_OPEN;
+ # Can we do bug charts?
+ (-d $dir && -d $graph_dir)
+ || ThrowCodeError('chart_dir_nonexistent',
+ {dir => $dir, graph_dir => $graph_dir});
- my @datasets;
- my @data = get_data($dir);
+ my %default_sel = map { $_ => 1 } BUG_STATE_OPEN;
- foreach my $dataset (@data) {
- my $datasets = {};
- $datasets->{'value'} = $dataset;
- $datasets->{'selected'} = $default_sel{$dataset} ? 1 : 0;
- push(@datasets, $datasets);
- }
+ my @datasets;
+ my @data = get_data($dir);
+
+ foreach my $dataset (@data) {
+ my $datasets = {};
+ $datasets->{'value'} = $dataset;
+ $datasets->{'selected'} = $default_sel{$dataset} ? 1 : 0;
+ push(@datasets, $datasets);
+ }
- $vars->{'datasets'} = \@datasets;
+ $vars->{'datasets'} = \@datasets;
- print $cgi->header();
+ print $cgi->header();
}
else {
- my $product;
- # For security and correctness, validate the value of the "product_id" form
- # variable. Valid values are IDs of those products for which the user has
- # permissions which appear in the "product_id" drop-down menu on the report
- # generation form. The product_id 0 is a special case, meaning "All
- # Products".
- if ($product_id) {
- $product = Bugzilla::Product->new($product_id);
- $product && $user->can_see_product($product->name)
- || ThrowUserError('product_access_denied',
- {id => $product_id});
- }
- else {
- $product = $product_all;
- }
-
- # Make sure there is something to plot.
- my @datasets = $cgi->param('datasets');
- scalar(@datasets) || ThrowUserError('missing_datasets');
-
- if (grep { $_ !~ /^[A-Za-z0-9:_-]+$/ } @datasets) {
- ThrowUserError('invalid_datasets', {'datasets' => \@datasets});
- }
-
- # Filenames must not be guessable as they can point to products
- # you are not allowed to see. Also, different projects can have
- # the same product IDs.
- my $project = bz_locations()->{'project'} || '';
- my $image_file = join(':', ($project, $product->id, @datasets));
- my $key = Bugzilla->localconfig->{'site_wide_secret'};
- $image_file = hmac_sha256_base64($image_file, $key) . '.png';
- $image_file =~ s/\+/-/g;
- $image_file =~ s/\//_/g;
- trick_taint($image_file);
-
- if (! -e "$graph_dir/$image_file") {
- generate_chart($dir, "$graph_dir/$image_file", $product, \@datasets);
- }
-
- $vars->{'url_image'} = "$graph_url/$image_file";
-
- print $cgi->header(-Content_Disposition=>'inline; filename=bugzilla_report.html');
+ my $product;
+
+ # For security and correctness, validate the value of the "product_id" form
+ # variable. Valid values are IDs of those products for which the user has
+ # permissions which appear in the "product_id" drop-down menu on the report
+ # generation form. The product_id 0 is a special case, meaning "All
+ # Products".
+ if ($product_id) {
+ $product = Bugzilla::Product->new($product_id);
+ $product && $user->can_see_product($product->name)
+ || ThrowUserError('product_access_denied', {id => $product_id});
+ }
+ else {
+ $product = $product_all;
+ }
+
+ # Make sure there is something to plot.
+ my @datasets = $cgi->param('datasets');
+ scalar(@datasets) || ThrowUserError('missing_datasets');
+
+ if (grep { $_ !~ /^[A-Za-z0-9:_-]+$/ } @datasets) {
+ ThrowUserError('invalid_datasets', {'datasets' => \@datasets});
+ }
+
+ # Filenames must not be guessable as they can point to products
+ # you are not allowed to see. Also, different projects can have
+ # the same product IDs.
+ my $project = bz_locations()->{'project'} || '';
+ my $image_file = join(':', ($project, $product->id, @datasets));
+ my $key = Bugzilla->localconfig->{'site_wide_secret'};
+ $image_file = hmac_sha256_base64($image_file, $key) . '.png';
+ $image_file =~ s/\+/-/g;
+ $image_file =~ s/\//_/g;
+ trick_taint($image_file);
+
+ if (!-e "$graph_dir/$image_file") {
+ generate_chart($dir, "$graph_dir/$image_file", $product, \@datasets);
+ }
+
+ $vars->{'url_image'} = "$graph_url/$image_file";
+
+ print $cgi->header(
+ -Content_Disposition => 'inline; filename=bugzilla_report.html');
}
$template->process('reports/old-charts.html.tmpl', $vars)
@@ -118,106 +120,107 @@ $template->process('reports/old-charts.html.tmpl', $vars)
#####################
sub get_data {
- my $dir = shift;
+ my $dir = shift;
- my @datasets;
- open(DATA, '<', "$dir/0")
- || ThrowCodeError('chart_file_open_fail', {filename => "$dir/0"});
+ my @datasets;
+ open(DATA, '<', "$dir/0")
+ || ThrowCodeError('chart_file_open_fail', {filename => "$dir/0"});
- while (<DATA>) {
- if (/^# fields?: (.+)\s*$/) {
- @datasets = grep ! /date/i, (split /\|/, $1);
- last;
- }
+ while (<DATA>) {
+ if (/^# fields?: (.+)\s*$/) {
+ @datasets = grep !/date/i, (split /\|/, $1);
+ last;
}
- close(DATA);
- return @datasets;
+ }
+ close(DATA);
+ return @datasets;
}
sub generate_chart {
- my ($dir, $image_file, $product, $datasets) = @_;
- my $data_file = $dir . '/' . $product->id;
-
- if (!open(FILE, '<', $data_file)) {
- ThrowCodeError('chart_data_not_generated', {'product' => $product});
- }
-
- my $product_in_title = $product->id ? $product->name : 'All Products';
- my @fields;
- my @labels = qw(DATE);
- my %datasets = map { $_ => 1 } @$datasets;
-
- my %data = ();
- while (<FILE>) {
- chomp;
- next unless $_;
- if (/^#/) {
- if (/^# fields?: (.*)\s*$/) {
- @fields = split /\||\r/, $1;
- $data{$_} ||= [] foreach @fields;
- unless ($fields[0] =~ /date/i) {
- ThrowCodeError('chart_datafile_corrupt', {'file' => $data_file});
- }
- push @labels, grep($datasets{$_}, @fields);
- }
- next;
- }
-
- unless (@fields) {
- ThrowCodeError('chart_datafile_corrupt', {'file' => $data_file});
- }
-
- my @line = split /\|/;
- my $date = $line[0];
- my ($yy, $mm, $dd) = $date =~ /^\d{2}(\d{2})(\d{2})(\d{2})$/;
- push @{$data{DATE}}, "$mm/$dd/$yy";
-
- for my $i (1 .. $#fields) {
- my $field = $fields[$i];
- if (! defined $line[$i] or $line[$i] eq '') {
- # no data point given, don't plot (this will probably
- # generate loads of Chart::Base warnings, but that's not
- # our fault.)
- push @{$data{$field}}, undef;
- }
- else {
- push @{$data{$field}}, $line[$i];
- }
+ my ($dir, $image_file, $product, $datasets) = @_;
+ my $data_file = $dir . '/' . $product->id;
+
+ if (!open(FILE, '<', $data_file)) {
+ ThrowCodeError('chart_data_not_generated', {'product' => $product});
+ }
+
+ my $product_in_title = $product->id ? $product->name : 'All Products';
+ my @fields;
+ my @labels = qw(DATE);
+ my %datasets = map { $_ => 1 } @$datasets;
+
+ my %data = ();
+ while (<FILE>) {
+ chomp;
+ next unless $_;
+ if (/^#/) {
+ if (/^# fields?: (.*)\s*$/) {
+ @fields = split /\||\r/, $1;
+ $data{$_} ||= [] foreach @fields;
+ unless ($fields[0] =~ /date/i) {
+ ThrowCodeError('chart_datafile_corrupt', {'file' => $data_file});
}
+ push @labels, grep($datasets{$_}, @fields);
+ }
+ next;
}
-
- shift @labels;
-
- close FILE;
- if (! @{$data{DATE}}) {
- ThrowUserError('insufficient_data_points');
+ unless (@fields) {
+ ThrowCodeError('chart_datafile_corrupt', {'file' => $data_file});
}
- my $img = Chart::Lines->new (800, 600);
- my $i = 0;
-
- my $MAXTICKS = 20; # Try not to show any more x ticks than this.
- my $skip = 1;
- if (@{$data{DATE}} > $MAXTICKS) {
- $skip = int((@{$data{DATE}} + $MAXTICKS - 1) / $MAXTICKS);
+ my @line = split /\|/;
+ my $date = $line[0];
+ my ($yy, $mm, $dd) = $date =~ /^\d{2}(\d{2})(\d{2})(\d{2})$/;
+ push @{$data{DATE}}, "$mm/$dd/$yy";
+
+ for my $i (1 .. $#fields) {
+ my $field = $fields[$i];
+ if (!defined $line[$i] or $line[$i] eq '') {
+
+ # no data point given, don't plot (this will probably
+ # generate loads of Chart::Base warnings, but that's not
+ # our fault.)
+ push @{$data{$field}}, undef;
+ }
+ else {
+ push @{$data{$field}}, $line[$i];
+ }
}
-
- my %settings =
- (
- "title" => "Status Counts for $product_in_title",
- "x_label" => "Dates",
- "y_label" => "Bug Counts",
- "legend_labels" => \@labels,
- "skip_x_ticks" => $skip,
- "y_grid_lines" => "true",
- "grey_background" => "false",
- "colors" => {
- # default dataset colours are too alike
- dataset4 => [0, 0, 0], # black
- },
- );
-
- $img->set (%settings);
- $img->png($image_file, [ @data{('DATE', @labels)} ]);
+ }
+
+ shift @labels;
+
+ close FILE;
+
+ if (!@{$data{DATE}}) {
+ ThrowUserError('insufficient_data_points');
+ }
+
+ my $img = Chart::Lines->new(800, 600);
+ my $i = 0;
+
+ my $MAXTICKS = 20; # Try not to show any more x ticks than this.
+ my $skip = 1;
+ if (@{$data{DATE}} > $MAXTICKS) {
+ $skip = int((@{$data{DATE}} + $MAXTICKS - 1) / $MAXTICKS);
+ }
+
+ my %settings = (
+ "title" => "Status Counts for $product_in_title",
+ "x_label" => "Dates",
+ "y_label" => "Bug Counts",
+ "legend_labels" => \@labels,
+ "skip_x_ticks" => $skip,
+ "y_grid_lines" => "true",
+ "grey_background" => "false",
+ "colors" => {
+
+ # default dataset colours are too alike
+ dataset4 => [0, 0, 0], # black
+ },
+ );
+
+ $img->set(%settings);
+ $img->png($image_file, [@data{('DATE', @labels)}]);
}
diff --git a/request.cgi b/request.cgi
index 347cb7a44..989e5356d 100755
--- a/request.cgi
+++ b/request.cgi
@@ -23,51 +23,53 @@ use Bugzilla::Component;
# Make sure the user is logged in.
my $user = Bugzilla->login();
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
+
# Force the script to run against the shadow DB. We already validated credentials.
Bugzilla->switch_to_shadow_db;
my $template = Bugzilla->template;
-my $action = $cgi->param('action') || '';
-my $format = $template->get_format('request/queue',
- scalar($cgi->param('format')),
- scalar($cgi->param('ctype')));
+my $action = $cgi->param('action') || '';
+my $format = $template->get_format(
+ 'request/queue',
+ scalar($cgi->param('format')),
+ scalar($cgi->param('ctype'))
+);
$cgi->set_dated_content_disp("inline", "requests", $format->{extension});
print $cgi->header($format->{'ctype'});
my $fields;
$fields->{'requester'}->{'type'} = 'single';
+
# If the user doesn't restrict their search to requests from the wind
# (requestee ne '-'), include the requestee for completion.
-unless (defined $cgi->param('requestee')
- && $cgi->param('requestee') eq '-')
-{
- $fields->{'requestee'}->{'type'} = 'single';
+unless (defined $cgi->param('requestee') && $cgi->param('requestee') eq '-') {
+ $fields->{'requestee'}->{'type'} = 'single';
}
Bugzilla::User::match_field($fields);
if ($action eq 'queue') {
- queue($format);
+ queue($format);
}
else {
- my $flagtypes = get_flag_types();
- my @types = ('all', @$flagtypes);
-
- my $vars = {};
- $vars->{'types'} = \@types;
- $vars->{'requests'} = {};
-
- my %components;
- foreach my $prod (@{$user->get_selectable_products}) {
- foreach my $comp (@{$prod->components}) {
- $components{$comp->name} = 1;
- }
+ my $flagtypes = get_flag_types();
+ my @types = ('all', @$flagtypes);
+
+ my $vars = {};
+ $vars->{'types'} = \@types;
+ $vars->{'requests'} = {};
+
+ my %components;
+ foreach my $prod (@{$user->get_selectable_products}) {
+ foreach my $comp (@{$prod->components}) {
+ $components{$comp->name} = 1;
}
- $vars->{'components'} = [ sort { $a cmp $b } keys %components ];
+ }
+ $vars->{'components'} = [sort { $a cmp $b } keys %components];
- $template->process($format->{'template'}, $vars)
- || ThrowTemplateError($template->error());
+ $template->process($format->{'template'}, $vars)
+ || ThrowTemplateError($template->error());
}
exit;
@@ -76,18 +78,19 @@ exit;
################################################################################
sub queue {
- my $format = shift;
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
- my $template = Bugzilla->template;
- my $user = Bugzilla->user;
- my $userid = $user->id;
- my $vars = {};
-
- my $status = validateStatus($cgi->param('status'));
- my $form_group = validateGroup($cgi->param('group'));
-
- my $query =
+ my $format = shift;
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+ my $template = Bugzilla->template;
+ my $user = Bugzilla->user;
+ my $userid = $user->id;
+ my $vars = {};
+
+ my $status = validateStatus($cgi->param('status'));
+ my $form_group = validateGroup($cgi->param('group'));
+
+ my $query =
+
# Select columns describing each flag, the bug/attachment on which
# it has been set, who set it, and of whom they are requesting it.
" SELECT flags.id, flagtypes.name,
@@ -98,6 +101,7 @@ sub queue {
requesters.realname, requesters.login_name,
requestees.realname, requestees.login_name, COUNT(privs.group_id),
" . $dbh->sql_date_format('flags.modification_date', '%Y.%m.%d %H:%i') .
+
# Use the flags and flagtypes tables for information about the flags,
# the bugs and attachments tables for target info, the profiles tables
# for setter and requestee info, the products/components tables
@@ -129,204 +133,207 @@ sub queue {
ON bgmap.bug_id = bugs.bug_id
";
- if (Bugzilla->params->{or_groups}) {
- $query .= " AND bgmap.group_id IN (" . $user->groups_as_string . ")";
- $query .= " WHERE (privs.group_id IS NULL OR bgmap.group_id IS NOT NULL OR";
- }
- else {
- $query .= " AND bgmap.group_id NOT IN (" . $user->groups_as_string . ")";
- $query .= " WHERE (bgmap.group_id IS NULL OR";
- }
-
- # Weed out bug the user does not have access to
- $query .=
- " (ccmap.who IS NOT NULL AND cclist_accessible = 1) OR
+ if (Bugzilla->params->{or_groups}) {
+ $query .= " AND bgmap.group_id IN (" . $user->groups_as_string . ")";
+ $query .= " WHERE (privs.group_id IS NULL OR bgmap.group_id IS NOT NULL OR";
+ }
+ else {
+ $query .= " AND bgmap.group_id NOT IN (" . $user->groups_as_string . ")";
+ $query .= " WHERE (bgmap.group_id IS NULL OR";
+ }
+
+ # Weed out bug the user does not have access to
+ $query .= " (ccmap.who IS NOT NULL AND cclist_accessible = 1) OR
(bugs.reporter = $userid AND bugs.reporter_accessible = 1) OR
- (bugs.assigned_to = $userid) " .
- (Bugzilla->params->{'useqacontact'} ? "OR
- (bugs.qa_contact = $userid))" : ")");
-
- unless ($user->is_insider) {
- $query .= " AND (attachments.attach_id IS NULL
+ (bugs.assigned_to = $userid) " . (
+ Bugzilla->params->{'useqacontact'}
+ ? "OR
+ (bugs.qa_contact = $userid))"
+ : ")"
+ );
+
+ unless ($user->is_insider) {
+ $query .= " AND (attachments.attach_id IS NULL
OR attachments.isprivate = 0
OR attachments.submitter_id = $userid)";
+ }
+
+ # Limit query to pending requests.
+ $query .= " AND flags.status = '?' " unless $status;
+
+ # The set of criteria by which we filter records to display in the queue.
+ my @criteria = ();
+
+ # A list of columns to exclude from the report because the report conditions
+ # limit the data being displayed to exact matches for those columns.
+ # In other words, if we are only displaying "pending" , we don't
+ # need to display a "status" column in the report because the value for that
+ # column will always be the same.
+ my @excluded_columns = ();
+ my $do_union = $cgi->param('do_union');
+
+ # Filter results by exact email address of requester or requestee.
+ if (defined $cgi->param('requester') && $cgi->param('requester') ne "") {
+ my $requester = $dbh->quote($cgi->param('requester'));
+ trick_taint($requester); # Quoted above
+ push(@criteria, $dbh->sql_istrcmp('requesters.login_name', $requester));
+ push(@excluded_columns, 'requester') unless $do_union;
+ }
+ if (defined $cgi->param('requestee') && $cgi->param('requestee') ne "") {
+ if ($cgi->param('requestee') ne "-") {
+ my $requestee = $dbh->quote($cgi->param('requestee'));
+ trick_taint($requestee); # Quoted above
+ push(@criteria, $dbh->sql_istrcmp('requestees.login_name', $requestee));
}
-
- # Limit query to pending requests.
- $query .= " AND flags.status = '?' " unless $status;
-
- # The set of criteria by which we filter records to display in the queue.
- my @criteria = ();
-
- # A list of columns to exclude from the report because the report conditions
- # limit the data being displayed to exact matches for those columns.
- # In other words, if we are only displaying "pending" , we don't
- # need to display a "status" column in the report because the value for that
- # column will always be the same.
- my @excluded_columns = ();
- my $do_union = $cgi->param('do_union');
-
- # Filter results by exact email address of requester or requestee.
- if (defined $cgi->param('requester') && $cgi->param('requester') ne "") {
- my $requester = $dbh->quote($cgi->param('requester'));
- trick_taint($requester); # Quoted above
- push(@criteria, $dbh->sql_istrcmp('requesters.login_name', $requester));
- push(@excluded_columns, 'requester') unless $do_union;
+ else {
+ push(@criteria, "flags.requestee_id IS NULL");
}
- if (defined $cgi->param('requestee') && $cgi->param('requestee') ne "") {
- if ($cgi->param('requestee') ne "-") {
- my $requestee = $dbh->quote($cgi->param('requestee'));
- trick_taint($requestee); # Quoted above
- push(@criteria, $dbh->sql_istrcmp('requestees.login_name', $requestee));
- }
- else {
- push(@criteria, "flags.requestee_id IS NULL");
- }
- push(@excluded_columns, 'requestee') unless $do_union;
+ push(@excluded_columns, 'requestee') unless $do_union;
+ }
+
+ # If the user wants requester = foo OR requestee = bar, we have to join
+ # these criteria separately as all other criteria use AND.
+ if (@criteria == 2 && $do_union) {
+ my $union = join(' OR ', @criteria);
+ @criteria = ("($union)");
+ }
+
+ # Filter requests by status: "pending", "granted", "denied", "all"
+ # (which means any), or "fulfilled" (which means "granted" or "denied").
+ if ($status) {
+ if ($status eq "+-") {
+ push(@criteria, "flags.status IN ('+', '-')");
+ push(@excluded_columns, 'status');
}
-
- # If the user wants requester = foo OR requestee = bar, we have to join
- # these criteria separately as all other criteria use AND.
- if (@criteria == 2 && $do_union) {
- my $union = join(' OR ', @criteria);
- @criteria = ("($union)");
+ elsif ($status ne "all") {
+ push(@criteria, "flags.status = '$status'");
+ push(@excluded_columns, 'status');
}
-
- # Filter requests by status: "pending", "granted", "denied", "all"
- # (which means any), or "fulfilled" (which means "granted" or "denied").
- if ($status) {
- if ($status eq "+-") {
- push(@criteria, "flags.status IN ('+', '-')");
- push(@excluded_columns, 'status');
- }
- elsif ($status ne "all") {
- push(@criteria, "flags.status = '$status'");
- push(@excluded_columns, 'status');
- }
+ }
+
+ # Filter results by exact product or component.
+ if (defined $cgi->param('product') && $cgi->param('product') ne "") {
+ my $product = Bugzilla::Product->check(scalar $cgi->param('product'));
+ push(@criteria, "bugs.product_id = " . $product->id);
+ push(@excluded_columns, 'product');
+ if (defined $cgi->param('component') && $cgi->param('component') ne "") {
+ my $component = Bugzilla::Component->check(
+ {product => $product, name => scalar $cgi->param('component')});
+ push(@criteria, "bugs.component_id = " . $component->id);
+ push(@excluded_columns, 'component');
}
+ }
- # Filter results by exact product or component.
- if (defined $cgi->param('product') && $cgi->param('product') ne "") {
- my $product = Bugzilla::Product->check(scalar $cgi->param('product'));
- push(@criteria, "bugs.product_id = " . $product->id);
- push(@excluded_columns, 'product');
- if (defined $cgi->param('component') && $cgi->param('component') ne "") {
- my $component = Bugzilla::Component->check({ product => $product,
- name => scalar $cgi->param('component') });
- push(@criteria, "bugs.component_id = " . $component->id);
- push(@excluded_columns, 'component');
- }
- }
+ # Filter results by flag types.
+ my $form_type = $cgi->param('type');
+ if (defined $form_type && !grep($form_type eq $_, ("", "all"))) {
- # Filter results by flag types.
- my $form_type = $cgi->param('type');
- if (defined $form_type && !grep($form_type eq $_, ("", "all"))) {
- # Check if any matching types are for attachments. If not, don't show
- # the attachment column in the report.
- my $has_attachment_type =
- Bugzilla::FlagType::count({ 'name' => $form_type,
- 'target_type' => 'attachment' });
-
- if (!$has_attachment_type) { push(@excluded_columns, 'attachment') }
-
- my $quoted_form_type = $dbh->quote($form_type);
- trick_taint($quoted_form_type); # Already SQL quoted
- push(@criteria, "flagtypes.name = " . $quoted_form_type);
- push(@excluded_columns, 'type');
- }
+ # Check if any matching types are for attachments. If not, don't show
+ # the attachment column in the report.
+ my $has_attachment_type = Bugzilla::FlagType::count(
+ {'name' => $form_type, 'target_type' => 'attachment'});
+
+ if (!$has_attachment_type) { push(@excluded_columns, 'attachment') }
+
+ my $quoted_form_type = $dbh->quote($form_type);
+ trick_taint($quoted_form_type); # Already SQL quoted
+ push(@criteria, "flagtypes.name = " . $quoted_form_type);
+ push(@excluded_columns, 'type');
+ }
- $query .= ' AND ' . join(' AND ', @criteria) if scalar(@criteria);
+ $query .= ' AND ' . join(' AND ', @criteria) if scalar(@criteria);
- # Group the records by flag ID so we don't get multiple rows of data
- # for each flag. This is only necessary because of the code that
- # removes flags on bugs the user is unauthorized to access.
- $query .= ' ' . $dbh->sql_group_by('flags.id',
- 'flagtypes.name, flags.status, flags.bug_id, bugs.short_desc,
+ # Group the records by flag ID so we don't get multiple rows of data
+ # for each flag. This is only necessary because of the code that
+ # removes flags on bugs the user is unauthorized to access.
+ $query .= ' ' . $dbh->sql_group_by(
+ 'flags.id', 'flagtypes.name, flags.status, flags.bug_id, bugs.short_desc,
products.name, components.name, flags.attach_id,
attachments.description, requesters.realname,
requesters.login_name, requestees.realname,
requestees.login_name, flags.modification_date,
cclist_accessible, bugs.reporter, bugs.reporter_accessible,
- bugs.assigned_to');
-
- # Group the records, in other words order them by the group column
- # so the loop in the display template can break them up into separate
- # tables every time the value in the group column changes.
-
- $form_group ||= "requestee";
- if ($form_group eq "requester") {
- $query .= " ORDER BY requesters.realname, requesters.login_name";
- }
- elsif ($form_group eq "requestee") {
- $query .= " ORDER BY requestees.realname, requestees.login_name";
- }
- elsif ($form_group eq "category") {
- $query .= " ORDER BY products.name, components.name";
- }
- elsif ($form_group eq "type") {
- $query .= " ORDER BY flagtypes.name";
- }
-
- # Order the records (within each group).
- $query .= " , flags.modification_date";
-
- # Pass the query to the template for use when debugging this script.
- $vars->{'query'} = $query;
- $vars->{'debug'} = $cgi->param('debug') ? 1 : 0;
-
- my $results = $dbh->selectall_arrayref($query);
- my @requests = ();
- foreach my $result (@$results) {
- my @data = @$result;
- my $request = {
- 'id' => $data[0] ,
- 'type' => $data[1] ,
- 'status' => $data[2] ,
- 'bug_id' => $data[3] ,
- 'bug_summary' => $data[4] ,
- 'category' => "$data[5]: $data[6]" ,
- 'attach_id' => $data[7] ,
- 'attach_summary' => $data[8] ,
- 'requester' => ($data[9] ? "$data[9] <$data[10]>" : $data[10]) ,
- 'requestee' => ($data[11] ? "$data[11] <$data[12]>" : $data[12]) ,
- 'restricted' => $data[13] ? 1 : 0,
- 'created' => $data[14]
- };
- push(@requests, $request);
- }
-
- # Get a list of request type names to use in the filter form.
- my @types = ("all");
- my $flagtypes = get_flag_types();
- push(@types, @$flagtypes);
-
- $vars->{'excluded_columns'} = \@excluded_columns;
- $vars->{'group_field'} = $form_group;
- $vars->{'requests'} = \@requests;
- $vars->{'types'} = \@types;
-
- # This code is needed to populate the Product and Component select fields.
- my ($products, %components);
- if (Bugzilla->params->{useclassification}) {
- foreach my $class (@{$user->get_selectable_classifications}) {
- push @$products, @{$user->get_selectable_products($class->id)};
- }
- }
- else {
- $products = $user->get_selectable_products;
+ bugs.assigned_to'
+ );
+
+ # Group the records, in other words order them by the group column
+ # so the loop in the display template can break them up into separate
+ # tables every time the value in the group column changes.
+
+ $form_group ||= "requestee";
+ if ($form_group eq "requester") {
+ $query .= " ORDER BY requesters.realname, requesters.login_name";
+ }
+ elsif ($form_group eq "requestee") {
+ $query .= " ORDER BY requestees.realname, requestees.login_name";
+ }
+ elsif ($form_group eq "category") {
+ $query .= " ORDER BY products.name, components.name";
+ }
+ elsif ($form_group eq "type") {
+ $query .= " ORDER BY flagtypes.name";
+ }
+
+ # Order the records (within each group).
+ $query .= " , flags.modification_date";
+
+ # Pass the query to the template for use when debugging this script.
+ $vars->{'query'} = $query;
+ $vars->{'debug'} = $cgi->param('debug') ? 1 : 0;
+
+ my $results = $dbh->selectall_arrayref($query);
+ my @requests = ();
+ foreach my $result (@$results) {
+ my @data = @$result;
+ my $request = {
+ 'id' => $data[0],
+ 'type' => $data[1],
+ 'status' => $data[2],
+ 'bug_id' => $data[3],
+ 'bug_summary' => $data[4],
+ 'category' => "$data[5]: $data[6]",
+ 'attach_id' => $data[7],
+ 'attach_summary' => $data[8],
+ 'requester' => ($data[9] ? "$data[9] <$data[10]>" : $data[10]),
+ 'requestee' => ($data[11] ? "$data[11] <$data[12]>" : $data[12]),
+ 'restricted' => $data[13] ? 1 : 0,
+ 'created' => $data[14]
+ };
+ push(@requests, $request);
+ }
+
+ # Get a list of request type names to use in the filter form.
+ my @types = ("all");
+ my $flagtypes = get_flag_types();
+ push(@types, @$flagtypes);
+
+ $vars->{'excluded_columns'} = \@excluded_columns;
+ $vars->{'group_field'} = $form_group;
+ $vars->{'requests'} = \@requests;
+ $vars->{'types'} = \@types;
+
+ # This code is needed to populate the Product and Component select fields.
+ my ($products, %components);
+ if (Bugzilla->params->{useclassification}) {
+ foreach my $class (@{$user->get_selectable_classifications}) {
+ push @$products, @{$user->get_selectable_products($class->id)};
}
-
- foreach my $product (@$products) {
- $components{$_->name} = 1 foreach @{$product->components};
- }
- $vars->{'products'} = $products;
- $vars->{'components'} = [ sort keys %components ];
-
- $vars->{'urlquerypart'} = $cgi->canonicalise_query('ctype');
-
- # Generate and return the UI (HTML page) from the appropriate template.
- $template->process($format->{'template'}, $vars)
- || ThrowTemplateError($template->error());
+ }
+ else {
+ $products = $user->get_selectable_products;
+ }
+
+ foreach my $product (@$products) {
+ $components{$_->name} = 1 foreach @{$product->components};
+ }
+ $vars->{'products'} = $products;
+ $vars->{'components'} = [sort keys %components];
+
+ $vars->{'urlquerypart'} = $cgi->canonicalise_query('ctype');
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process($format->{'template'}, $vars)
+ || ThrowTemplateError($template->error());
}
################################################################################
@@ -334,33 +341,35 @@ sub queue {
################################################################################
sub validateStatus {
- my $status = shift;
- return if !defined $status;
+ my $status = shift;
+ return if !defined $status;
- grep($status eq $_, qw(? +- + - all))
- || ThrowUserError("flag_status_invalid", { status => $status });
- trick_taint($status);
- return $status;
+ grep($status eq $_, qw(? +- + - all))
+ || ThrowUserError("flag_status_invalid", {status => $status});
+ trick_taint($status);
+ return $status;
}
sub validateGroup {
- my $group = shift;
- return if !defined $group;
+ my $group = shift;
+ return if !defined $group;
- grep($group eq $_, qw(requester requestee category type))
- || ThrowUserError("request_queue_group_invalid", { group => $group });
- trick_taint($group);
- return $group;
+ grep($group eq $_, qw(requester requestee category type))
+ || ThrowUserError("request_queue_group_invalid", {group => $group});
+ trick_taint($group);
+ return $group;
}
# Returns all flag types which have at least one flag of this type.
# If a flag type is inactive but still has flags, we want it.
sub get_flag_types {
- my $dbh = Bugzilla->dbh;
- my $flag_types = $dbh->selectcol_arrayref('SELECT DISTINCT name
+ my $dbh = Bugzilla->dbh;
+ my $flag_types = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT name
FROM flagtypes
WHERE flagtypes.id IN
(SELECT DISTINCT type_id FROM flags)
- ORDER BY name');
- return $flag_types;
+ ORDER BY name'
+ );
+ return $flag_types;
}
diff --git a/rest.cgi b/rest.cgi
index f12fb64c4..892cb62dd 100755
--- a/rest.cgi
+++ b/rest.cgi
@@ -16,12 +16,11 @@ use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::WebService::Constants;
+
BEGIN {
- if (!Bugzilla->feature('rest')
- || !Bugzilla->feature('jsonrpc'))
- {
- ThrowUserError('feature_disabled', { feature => 'rest' });
- }
+ if (!Bugzilla->feature('rest') || !Bugzilla->feature('jsonrpc')) {
+ ThrowUserError('feature_disabled', {feature => 'rest'});
+ }
}
use Bugzilla::WebService::Server::REST;
Bugzilla->usage_mode(USAGE_MODE_REST);
diff --git a/runtests.pl b/runtests.pl
index 52de88e10..92e9df806 100755
--- a/runtests.pl
+++ b/runtests.pl
@@ -19,12 +19,12 @@ $verbose = 0;
my $onlytest = "";
foreach (@ARGV) {
- if (/^(?:-v|--verbose)$/) {
- $verbose = 1;
- }
- else {
- $onlytest = sprintf("%0.3d",$_);
- }
+ if (/^(?:-v|--verbose)$/) {
+ $verbose = 1;
+ }
+ else {
+ $onlytest = sprintf("%0.3d", $_);
+ }
}
runtests(glob("t/$onlytest*.t"));
diff --git a/sanitycheck.cgi b/sanitycheck.cgi
index 889d7f3e6..3d4334e93 100755
--- a/sanitycheck.cgi
+++ b/sanitycheck.cgi
@@ -26,26 +26,29 @@ use Bugzilla::Token;
###########################################################################
sub get_string {
- my ($san_tag, $vars) = @_;
- $vars->{'san_tag'} = $san_tag;
- return get_text('sanitycheck', $vars);
+ my ($san_tag, $vars) = @_;
+ $vars->{'san_tag'} = $san_tag;
+ return get_text('sanitycheck', $vars);
}
sub Status {
- my ($san_tag, $vars, $alert) = @_;
- my $cgi = Bugzilla->cgi;
- return if (!$alert && Bugzilla->usage_mode == USAGE_MODE_CMDLINE && !$cgi->param('verbose'));
-
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- my $output = $cgi->param('output') || '';
- my $linebreak = $alert ? "\nALERT: " : "\n";
- $cgi->param('error_found', 1) if $alert;
- $cgi->param('output', $output . $linebreak . get_string($san_tag, $vars));
- }
- else {
- my $start_tag = $alert ? '<p class="alert">' : '<p>';
- say $start_tag . get_string($san_tag, $vars) . "</p>";
- }
+ my ($san_tag, $vars, $alert) = @_;
+ my $cgi = Bugzilla->cgi;
+ return
+ if (!$alert
+ && Bugzilla->usage_mode == USAGE_MODE_CMDLINE
+ && !$cgi->param('verbose'));
+
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ my $output = $cgi->param('output') || '';
+ my $linebreak = $alert ? "\nALERT: " : "\n";
+ $cgi->param('error_found', 1) if $alert;
+ $cgi->param('output', $output . $linebreak . get_string($san_tag, $vars));
+ }
+ else {
+ my $start_tag = $alert ? '<p class="alert">' : '<p>';
+ say $start_tag . get_string($san_tag, $vars) . "</p>";
+ }
}
###########################################################################
@@ -56,25 +59,26 @@ my $user = Bugzilla->login(LOGIN_REQUIRED);
my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh;
+
# If the result of the sanity check is sent per email, then we have to
# take the user prefs into account rather than querying the web browser.
my $template;
if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- $template = Bugzilla->template_inner($user->setting('lang'));
+ $template = Bugzilla->template_inner($user->setting('lang'));
}
else {
- $template = Bugzilla->template;
-
- # Only check the token if we are running this script from the
- # web browser and a parameter is passed to the script.
- # XXX - Maybe these two parameters should be deleted once logged in?
- $cgi->delete('GoAheadAndLogIn', 'Bugzilla_restrictlogin');
- if (scalar($cgi->param())) {
- my $token = $cgi->param('token');
- check_hash_token($token, ['sanitycheck']);
- }
+ $template = Bugzilla->template;
+
+ # Only check the token if we are running this script from the
+ # web browser and a parameter is passed to the script.
+ # XXX - Maybe these two parameters should be deleted once logged in?
+ $cgi->delete('GoAheadAndLogIn', 'Bugzilla_restrictlogin');
+ if (scalar($cgi->param())) {
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['sanitycheck']);
+ }
}
-my $vars = {};
+my $vars = {};
my $clear_memcached = 0;
print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
@@ -83,13 +87,12 @@ print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
# As this script can now alter the group_control_map table, we no longer
# let users with editbugs privs run it anymore.
$user->in_group("editcomponents")
- || ThrowUserError("auth_failure", {group => "editcomponents",
- action => "run",
- object => "sanity_check"});
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "run", object => "sanity_check"});
unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- $template->process('admin/sanitycheck/list.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ $template->process('admin/sanitycheck/list.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
###########################################################################
@@ -97,25 +100,26 @@ unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
###########################################################################
if ($cgi->param('createmissinggroupcontrolmapentries')) {
- Status('group_control_map_entries_creation');
+ Status('group_control_map_entries_creation');
- my $na = CONTROLMAPNA;
- my $shown = CONTROLMAPSHOWN;
- my $insertsth = $dbh->prepare(
- qq{INSERT INTO group_control_map
+ my $na = CONTROLMAPNA;
+ my $shown = CONTROLMAPSHOWN;
+ my $insertsth = $dbh->prepare(qq{INSERT INTO group_control_map
(group_id, product_id, membercontrol, othercontrol)
- VALUES (?, ?, $shown, $na)});
+ VALUES (?, ?, $shown, $na)}
+ );
- my $updatesth = $dbh->prepare(qq{UPDATE group_control_map
+ my $updatesth = $dbh->prepare(qq{UPDATE group_control_map
SET membercontrol = $shown
WHERE group_id = ?
- AND product_id = ?});
- my $counter = 0;
-
- # Find all group/product combinations used for bugs but not set up
- # correctly in group_control_map
- my $invalid_combinations = $dbh->selectall_arrayref(
- qq{ SELECT bugs.product_id,
+ AND product_id = ?}
+ );
+ my $counter = 0;
+
+ # Find all group/product combinations used for bugs but not set up
+ # correctly in group_control_map
+ my $invalid_combinations = $dbh->selectall_arrayref(
+ qq{ SELECT bugs.product_id,
bgm.group_id,
gcm.membercontrol,
groups.name,
@@ -131,28 +135,32 @@ if ($cgi->param('createmissinggroupcontrolmapentries')) {
ON bugs.product_id = gcm.product_id
AND bgm.group_id = gcm.group_id
WHERE COALESCE(gcm.membercontrol, $na) = $na
- } . $dbh->sql_group_by('bugs.product_id, bgm.group_id',
- 'gcm.membercontrol, groups.name, products.name'));
-
- foreach (@$invalid_combinations) {
- my ($product_id, $group_id, $currentmembercontrol,
- $group_name, $product_name) = @$_;
-
- $counter++;
- if (defined($currentmembercontrol)) {
- Status('group_control_map_entries_update',
- {group_name => $group_name, product_name => $product_name});
- $updatesth->execute($group_id, $product_id);
- }
- else {
- Status('group_control_map_entries_generation',
- {group_name => $group_name, product_name => $product_name});
- $insertsth->execute($group_id, $product_id);
- }
+ }
+ . $dbh->sql_group_by(
+ 'bugs.product_id, bgm.group_id',
+ 'gcm.membercontrol, groups.name, products.name'
+ )
+ );
+
+ foreach (@$invalid_combinations) {
+ my ($product_id, $group_id, $currentmembercontrol, $group_name, $product_name)
+ = @$_;
+
+ $counter++;
+ if (defined($currentmembercontrol)) {
+ Status('group_control_map_entries_update',
+ {group_name => $group_name, product_name => $product_name});
+ $updatesth->execute($group_id, $product_id);
}
+ else {
+ Status('group_control_map_entries_generation',
+ {group_name => $group_name, product_name => $product_name});
+ $insertsth->execute($group_id, $product_id);
+ }
+ }
- Status('group_control_map_entries_repaired', {counter => $counter});
- $clear_memcached = 1 if $counter;
+ Status('group_control_map_entries_repaired', {counter => $counter});
+ $clear_memcached = 1 if $counter;
}
###########################################################################
@@ -160,26 +168,32 @@ if ($cgi->param('createmissinggroupcontrolmapentries')) {
###########################################################################
if ($cgi->param('repair_creation_date')) {
- Status('bug_creation_date_start');
-
- my $bug_ids = $dbh->selectcol_arrayref('SELECT bug_id FROM bugs
- WHERE creation_ts IS NULL');
-
- my $sth_UpdateDate = $dbh->prepare('UPDATE bugs SET creation_ts = ?
- WHERE bug_id = ?');
-
- # All bugs have an entry in the 'longdescs' table when they are created,
- # even if no comment is required.
- my $sth_getDate = $dbh->prepare('SELECT MIN(bug_when) FROM longdescs
- WHERE bug_id = ?');
-
- foreach my $bugid (@$bug_ids) {
- $sth_getDate->execute($bugid);
- my $date = $sth_getDate->fetchrow_array;
- $sth_UpdateDate->execute($date, $bugid);
- }
- Status('bug_creation_date_fixed', {bug_count => scalar(@$bug_ids)});
- $clear_memcached = 1 if @$bug_ids;
+ Status('bug_creation_date_start');
+
+ my $bug_ids = $dbh->selectcol_arrayref(
+ 'SELECT bug_id FROM bugs
+ WHERE creation_ts IS NULL'
+ );
+
+ my $sth_UpdateDate = $dbh->prepare(
+ 'UPDATE bugs SET creation_ts = ?
+ WHERE bug_id = ?'
+ );
+
+ # All bugs have an entry in the 'longdescs' table when they are created,
+ # even if no comment is required.
+ my $sth_getDate = $dbh->prepare(
+ 'SELECT MIN(bug_when) FROM longdescs
+ WHERE bug_id = ?'
+ );
+
+ foreach my $bugid (@$bug_ids) {
+ $sth_getDate->execute($bugid);
+ my $date = $sth_getDate->fetchrow_array;
+ $sth_UpdateDate->execute($date, $bugid);
+ }
+ Status('bug_creation_date_fixed', {bug_count => scalar(@$bug_ids)});
+ $clear_memcached = 1 if @$bug_ids;
}
###########################################################################
@@ -187,16 +201,19 @@ if ($cgi->param('repair_creation_date')) {
###########################################################################
if ($cgi->param('repair_everconfirmed')) {
- Status('everconfirmed_start');
+ Status('everconfirmed_start');
- my @confirmed_open_states = grep {$_ ne 'UNCONFIRMED'} BUG_STATE_OPEN;
- my $confirmed_open_states = join(', ', map {$dbh->quote($_)} @confirmed_open_states);
+ my @confirmed_open_states = grep { $_ ne 'UNCONFIRMED' } BUG_STATE_OPEN;
+ my $confirmed_open_states
+ = join(', ', map { $dbh->quote($_) } @confirmed_open_states);
- $dbh->do("UPDATE bugs SET everconfirmed = 0 WHERE bug_status = 'UNCONFIRMED'");
- $dbh->do("UPDATE bugs SET everconfirmed = 1 WHERE bug_status IN ($confirmed_open_states)");
+ $dbh->do("UPDATE bugs SET everconfirmed = 0 WHERE bug_status = 'UNCONFIRMED'");
+ $dbh->do(
+ "UPDATE bugs SET everconfirmed = 1 WHERE bug_status IN ($confirmed_open_states)"
+ );
- Status('everconfirmed_end');
- $clear_memcached = 1;
+ Status('everconfirmed_end');
+ $clear_memcached = 1;
}
###########################################################################
@@ -204,20 +221,22 @@ if ($cgi->param('repair_everconfirmed')) {
###########################################################################
if ($cgi->param('repair_bugs_fulltext')) {
- Status('bugs_fulltext_start');
+ Status('bugs_fulltext_start');
- my $bug_ids = $dbh->selectcol_arrayref('SELECT bugs.bug_id
+ my $bug_ids = $dbh->selectcol_arrayref(
+ 'SELECT bugs.bug_id
FROM bugs
LEFT JOIN bugs_fulltext
ON bugs_fulltext.bug_id = bugs.bug_id
- WHERE bugs_fulltext.bug_id IS NULL');
+ WHERE bugs_fulltext.bug_id IS NULL'
+ );
- foreach my $bugid (@$bug_ids) {
- Bugzilla::Bug->new($bugid)->_sync_fulltext( new_bug => 1 );
- }
+ foreach my $bugid (@$bug_ids) {
+ Bugzilla::Bug->new($bugid)->_sync_fulltext(new_bug => 1);
+ }
- Status('bugs_fulltext_fixed', {bug_count => scalar(@$bug_ids)});
- $clear_memcached = 1 if @$bug_ids;
+ Status('bugs_fulltext_fixed', {bug_count => scalar(@$bug_ids)});
+ $clear_memcached = 1 if @$bug_ids;
}
###########################################################################
@@ -225,44 +244,45 @@ if ($cgi->param('repair_bugs_fulltext')) {
###########################################################################
if ($cgi->param('rescanallBugMail')) {
- require Bugzilla::BugMail;
+ require Bugzilla::BugMail;
- Status('send_bugmail_start');
- my $time = $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE');
+ Status('send_bugmail_start');
+ my $time = $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE');
- my $list = $dbh->selectcol_arrayref(qq{
+ my $list = $dbh->selectcol_arrayref(qq{
SELECT bug_id
FROM bugs
WHERE (lastdiffed IS NULL
OR lastdiffed < delta_ts)
AND delta_ts < $time
- ORDER BY bug_id});
-
- Status('send_bugmail_status', {bug_count => scalar(@$list)});
-
- # We cannot simply look at the bugs_activity table to find who did the
- # last change in a given bug, as e.g. adding a comment doesn't add any
- # entry to this table. And some other changes may be private
- # (such as time-related changes or private attachments or comments)
- # and so choosing this user as being the last one having done a change
- # for the bug may be problematic. So the best we can do at this point
- # is to choose the currently logged in user for email notification.
- $vars->{'changer'} = $user;
-
- foreach my $bugid (@$list) {
- Bugzilla::BugMail::Send($bugid, $vars);
- }
-
- if (@$list) {
- Status('send_bugmail_end');
- Bugzilla->memcached->clear_all();
- }
-
- unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- $template->process('global/footer.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
- }
- exit;
+ ORDER BY bug_id}
+ );
+
+ Status('send_bugmail_status', {bug_count => scalar(@$list)});
+
+ # We cannot simply look at the bugs_activity table to find who did the
+ # last change in a given bug, as e.g. adding a comment doesn't add any
+ # entry to this table. And some other changes may be private
+ # (such as time-related changes or private attachments or comments)
+ # and so choosing this user as being the last one having done a change
+ # for the bug may be problematic. So the best we can do at this point
+ # is to choose the currently logged in user for email notification.
+ $vars->{'changer'} = $user;
+
+ foreach my $bugid (@$list) {
+ Bugzilla::BugMail::Send($bugid, $vars);
+ }
+
+ if (@$list) {
+ Status('send_bugmail_end');
+ Bugzilla->memcached->clear_all();
+ }
+
+ unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ $template->process('global/footer.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+ }
+ exit;
}
###########################################################################
@@ -270,32 +290,37 @@ if ($cgi->param('rescanallBugMail')) {
###########################################################################
if ($cgi->param('remove_invalid_bug_references')) {
- Status('bug_reference_deletion_start');
+ Status('bug_reference_deletion_start');
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- foreach my $pair ('attachments/', 'bug_group_map/', 'bugs_activity/',
- 'bugs_fulltext/', 'cc/',
- 'dependencies/blocked', 'dependencies/dependson',
- 'duplicates/dupe', 'duplicates/dupe_of',
- 'flags/', 'keywords/', 'longdescs/') {
+ foreach my $pair (
+ 'attachments/', 'bug_group_map/',
+ 'bugs_activity/', 'bugs_fulltext/',
+ 'cc/', 'dependencies/blocked',
+ 'dependencies/dependson', 'duplicates/dupe',
+ 'duplicates/dupe_of', 'flags/',
+ 'keywords/', 'longdescs/'
+ )
+ {
- my ($table, $field) = split('/', $pair);
- $field ||= "bug_id";
+ my ($table, $field) = split('/', $pair);
+ $field ||= "bug_id";
- my $bug_ids =
- $dbh->selectcol_arrayref("SELECT $table.$field FROM $table
+ my $bug_ids = $dbh->selectcol_arrayref(
+ "SELECT $table.$field FROM $table
LEFT JOIN bugs ON $table.$field = bugs.bug_id
- WHERE bugs.bug_id IS NULL");
+ WHERE bugs.bug_id IS NULL"
+ );
- if (scalar(@$bug_ids)) {
- $dbh->do("DELETE FROM $table WHERE $field IN (" . join(',', @$bug_ids) . ")");
- $clear_memcached = 1;
- }
+ if (scalar(@$bug_ids)) {
+ $dbh->do("DELETE FROM $table WHERE $field IN (" . join(',', @$bug_ids) . ")");
+ $clear_memcached = 1;
}
+ }
- $dbh->bz_commit_transaction();
- Status('bug_reference_deletion_end');
+ $dbh->bz_commit_transaction();
+ Status('bug_reference_deletion_end');
}
###########################################################################
@@ -303,25 +328,26 @@ if ($cgi->param('remove_invalid_bug_references')) {
###########################################################################
if ($cgi->param('remove_invalid_attach_references')) {
- Status('attachment_reference_deletion_start');
+ Status('attachment_reference_deletion_start');
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- my $attach_ids =
- $dbh->selectcol_arrayref('SELECT attach_data.id
+ my $attach_ids = $dbh->selectcol_arrayref(
+ 'SELECT attach_data.id
FROM attach_data
LEFT JOIN attachments
ON attachments.attach_id = attach_data.id
- WHERE attachments.attach_id IS NULL');
+ WHERE attachments.attach_id IS NULL'
+ );
- if (scalar(@$attach_ids)) {
- $dbh->do('DELETE FROM attach_data WHERE id IN (' .
- join(',', @$attach_ids) . ')');
- }
+ if (scalar(@$attach_ids)) {
+ $dbh->do(
+ 'DELETE FROM attach_data WHERE id IN (' . join(',', @$attach_ids) . ')');
+ }
- $dbh->bz_commit_transaction();
- Status('attachment_reference_deletion_end');
- $clear_memcached = 1 if @$attach_ids;
+ $dbh->bz_commit_transaction();
+ Status('attachment_reference_deletion_end');
+ $clear_memcached = 1 if @$attach_ids;
}
###########################################################################
@@ -329,30 +355,33 @@ if ($cgi->param('remove_invalid_attach_references')) {
###########################################################################
if ($cgi->param('remove_old_whine_targets')) {
- Status('whines_obsolete_target_deletion_start');
+ Status('whines_obsolete_target_deletion_start');
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- foreach my $target (['groups', 'id', MAILTO_GROUP],
- ['profiles', 'userid', MAILTO_USER])
- {
- my ($table, $col, $type) = @$target;
- my $old_ids =
- $dbh->selectcol_arrayref("SELECT DISTINCT mailto
+ foreach my $target (['groups', 'id', MAILTO_GROUP],
+ ['profiles', 'userid', MAILTO_USER])
+ {
+ my ($table, $col, $type) = @$target;
+ my $old_ids = $dbh->selectcol_arrayref(
+ "SELECT DISTINCT mailto
FROM whine_schedules
LEFT JOIN $table
ON $table.$col = whine_schedules.mailto
- WHERE mailto_type = $type AND $table.$col IS NULL");
-
- if (scalar(@$old_ids)) {
- $dbh->do("DELETE FROM whine_schedules
- WHERE mailto_type = $type AND mailto IN (" .
- join(',', @$old_ids) . ")");
- $clear_memcached = 1;
- }
+ WHERE mailto_type = $type AND $table.$col IS NULL"
+ );
+
+ if (scalar(@$old_ids)) {
+ $dbh->do(
+ "DELETE FROM whine_schedules
+ WHERE mailto_type = $type AND mailto IN ("
+ . join(',', @$old_ids) . ")"
+ );
+ $clear_memcached = 1;
}
- $dbh->bz_commit_transaction();
- Status('whines_obsolete_target_deletion_end');
+ }
+ $dbh->bz_commit_transaction();
+ Status('whines_obsolete_target_deletion_end');
}
# If any repairs were attempted or made, we need to clear memcached to ensure
@@ -363,7 +392,7 @@ Bugzilla->memcached->clear_all() if $clear_memcached;
# Repair hook
###########################################################################
-Bugzilla::Hook::process('sanitycheck_repair', { status => \&Status });
+Bugzilla::Hook::process('sanitycheck_repair', {status => \&Status});
###########################################################################
# Checks
@@ -393,182 +422,211 @@ Status('checks_start');
# The same goes for series; no bug for that yet.
sub CrossCheck {
- my $table = shift @_;
- my $field = shift @_;
- my $dbh = Bugzilla->dbh;
+ my $table = shift @_;
+ my $field = shift @_;
+ my $dbh = Bugzilla->dbh;
- Status('cross_check_to', {table => $table, field => $field});
+ Status('cross_check_to', {table => $table, field => $field});
- while (@_) {
- my $ref = shift @_;
- my ($refertable, $referfield, $keyname, $exceptions) = @$ref;
+ while (@_) {
+ my $ref = shift @_;
+ my ($refertable, $referfield, $keyname, $exceptions) = @$ref;
- $exceptions ||= [];
- my %exceptions = map { $_ => 1 } @$exceptions;
+ $exceptions ||= [];
+ my %exceptions = map { $_ => 1 } @$exceptions;
- Status('cross_check_from', {table => $refertable, field => $referfield});
+ Status('cross_check_from', {table => $refertable, field => $referfield});
- my $query = qq{SELECT DISTINCT $refertable.$referfield} .
- ($keyname ? qq{, $refertable.$keyname } : q{}) .
- qq{ FROM $refertable
+ my $query
+ = qq{SELECT DISTINCT $refertable.$referfield}
+ . ($keyname ? qq{, $refertable.$keyname } : q{})
+ . qq{ FROM $refertable
LEFT JOIN $table
ON $refertable.$referfield = $table.$field
WHERE $table.$field IS NULL
AND $refertable.$referfield IS NOT NULL};
- my $sth = $dbh->prepare($query);
- $sth->execute;
-
- my $has_bad_references = 0;
-
- while (my ($value, $key) = $sth->fetchrow_array) {
- next if $exceptions{$value};
- Status('cross_check_alert', {value => $value, table => $refertable,
- field => $referfield, keyname => $keyname,
- key => $key}, 'alert');
- $has_bad_references = 1;
- }
- # References to non existent bugs can be safely removed, bug 288461
- if ($table eq 'bugs' && $has_bad_references) {
- Status('cross_check_bug_has_references');
- }
- # References to non existent attachments can be safely removed.
- if ($table eq 'attachments' && $has_bad_references) {
- Status('cross_check_attachment_has_references');
- }
+ my $sth = $dbh->prepare($query);
+ $sth->execute;
+
+ my $has_bad_references = 0;
+
+ while (my ($value, $key) = $sth->fetchrow_array) {
+ next if $exceptions{$value};
+ Status(
+ 'cross_check_alert',
+ {
+ value => $value,
+ table => $refertable,
+ field => $referfield,
+ keyname => $keyname,
+ key => $key
+ },
+ 'alert'
+ );
+ $has_bad_references = 1;
+ }
+
+ # References to non existent bugs can be safely removed, bug 288461
+ if ($table eq 'bugs' && $has_bad_references) {
+ Status('cross_check_bug_has_references');
+ }
+
+ # References to non existent attachments can be safely removed.
+ if ($table eq 'attachments' && $has_bad_references) {
+ Status('cross_check_attachment_has_references');
}
+ }
}
-CrossCheck('classifications', 'id',
- ['products', 'classification_id']);
-
-CrossCheck("keyworddefs", "id",
- ["keywords", "keywordid"]);
-
-CrossCheck("fielddefs", "id",
- ["bugs_activity", "fieldid"],
- ['profiles_activity', 'fieldid']);
-
-CrossCheck("flagtypes", "id",
- ["flags", "type_id"],
- ["flagexclusions", "type_id"],
- ["flaginclusions", "type_id"]);
-
-CrossCheck("bugs", "bug_id",
- ["bugs_activity", "bug_id"],
- ["bug_group_map", "bug_id"],
- ["bugs_fulltext", "bug_id"],
- ["attachments", "bug_id"],
- ["cc", "bug_id"],
- ["longdescs", "bug_id"],
- ["dependencies", "blocked"],
- ["dependencies", "dependson"],
- ['flags', 'bug_id'],
- ["keywords", "bug_id"],
- ["duplicates", "dupe_of", "dupe"],
- ["duplicates", "dupe", "dupe_of"]);
-
-CrossCheck("groups", "id",
- ["bug_group_map", "group_id"],
- ['category_group_map', 'group_id'],
- ["group_group_map", "grantor_id"],
- ["group_group_map", "member_id"],
- ["group_control_map", "group_id"],
- ["namedquery_group_map", "group_id"],
- ["user_group_map", "group_id"],
- ["flagtypes", "grant_group_id"],
- ["flagtypes", "request_group_id"]);
-
-CrossCheck("namedqueries", "id",
- ["namedqueries_link_in_footer", "namedquery_id"],
- ["namedquery_group_map", "namedquery_id"],
- );
-
-CrossCheck("profiles", "userid",
- ['profiles_activity', 'userid'],
- ['profiles_activity', 'who'],
- ['email_setting', 'user_id'],
- ['profile_setting', 'user_id'],
- ["bugs", "reporter", "bug_id"],
- ["bugs", "assigned_to", "bug_id"],
- ["bugs", "qa_contact", "bug_id"],
- ["attachments", "submitter_id", "bug_id"],
- ['flags', 'setter_id', 'bug_id'],
- ['flags', 'requestee_id', 'bug_id'],
- ["bugs_activity", "who", "bug_id"],
- ["cc", "who", "bug_id"],
- ['quips', 'userid'],
- ["longdescs", "who", "bug_id"],
- ["logincookies", "userid"],
- ["namedqueries", "userid"],
- ["namedqueries_link_in_footer", "user_id"],
- ['series', 'creator', 'series_id'],
- ["watch", "watcher"],
- ["watch", "watched"],
- ['whine_events', 'owner_userid'],
- ["tokens", "userid"],
- ["user_group_map", "user_id"],
- ["components", "initialowner", "name"],
- ["components", "initialqacontact", "name"],
- ["component_cc", "user_id"]);
-
-CrossCheck("products", "id",
- ["bugs", "product_id", "bug_id"],
- ["components", "product_id", "name"],
- ["milestones", "product_id", "value"],
- ["versions", "product_id", "value"],
- ["group_control_map", "product_id"],
- ["flaginclusions", "product_id", "type_id"],
- ["flagexclusions", "product_id", "type_id"]);
-
-CrossCheck("components", "id",
- ["component_cc", "component_id"],
- ["flagexclusions", "component_id", "type_id"],
- ["flaginclusions", "component_id", "type_id"]);
+CrossCheck('classifications', 'id', ['products', 'classification_id']);
+
+CrossCheck("keyworddefs", "id", ["keywords", "keywordid"]);
+
+CrossCheck(
+ "fielddefs", "id",
+ ["bugs_activity", "fieldid"],
+ ['profiles_activity', 'fieldid']
+);
+
+CrossCheck(
+ "flagtypes", "id",
+ ["flags", "type_id"],
+ ["flagexclusions", "type_id"],
+ ["flaginclusions", "type_id"]
+);
+
+CrossCheck(
+ "bugs",
+ "bug_id",
+ ["bugs_activity", "bug_id"],
+ ["bug_group_map", "bug_id"],
+ ["bugs_fulltext", "bug_id"],
+ ["attachments", "bug_id"],
+ ["cc", "bug_id"],
+ ["longdescs", "bug_id"],
+ ["dependencies", "blocked"],
+ ["dependencies", "dependson"],
+ ['flags', 'bug_id'],
+ ["keywords", "bug_id"],
+ ["duplicates", "dupe_of", "dupe"],
+ ["duplicates", "dupe", "dupe_of"]
+);
+
+CrossCheck(
+ "groups",
+ "id",
+ ["bug_group_map", "group_id"],
+ ['category_group_map', 'group_id'],
+ ["group_group_map", "grantor_id"],
+ ["group_group_map", "member_id"],
+ ["group_control_map", "group_id"],
+ ["namedquery_group_map", "group_id"],
+ ["user_group_map", "group_id"],
+ ["flagtypes", "grant_group_id"],
+ ["flagtypes", "request_group_id"]
+);
+
+CrossCheck(
+ "namedqueries", "id",
+ ["namedqueries_link_in_footer", "namedquery_id"],
+ ["namedquery_group_map", "namedquery_id"],
+);
+
+CrossCheck(
+ "profiles",
+ "userid",
+ ['profiles_activity', 'userid'],
+ ['profiles_activity', 'who'],
+ ['email_setting', 'user_id'],
+ ['profile_setting', 'user_id'],
+ ["bugs", "reporter", "bug_id"],
+ ["bugs", "assigned_to", "bug_id"],
+ ["bugs", "qa_contact", "bug_id"],
+ ["attachments", "submitter_id", "bug_id"],
+ ['flags', 'setter_id', 'bug_id'],
+ ['flags', 'requestee_id', 'bug_id'],
+ ["bugs_activity", "who", "bug_id"],
+ ["cc", "who", "bug_id"],
+ ['quips', 'userid'],
+ ["longdescs", "who", "bug_id"],
+ ["logincookies", "userid"],
+ ["namedqueries", "userid"],
+ ["namedqueries_link_in_footer", "user_id"],
+ ['series', 'creator', 'series_id'],
+ ["watch", "watcher"],
+ ["watch", "watched"],
+ ['whine_events', 'owner_userid'],
+ ["tokens", "userid"],
+ ["user_group_map", "user_id"],
+ ["components", "initialowner", "name"],
+ ["components", "initialqacontact", "name"],
+ ["component_cc", "user_id"]
+);
+
+CrossCheck(
+ "products",
+ "id",
+ ["bugs", "product_id", "bug_id"],
+ ["components", "product_id", "name"],
+ ["milestones", "product_id", "value"],
+ ["versions", "product_id", "value"],
+ ["group_control_map", "product_id"],
+ ["flaginclusions", "product_id", "type_id"],
+ ["flagexclusions", "product_id", "type_id"]
+);
+
+CrossCheck(
+ "components", "id",
+ ["component_cc", "component_id"],
+ ["flagexclusions", "component_id", "type_id"],
+ ["flaginclusions", "component_id", "type_id"]
+);
# Check the former enum types -mkanat@bugzilla.org
-CrossCheck("bug_status", "value",
- ["bugs", "bug_status", "bug_id"]);
+CrossCheck("bug_status", "value", ["bugs", "bug_status", "bug_id"]);
-CrossCheck("resolution", "value",
- ["bugs", "resolution", "bug_id"]);
+CrossCheck("resolution", "value", ["bugs", "resolution", "bug_id"]);
-CrossCheck("bug_severity", "value",
- ["bugs", "bug_severity", "bug_id"]);
+CrossCheck("bug_severity", "value", ["bugs", "bug_severity", "bug_id"]);
-CrossCheck("op_sys", "value",
- ["bugs", "op_sys", "bug_id"]);
+CrossCheck("op_sys", "value", ["bugs", "op_sys", "bug_id"]);
-CrossCheck("priority", "value",
- ["bugs", "priority", "bug_id"]);
+CrossCheck("priority", "value", ["bugs", "priority", "bug_id"]);
-CrossCheck("rep_platform", "value",
- ["bugs", "rep_platform", "bug_id"]);
+CrossCheck("rep_platform", "value", ["bugs", "rep_platform", "bug_id"]);
-CrossCheck('series', 'series_id',
- ['series_data', 'series_id']);
+CrossCheck('series', 'series_id', ['series_data', 'series_id']);
-CrossCheck('series_categories', 'id',
- ['series', 'category'],
- ["category_group_map", "category_id"],
- ["series", "subcategory"]);
+CrossCheck(
+ 'series_categories', 'id',
+ ['series', 'category'],
+ ["category_group_map", "category_id"],
+ ["series", "subcategory"]
+);
-CrossCheck('whine_events', 'id',
- ['whine_queries', 'eventid'],
- ['whine_schedules', 'eventid']);
+CrossCheck(
+ 'whine_events', 'id',
+ ['whine_queries', 'eventid'],
+ ['whine_schedules', 'eventid']
+);
-CrossCheck('attachments', 'attach_id',
- ['attach_data', 'id'],
- ['bugs_activity', 'attach_id']);
+CrossCheck(
+ 'attachments', 'attach_id',
+ ['attach_data', 'id'],
+ ['bugs_activity', 'attach_id']
+);
-CrossCheck('bug_status', 'id',
- ['status_workflow', 'old_status'],
- ['status_workflow', 'new_status']);
+CrossCheck(
+ 'bug_status', 'id',
+ ['status_workflow', 'old_status'],
+ ['status_workflow', 'new_status']
+);
###########################################################################
# Perform double field referential (cross) checks
###########################################################################
-
+
# This checks that a compound two-field foreign key has a valid primary key
# value. NULL references are acceptable and cause no problem.
#
@@ -584,60 +642,77 @@ CrossCheck('bug_status', 'id',
# table to display when the check fails
sub DoubleCrossCheck {
- my $table = shift @_;
- my $field1 = shift @_;
- my $field2 = shift @_;
- my $dbh = Bugzilla->dbh;
+ my $table = shift @_;
+ my $field1 = shift @_;
+ my $field2 = shift @_;
+ my $dbh = Bugzilla->dbh;
- Status('double_cross_check_to',
- {table => $table, field1 => $field1, field2 => $field2});
+ Status('double_cross_check_to',
+ {table => $table, field1 => $field1, field2 => $field2});
- while (@_) {
- my $ref = shift @_;
- my ($refertable, $referfield1, $referfield2, $keyname) = @$ref;
+ while (@_) {
+ my $ref = shift @_;
+ my ($refertable, $referfield1, $referfield2, $keyname) = @$ref;
- Status('double_cross_check_from',
- {table => $refertable, field1 => $referfield1, field2 =>$referfield2});
+ Status('double_cross_check_from',
+ {table => $refertable, field1 => $referfield1, field2 => $referfield2});
- my $d_cross_check = $dbh->selectall_arrayref(qq{
+ my $d_cross_check = $dbh->selectall_arrayref(
+ qq{
SELECT DISTINCT $refertable.$referfield1,
- $refertable.$referfield2 } .
- ($keyname ? qq{, $refertable.$keyname } : q{}) .
- qq{ FROM $refertable
+ $refertable.$referfield2 }
+ . ($keyname ? qq{, $refertable.$keyname } : q{}) . qq{ FROM $refertable
LEFT JOIN $table
ON $refertable.$referfield1 = $table.$field1
AND $refertable.$referfield2 = $table.$field2
WHERE $table.$field1 IS NULL
AND $table.$field2 IS NULL
AND $refertable.$referfield1 IS NOT NULL
- AND $refertable.$referfield2 IS NOT NULL});
-
- foreach my $check (@$d_cross_check) {
- my ($value1, $value2, $key) = @$check;
- Status('double_cross_check_alert',
- {value1 => $value1, value2 => $value2,
- table => $refertable,
- field1 => $referfield1, field2 => $referfield2,
- keyname => $keyname, key => $key}, 'alert');
- }
+ AND $refertable.$referfield2 IS NOT NULL}
+ );
+
+ foreach my $check (@$d_cross_check) {
+ my ($value1, $value2, $key) = @$check;
+ Status(
+ 'double_cross_check_alert',
+ {
+ value1 => $value1,
+ value2 => $value2,
+ table => $refertable,
+ field1 => $referfield1,
+ field2 => $referfield2,
+ keyname => $keyname,
+ key => $key
+ },
+ 'alert'
+ );
}
+ }
}
-DoubleCrossCheck('attachments', 'bug_id', 'attach_id',
- ['flags', 'bug_id', 'attach_id'],
- ['bugs_activity', 'bug_id', 'attach_id']);
-
-DoubleCrossCheck("components", "product_id", "id",
- ["bugs", "product_id", "component_id", "bug_id"],
- ['flagexclusions', 'product_id', 'component_id'],
- ['flaginclusions', 'product_id', 'component_id']);
+DoubleCrossCheck(
+ 'attachments', 'bug_id', 'attach_id',
+ ['flags', 'bug_id', 'attach_id'],
+ ['bugs_activity', 'bug_id', 'attach_id']
+);
+
+DoubleCrossCheck(
+ "components",
+ "product_id",
+ "id",
+ ["bugs", "product_id", "component_id", "bug_id"],
+ ['flagexclusions', 'product_id', 'component_id'],
+ ['flaginclusions', 'product_id', 'component_id']
+);
DoubleCrossCheck("versions", "product_id", "value",
- ["bugs", "product_id", "version", "bug_id"]);
-
-DoubleCrossCheck("milestones", "product_id", "value",
- ["bugs", "product_id", "target_milestone", "bug_id"],
- ["products", "id", "defaultmilestone", "name"]);
+ ["bugs", "product_id", "version", "bug_id"]);
+
+DoubleCrossCheck(
+ "milestones", "product_id", "value",
+ ["bugs", "product_id", "target_milestone", "bug_id"],
+ ["products", "id", "defaultmilestone", "name"]
+);
###########################################################################
# Perform login checks
@@ -649,8 +724,8 @@ my $sth = $dbh->prepare(q{SELECT userid, login_name FROM profiles});
$sth->execute;
while (my ($id, $email) = $sth->fetchrow_array) {
- validate_email_syntax($email)
- || Status('profile_login_alert', {id => $id, email => $email}, 'alert');
+ validate_email_syntax($email)
+ || Status('profile_login_alert', {id => $id, email => $email}, 'alert');
}
###########################################################################
@@ -658,42 +733,44 @@ while (my ($id, $email) = $sth->fetchrow_array) {
###########################################################################
sub check_keywords {
- my $dbh = Bugzilla->dbh;
- my $cgi = Bugzilla->cgi;
-
- Status('keyword_check_start');
-
- my %keywordids;
- my $keywords = $dbh->selectall_arrayref(q{SELECT id, name
- FROM keyworddefs});
-
- foreach (@$keywords) {
- my ($id, $name) = @$_;
- if ($keywordids{$id}) {
- Status('keyword_check_alert', {id => $id}, 'alert');
- }
- $keywordids{$id} = 1;
- if ($name =~ /[\s,]/) {
- Status('keyword_check_invalid_name', {id => $id}, 'alert');
- }
+ my $dbh = Bugzilla->dbh;
+ my $cgi = Bugzilla->cgi;
+
+ Status('keyword_check_start');
+
+ my %keywordids;
+ my $keywords = $dbh->selectall_arrayref(q{SELECT id, name
+ FROM keyworddefs}
+ );
+
+ foreach (@$keywords) {
+ my ($id, $name) = @$_;
+ if ($keywordids{$id}) {
+ Status('keyword_check_alert', {id => $id}, 'alert');
}
+ $keywordids{$id} = 1;
+ if ($name =~ /[\s,]/) {
+ Status('keyword_check_invalid_name', {id => $id}, 'alert');
+ }
+ }
- my $sth = $dbh->prepare(q{SELECT bug_id, keywordid
+ my $sth = $dbh->prepare(q{SELECT bug_id, keywordid
FROM keywords
- ORDER BY bug_id, keywordid});
- $sth->execute;
- my $lastid;
- my $lastk;
- while (my ($id, $k) = $sth->fetchrow_array) {
- if (!$keywordids{$k}) {
- Status('keyword_check_invalid_id', {id => $k}, 'alert');
- }
- if (defined $lastid && $id eq $lastid && $k eq $lastk) {
- Status('keyword_check_duplicated_ids', {id => $id}, 'alert');
- }
- $lastid = $id;
- $lastk = $k;
+ ORDER BY bug_id, keywordid}
+ );
+ $sth->execute;
+ my $lastid;
+ my $lastk;
+ while (my ($id, $k) = $sth->fetchrow_array) {
+ if (!$keywordids{$k}) {
+ Status('keyword_check_invalid_id', {id => $k}, 'alert');
}
+ if (defined $lastid && $id eq $lastid && $k eq $lastk) {
+ Status('keyword_check_duplicated_ids', {id => $id}, 'alert');
+ }
+ $lastid = $id;
+ $lastk = $k;
+ }
}
###########################################################################
@@ -703,7 +780,7 @@ sub check_keywords {
Status('flag_check_start');
my $invalid_flags = $dbh->selectall_arrayref(
- 'SELECT DISTINCT flags.id, flags.bug_id, flags.attach_id
+ 'SELECT DISTINCT flags.id, flags.bug_id, flags.attach_id
FROM flags
INNER JOIN bugs
ON flags.bug_id = bugs.bug_id
@@ -711,40 +788,42 @@ my $invalid_flags = $dbh->selectall_arrayref(
ON flags.type_id = i.type_id
AND (bugs.product_id = i.product_id OR i.product_id IS NULL)
AND (bugs.component_id = i.component_id OR i.component_id IS NULL)
- WHERE i.type_id IS NULL');
+ WHERE i.type_id IS NULL'
+);
my @invalid_flags = @$invalid_flags;
$invalid_flags = $dbh->selectall_arrayref(
- 'SELECT DISTINCT flags.id, flags.bug_id, flags.attach_id
+ 'SELECT DISTINCT flags.id, flags.bug_id, flags.attach_id
FROM flags
INNER JOIN bugs
ON flags.bug_id = bugs.bug_id
INNER JOIN flagexclusions AS e
ON flags.type_id = e.type_id
WHERE (bugs.product_id = e.product_id OR e.product_id IS NULL)
- AND (bugs.component_id = e.component_id OR e.component_id IS NULL)');
+ AND (bugs.component_id = e.component_id OR e.component_id IS NULL)'
+);
push(@invalid_flags, @$invalid_flags);
if (scalar(@invalid_flags)) {
- if ($cgi->param('remove_invalid_flags')) {
- Status('flag_deletion_start');
- my @flag_ids = map {$_->[0]} @invalid_flags;
- # Silently delete these flags, with no notification to requesters/setters.
- $dbh->do('DELETE FROM flags WHERE id IN (' . join(',', @flag_ids) .')');
- Status('flag_deletion_end');
- Bugzilla->memcached->clear_all();
- }
- else {
- foreach my $flag (@$invalid_flags) {
- my ($flag_id, $bug_id, $attach_id) = @$flag;
- Status('flag_alert',
- {flag_id => $flag_id, attach_id => $attach_id, bug_id => $bug_id},
- 'alert');
- }
- Status('flag_fix');
+ if ($cgi->param('remove_invalid_flags')) {
+ Status('flag_deletion_start');
+ my @flag_ids = map { $_->[0] } @invalid_flags;
+
+ # Silently delete these flags, with no notification to requesters/setters.
+ $dbh->do('DELETE FROM flags WHERE id IN (' . join(',', @flag_ids) . ')');
+ Status('flag_deletion_end');
+ Bugzilla->memcached->clear_all();
+ }
+ else {
+ foreach my $flag (@$invalid_flags) {
+ my ($flag_id, $bug_id, $attach_id) = @$flag;
+ Status('flag_alert',
+ {flag_id => $flag_id, attach_id => $attach_id, bug_id => $bug_id}, 'alert');
}
+ Status('flag_fix');
+ }
}
###########################################################################
@@ -754,17 +833,18 @@ if (scalar(@invalid_flags)) {
Status('product_check_start');
my $products_missing_data = $dbh->selectcol_arrayref(
- 'SELECT DISTINCT products.name
+ 'SELECT DISTINCT products.name
FROM products
LEFT JOIN components
ON components.product_id = products.id
LEFT JOIN versions
ON versions.product_id = products.id
WHERE components.id IS NULL
- OR versions.id IS NULL');
+ OR versions.id IS NULL'
+);
if (scalar(@$products_missing_data)) {
- Status('product_alert', { name => $_ }, 'alert') foreach @$products_missing_data;
+ Status('product_alert', {name => $_}, 'alert') foreach @$products_missing_data;
}
###########################################################################
@@ -772,45 +852,57 @@ if (scalar(@$products_missing_data)) {
###########################################################################
sub BugCheck {
- my ($middlesql, $errortext, $repairparam, $repairtext) = @_;
- my $dbh = Bugzilla->dbh;
-
- my $badbugs = $dbh->selectcol_arrayref(qq{SELECT DISTINCT bugs.bug_id
+ my ($middlesql, $errortext, $repairparam, $repairtext) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $badbugs = $dbh->selectcol_arrayref(qq{SELECT DISTINCT bugs.bug_id
FROM $middlesql
- ORDER BY bugs.bug_id});
-
- if (scalar(@$badbugs)) {
- Status('bug_check_alert',
- {errortext => get_string($errortext), badbugs => $badbugs},
- 'alert');
-
- if ($repairparam) {
- $repairtext ||= 'repair_bugs';
- Status('bug_check_repair',
- {param => $repairparam, text => get_string($repairtext)});
- }
+ ORDER BY bugs.bug_id}
+ );
+
+ if (scalar(@$badbugs)) {
+ Status('bug_check_alert',
+ {errortext => get_string($errortext), badbugs => $badbugs}, 'alert');
+
+ if ($repairparam) {
+ $repairtext ||= 'repair_bugs';
+ Status('bug_check_repair',
+ {param => $repairparam, text => get_string($repairtext)});
}
+ }
}
Status('bug_check_creation_date');
-BugCheck("bugs WHERE creation_ts IS NULL", 'bug_check_creation_date_error_text',
- 'repair_creation_date', 'bug_check_creation_date_repair_text');
+BugCheck(
+ "bugs WHERE creation_ts IS NULL", 'bug_check_creation_date_error_text',
+ 'repair_creation_date', 'bug_check_creation_date_repair_text'
+);
Status('bug_check_bugs_fulltext');
-BugCheck("bugs LEFT JOIN bugs_fulltext ON bugs_fulltext.bug_id = bugs.bug_id " .
- "WHERE bugs_fulltext.bug_id IS NULL", 'bug_check_bugs_fulltext_error_text',
- 'repair_bugs_fulltext', 'bug_check_bugs_fulltext_repair_text');
+BugCheck(
+ "bugs LEFT JOIN bugs_fulltext ON bugs_fulltext.bug_id = bugs.bug_id "
+ . "WHERE bugs_fulltext.bug_id IS NULL",
+ 'bug_check_bugs_fulltext_error_text',
+ 'repair_bugs_fulltext',
+ 'bug_check_bugs_fulltext_repair_text'
+);
Status('bug_check_res_dupl');
-BugCheck("bugs INNER JOIN duplicates ON bugs.bug_id = duplicates.dupe " .
- "WHERE bugs.resolution != 'DUPLICATE'", 'bug_check_res_dupl_error_text');
+BugCheck(
+ "bugs INNER JOIN duplicates ON bugs.bug_id = duplicates.dupe "
+ . "WHERE bugs.resolution != 'DUPLICATE'",
+ 'bug_check_res_dupl_error_text'
+);
-BugCheck("bugs LEFT JOIN duplicates ON bugs.bug_id = duplicates.dupe WHERE " .
- "bugs.resolution = 'DUPLICATE' AND " .
- "duplicates.dupe IS NULL", 'bug_check_res_dupl_error_text2');
+BugCheck(
+ "bugs LEFT JOIN duplicates ON bugs.bug_id = duplicates.dupe WHERE "
+ . "bugs.resolution = 'DUPLICATE' AND "
+ . "duplicates.dupe IS NULL",
+ 'bug_check_res_dupl_error_text2'
+);
Status('bug_check_status_res');
@@ -818,20 +910,27 @@ my @open_states = map($dbh->quote($_), BUG_STATE_OPEN);
my $open_states = join(', ', @open_states);
BugCheck("bugs WHERE bug_status IN ($open_states) AND resolution != ''",
- 'bug_check_status_res_error_text');
+ 'bug_check_status_res_error_text');
BugCheck("bugs WHERE bug_status NOT IN ($open_states) AND resolution = ''",
- 'bug_check_status_res_error_text2');
+ 'bug_check_status_res_error_text2');
Status('bug_check_status_everconfirmed');
-BugCheck("bugs WHERE bug_status = 'UNCONFIRMED' AND everconfirmed = 1",
- 'bug_check_status_everconfirmed_error_text', 'repair_everconfirmed');
+BugCheck(
+ "bugs WHERE bug_status = 'UNCONFIRMED' AND everconfirmed = 1",
+ 'bug_check_status_everconfirmed_error_text',
+ 'repair_everconfirmed'
+);
-my @confirmed_open_states = grep {$_ ne 'UNCONFIRMED'} BUG_STATE_OPEN;
-my $confirmed_open_states = join(', ', map {$dbh->quote($_)} @confirmed_open_states);
+my @confirmed_open_states = grep { $_ ne 'UNCONFIRMED' } BUG_STATE_OPEN;
+my $confirmed_open_states
+ = join(', ', map { $dbh->quote($_) } @confirmed_open_states);
-BugCheck("bugs WHERE bug_status IN ($confirmed_open_states) AND everconfirmed = 0",
- 'bug_check_status_everconfirmed_error_text2', 'repair_everconfirmed');
+BugCheck(
+ "bugs WHERE bug_status IN ($confirmed_open_states) AND everconfirmed = 0",
+ 'bug_check_status_everconfirmed_error_text2',
+ 'repair_everconfirmed'
+);
###########################################################################
# Control Values
@@ -840,8 +939,8 @@ BugCheck("bugs WHERE bug_status IN ($confirmed_open_states) AND everconfirmed =
# Checks for values that are invalid OR
# not among the 9 valid combinations
Status('bug_check_control_values');
-my $groups = join(", ", (CONTROLMAPNA, CONTROLMAPSHOWN, CONTROLMAPDEFAULT,
-CONTROLMAPMANDATORY));
+my $groups = join(", ",
+ (CONTROLMAPNA, CONTROLMAPSHOWN, CONTROLMAPDEFAULT, CONTROLMAPMANDATORY));
my $query = qq{
SELECT COUNT(product_id)
FROM group_control_map
@@ -853,10 +952,12 @@ my $query = qq{
OR (othercontrol = } . CONTROLMAPSHOWN . q{)))};
my $entries = $dbh->selectrow_array($query);
-Status('bug_check_control_values_alert', {entries => $entries}, 'alert') if $entries;
+Status('bug_check_control_values_alert', {entries => $entries}, 'alert')
+ if $entries;
Status('bug_check_control_values_violation');
-BugCheck("bugs
+BugCheck(
+ "bugs
INNER JOIN bug_group_map
ON bugs.bug_id = bug_group_map.bug_id
LEFT JOIN group_control_map
@@ -864,11 +965,12 @@ BugCheck("bugs
AND bug_group_map.group_id = group_control_map.group_id
WHERE ((group_control_map.membercontrol = " . CONTROLMAPNA . ")
OR (group_control_map.membercontrol IS NULL))",
- 'bug_check_control_values_error_text',
- 'createmissinggroupcontrolmapentries',
- 'bug_check_control_values_repair_text');
+ 'bug_check_control_values_error_text', 'createmissinggroupcontrolmapentries',
+ 'bug_check_control_values_repair_text'
+);
-BugCheck("bugs
+BugCheck(
+ "bugs
INNER JOIN group_control_map
ON bugs.product_id = group_control_map.product_id
INNER JOIN groups
@@ -878,8 +980,8 @@ BugCheck("bugs
AND group_control_map.group_id = bug_group_map.group_id
WHERE group_control_map.membercontrol = " . CONTROLMAPMANDATORY . "
AND bug_group_map.group_id IS NULL
- AND groups.isactive != 0",
- 'bug_check_control_values_error_text2');
+ AND groups.isactive != 0", 'bug_check_control_values_error_text2'
+);
###########################################################################
# Unsent mail
@@ -887,18 +989,19 @@ BugCheck("bugs
Status('unsent_bugmail_check');
-my $time = $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE');
+my $time = $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE');
my $badbugs = $dbh->selectcol_arrayref(qq{
SELECT bug_id
FROM bugs
WHERE (lastdiffed IS NULL OR lastdiffed < delta_ts)
AND delta_ts < $time
- ORDER BY bug_id});
+ ORDER BY bug_id}
+);
if (scalar(@$badbugs > 0)) {
- Status('unsent_bugmail_alert', {badbugs => $badbugs}, 'alert');
- Status('unsent_bugmail_fix');
+ Status('unsent_bugmail_alert', {badbugs => $badbugs}, 'alert');
+ Status('unsent_bugmail_fix');
}
###########################################################################
@@ -909,19 +1012,22 @@ Status('whines_obsolete_target_start');
my $display_repair_whines_link = 0;
foreach my $target (['groups', 'id', MAILTO_GROUP],
- ['profiles', 'userid', MAILTO_USER])
+ ['profiles', 'userid', MAILTO_USER])
{
- my ($table, $col, $type) = @$target;
- my $old = $dbh->selectall_arrayref("SELECT whine_schedules.id, mailto
+ my ($table, $col, $type) = @$target;
+ my $old = $dbh->selectall_arrayref(
+ "SELECT whine_schedules.id, mailto
FROM whine_schedules
LEFT JOIN $table
ON $table.$col = whine_schedules.mailto
- WHERE mailto_type = $type AND $table.$col IS NULL");
-
- if (scalar(@$old)) {
- Status('whines_obsolete_target_alert', {schedules => $old, type => $type}, 'alert');
- $display_repair_whines_link = 1;
- }
+ WHERE mailto_type = $type AND $table.$col IS NULL"
+ );
+
+ if (scalar(@$old)) {
+ Status('whines_obsolete_target_alert',
+ {schedules => $old, type => $type}, 'alert');
+ $display_repair_whines_link = 1;
+ }
}
Status('whines_obsolete_target_fix') if $display_repair_whines_link;
@@ -929,7 +1035,7 @@ Status('whines_obsolete_target_fix') if $display_repair_whines_link;
# Check hook
###########################################################################
-Bugzilla::Hook::process('sanitycheck_check', { status => \&Status });
+Bugzilla::Hook::process('sanitycheck_check', {status => \&Status});
###########################################################################
# End
@@ -938,6 +1044,6 @@ Bugzilla::Hook::process('sanitycheck_check', { status => \&Status });
Status('checks_completed');
unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- $template->process('global/footer.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ $template->process('global/footer.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
diff --git a/sanitycheck.pl b/sanitycheck.pl
index 7fd12b176..bd1bec8d3 100755
--- a/sanitycheck.pl
+++ b/sanitycheck.pl
@@ -21,23 +21,25 @@ use Bugzilla::Mailer;
use Getopt::Long;
use Pod::Usage;
-my $verbose = 0; # Return all comments if true, else errors only.
+my $verbose = 0; # Return all comments if true, else errors only.
my $login = ''; # Login name of the user which is used to call sanitycheck.cgi.
-my $help = 0; # Has user asked for help on this script?
+my $help = 0; # Has user asked for help on this script?
-my $result = GetOptions('verbose' => \$verbose,
- 'login=s' => \$login,
- 'help|h|?' => \$help);
+my $result = GetOptions(
+ 'verbose' => \$verbose,
+ 'login=s' => \$login,
+ 'help|h|?' => \$help
+);
pod2usage({-verbose => 1, -exitval => 1}) if $help;
# Be sure a login name if given.
$login || ThrowUserError('invalid_username');
-my $user = new Bugzilla::User({ name => $login })
- || ThrowUserError('invalid_username', { name => $login });
+my $user = new Bugzilla::User({name => $login})
+ || ThrowUserError('invalid_username', {name => $login});
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
# Authenticate using this user account.
@@ -50,16 +52,16 @@ require 'sanitycheck.cgi';
# Now it's time to send an email to the user if there is something to notify.
if ($cgi->param('output')) {
- my $message;
- my $vars = {};
- $vars->{'addressee'} = $user->email;
- $vars->{'output'} = $cgi->param('output');
- $vars->{'error_found'} = $cgi->param('error_found') ? 1 : 0;
+ my $message;
+ my $vars = {};
+ $vars->{'addressee'} = $user->email;
+ $vars->{'output'} = $cgi->param('output');
+ $vars->{'error_found'} = $cgi->param('error_found') ? 1 : 0;
- $template->process('email/sanitycheck.txt.tmpl', $vars, \$message)
- || ThrowTemplateError($template->error());
+ $template->process('email/sanitycheck.txt.tmpl', $vars, \$message)
+ || ThrowTemplateError($template->error());
- MessageToMTA($message);
+ MessageToMTA($message);
}
diff --git a/search_plugin.cgi b/search_plugin.cgi
index 0b628f32e..46dce5ee2 100755
--- a/search_plugin.cgi
+++ b/search_plugin.cgi
@@ -18,9 +18,9 @@ use Bugzilla::Constants;
Bugzilla->login();
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
# Return the appropriate HTTP response headers.
print $cgi->header('application/xml');
@@ -28,10 +28,10 @@ print $cgi->header('application/xml');
# Get the contents of favicon.ico
my $filename = bz_locations()->{'libpath'} . "/images/favicon.ico";
if (open(IN, '<', $filename)) {
- local $/;
- binmode IN;
- $vars->{'favicon'} = <IN>;
- close IN;
+ local $/;
+ binmode IN;
+ $vars->{'favicon'} = <IN>;
+ close IN;
}
$template->process("search/search-plugin.xml.tmpl", $vars)
|| ThrowTemplateError($template->error());
diff --git a/show_activity.cgi b/show_activity.cgi
index 7df10fcb8..fefd68b8a 100755
--- a/show_activity.cgi
+++ b/show_activity.cgi
@@ -16,20 +16,20 @@ use Bugzilla;
use Bugzilla::Error;
use Bugzilla::Bug;
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
###############################################################################
# Begin Data/Security Validation
###############################################################################
-# Check whether or not the user is currently logged in.
+# Check whether or not the user is currently logged in.
Bugzilla->login();
# Make sure the bug ID is a positive integer representing an existing
# bug that the user is authorized to access.
-my $id = $cgi->param('id');
+my $id = $cgi->param('id');
my $bug = Bugzilla::Bug->check($id);
###############################################################################
@@ -40,7 +40,8 @@ my $bug = Bugzilla::Bug->check($id);
# visible immediately due to replication lag.
Bugzilla->switch_to_shadow_db;
-($vars->{'operations'}, $vars->{'incomplete_data'}) = $bug->get_activity(undef, undef, 1);
+($vars->{'operations'}, $vars->{'incomplete_data'})
+ = $bug->get_activity(undef, undef, 1);
$vars->{'bug'} = $bug;
diff --git a/show_bug.cgi b/show_bug.cgi
index 9e31fc4a7..bed27b8d9 100755
--- a/show_bug.cgi
+++ b/show_bug.cgi
@@ -18,24 +18,27 @@ use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Bug;
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
my $user = Bugzilla->login();
-my $format = $template->get_format("bug/show", scalar $cgi->param('format'),
- scalar $cgi->param('ctype'));
+my $format = $template->get_format(
+ "bug/show",
+ scalar $cgi->param('format'),
+ scalar $cgi->param('ctype')
+);
# Editable, 'single' HTML bugs are treated slightly specially in a few places
my $single = !$format->{format} && $format->{extension} eq 'html';
# If we don't have an ID, _AND_ we're only doing a single bug, then prompt
if (!$cgi->param('id') && $single) {
- print $cgi->header();
- $template->process("bug/choose.html.tmpl", $vars) ||
- ThrowTemplateError($template->error());
- exit;
+ print $cgi->header();
+ $template->process("bug/choose.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
my (@bugs, @illegal_bugs);
@@ -47,83 +50,90 @@ my %marks;
Bugzilla->switch_to_shadow_db unless $user->id;
if ($single) {
- my $id = $cgi->param('id');
- push @bugs, Bugzilla::Bug->check({ id => $id, cache => 1 });
- if (defined $cgi->param('mark')) {
- foreach my $range (split ',', $cgi->param('mark')) {
- if ($range =~ /^(\d+)-(\d+)$/) {
- foreach my $i ($1..$2) {
- $marks{$i} = 1;
- }
- } elsif ($range =~ /^(\d+)$/) {
- $marks{$1} = 1;
- }
+ my $id = $cgi->param('id');
+ push @bugs, Bugzilla::Bug->check({id => $id, cache => 1});
+ if (defined $cgi->param('mark')) {
+ foreach my $range (split ',', $cgi->param('mark')) {
+ if ($range =~ /^(\d+)-(\d+)$/) {
+ foreach my $i ($1 .. $2) {
+ $marks{$i} = 1;
}
+ }
+ elsif ($range =~ /^(\d+)$/) {
+ $marks{$1} = 1;
+ }
+ }
+ }
+}
+else {
+ my $count = 0;
+ foreach my $id ($cgi->param('id')) {
+
+ # Be kind enough and accept URLs of the form: id=1,2,3.
+ my @ids = split(/,/, $id);
+ my @check_bugs;
+
+ foreach my $bug_id (@ids) {
+ last if $count == 100;
+ next unless $bug_id;
+ my $bug = new Bugzilla::Bug({id => $bug_id, cache => 1});
+ if (!$bug->{error}) {
+ push(@check_bugs, $bug);
+ }
+ else {
+ push(@illegal_bugs, {bug_id => trim($bug_id), error => $bug->{error}});
+ }
+ $count++;
}
-} else {
- my $count = 0;
- foreach my $id ($cgi->param('id')) {
- # Be kind enough and accept URLs of the form: id=1,2,3.
- my @ids = split(/,/, $id);
- my @check_bugs;
-
- foreach my $bug_id (@ids) {
- last if $count == 100;
- next unless $bug_id;
- my $bug = new Bugzilla::Bug({ id => $bug_id, cache => 1 });
- if (!$bug->{error}) {
- push(@check_bugs, $bug);
- }
- else {
- push(@illegal_bugs, { bug_id => trim($bug_id), error => $bug->{error} });
- }
- $count++;
- }
- $user->visible_bugs(\@check_bugs);
+ $user->visible_bugs(\@check_bugs);
- foreach my $bug (@check_bugs) {
- if ($user->can_see_bug($bug->id)) {
- push(@bugs, $bug);
- }
- else {
- my $error = 'NotPermitted'; # Trick to make 012throwables.t happy.
- push(@illegal_bugs, { bug_id => $bug->id, error => $error });
- }
- }
+ foreach my $bug (@check_bugs) {
+ if ($user->can_see_bug($bug->id)) {
+ push(@bugs, $bug);
+ }
+ else {
+ my $error = 'NotPermitted'; # Trick to make 012throwables.t happy.
+ push(@illegal_bugs, {bug_id => $bug->id, error => $error});
+ }
}
+ }
}
Bugzilla::Bug->preload(\@bugs);
-$vars->{'bugs'} = [@bugs, @illegal_bugs];
+$vars->{'bugs'} = [@bugs, @illegal_bugs];
$vars->{'marks'} = \%marks;
-my @bugids = map {$_->bug_id} grep {!$_->error} @bugs;
+my @bugids = map { $_->bug_id } grep { !$_->error } @bugs;
$vars->{'bugids'} = join(", ", @bugids);
# Work out which fields we are displaying (currently XML only.)
# If no explicit list is defined, we show all fields. We then exclude any
-# on the exclusion list. This is so you can say e.g. "Everything except
+# on the exclusion list. This is so you can say e.g. "Everything except
# attachments" without listing almost all the fields.
-my @fieldlist = (Bugzilla::Bug->fields, 'flag', 'group', 'long_desc',
- 'attachment', 'attachmentdata', 'token');
+my @fieldlist = (
+ Bugzilla::Bug->fields, 'flag',
+ 'group', 'long_desc',
+ 'attachment', 'attachmentdata',
+ 'token'
+);
my %displayfields;
if ($cgi->param("field")) {
- @fieldlist = $cgi->param("field");
+ @fieldlist = $cgi->param("field");
}
unless ($user->is_timetracker) {
- @fieldlist = grep($_ !~ /_time$/, @fieldlist);
+ @fieldlist = grep($_ !~ /_time$/, @fieldlist);
}
foreach (@fieldlist) {
- $displayfields{$_} = 1;
+ $displayfields{$_} = 1;
}
foreach ($cgi->param("excludefield")) {
- $displayfields{$_} = undef;
+ $displayfields{$_} = undef;
}
$vars->{'displayfields'} = \%displayfields;
diff --git a/showdependencygraph.cgi b/showdependencygraph.cgi
index 7b2d2f55d..666a6626e 100755
--- a/showdependencygraph.cgi
+++ b/showdependencygraph.cgi
@@ -24,9 +24,10 @@ use Bugzilla::Status;
my $user = Bugzilla->login();
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
+
# Connect to the shadow database if this installation is using one to improve
# performance.
my $dbh = Bugzilla->switch_to_shadow_db();
@@ -34,7 +35,7 @@ my $dbh = Bugzilla->switch_to_shadow_db();
our (%seen, %edgesdone, %bugtitles);
our $bug_count = 0;
-# CreateImagemap: This sub grabs a local filename as a parameter, reads the
+# CreateImagemap: This sub grabs a local filename as a parameter, reads the
# dot-generated image map datafile residing in that file and turns it into
# an HTML map element. THIS SUB IS ONLY USED FOR LOCAL DOT INSTALLATIONS.
# The map datafile won't necessarily contain the bug summaries, so we'll
@@ -45,50 +46,53 @@ our $bug_count = 0;
# rectangle (LEFTX,TOPY) (RIGHTX,BOTTOMY) URLBASE/show_bug.cgi?id=BUGNUM BUGNUM[\nSUMMARY]
sub CreateImagemap {
- my $mapfilename = shift;
- my $map = "<map name=\"imagemap\">\n";
- my $default = "";
-
- open MAP, "<", $mapfilename;
- while(my $line = <MAP>) {
- if($line =~ /^default ([^ ]*)(.*)$/) {
- $default = qq{<area alt="" shape="default" href="$1">\n};
- }
-
- if ($line =~ /^rectangle \((\d+),(\d+)\) \((\d+),(\d+)\) (http[^ ]*) (\d+)(?:\\n.*)?$/) {
- my ($leftx, $rightx, $topy, $bottomy, $url, $bugid) = ($1, $3, $2, $4, $5, $6);
-
- # Pick up bugid from the mapdata label field. Getting the title from
- # bugtitle hash instead of mapdata allows us to get the summary even
- # when showsummary is off, and also gives us status and resolution.
- # This text is safe; it has already been escaped.
- my $bugtitle = $bugtitles{$bugid};
-
- # The URL is supposed to be safe, because it's built manually.
- # But in case someone manages to inject code, it's safer to escape it.
- $url = html_quote($url);
-
- $map .= qq{<area alt="bug $bugid" name="bug$bugid" shape="rect" } .
- qq{title="$bugtitle" href="$url" } .
- qq{coords="$leftx,$topy,$rightx,$bottomy">\n};
- }
+ my $mapfilename = shift;
+ my $map = "<map name=\"imagemap\">\n";
+ my $default = "";
+
+ open MAP, "<", $mapfilename;
+ while (my $line = <MAP>) {
+ if ($line =~ /^default ([^ ]*)(.*)$/) {
+ $default = qq{<area alt="" shape="default" href="$1">\n};
+ }
+
+ if ($line
+ =~ /^rectangle \((\d+),(\d+)\) \((\d+),(\d+)\) (http[^ ]*) (\d+)(?:\\n.*)?$/)
+ {
+ my ($leftx, $rightx, $topy, $bottomy, $url, $bugid) = ($1, $3, $2, $4, $5, $6);
+
+ # Pick up bugid from the mapdata label field. Getting the title from
+ # bugtitle hash instead of mapdata allows us to get the summary even
+ # when showsummary is off, and also gives us status and resolution.
+ # This text is safe; it has already been escaped.
+ my $bugtitle = $bugtitles{$bugid};
+
+ # The URL is supposed to be safe, because it's built manually.
+ # But in case someone manages to inject code, it's safer to escape it.
+ $url = html_quote($url);
+
+ $map
+ .= qq{<area alt="bug $bugid" name="bug$bugid" shape="rect" }
+ . qq{title="$bugtitle" href="$url" }
+ . qq{coords="$leftx,$topy,$rightx,$bottomy">\n};
}
- close MAP;
+ }
+ close MAP;
- $map .= "$default</map>";
- return $map;
+ $map .= "$default</map>";
+ return $map;
}
sub AddLink {
- my ($blocked, $dependson, $fh) = (@_);
- my $key = "$blocked,$dependson";
- if (!exists $edgesdone{$key}) {
- $edgesdone{$key} = 1;
- print $fh "$dependson -> $blocked\n";
- $bug_count++;
- $seen{$blocked} = 1;
- $seen{$dependson} = 1;
- }
+ my ($blocked, $dependson, $fh) = (@_);
+ my $key = "$blocked,$dependson";
+ if (!exists $edgesdone{$key}) {
+ $edgesdone{$key} = 1;
+ print $fh "$dependson -> $blocked\n";
+ $bug_count++;
+ $seen{$blocked} = 1;
+ $seen{$dependson} = 1;
+ }
}
ThrowUserError("missing_bug_id") unless $cgi->param('id');
@@ -98,22 +102,24 @@ ThrowUserError("missing_bug_id") unless $cgi->param('id');
my @valid_rankdirs = ('LR', 'RL', 'TB', 'BT');
my $rankdir = $cgi->param('rankdir') || 'TB';
+
# Make sure the submitted 'rankdir' value is valid.
if (!grep { $_ eq $rankdir } @valid_rankdirs) {
- $rankdir = 'TB';
+ $rankdir = 'TB';
}
-my $display = $cgi->param('display') || 'tree';
+my $display = $cgi->param('display') || 'tree';
my $webdotdir = bz_locations()->{'webdotdir'};
-my ($fh, $filename) = File::Temp::tempfile("XXXXXXXXXX",
- SUFFIX => '.dot',
- DIR => $webdotdir,
- UNLINK => 1);
+my ($fh, $filename) = File::Temp::tempfile(
+ "XXXXXXXXXX",
+ SUFFIX => '.dot',
+ DIR => $webdotdir,
+ UNLINK => 1
+);
chmod Bugzilla::Install::Filesystem::CGI_WRITE, $filename
- or warn install_string('chmod_failed', { path => $filename,
- error => $! });
+ or warn install_string('chmod_failed', {path => $filename, error => $!});
my $urlbase = correct_urlbase();
@@ -126,116 +132,122 @@ node [URL="${urlbase}show_bug.cgi?id=\\N", style=filled, color=lightgrey]
my %baselist;
foreach my $i (split('[\s,]+', $cgi->param('id'))) {
- my $bug = Bugzilla::Bug->check($i);
- $baselist{$bug->id} = 1;
+ my $bug = Bugzilla::Bug->check($i);
+ $baselist{$bug->id} = 1;
}
my @stack = keys(%baselist);
if ($display eq 'web') {
- my $sth = $dbh->prepare(q{SELECT blocked, dependson
+ my $sth = $dbh->prepare(q{SELECT blocked, dependson
FROM dependencies
- WHERE blocked = ? OR dependson = ?});
-
- foreach my $id (@stack) {
- my $dependencies = $dbh->selectall_arrayref($sth, undef, ($id, $id));
- foreach my $dependency (@$dependencies) {
- my ($blocked, $dependson) = @$dependency;
- if ($blocked != $id && !exists $seen{$blocked}) {
- push @stack, $blocked;
- }
- if ($dependson != $id && !exists $seen{$dependson}) {
- push @stack, $dependson;
- }
- AddLink($blocked, $dependson, $fh);
- }
+ WHERE blocked = ? OR dependson = ?}
+ );
+
+ foreach my $id (@stack) {
+ my $dependencies = $dbh->selectall_arrayref($sth, undef, ($id, $id));
+ foreach my $dependency (@$dependencies) {
+ my ($blocked, $dependson) = @$dependency;
+ if ($blocked != $id && !exists $seen{$blocked}) {
+ push @stack, $blocked;
+ }
+ if ($dependson != $id && !exists $seen{$dependson}) {
+ push @stack, $dependson;
+ }
+ AddLink($blocked, $dependson, $fh);
}
+ }
}
+
# This is the default: a tree instead of a spider web.
else {
- my @blocker_stack = @stack;
- foreach my $id (@blocker_stack) {
- my $blocker_ids = Bugzilla::Bug::EmitDependList('blocked', 'dependson', $id);
- foreach my $blocker_id (@$blocker_ids) {
- push(@blocker_stack, $blocker_id) unless $seen{$blocker_id};
- AddLink($id, $blocker_id, $fh);
- }
+ my @blocker_stack = @stack;
+ foreach my $id (@blocker_stack) {
+ my $blocker_ids = Bugzilla::Bug::EmitDependList('blocked', 'dependson', $id);
+ foreach my $blocker_id (@$blocker_ids) {
+ push(@blocker_stack, $blocker_id) unless $seen{$blocker_id};
+ AddLink($id, $blocker_id, $fh);
}
- my @dependent_stack = @stack;
- foreach my $id (@dependent_stack) {
- my $dep_bug_ids = Bugzilla::Bug::EmitDependList('dependson', 'blocked', $id);
- foreach my $dep_bug_id (@$dep_bug_ids) {
- push(@dependent_stack, $dep_bug_id) unless $seen{$dep_bug_id};
- AddLink($dep_bug_id, $id, $fh);
- }
+ }
+ my @dependent_stack = @stack;
+ foreach my $id (@dependent_stack) {
+ my $dep_bug_ids = Bugzilla::Bug::EmitDependList('dependson', 'blocked', $id);
+ foreach my $dep_bug_id (@$dep_bug_ids) {
+ push(@dependent_stack, $dep_bug_id) unless $seen{$dep_bug_id};
+ AddLink($dep_bug_id, $id, $fh);
}
+ }
}
foreach my $k (keys(%baselist)) {
- $seen{$k} = 1;
+ $seen{$k} = 1;
}
-my $sth = $dbh->prepare(
- q{SELECT bug_status, resolution, short_desc
+my $sth = $dbh->prepare(q{SELECT bug_status, resolution, short_desc
FROM bugs
- WHERE bugs.bug_id = ?});
+ WHERE bugs.bug_id = ?}
+);
my @bug_ids = keys %seen;
$user->visible_bugs(\@bug_ids);
foreach my $k (@bug_ids) {
- # Retrieve bug information from the database
- my ($stat, $resolution, $summary) = $dbh->selectrow_array($sth, undef, $k);
- $vars->{'short_desc'} = $summary if ($k eq $cgi->param('id'));
+ # Retrieve bug information from the database
+ my ($stat, $resolution, $summary) = $dbh->selectrow_array($sth, undef, $k);
- # The bug summary is shown only if the user can see the bug.
- if ($user->can_see_bug($k)) {
- $summary = html_quote(clean_text($summary));
- }
- else {
- $summary = '';
- }
-
- my @params;
-
- if ($summary ne "" && $cgi->param('showsummary')) {
- # Wide characters cause GraphViz to die.
- if (Bugzilla->params->{'utf8'}) {
- utf8::encode($summary) if utf8::is_utf8($summary);
- }
- $summary =~ s/([\\\"])/\\$1/g;
- # Newlines must be escaped too, to not break the .map file
- # and to prevent code injection.
- $summary =~ s/\n/\\n/g;
- push(@params, qq{label="$k\\n$summary"});
- }
+ $vars->{'short_desc'} = $summary if ($k eq $cgi->param('id'));
- if (exists $baselist{$k}) {
- push(@params, "shape=box");
- }
+ # The bug summary is shown only if the user can see the bug.
+ if ($user->can_see_bug($k)) {
+ $summary = html_quote(clean_text($summary));
+ }
+ else {
+ $summary = '';
+ }
- if (is_open_state($stat)) {
- push(@params, "color=green");
- }
+ my @params;
- if (@params) {
- print $fh "$k [" . join(',', @params) . "]\n";
- } else {
- print $fh "$k\n";
- }
+ if ($summary ne "" && $cgi->param('showsummary')) {
- # Push the bug tooltip texts into a global hash so that
- # CreateImagemap sub (used with local dot installations) can
- # use them later on.
- my $stat_display = display_value('bug_status', $stat);
- my $resolution_display = display_value('resolution', $resolution);
- $bugtitles{$k} = trim("$stat_display $resolution_display");
-
- # Show the bug summary in tooltips only if not shown on
- # the graph and it is non-empty (the user can see the bug)
- if (!$cgi->param('showsummary') && $summary ne "") {
- $bugtitles{$k} .= " - $summary";
+ # Wide characters cause GraphViz to die.
+ if (Bugzilla->params->{'utf8'}) {
+ utf8::encode($summary) if utf8::is_utf8($summary);
}
+ $summary =~ s/([\\\"])/\\$1/g;
+
+ # Newlines must be escaped too, to not break the .map file
+ # and to prevent code injection.
+ $summary =~ s/\n/\\n/g;
+ push(@params, qq{label="$k\\n$summary"});
+ }
+
+ if (exists $baselist{$k}) {
+ push(@params, "shape=box");
+ }
+
+ if (is_open_state($stat)) {
+ push(@params, "color=green");
+ }
+
+ if (@params) {
+ print $fh "$k [" . join(',', @params) . "]\n";
+ }
+ else {
+ print $fh "$k\n";
+ }
+
+ # Push the bug tooltip texts into a global hash so that
+ # CreateImagemap sub (used with local dot installations) can
+ # use them later on.
+ my $stat_display = display_value('bug_status', $stat);
+ my $resolution_display = display_value('resolution', $resolution);
+ $bugtitles{$k} = trim("$stat_display $resolution_display");
+
+ # Show the bug summary in tooltips only if not shown on
+ # the graph and it is non-empty (the user can see the bug)
+ if (!$cgi->param('showsummary') && $summary ne "") {
+ $bugtitles{$k} .= " - $summary";
+ }
}
@@ -243,98 +255,97 @@ print $fh "}\n";
close $fh;
if ($bug_count > MAX_WEBDOT_BUGS) {
- unlink($filename);
- ThrowUserError("webdot_too_large");
+ unlink($filename);
+ ThrowUserError("webdot_too_large");
}
my $webdotbase = Bugzilla->params->{'webdotbase'};
if ($webdotbase =~ /^https?:/) {
- # Remote dot server. We don't hardcode 'urlbase' here in case
- # 'sslbase' is in use.
- $webdotbase =~ s/%([a-z]*)%/Bugzilla->params->{$1}/eg;
- my $url = $webdotbase . $filename;
- $vars->{'image_url'} = $url . ".gif";
- $vars->{'map_url'} = $url . ".map";
-} else {
- # Local dot installation
-
- # First, generate the png image file from the .dot source
-
- my ($pngfh, $pngfilename) = File::Temp::tempfile("XXXXXXXXXX",
- SUFFIX => '.png',
- DIR => $webdotdir);
-
- chmod Bugzilla::Install::Filesystem::WS_SERVE, $pngfilename
- or warn install_string('chmod_failed', { path => $pngfilename,
- error => $! });
-
- binmode $pngfh;
- open(DOT, '-|', "\"$webdotbase\" -Tpng $filename");
- binmode DOT;
- print $pngfh $_ while <DOT>;
- close DOT;
- close $pngfh;
-
- # On Windows $pngfilename will contain \ instead of /
- $pngfilename =~ s|\\|/|g if ON_WINDOWS;
-
- # Under mod_perl, pngfilename will have an absolute path, and we
- # need to make that into a relative path.
- my $cgi_root = bz_locations()->{cgi_path};
- $pngfilename =~ s#^\Q$cgi_root\E/?##;
-
- $vars->{'image_url'} = $pngfilename;
-
- # Then, generate a imagemap datafile that contains the corner data
- # for drawn bug objects. Pass it on to CreateImagemap that
- # turns this monster into html.
-
- my ($mapfh, $mapfilename) = File::Temp::tempfile("XXXXXXXXXX",
- SUFFIX => '.map',
- DIR => $webdotdir);
-
- chmod Bugzilla::Install::Filesystem::WS_SERVE, $mapfilename
- or warn install_string('chmod_failed', { path => $mapfilename,
- error => $! });
-
- binmode $mapfh;
- open(DOT, '-|', "\"$webdotbase\" -Tismap $filename");
- binmode DOT;
- print $mapfh $_ while <DOT>;
- close DOT;
- close $mapfh;
-
- $vars->{'image_map'} = CreateImagemap($mapfilename);
+
+ # Remote dot server. We don't hardcode 'urlbase' here in case
+ # 'sslbase' is in use.
+ $webdotbase =~ s/%([a-z]*)%/Bugzilla->params->{$1}/eg;
+ my $url = $webdotbase . $filename;
+ $vars->{'image_url'} = $url . ".gif";
+ $vars->{'map_url'} = $url . ".map";
+}
+else {
+ # Local dot installation
+
+ # First, generate the png image file from the .dot source
+
+ my ($pngfh, $pngfilename)
+ = File::Temp::tempfile("XXXXXXXXXX", SUFFIX => '.png', DIR => $webdotdir);
+
+ chmod Bugzilla::Install::Filesystem::WS_SERVE, $pngfilename
+ or warn install_string('chmod_failed', {path => $pngfilename, error => $!});
+
+ binmode $pngfh;
+ open(DOT, '-|', "\"$webdotbase\" -Tpng $filename");
+ binmode DOT;
+ print $pngfh $_ while <DOT>;
+ close DOT;
+ close $pngfh;
+
+ # On Windows $pngfilename will contain \ instead of /
+ $pngfilename =~ s|\\|/|g if ON_WINDOWS;
+
+ # Under mod_perl, pngfilename will have an absolute path, and we
+ # need to make that into a relative path.
+ my $cgi_root = bz_locations()->{cgi_path};
+ $pngfilename =~ s#^\Q$cgi_root\E/?##;
+
+ $vars->{'image_url'} = $pngfilename;
+
+ # Then, generate a imagemap datafile that contains the corner data
+ # for drawn bug objects. Pass it on to CreateImagemap that
+ # turns this monster into html.
+
+ my ($mapfh, $mapfilename)
+ = File::Temp::tempfile("XXXXXXXXXX", SUFFIX => '.map', DIR => $webdotdir);
+
+ chmod Bugzilla::Install::Filesystem::WS_SERVE, $mapfilename
+ or warn install_string('chmod_failed', {path => $mapfilename, error => $!});
+
+ binmode $mapfh;
+ open(DOT, '-|', "\"$webdotbase\" -Tismap $filename");
+ binmode DOT;
+ print $mapfh $_ while <DOT>;
+ close DOT;
+ close $mapfh;
+
+ $vars->{'image_map'} = CreateImagemap($mapfilename);
}
# Cleanup any old .dot files created from previous runs.
my $since = time() - 24 * 60 * 60;
+
# Can't use glob, since even calling that fails taint checks for perl < 5.6
opendir(DIR, $webdotdir);
my @files = grep { /\.dot$|\.png$|\.map$/ && -f "$webdotdir/$_" } readdir(DIR);
closedir DIR;
-foreach my $f (@files)
-{
- $f = "$webdotdir/$f";
- # Here we are deleting all old files. All entries are from the
- # $webdot directory. Since we're deleting the file (not following
- # symlinks), this can't escape to delete anything it shouldn't
- # (unless someone moves the location of $webdotdir, of course)
- trick_taint($f);
- my $mtime = (stat($f))[9];
- if ($mtime && $mtime < $since) {
- unlink $f;
- }
+foreach my $f (@files) {
+ $f = "$webdotdir/$f";
+
+ # Here we are deleting all old files. All entries are from the
+ # $webdot directory. Since we're deleting the file (not following
+ # symlinks), this can't escape to delete anything it shouldn't
+ # (unless someone moves the location of $webdotdir, of course)
+ trick_taint($f);
+ my $mtime = (stat($f))[9];
+ if ($mtime && $mtime < $since) {
+ unlink $f;
+ }
}
# Make sure we only include valid integers (protects us from XSS attacks).
my @bugs = grep(detaint_natural($_), split(/[\s,]+/, $cgi->param('id')));
-$vars->{'bug_id'} = join(', ', @bugs);
+$vars->{'bug_id'} = join(', ', @bugs);
$vars->{'multiple_bugs'} = ($cgi->param('id') =~ /[ ,]/);
-$vars->{'display'} = $display;
-$vars->{'rankdir'} = $rankdir;
-$vars->{'showsummary'} = $cgi->param('showsummary');
+$vars->{'display'} = $display;
+$vars->{'rankdir'} = $rankdir;
+$vars->{'showsummary'} = $cgi->param('showsummary');
# Generate and return the UI (HTML page) from the appropriate template.
print $cgi->header();
diff --git a/showdependencytree.cgi b/showdependencytree.cgi
index 9e193df02..94ac15107 100755
--- a/showdependencytree.cgi
+++ b/showdependencytree.cgi
@@ -20,9 +20,10 @@ use List::Util qw(max);
my $user = Bugzilla->login();
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
+
# Connect to the shadow database if this installation is using one to improve
# performance.
my $dbh = Bugzilla->switch_to_shadow_db();
@@ -34,12 +35,12 @@ my $dbh = Bugzilla->switch_to_shadow_db();
# Make sure the bug ID is a positive integer representing an existing
# bug that the user is authorized to access.
my $bug = Bugzilla::Bug->check(scalar $cgi->param('id'));
-my $id = $bug->id;
+my $id = $bug->id;
local our $hide_resolved = $cgi->param('hide_resolved') ? 1 : 0;
local our $maxdepth = $cgi->param('maxdepth') || 0;
if ($maxdepth !~ /^\d+$/) {
- $maxdepth = 0;
+ $maxdepth = 0;
}
################################################################################
@@ -51,16 +52,16 @@ local our $realdepth = 0;
# Generate the tree of bugs that this bug depends on and a list of IDs
# appearing in the tree.
-my $dependson_tree = { $id => $bug };
-my $dependson_ids = {};
+my $dependson_tree = {$id => $bug};
+my $dependson_ids = {};
GenerateTree($id, "dependson", 1, $dependson_tree, $dependson_ids);
$vars->{'dependson_tree'} = $dependson_tree;
$vars->{'dependson_ids'} = [keys(%$dependson_ids)];
# Generate the tree of bugs that this bug blocks and a list of IDs
# appearing in the tree.
-my $blocked_tree = { $id => $bug };
-my $blocked_ids = {};
+my $blocked_tree = {$id => $bug};
+my $blocked_ids = {};
GenerateTree($id, "blocked", 1, $blocked_tree, $blocked_ids);
$vars->{'blocked_tree'} = $blocked_tree;
$vars->{'blocked_ids'} = [keys(%$blocked_ids)];
@@ -77,70 +78,73 @@ $template->process("bug/dependency-tree.html.tmpl", $vars)
# Tree Generation Functions
sub GenerateTree {
- my ($bug_id, $relationship, $depth, $bugs, $ids) = @_;
-
- # determine just the list of bug ids
- _generate_bug_ids($bug_id, $relationship, $depth, $ids);
- my $bug_ids = [ keys %$ids ];
- return unless @$bug_ids;
-
- # load all the bugs at once
- foreach my $bug (@{ Bugzilla::Bug->new_from_list($bug_ids) }) {
- if (!$bug->{error}) {
- $bugs->{$bug->id} = $bug;
- }
+ my ($bug_id, $relationship, $depth, $bugs, $ids) = @_;
+
+ # determine just the list of bug ids
+ _generate_bug_ids($bug_id, $relationship, $depth, $ids);
+ my $bug_ids = [keys %$ids];
+ return unless @$bug_ids;
+
+ # load all the bugs at once
+ foreach my $bug (@{Bugzilla::Bug->new_from_list($bug_ids)}) {
+ if (!$bug->{error}) {
+ $bugs->{$bug->id} = $bug;
}
+ }
- # preload bug visibility
- Bugzilla->user->visible_bugs($bug_ids);
+ # preload bug visibility
+ Bugzilla->user->visible_bugs($bug_ids);
- # and generate the tree
- _generate_tree($bug_id, $relationship, $depth, $bugs, $ids);
+ # and generate the tree
+ _generate_tree($bug_id, $relationship, $depth, $bugs, $ids);
}
sub _generate_bug_ids {
- my ($bug_id, $relationship, $depth, $ids) = @_;
-
- # Record this depth in the global $realdepth variable if it's farther
- # than we've gone before.
- $realdepth = max($realdepth, $depth);
-
- my $dependencies = _get_dependencies($bug_id, $relationship);
- foreach my $dep_id (@$dependencies) {
- if (!$maxdepth || $depth <= $maxdepth) {
- $ids->{$dep_id} = 1;
- _generate_bug_ids($dep_id, $relationship, $depth + 1, $ids);
- }
+ my ($bug_id, $relationship, $depth, $ids) = @_;
+
+ # Record this depth in the global $realdepth variable if it's farther
+ # than we've gone before.
+ $realdepth = max($realdepth, $depth);
+
+ my $dependencies = _get_dependencies($bug_id, $relationship);
+ foreach my $dep_id (@$dependencies) {
+ if (!$maxdepth || $depth <= $maxdepth) {
+ $ids->{$dep_id} = 1;
+ _generate_bug_ids($dep_id, $relationship, $depth + 1, $ids);
}
+ }
}
sub _generate_tree {
- my ($bug_id, $relationship, $depth, $bugs, $ids) = @_;
-
- my $dependencies = _get_dependencies($bug_id, $relationship);
-
- foreach my $dep_id (@$dependencies) {
- # recurse
- if (!$maxdepth || $depth < $maxdepth) {
- _generate_tree($dep_id, $relationship, $depth + 1, $bugs, $ids);
- }
-
- # remove bugs according to visiblity
- if (!Bugzilla->user->can_see_bug($dep_id)) {
- delete $ids->{$dep_id};
- }
- elsif (!grep { $_ == $dep_id } @{ $bugs->{dependencies}->{$bug_id} }) {
- push @{ $bugs->{dependencies}->{$bug_id} }, $dep_id;
- }
+ my ($bug_id, $relationship, $depth, $bugs, $ids) = @_;
+
+ my $dependencies = _get_dependencies($bug_id, $relationship);
+
+ foreach my $dep_id (@$dependencies) {
+
+ # recurse
+ if (!$maxdepth || $depth < $maxdepth) {
+ _generate_tree($dep_id, $relationship, $depth + 1, $bugs, $ids);
+ }
+
+ # remove bugs according to visiblity
+ if (!Bugzilla->user->can_see_bug($dep_id)) {
+ delete $ids->{$dep_id};
+ }
+ elsif (!grep { $_ == $dep_id } @{$bugs->{dependencies}->{$bug_id}}) {
+ push @{$bugs->{dependencies}->{$bug_id}}, $dep_id;
}
+ }
}
sub _get_dependencies {
- my ($bug_id, $relationship) = @_;
- my $cache = Bugzilla->request_cache->{dependency_cache} ||= {};
- return $cache->{$bug_id}->{$relationship} ||=
- $relationship eq 'dependson'
- ? Bugzilla::Bug::EmitDependList('blocked', 'dependson', $bug_id, $hide_resolved)
- : Bugzilla::Bug::EmitDependList('dependson', 'blocked', $bug_id, $hide_resolved);
+ my ($bug_id, $relationship) = @_;
+ my $cache = Bugzilla->request_cache->{dependency_cache} ||= {};
+ return $cache->{$bug_id}->{$relationship}
+ ||= $relationship eq 'dependson'
+ ? Bugzilla::Bug::EmitDependList('blocked', 'dependson', $bug_id,
+ $hide_resolved)
+ : Bugzilla::Bug::EmitDependList('dependson', 'blocked', $bug_id,
+ $hide_resolved);
}
diff --git a/summarize_time.cgi b/summarize_time.cgi
index 3bdf8ee52..9f4e658f3 100755
--- a/summarize_time.cgi
+++ b/summarize_time.cgi
@@ -12,12 +12,12 @@ use warnings;
use lib qw(. lib);
-use Date::Parse; # strptime
+use Date::Parse; # strptime
use Bugzilla;
-use Bugzilla::Constants; # LOGIN_*
-use Bugzilla::Bug; # EmitDependList
-use Bugzilla::Util; # trim
+use Bugzilla::Constants; # LOGIN_*
+use Bugzilla::Bug; # EmitDependList
+use Bugzilla::Util; # trim
use Bugzilla::Error;
#
@@ -25,152 +25,159 @@ use Bugzilla::Error;
#
sub date_adjust_down {
-
- my ($year, $month, $day) = @_;
-
- if ($day == 0) {
- $month -= 1;
- $day = 31;
- # Proper day adjustment is done later.
-
- if ($month == 0) {
- $year -= 1;
- $month = 12;
- }
- }
- if (($month == 2) && ($day > 28)) {
- if ($year % 4 == 0 && $year % 100 != 0) {
- $day = 29;
- } else {
- $day = 28;
- }
+ my ($year, $month, $day) = @_;
+
+ if ($day == 0) {
+ $month -= 1;
+ $day = 31;
+
+ # Proper day adjustment is done later.
+
+ if ($month == 0) {
+ $year -= 1;
+ $month = 12;
}
+ }
- if (($month == 4 || $month == 6 || $month == 9 || $month == 11) &&
- ($day == 31) )
- {
- $day = 30;
+ if (($month == 2) && ($day > 28)) {
+ if ($year % 4 == 0 && $year % 100 != 0) {
+ $day = 29;
+ }
+ else {
+ $day = 28;
}
- return ($year, $month, $day);
+ }
+
+ if (($month == 4 || $month == 6 || $month == 9 || $month == 11) && ($day == 31))
+ {
+ $day = 30;
+ }
+ return ($year, $month, $day);
}
sub date_adjust_up {
- my ($year, $month, $day) = @_;
+ my ($year, $month, $day) = @_;
- if ($day > 31) {
- $month += 1;
- $day = 1;
+ if ($day > 31) {
+ $month += 1;
+ $day = 1;
- if ($month == 13) {
- $month = 1;
- $year += 1;
- }
+ if ($month == 13) {
+ $month = 1;
+ $year += 1;
}
+ }
- if ($month == 2 && $day > 28) {
- if ($year % 4 != 0 || $year % 100 == 0 || $day > 29) {
- $month = 3;
- $day = 1;
- }
+ if ($month == 2 && $day > 28) {
+ if ($year % 4 != 0 || $year % 100 == 0 || $day > 29) {
+ $month = 3;
+ $day = 1;
}
+ }
- if (($month == 4 || $month == 6 || $month == 9 || $month == 11) &&
- ($day == 31) )
- {
- $month += 1;
- $day = 1;
- }
+ if (($month == 4 || $month == 6 || $month == 9 || $month == 11) && ($day == 31))
+ {
+ $month += 1;
+ $day = 1;
+ }
- return ($year, $month, $day);
+ return ($year, $month, $day);
}
sub split_by_month {
- # Takes start and end dates and splits them into a list of
- # monthly-spaced 2-lists of dates.
- my ($start_date, $end_date) = @_;
- # We assume at this point that the dates are provided and sane
- my (undef, undef, undef, $sd, $sm, $sy, undef) = strptime($start_date);
- my (undef, undef, undef, $ed, $em, $ey, undef) = strptime($end_date);
+ # Takes start and end dates and splits them into a list of
+ # monthly-spaced 2-lists of dates.
+ my ($start_date, $end_date) = @_;
- # Find out how many months fit between the two dates so we know
- # how many times we loop.
- my $yd = $ey - $sy;
- my $md = 12 * $yd + $em - $sm;
- # If the end day is smaller than the start day, last interval is not a whole month.
- if ($sd > $ed) {
- $md -= 1;
- }
+ # We assume at this point that the dates are provided and sane
+ my (undef, undef, undef, $sd, $sm, $sy, undef) = strptime($start_date);
+ my (undef, undef, undef, $ed, $em, $ey, undef) = strptime($end_date);
- my (@months, $sub_start, $sub_end);
- # This +1 and +1900 are a result of strptime's bizarre semantics
- my $year = $sy + 1900;
- my $month = $sm + 1;
-
- # Keep the original date, when the date will be changed in the adjust_date.
- my $sd_tmp = $sd;
- my $month_tmp = $month;
- my $year_tmp = $year;
-
- # This section handles only the whole months.
- for (my $i=0; $i < $md; $i++) {
- # Start of interval is adjusted up: 31.2. -> 1.3.
- ($year_tmp, $month_tmp, $sd_tmp) = date_adjust_up($year, $month, $sd);
- $sub_start = sprintf("%04d-%02d-%02d", $year_tmp, $month_tmp, $sd_tmp);
- $month += 1;
- if ($month == 13) {
- $month = 1;
- $year += 1;
- }
- # End of interval is adjusted down: 31.2 -> 28.2.
- ($year_tmp, $month_tmp, $sd_tmp) = date_adjust_down($year, $month, $sd - 1);
- $sub_end = sprintf("%04d-%02d-%02d", $year_tmp, $month_tmp, $sd_tmp);
- push @months, [$sub_start, $sub_end];
- }
-
- # This section handles the last (unfinished) month.
- $sub_end = sprintf("%04d-%02d-%02d", $ey + 1900, $em + 1, $ed);
+ # Find out how many months fit between the two dates so we know
+ # how many times we loop.
+ my $yd = $ey - $sy;
+ my $md = 12 * $yd + $em - $sm;
+
+# If the end day is smaller than the start day, last interval is not a whole month.
+ if ($sd > $ed) {
+ $md -= 1;
+ }
+
+ my (@months, $sub_start, $sub_end);
+
+ # This +1 and +1900 are a result of strptime's bizarre semantics
+ my $year = $sy + 1900;
+ my $month = $sm + 1;
+
+ # Keep the original date, when the date will be changed in the adjust_date.
+ my $sd_tmp = $sd;
+ my $month_tmp = $month;
+ my $year_tmp = $year;
+
+ # This section handles only the whole months.
+ for (my $i = 0; $i < $md; $i++) {
+
+ # Start of interval is adjusted up: 31.2. -> 1.3.
($year_tmp, $month_tmp, $sd_tmp) = date_adjust_up($year, $month, $sd);
$sub_start = sprintf("%04d-%02d-%02d", $year_tmp, $month_tmp, $sd_tmp);
+ $month += 1;
+ if ($month == 13) {
+ $month = 1;
+ $year += 1;
+ }
+
+ # End of interval is adjusted down: 31.2 -> 28.2.
+ ($year_tmp, $month_tmp, $sd_tmp) = date_adjust_down($year, $month, $sd - 1);
+ $sub_end = sprintf("%04d-%02d-%02d", $year_tmp, $month_tmp, $sd_tmp);
push @months, [$sub_start, $sub_end];
+ }
+
+ # This section handles the last (unfinished) month.
+ $sub_end = sprintf("%04d-%02d-%02d", $ey + 1900, $em + 1, $ed);
+ ($year_tmp, $month_tmp, $sd_tmp) = date_adjust_up($year, $month, $sd);
+ $sub_start = sprintf("%04d-%02d-%02d", $year_tmp, $month_tmp, $sd_tmp);
+ push @months, [$sub_start, $sub_end];
- return @months;
+ return @months;
}
sub sqlize_dates {
- my ($start_date, $end_date) = @_;
- my $date_bits = "";
- my @date_values;
- if ($start_date) {
- # we've checked, trick_taint is fine
- trick_taint($start_date);
- $date_bits = " AND longdescs.bug_when > ?";
- push @date_values, $start_date;
- }
- if ($end_date) {
- # we need to add one day to end_date to catch stuff done today
- # do not forget to adjust date if it was the last day of month
- my (undef, undef, undef, $ed, $em, $ey, undef) = strptime($end_date);
- ($ey, $em, $ed) = date_adjust_up($ey+1900, $em+1, $ed+1);
- $end_date = sprintf("%04d-%02d-%02d", $ey, $em, $ed);
-
- $date_bits .= " AND longdescs.bug_when < ?";
- push @date_values, $end_date;
- }
- return ($date_bits, \@date_values);
+ my ($start_date, $end_date) = @_;
+ my $date_bits = "";
+ my @date_values;
+ if ($start_date) {
+
+ # we've checked, trick_taint is fine
+ trick_taint($start_date);
+ $date_bits = " AND longdescs.bug_when > ?";
+ push @date_values, $start_date;
+ }
+ if ($end_date) {
+
+ # we need to add one day to end_date to catch stuff done today
+ # do not forget to adjust date if it was the last day of month
+ my (undef, undef, undef, $ed, $em, $ey, undef) = strptime($end_date);
+ ($ey, $em, $ed) = date_adjust_up($ey + 1900, $em + 1, $ed + 1);
+ $end_date = sprintf("%04d-%02d-%02d", $ey, $em, $ed);
+
+ $date_bits .= " AND longdescs.bug_when < ?";
+ push @date_values, $end_date;
+ }
+ return ($date_bits, \@date_values);
}
# Return all blockers of the current bug, recursively.
sub get_blocker_ids {
- my ($bug_id, $unique) = @_;
- $unique ||= {$bug_id => 1};
- my $deps = Bugzilla::Bug::EmitDependList("blocked", "dependson", $bug_id);
- my @unseen = grep { !$unique->{$_}++ } @$deps;
- foreach $bug_id (@unseen) {
- get_blocker_ids($bug_id, $unique);
- }
- return keys %$unique;
+ my ($bug_id, $unique) = @_;
+ $unique ||= {$bug_id => 1};
+ my $deps = Bugzilla::Bug::EmitDependList("blocked", "dependson", $bug_id);
+ my @unseen = grep { !$unique->{$_}++ } @$deps;
+ foreach $bug_id (@unseen) {
+ get_blocker_ids($bug_id, $unique);
+ }
+ return keys %$unique;
}
# Return a hashref whose key is chosen by the user (bug ID or commenter)
@@ -178,63 +185,66 @@ sub get_blocker_ids {
# So you can either view it as the time spent by commenters on each bug
# or the time spent in bugs by each commenter.
sub get_list {
- my ($bugids, $start_date, $end_date, $keyname) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($bugids, $start_date, $end_date, $keyname) = @_;
+ my $dbh = Bugzilla->dbh;
- my ($date_bits, $date_values) = sqlize_dates($start_date, $end_date);
- my $buglist = join(", ", @$bugids);
+ my ($date_bits, $date_values) = sqlize_dates($start_date, $end_date);
+ my $buglist = join(", ", @$bugids);
- # Returns the total time worked on each bug *per developer*.
- my $data = $dbh->selectall_arrayref(
- qq{SELECT SUM(work_time) AS total_time, login_name, longdescs.bug_id
+ # Returns the total time worked on each bug *per developer*.
+ my $data = $dbh->selectall_arrayref(
+ qq{SELECT SUM(work_time) AS total_time, login_name, longdescs.bug_id
FROM longdescs
INNER JOIN profiles
ON longdescs.who = profiles.userid
INNER JOIN bugs
ON bugs.bug_id = longdescs.bug_id
- WHERE longdescs.bug_id IN ($buglist) $date_bits } .
- $dbh->sql_group_by('longdescs.bug_id, login_name', 'longdescs.bug_when') .
- qq{ HAVING SUM(work_time) > 0}, {Slice => {}}, @$date_values);
-
- my %list;
- # What this loop does is to push data having the same key in an array.
- push(@{$list{ $_->{$keyname} }}, $_) foreach @$data;
- return \%list;
+ WHERE longdescs.bug_id IN ($buglist) $date_bits }
+ . $dbh->sql_group_by('longdescs.bug_id, login_name', 'longdescs.bug_when')
+ . qq{ HAVING SUM(work_time) > 0}, {Slice => {}}, @$date_values
+ );
+
+ my %list;
+
+ # What this loop does is to push data having the same key in an array.
+ push(@{$list{$_->{$keyname}}}, $_) foreach @$data;
+ return \%list;
}
# Return bugs which had no activity (a.k.a work_time = 0) during the given time range.
sub get_inactive_bugs {
- my ($bugids, $start_date, $end_date) = @_;
- my $dbh = Bugzilla->dbh;
- my ($date_bits, $date_values) = sqlize_dates($start_date, $end_date);
- my $buglist = join(", ", @$bugids);
+ my ($bugids, $start_date, $end_date) = @_;
+ my $dbh = Bugzilla->dbh;
+ my ($date_bits, $date_values) = sqlize_dates($start_date, $end_date);
+ my $buglist = join(", ", @$bugids);
- my $bugs = $dbh->selectcol_arrayref(
- "SELECT bug_id
+ my $bugs = $dbh->selectcol_arrayref(
+ "SELECT bug_id
FROM bugs
WHERE bugs.bug_id IN ($buglist)
AND NOT EXISTS (
SELECT 1
FROM longdescs
WHERE bugs.bug_id = longdescs.bug_id
- AND work_time > 0 $date_bits)",
- undef, @$date_values);
+ AND work_time > 0 $date_bits)", undef, @$date_values
+ );
- return $bugs;
+ return $bugs;
}
# Return 1st day of the month of the earliest activity date for a given list of bugs.
sub get_earliest_activity_date {
- my ($bugids) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($bugids) = @_;
+ my $dbh = Bugzilla->dbh;
- my ($date) = $dbh->selectrow_array(
- 'SELECT ' . $dbh->sql_date_format('MIN(bug_when)', '%Y-%m-01')
- . ' FROM longdescs
- WHERE ' . $dbh->sql_in('bug_id', $bugids)
- . ' AND work_time > 0');
+ my ($date) = $dbh->selectrow_array(
+ 'SELECT '
+ . $dbh->sql_date_format('MIN(bug_when)', '%Y-%m-01')
+ . ' FROM longdescs
+ WHERE ' . $dbh->sql_in('bug_id', $bugids) . ' AND work_time > 0'
+ );
- return $date;
+ return $date;
}
#
@@ -243,122 +253,133 @@ sub get_earliest_activity_date {
my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
Bugzilla->switch_to_shadow_db();
-$user->is_timetracker
- || ThrowUserError("auth_failure", {group => "time-tracking",
- action => "access",
- object => "timetracking_summaries"});
+$user->is_timetracker || ThrowUserError(
+ "auth_failure",
+ {
+ group => "time-tracking",
+ action => "access",
+ object => "timetracking_summaries"
+ }
+);
my @ids = split(",", $cgi->param('id') || '');
@ids = map { Bugzilla::Bug->check($_)->id } @ids;
scalar(@ids) || ThrowUserError('no_bugs_chosen', {action => 'summarize'});
-my $group_by = $cgi->param('group_by') || "number";
-my $monthly = $cgi->param('monthly');
-my $detailed = $cgi->param('detailed');
-my $do_report = $cgi->param('do_report');
-my $inactive = $cgi->param('inactive');
+my $group_by = $cgi->param('group_by') || "number";
+my $monthly = $cgi->param('monthly');
+my $detailed = $cgi->param('detailed');
+my $do_report = $cgi->param('do_report');
+my $inactive = $cgi->param('inactive');
my $do_depends = $cgi->param('do_depends');
-my $ctype = scalar($cgi->param("ctype"));
+my $ctype = scalar($cgi->param("ctype"));
my ($start_date, $end_date);
if ($do_report) {
- my @bugs = @ids;
-
- # Dependency mode requires a single bug and grabs dependents.
- if ($do_depends) {
- if (scalar(@bugs) != 1) {
- ThrowCodeError("bad_arg", { argument=>"id",
- function=>"summarize_time"});
- }
- @bugs = get_blocker_ids($bugs[0]);
- @bugs = @{ $user->visible_bugs(\@bugs) };
- }
+ my @bugs = @ids;
- $start_date = trim $cgi->param('start_date');
- $end_date = trim $cgi->param('end_date');
-
- foreach my $date ($start_date, $end_date) {
- next unless $date;
- validate_date($date)
- || ThrowUserError('illegal_date', {date => $date, format => 'YYYY-MM-DD'});
- }
- # Swap dates in case the user put an end_date before the start_date
- if ($start_date && $end_date &&
- str2time($start_date) > str2time($end_date)) {
- $vars->{'warn_swap_dates'} = 1;
- ($start_date, $end_date) = ($end_date, $start_date);
- }
-
- # Store dates in a session cookie so re-visiting the page
- # for other bugs keeps them around.
- $cgi->send_cookie(-name => 'time-summary-dates',
- -value => join ";", ($start_date, $end_date));
-
- my (@parts, $part_data, @part_list);
-
- # Break dates apart into months if necessary; if not, we use the
- # same @parts list to allow us to use a common codepath.
- if ($monthly) {
- # Calculate the earliest activity date if the user doesn't
- # specify a start date.
- if (!$start_date) {
- $start_date = get_earliest_activity_date(\@bugs);
- }
- # Provide a default end date. Note that this differs in semantics
- # from the open-ended queries we use when start/end_date aren't
- # provided -- and clock skews will make this evident!
- @parts = split_by_month($start_date,
- $end_date || format_time(scalar localtime(time()), '%Y-%m-%d'));
- } else {
- @parts = ([$start_date, $end_date]);
+ # Dependency mode requires a single bug and grabs dependents.
+ if ($do_depends) {
+ if (scalar(@bugs) != 1) {
+ ThrowCodeError("bad_arg", {argument => "id", function => "summarize_time"});
}
-
- # For each of the separate divisions, grab the relevant data.
- my $keyname = ($group_by eq 'owner') ? 'login_name' : 'bug_id';
- foreach my $part (@parts) {
- my ($sub_start, $sub_end) = @$part;
- $part_data = get_list(\@bugs, $sub_start, $sub_end, $keyname);
- push(@part_list, $part_data);
- }
-
- # Do we want to see inactive bugs?
- if ($inactive) {
- $vars->{'null'} = get_inactive_bugs(\@bugs, $start_date, $end_date);
- } else {
- $vars->{'null'} = {};
+ @bugs = get_blocker_ids($bugs[0]);
+ @bugs = @{$user->visible_bugs(\@bugs)};
+ }
+
+ $start_date = trim $cgi->param('start_date');
+ $end_date = trim $cgi->param('end_date');
+
+ foreach my $date ($start_date, $end_date) {
+ next unless $date;
+ validate_date($date)
+ || ThrowUserError('illegal_date', {date => $date, format => 'YYYY-MM-DD'});
+ }
+
+ # Swap dates in case the user put an end_date before the start_date
+ if ($start_date && $end_date && str2time($start_date) > str2time($end_date)) {
+ $vars->{'warn_swap_dates'} = 1;
+ ($start_date, $end_date) = ($end_date, $start_date);
+ }
+
+ # Store dates in a session cookie so re-visiting the page
+ # for other bugs keeps them around.
+ $cgi->send_cookie(
+ -name => 'time-summary-dates',
+ -value => join ";",
+ ($start_date, $end_date)
+ );
+
+ my (@parts, $part_data, @part_list);
+
+ # Break dates apart into months if necessary; if not, we use the
+ # same @parts list to allow us to use a common codepath.
+ if ($monthly) {
+
+ # Calculate the earliest activity date if the user doesn't
+ # specify a start date.
+ if (!$start_date) {
+ $start_date = get_earliest_activity_date(\@bugs);
}
- # Convert bug IDs to bug objects.
- @bugs = map {new Bugzilla::Bug($_)} @bugs;
-
- $vars->{'part_list'} = \@part_list;
- $vars->{'parts'} = \@parts;
- # We pass the list of bugs as a hashref.
- $vars->{'bugs'} = {map { $_->id => $_ } @bugs};
+ # Provide a default end date. Note that this differs in semantics
+ # from the open-ended queries we use when start/end_date aren't
+ # provided -- and clock skews will make this evident!
+ @parts = split_by_month($start_date,
+ $end_date || format_time(scalar localtime(time()), '%Y-%m-%d'));
+ }
+ else {
+ @parts = ([$start_date, $end_date]);
+ }
+
+ # For each of the separate divisions, grab the relevant data.
+ my $keyname = ($group_by eq 'owner') ? 'login_name' : 'bug_id';
+ foreach my $part (@parts) {
+ my ($sub_start, $sub_end) = @$part;
+ $part_data = get_list(\@bugs, $sub_start, $sub_end, $keyname);
+ push(@part_list, $part_data);
+ }
+
+ # Do we want to see inactive bugs?
+ if ($inactive) {
+ $vars->{'null'} = get_inactive_bugs(\@bugs, $start_date, $end_date);
+ }
+ else {
+ $vars->{'null'} = {};
+ }
+
+ # Convert bug IDs to bug objects.
+ @bugs = map { new Bugzilla::Bug($_) } @bugs;
+
+ $vars->{'part_list'} = \@part_list;
+ $vars->{'parts'} = \@parts;
+
+ # We pass the list of bugs as a hashref.
+ $vars->{'bugs'} = {map { $_->id => $_ } @bugs};
}
elsif ($cgi->cookie("time-summary-dates")) {
- ($start_date, $end_date) = split ";", $cgi->cookie('time-summary-dates');
+ ($start_date, $end_date) = split ";", $cgi->cookie('time-summary-dates');
}
-$vars->{'ids'} = \@ids;
+$vars->{'ids'} = \@ids;
$vars->{'start_date'} = $start_date;
-$vars->{'end_date'} = $end_date;
-$vars->{'group_by'} = $group_by;
-$vars->{'monthly'} = $monthly;
-$vars->{'detailed'} = $detailed;
-$vars->{'inactive'} = $inactive;
-$vars->{'do_report'} = $do_report;
+$vars->{'end_date'} = $end_date;
+$vars->{'group_by'} = $group_by;
+$vars->{'monthly'} = $monthly;
+$vars->{'detailed'} = $detailed;
+$vars->{'inactive'} = $inactive;
+$vars->{'do_report'} = $do_report;
$vars->{'do_depends'} = $do_depends;
my $format = $template->get_format("bug/summarize-time", undef, $ctype);
# Get the proper content-type
-print $cgi->header(-type=> $format->{'ctype'});
+print $cgi->header(-type => $format->{'ctype'});
$template->process("$format->{'template'}", $vars)
|| ThrowTemplateError($template->error());
diff --git a/t/001compile.t b/t/001compile.t
index 7097ad361..ad29975af 100644
--- a/t/001compile.t
+++ b/t/001compile.t
@@ -18,78 +18,78 @@ use lib qw(. lib t);
use Config;
use Support::Files;
use Test::More tests => scalar(@Support::Files::testitems)
- + scalar(@Support::Files::test_files);
+ + scalar(@Support::Files::test_files);
-BEGIN {
- use_ok('Bugzilla::Constants');
- use_ok('Bugzilla::Install::Requirements');
- use_ok('Bugzilla');
+BEGIN {
+ use_ok('Bugzilla::Constants');
+ use_ok('Bugzilla::Install::Requirements');
+ use_ok('Bugzilla');
}
sub compile_file {
- my ($file) = @_;
+ my ($file) = @_;
- # Don't allow CPAN.pm to modify the global @INC, which the version
- # shipped with Perl 5.8.8 does. (It gets loaded by
- # Bugzilla::Install::CPAN.)
- local @INC = @INC;
+ # Don't allow CPAN.pm to modify the global @INC, which the version
+ # shipped with Perl 5.8.8 does. (It gets loaded by
+ # Bugzilla::Install::CPAN.)
+ local @INC = @INC;
- if ($file =~ s/\.pm$//) {
- $file =~ s{/}{::}g;
- use_ok($file);
- return;
- }
+ if ($file =~ s/\.pm$//) {
+ $file =~ s{/}{::}g;
+ use_ok($file);
+ return;
+ }
- open(my $fh, $file);
- my $bang = <$fh>;
- close $fh;
+ open(my $fh, $file);
+ my $bang = <$fh>;
+ close $fh;
- my $T = "";
- if ($bang =~ m/#!\S*perl\s+-.*T/) {
- $T = "T";
- }
+ my $T = "";
+ if ($bang =~ m/#!\S*perl\s+-.*T/) {
+ $T = "T";
+ }
- my $libs = '';
- if ($ENV{PERL5LIB}) {
- $libs = join " ", map { "-I\"$_\"" } split /$Config{path_sep}/, $ENV{PERL5LIB};
- }
- my $perl = qq{"$^X"};
- my $output = `$perl $libs -c$T $file 2>&1`;
- chomp($output);
- my $return_val = $?;
- $output =~ s/^\Q$file\E syntax OK$//ms;
- diag($output) if $output;
- ok(!$return_val, $file) or diag('--ERROR');
+ my $libs = '';
+ if ($ENV{PERL5LIB}) {
+ $libs = join " ", map {"-I\"$_\""} split /$Config{path_sep}/, $ENV{PERL5LIB};
+ }
+ my $perl = qq{"$^X"};
+ my $output = `$perl $libs -c$T $file 2>&1`;
+ chomp($output);
+ my $return_val = $?;
+ $output =~ s/^\Q$file\E syntax OK$//ms;
+ diag($output) if $output;
+ ok(!$return_val, $file) or diag('--ERROR');
}
-my @testitems = (@Support::Files::testitems, @Support::Files::test_files);
+my @testitems = (@Support::Files::testitems, @Support::Files::test_files);
my $file_features = map_files_to_features();
# Test the scripts by compiling them
foreach my $file (@testitems) {
- # These were already compiled, above.
- next if ($file eq 'Bugzilla.pm'
- or $file eq 'Bugzilla/Constants.pm'
- or $file eq 'Bugzilla/Install/Requirements.pm');
- SKIP: {
- if ($file eq 'mod_perl.pl') {
- skip 'mod_perl.pl cannot be compiled from the command line', 1;
- }
- my $feature = $file_features->{$file};
- if ($feature and !Bugzilla->feature($feature)) {
- skip "$file: $feature not enabled", 1;
- }
-
- # Check that we have a DBI module to support the DB, if this
- # is a database module (but not Schema)
- if ($file =~ m{Bugzilla/DB/([^/]+)\.pm$}
- and $file ne "Bugzilla/DB/Schema.pm")
- {
- my $module = lc($1);
- my $dbd = DB_MODULE->{$module}->{dbd}->{module};
- eval("use $dbd; 1") or skip "$file: $dbd not installed", 1;
- }
-
- compile_file($file);
+
+ # These were already compiled, above.
+ next
+ if ($file eq 'Bugzilla.pm'
+ or $file eq 'Bugzilla/Constants.pm'
+ or $file eq 'Bugzilla/Install/Requirements.pm');
+SKIP: {
+ if ($file eq 'mod_perl.pl') {
+ skip 'mod_perl.pl cannot be compiled from the command line', 1;
+ }
+ my $feature = $file_features->{$file};
+ if ($feature and !Bugzilla->feature($feature)) {
+ skip "$file: $feature not enabled", 1;
}
-}
+
+ # Check that we have a DBI module to support the DB, if this
+ # is a database module (but not Schema)
+ if ($file =~ m{Bugzilla/DB/([^/]+)\.pm$} and $file ne "Bugzilla/DB/Schema.pm") {
+ my $module = lc($1);
+ my $dbd = DB_MODULE->{$module}->{dbd}->{module};
+ eval("use $dbd; 1") or skip "$file: $dbd not installed", 1;
+ }
+
+ compile_file($file);
+ }
+}
diff --git a/t/002goodperl.t b/t/002goodperl.t
index d1858361f..837849282 100644
--- a/t/002goodperl.t
+++ b/t/002goodperl.t
@@ -18,156 +18,171 @@ use lib 't';
use Support::Files;
-use Test::More tests => (scalar(@Support::Files::testitems)
- + scalar(@Support::Files::test_files)) * 6;
+use Test::More tests =>
+ (scalar(@Support::Files::testitems) + scalar(@Support::Files::test_files))
+ * 6;
-my @testitems = (@Support::Files::test_files, @Support::Files::testitems);
+my @testitems = (@Support::Files::test_files, @Support::Files::testitems);
my @require_taint = qw(email_in.pl importxml.pl mod_perl.pl whine.pl);
foreach my $file (@testitems) {
- $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
- next if (!$file); # skip null entries
- if (! open (FILE, $file)) {
- ok(0,"could not open $file --WARNING");
+ $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
+ next if (!$file); # skip null entries
+ if (!open(FILE, $file)) {
+ ok(0, "could not open $file --WARNING");
+ }
+ my $file_line1 = <FILE>;
+ close(FILE);
+
+ $file =~ m/.*\.(.*)/;
+ my $ext = $1;
+
+ if ($file_line1 !~ m/^#\!/) {
+ ok(1, "$file does not have a shebang");
+ }
+ else {
+ my $flags;
+ if (!defined $ext || $ext eq "pl") {
+
+ # standalone programs aren't taint checked yet
+ if (grep { $file eq $_ } @require_taint) {
+ $flags = 'T';
+ }
+ else {
+ $flags = '';
+ }
}
- my $file_line1 = <FILE>;
- close (FILE);
-
- $file =~ m/.*\.(.*)/;
- my $ext = $1;
-
- if ($file_line1 !~ m/^#\!/) {
- ok(1,"$file does not have a shebang");
- } else {
- my $flags;
- if (!defined $ext || $ext eq "pl") {
- # standalone programs aren't taint checked yet
- if (grep { $file eq $_ } @require_taint) {
- $flags = 'T';
- }
- else {
- $flags = '';
- }
- } elsif ($ext eq "pm") {
- ok(0, "$file is a module, but has a shebang");
- next;
- } elsif ($ext eq "cgi") {
- # cgi files must be taint checked
- $flags = 'T';
- } else {
- ok(0, "$file has shebang but unknown extension");
- next;
- }
-
- if ($file_line1 =~ m#^\#\!/usr/bin/perl(?:\s-(\w+))?$#) {
- my $file_flags = $1 || '';
- if ($flags eq $file_flags) {
- ok(1, "$file uses standard perl location" . ($flags ? " and -$flags flag" : ""));
- }
- elsif ($flags) {
- ok(0, "$file is MISSING -$flags flag --WARNING");
- }
- else {
- ok(0, "$file has unexpected -$file_flags flag --WARNING");
- }
- } else {
- ok(0,"$file uses non-standard perl location");
- }
+ elsif ($ext eq "pm") {
+ ok(0, "$file is a module, but has a shebang");
+ next;
}
-}
+ elsif ($ext eq "cgi") {
-foreach my $file (@testitems) {
- my $found_use_perl = 0;
- my $found_use_strict = 0;
- my $found_use_warnings = 0;
-
- $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
- next if (!$file); # skip null entries
- if (! open (FILE, $file)) {
- ok(0,"could not open $file --WARNING");
- next;
- }
- while (my $file_line = <FILE>) {
- $found_use_perl = 1 if $file_line =~ m/^\s*use 5.10.1/;
- $found_use_strict = 1 if $file_line =~ m/^\s*use strict/;
- $found_use_warnings = 1 if $file_line =~ m/^\s*use warnings/;
- last if ($found_use_perl && $found_use_strict && $found_use_warnings);
+ # cgi files must be taint checked
+ $flags = 'T';
}
- close (FILE);
- if ($found_use_perl) {
- ok(1,"$file requires Perl 5.10.1");
- } else {
- ok(0,"$file DOES NOT require Perl 5.10.1 --WARNING");
+ else {
+ ok(0, "$file has shebang but unknown extension");
+ next;
}
- if ($found_use_strict) {
- ok(1,"$file uses strict");
- } else {
- ok(0,"$file DOES NOT use strict --WARNING");
+ if ($file_line1 =~ m#^\#\!/usr/bin/perl(?:\s-(\w+))?$#) {
+ my $file_flags = $1 || '';
+ if ($flags eq $file_flags) {
+ ok(1,
+ "$file uses standard perl location" . ($flags ? " and -$flags flag" : ""));
+ }
+ elsif ($flags) {
+ ok(0, "$file is MISSING -$flags flag --WARNING");
+ }
+ else {
+ ok(0, "$file has unexpected -$file_flags flag --WARNING");
+ }
}
-
- if ($found_use_warnings) {
- ok(1,"$file uses warnings");
- } else {
- ok(0,"$file DOES NOT use warnings --WARNING");
+ else {
+ ok(0, "$file uses non-standard perl location");
}
+ }
+}
+
+foreach my $file (@testitems) {
+ my $found_use_perl = 0;
+ my $found_use_strict = 0;
+ my $found_use_warnings = 0;
+
+ $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
+ next if (!$file); # skip null entries
+ if (!open(FILE, $file)) {
+ ok(0, "could not open $file --WARNING");
+ next;
+ }
+ while (my $file_line = <FILE>) {
+ $found_use_perl = 1 if $file_line =~ m/^\s*use 5.10.1/;
+ $found_use_strict = 1 if $file_line =~ m/^\s*use strict/;
+ $found_use_warnings = 1 if $file_line =~ m/^\s*use warnings/;
+ last if ($found_use_perl && $found_use_strict && $found_use_warnings);
+ }
+ close(FILE);
+ if ($found_use_perl) {
+ ok(1, "$file requires Perl 5.10.1");
+ }
+ else {
+ ok(0, "$file DOES NOT require Perl 5.10.1 --WARNING");
+ }
+
+ if ($found_use_strict) {
+ ok(1, "$file uses strict");
+ }
+ else {
+ ok(0, "$file DOES NOT use strict --WARNING");
+ }
+
+ if ($found_use_warnings) {
+ ok(1, "$file uses warnings");
+ }
+ else {
+ ok(0, "$file DOES NOT use warnings --WARNING");
+ }
}
# Check to see that all error messages use tags (for l10n reasons.)
foreach my $file (@testitems) {
- $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
- next if (!$file); # skip null entries
- if (! open (FILE, $file)) {
- ok(0,"could not open $file --WARNING");
- next;
+ $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
+ next if (!$file); # skip null entries
+ if (!open(FILE, $file)) {
+ ok(0, "could not open $file --WARNING");
+ next;
+ }
+ my $lineno = 0;
+ my $error = 0;
+
+ while (!$error && (my $file_line = <FILE>)) {
+ $lineno++;
+ if ($file_line =~ /Throw.*Error\("(.*?)"/) {
+ if ($1 =~ /\s/) {
+ ok(
+ 0, "$file has a Throw*Error call on line $lineno
+ which doesn't use a tag --ERROR"
+ );
+ $error = 1;
+ }
}
- my $lineno = 0;
- my $error = 0;
-
- while (!$error && (my $file_line = <FILE>)) {
- $lineno++;
- if ($file_line =~ /Throw.*Error\("(.*?)"/) {
- if ($1 =~ /\s/) {
- ok(0,"$file has a Throw*Error call on line $lineno
- which doesn't use a tag --ERROR");
- $error = 1;
- }
- }
- }
-
- ok(1,"$file uses Throw*Error calls correctly") if !$error;
-
- close(FILE);
+ }
+
+ ok(1, "$file uses Throw*Error calls correctly") if !$error;
+
+ close(FILE);
}
# Forbird the { foo => $cgi->param() } syntax, for security reasons.
foreach my $file (@testitems) {
- $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
- next unless $file; # skip null entries
- if (!open(FILE, $file)) {
- ok(0, "could not open $file --WARNING");
- next;
+ $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
+ next unless $file; # skip null entries
+ if (!open(FILE, $file)) {
+ ok(0, "could not open $file --WARNING");
+ next;
+ }
+ my $lineno = 0;
+ my @unsafe_args;
+
+ while (my $file_line = <FILE>) {
+ $lineno++;
+ $file_line =~ s/^\s*(.+)\s*$/$1/; # Remove leading and trailing whitespaces.
+ if ($file_line =~ /^[^#]+=> \$cgi\->param/) {
+ push(@unsafe_args, "$file_line on line $lineno");
}
- my $lineno = 0;
- my @unsafe_args;
-
- while (my $file_line = <FILE>) {
- $lineno++;
- $file_line =~ s/^\s*(.+)\s*$/$1/; # Remove leading and trailing whitespaces.
- if ($file_line =~ /^[^#]+=> \$cgi\->param/) {
- push(@unsafe_args, "$file_line on line $lineno");
- }
- }
-
- if (@unsafe_args) {
- ok(0, "$file incorrectly passes a CGI argument to a hash --ERROR\n" .
- join("\n", @unsafe_args));
- }
- else {
- ok(1, "$file has no vulnerable hash syntax");
- }
-
- close(FILE);
+ }
+
+ if (@unsafe_args) {
+ ok(0,
+ "$file incorrectly passes a CGI argument to a hash --ERROR\n"
+ . join("\n", @unsafe_args));
+ }
+ else {
+ ok(1, "$file has no vulnerable hash syntax");
+ }
+
+ close(FILE);
}
exit 0;
diff --git a/t/003safesys.t b/t/003safesys.t
index 443f96415..3cc55f835 100644
--- a/t/003safesys.t
+++ b/t/003safesys.t
@@ -24,39 +24,42 @@ use Test::More tests => scalar(@Support::Files::testitems);
# This will handle verbosity for us automatically.
my $fh;
{
- no warnings qw(unopened); # Don't complain about non-existent filehandles
- if (-e \*Test::More::TESTOUT) {
- $fh = \*Test::More::TESTOUT;
- } elsif (-e \*Test::Builder::TESTOUT) {
- $fh = \*Test::Builder::TESTOUT;
- } else {
- $fh = \*STDOUT;
- }
+ no warnings qw(unopened); # Don't complain about non-existent filehandles
+ if (-e \*Test::More::TESTOUT) {
+ $fh = \*Test::More::TESTOUT;
+ }
+ elsif (-e \*Test::Builder::TESTOUT) {
+ $fh = \*Test::Builder::TESTOUT;
+ }
+ else {
+ $fh = \*STDOUT;
+ }
}
-my @testitems = @Support::Files::testitems;
-my $perlapp = "\"$^X\"";
+my @testitems = @Support::Files::testitems;
+my $perlapp = "\"$^X\"";
foreach my $file (@testitems) {
- $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
- next if (!$file); # skip null entries
-
- open(my $fh2, '<', $file);
- my $bang = <$fh2>;
- close $fh2;
-
- my $T = "";
- if ($bang =~ m/#!\S*perl\s+-.*T/) {
- $T = "T";
- }
- my $command = "$perlapp -c$T -It -MSupport::Systemexec $file 2>&1";
- my $loginfo=`$command`;
- if ($loginfo =~ /arguments for Support::Systemexec::(system|exec)/im) {
- ok(0,"$file DOES NOT use proper system or exec calls");
- print $fh $loginfo;
- } else {
- ok(1,"$file uses proper system and exec calls");
- }
+ $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
+ next if (!$file); # skip null entries
+
+ open(my $fh2, '<', $file);
+ my $bang = <$fh2>;
+ close $fh2;
+
+ my $T = "";
+ if ($bang =~ m/#!\S*perl\s+-.*T/) {
+ $T = "T";
+ }
+ my $command = "$perlapp -c$T -It -MSupport::Systemexec $file 2>&1";
+ my $loginfo = `$command`;
+ if ($loginfo =~ /arguments for Support::Systemexec::(system|exec)/im) {
+ ok(0, "$file DOES NOT use proper system or exec calls");
+ print $fh $loginfo;
+ }
+ else {
+ ok(1, "$file uses proper system and exec calls");
+ }
}
exit 0;
diff --git a/t/004template.t b/t/004template.t
index 0a6f0e0aa..938ee8b18 100644
--- a/t/004template.t
+++ b/t/004template.t
@@ -22,20 +22,22 @@ use CGI qw(-no_debug);
use File::Spec;
use Template;
-use Test::More tests => ( scalar(@referenced_files) + 2 * $num_actual_files );
+use Test::More tests => (scalar(@referenced_files) + 2 * $num_actual_files);
# Capture the TESTOUT from Test::More or Test::Builder for printing errors.
# This will handle verbosity for us automatically.
my $fh;
{
- no warnings qw(unopened); # Don't complain about non-existent filehandles
- if (-e \*Test::More::TESTOUT) {
- $fh = \*Test::More::TESTOUT;
- } elsif (-e \*Test::Builder::TESTOUT) {
- $fh = \*Test::Builder::TESTOUT;
- } else {
- $fh = \*STDOUT;
- }
+ no warnings qw(unopened); # Don't complain about non-existent filehandles
+ if (-e \*Test::More::TESTOUT) {
+ $fh = \*Test::More::TESTOUT;
+ }
+ elsif (-e \*Test::Builder::TESTOUT) {
+ $fh = \*Test::Builder::TESTOUT;
+ }
+ else {
+ $fh = \*STDOUT;
+ }
}
# Check to make sure all templates that are referenced in Bugzilla
@@ -44,82 +46,106 @@ my $fh;
# fall back to English if necessary.
foreach my $file (@referenced_files) {
- my $found = 0;
- foreach my $path (@english_default_include_paths) {
- my $pathfile = File::Spec->catfile($path, $file);
- if (-e $pathfile) {
- $found = 1;
- last;
- }
+ my $found = 0;
+ foreach my $path (@english_default_include_paths) {
+ my $pathfile = File::Spec->catfile($path, $file);
+ if (-e $pathfile) {
+ $found = 1;
+ last;
}
+ }
- ok($found, "$file found");
+ ok($found, "$file found");
}
foreach my $include_path (@include_paths) {
- # Processes all the templates to make sure they have good syntax
- my $provider = Template::Provider->new(
- {
- INCLUDE_PATH => $include_path ,
- # Need to define filters used in the codebase, they don't
- # actually have to function in this test, just be defined.
- # See Template.pm for the actual codebase definitions.
-
- # Initialize templates (f.e. by loading plugins like Hook).
- PRE_PROCESS => "global/variables.none.tmpl",
-
- FILTERS =>
- {
- html_linebreak => sub { return $_; },
- js => sub { return $_ } ,
- base64 => sub { return $_ } ,
- inactive => [ sub { return sub { return $_; } }, 1] ,
- closed => [ sub { return sub { return $_; } }, 1] ,
- obsolete => [ sub { return sub { return $_; } }, 1] ,
- url_quote => sub { return $_ } ,
- css_class_quote => sub { return $_ } ,
- xml => sub { return $_ } ,
- quoteUrls => sub { return $_ } ,
- bug_link => [ sub { return sub { return $_; } }, 1] ,
- csv => sub { return $_ } ,
- unitconvert => sub { return $_ },
- time => sub { return $_ } ,
- wrap_comment => sub { return $_ },
- none => sub { return $_ } ,
- ics => [ sub { return sub { return $_; } }, 1] ,
+
+ # Processes all the templates to make sure they have good syntax
+ my $provider = Template::Provider->new({
+ INCLUDE_PATH => $include_path,
+
+ # Need to define filters used in the codebase, they don't
+ # actually have to function in this test, just be defined.
+ # See Template.pm for the actual codebase definitions.
+
+ # Initialize templates (f.e. by loading plugins like Hook).
+ PRE_PROCESS => "global/variables.none.tmpl",
+
+ FILTERS => {
+ html_linebreak => sub { return $_; },
+ js => sub { return $_ },
+ base64 => sub { return $_ },
+ inactive => [
+ sub {
+ return sub { return $_; }
+ },
+ 1
+ ],
+ closed => [
+ sub {
+ return sub { return $_; }
+ },
+ 1
+ ],
+ obsolete => [
+ sub {
+ return sub { return $_; }
+ },
+ 1
+ ],
+ url_quote => sub { return $_ },
+ css_class_quote => sub { return $_ },
+ xml => sub { return $_ },
+ quoteUrls => sub { return $_ },
+ bug_link => [
+ sub {
+ return sub { return $_; }
+ },
+ 1
+ ],
+ csv => sub { return $_ },
+ unitconvert => sub { return $_ },
+ time => sub { return $_ },
+ wrap_comment => sub { return $_ },
+ none => sub { return $_ },
+ ics => [
+ sub {
+ return sub { return $_; }
},
+ 1
+ ],
+ },
+ });
+
+ foreach my $file (@{$actual_files{$include_path}}) {
+ my $path = File::Spec->catfile($include_path, $file);
+
+ # These are actual files, so there's no need to check for existence.
+
+ my ($data, $err) = $provider->fetch($file);
+
+ if (!$err) {
+ ok(1, "$path syntax ok");
+ }
+ else {
+ ok(0, "$path has bad syntax --ERROR");
+ print $fh $data . "\n";
+ }
+
+ # Make sure no forbidden constructs are present.
+ local $/;
+ open(FILE, '<', $path) or die "Can't open $file: $!\n";
+ $data = <FILE>;
+ close(FILE);
+
+ # Forbid single quotes to delimit URLs, see bug 926085.
+ if ($data =~ /href=\\?'/) {
+ ok(0, "$path contains blacklisted constructs: href='...'");
}
- );
-
- foreach my $file (@{$actual_files{$include_path}}) {
- my $path = File::Spec->catfile($include_path, $file);
-
- # These are actual files, so there's no need to check for existence.
-
- my ($data, $err) = $provider->fetch($file);
-
- if (!$err) {
- ok(1, "$path syntax ok");
- }
- else {
- ok(0, "$path has bad syntax --ERROR");
- print $fh $data . "\n";
- }
-
- # Make sure no forbidden constructs are present.
- local $/;
- open(FILE, '<', $path) or die "Can't open $file: $!\n";
- $data = <FILE>;
- close (FILE);
-
- # Forbid single quotes to delimit URLs, see bug 926085.
- if ($data =~ /href=\\?'/) {
- ok(0, "$path contains blacklisted constructs: href='...'");
- }
- else {
- ok(1, "$path contains no blacklisted constructs");
- }
+ else {
+ ok(1, "$path contains no blacklisted constructs");
}
+ }
}
exit 0;
diff --git a/t/005whitespace.t b/t/005whitespace.t
index b6de8cee3..ef589e693 100644
--- a/t/005whitespace.t
+++ b/t/005whitespace.t
@@ -19,49 +19,57 @@ use Support::Files;
use Support::Templates;
use File::Spec;
-use Test::More tests => (scalar(@Support::Files::testitems)
- + scalar(@Support::Files::test_files)
- + $Support::Templates::num_actual_files) * 3;
+use Test::More tests => (
+ scalar(@Support::Files::testitems)
+ + scalar(@Support::Files::test_files)
+ + $Support::Templates::num_actual_files)
+ * 3;
my @testitems = (@Support::Files::testitems, @Support::Files::test_files);
for my $path (@Support::Templates::include_paths) {
- push(@testitems, map(File::Spec->catfile($path, $_),
- Support::Templates::find_actual_files($path)));
+ push(
+ @testitems,
+ map(File::Spec->catfile($path, $_),
+ Support::Templates::find_actual_files($path))
+ );
}
my %results;
foreach my $file (@testitems) {
- open (FILE, "$file");
- my @contents = <FILE>;
- if (grep /\t/, @contents) {
- ok(0, "$file contains tabs --WARNING");
- } else {
- ok(1, "$file has no tabs");
- }
- close (FILE);
+ open(FILE, "$file");
+ my @contents = <FILE>;
+ if (grep /\t/, @contents) {
+ ok(0, "$file contains tabs --WARNING");
+ }
+ else {
+ ok(1, "$file has no tabs");
+ }
+ close(FILE);
}
foreach my $file (@testitems) {
- open (FILE, "$file");
- my @contents = <FILE>;
- if (grep /\r/, @contents) {
- ok(0, "$file contains non-OS-conformant line endings --WARNING");
- } else {
- ok(1, "All line endings of $file are OS conformant");
- }
- close (FILE);
+ open(FILE, "$file");
+ my @contents = <FILE>;
+ if (grep /\r/, @contents) {
+ ok(0, "$file contains non-OS-conformant line endings --WARNING");
+ }
+ else {
+ ok(1, "All line endings of $file are OS conformant");
+ }
+ close(FILE);
}
foreach my $file (@testitems) {
- open (FILE, "$file");
- my $first_line = <FILE>;
- if ($first_line =~ /\xef\xbb\xbf/) {
- ok(0, "$file contains Byte Order Mark --WARNING");
- } else {
- ok(1, "$file is free of a Byte Order Mark");
- }
- close (FILE);
+ open(FILE, "$file");
+ my $first_line = <FILE>;
+ if ($first_line =~ /\xef\xbb\xbf/) {
+ ok(0, "$file contains Byte Order Mark --WARNING");
+ }
+ else {
+ ok(1, "$file is free of a Byte Order Mark");
+ }
+ close(FILE);
}
exit 0;
diff --git a/t/006spellcheck.t b/t/006spellcheck.t
index 24e00242d..ccbd69932 100644
--- a/t/006spellcheck.t
+++ b/t/006spellcheck.t
@@ -19,72 +19,76 @@ use Support::Files;
# -1 because 006spellcheck.t must not be checked.
use Test::More tests => scalar(@Support::Files::testitems)
- + scalar(@Support::Files::test_files) - 1;
+ + scalar(@Support::Files::test_files) - 1;
# Capture the TESTOUT from Test::More or Test::Builder for printing errors.
# This will handle verbosity for us automatically.
my $fh;
{
- no warnings qw(unopened); # Don't complain about non-existent filehandles
- if (-e \*Test::More::TESTOUT) {
- $fh = \*Test::More::TESTOUT;
- } elsif (-e \*Test::Builder::TESTOUT) {
- $fh = \*Test::Builder::TESTOUT;
- } else {
- $fh = \*STDOUT;
- }
+ no warnings qw(unopened); # Don't complain about non-existent filehandles
+ if (-e \*Test::More::TESTOUT) {
+ $fh = \*Test::More::TESTOUT;
+ }
+ elsif (-e \*Test::Builder::TESTOUT) {
+ $fh = \*Test::Builder::TESTOUT;
+ }
+ else {
+ $fh = \*STDOUT;
+ }
}
my @testitems = (@Support::Files::testitems, @Support::Files::test_files);
#add the words to check here:
my @evilwords = qw(
- anyways
- appearence
- arbitary
- cancelled
- critera
- databasa
- dependan
- existance
- existant
- paramater
- refered
- repsentation
- suported
- varsion
+ anyways
+ appearence
+ arbitary
+ cancelled
+ critera
+ databasa
+ dependan
+ existance
+ existant
+ paramater
+ refered
+ repsentation
+ suported
+ varsion
);
my $evilwordsregexp = join('|', @evilwords);
foreach my $file (@testitems) {
- $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
- next if (!$file); # skip null entries
- # Do not try to validate this file as it obviously contains a list
- # of wrongly spelled words.
- next if ($file eq 't/006spellcheck.t');
-
- if (open (FILE, $file)) { # open the file for reading
-
- my $found_word = '';
-
- while (my $file_line = <FILE>) { # and go through the file line by line
- if ($file_line =~ /($evilwordsregexp)/i) { # found an evil word
- $found_word = $1;
- last;
- }
- }
-
- close (FILE);
-
- if ($found_word) {
- ok(0,"$file: found SPELLING ERROR $found_word --WARNING");
- } else {
- ok(1,"$file does not contain registered spelling errors");
- }
- } else {
- ok(0,"could not open $file for spellcheck --WARNING");
+ $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
+ next if (!$file); # skip null entries
+ # Do not try to validate this file as it obviously contains a list
+ # of wrongly spelled words.
+ next if ($file eq 't/006spellcheck.t');
+
+ if (open(FILE, $file)) { # open the file for reading
+
+ my $found_word = '';
+
+ while (my $file_line = <FILE>) { # and go through the file line by line
+ if ($file_line =~ /($evilwordsregexp)/i) { # found an evil word
+ $found_word = $1;
+ last;
+ }
}
-}
+
+ close(FILE);
+
+ if ($found_word) {
+ ok(0, "$file: found SPELLING ERROR $found_word --WARNING");
+ }
+ else {
+ ok(1, "$file does not contain registered spelling errors");
+ }
+ }
+ else {
+ ok(0, "could not open $file for spellcheck --WARNING");
+ }
+}
exit 0;
diff --git a/t/007util.t b/t/007util.t
index 66c2df032..94f62dfc1 100644
--- a/t/007util.t
+++ b/t/007util.t
@@ -18,9 +18,9 @@ use Support::Files;
use Test::More tests => 17;
use DateTime;
-BEGIN {
- use_ok('Bugzilla');
- use_ok('Bugzilla::Util');
+BEGIN {
+ use_ok('Bugzilla');
+ use_ok('Bugzilla::Util');
}
# We need to override user preferences so we can get an expected value when
@@ -29,38 +29,57 @@ Bugzilla->user->{'settings'}->{'timezone'}->{'value'} = "local";
# We need to know the local timezone for the date chosen in our tests.
# Below, tests are run against Nov. 24, 2002.
-my $tz = Bugzilla->local_timezone->short_name_for_datetime(DateTime->new(year => 2002, month => 11, day => 24));
+my $tz = Bugzilla->local_timezone->short_name_for_datetime(
+ DateTime->new(year => 2002, month => 11, day => 24));
# we don't test the taint functions since that's going to take some more work.
# XXX: test taint functions
#html_quote():
-is(html_quote("<lala&@>"),"&lt;lala&amp;&#64;&gt;",'html_quote');
+is(html_quote("<lala&@>"), "&lt;lala&amp;&#64;&gt;", 'html_quote');
#url_quote():
-is(url_quote("<lala&>gaa\"'[]{\\"),"%3Clala%26%3Egaa%22%27%5B%5D%7B%5C",'url_quote');
+is(url_quote("<lala&>gaa\"'[]{\\"),
+ "%3Clala%26%3Egaa%22%27%5B%5D%7B%5C", 'url_quote');
#trim():
-is(trim(" fg<*\$%>+=~~ "),'fg<*$%>+=~~','trim()');
+is(trim(" fg<*\$%>+=~~ "), 'fg<*$%>+=~~', 'trim()');
#format_time();
-is(format_time("2002.11.24 00:05"), "2002-11-24 00:05 $tz",'format_time("2002.11.24 00:05") is ' . format_time("2002.11.24 00:05"));
-is(format_time("2002.11.24 00:05:56"), "2002-11-24 00:05:56 $tz",'format_time("2002.11.24 00:05:56")');
-is(format_time("2002.11.24 00:05:56", "%Y-%m-%d %R"), '2002-11-24 00:05', 'format_time("2002.11.24 00:05:56", "%Y-%m-%d %R") (with no timezone)');
-is(format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z"), "2002-11-24 00:05 $tz", 'format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z") (with timezone)');
+is(
+ format_time("2002.11.24 00:05"),
+ "2002-11-24 00:05 $tz",
+ 'format_time("2002.11.24 00:05") is ' . format_time("2002.11.24 00:05")
+);
+is(
+ format_time("2002.11.24 00:05:56"),
+ "2002-11-24 00:05:56 $tz",
+ 'format_time("2002.11.24 00:05:56")'
+);
+is(
+ format_time("2002.11.24 00:05:56", "%Y-%m-%d %R"),
+ '2002-11-24 00:05',
+ 'format_time("2002.11.24 00:05:56", "%Y-%m-%d %R") (with no timezone)'
+);
+is(
+ format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z"),
+ "2002-11-24 00:05 $tz",
+ 'format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z") (with timezone)'
+);
# email_filter
my %email_strings = (
- 'somebody@somewhere.com' => 'somebody',
- 'Somebody <somebody@somewhere.com>' => 'Somebody <somebody>',
- 'One Person <one@person.com>, Two Person <two@person.com>'
- => 'One Person <one>, Two Person <two>',
- 'This string contains somebody@somewhere.com and also this@that.com'
- => 'This string contains somebody and also this',
+ 'somebody@somewhere.com' => 'somebody',
+ 'Somebody <somebody@somewhere.com>' => 'Somebody <somebody>',
+ 'One Person <one@person.com>, Two Person <two@person.com>' =>
+ 'One Person <one>, Two Person <two>',
+ 'This string contains somebody@somewhere.com and also this@that.com' =>
+ 'This string contains somebody and also this',
);
+
foreach my $input (keys %email_strings) {
- is(Bugzilla::Util::email_filter($input), $email_strings{$input},
- "email_filter('$input')");
+ is(Bugzilla::Util::email_filter($input),
+ $email_strings{$input}, "email_filter('$input')");
}
# validate_email_syntax. We need to override some parameters.
@@ -68,14 +87,18 @@ my $params = Bugzilla->params;
$params->{emailregexp} = '.*';
$params->{emailsuffix} = '';
my $ascii_email = 'admin@company.com';
+
# U+0430 returns the Cyrillic "а", which looks similar to the ASCII "a".
my $utf8_email = "\N{U+0430}dmin\@company.com";
-ok(validate_email_syntax($ascii_email), 'correctly formatted ASCII-only email address is valid');
-ok(!validate_email_syntax($utf8_email), 'correctly formatted email address with non-ASCII characters is rejected');
+ok(validate_email_syntax($ascii_email),
+ 'correctly formatted ASCII-only email address is valid');
+ok(!validate_email_syntax($utf8_email),
+ 'correctly formatted email address with non-ASCII characters is rejected');
# diff_arrays():
my @old_array = qw(alpha beta alpha gamma gamma beta alpha delta epsilon gamma);
my @new_array = qw(alpha alpha beta gamma epsilon delta beta delta);
+
# The order is not relevant when comparing both arrays for matching items,
# i.e. (foo bar) and (bar foo) are the same arrays (same items).
# But when returning data, we try to respect the initial order.
@@ -83,5 +106,6 @@ my @new_array = qw(alpha alpha beta gamma epsilon delta beta delta);
# Removed (in this order): gamma alpha gamma.
# Added (in this order): delta
my ($removed, $added) = diff_arrays(\@old_array, \@new_array);
-is_deeply($removed, [qw(gamma alpha gamma)], 'diff_array(\@old, \@new) (check removal)');
+is_deeply($removed, [qw(gamma alpha gamma)],
+ 'diff_array(\@old, \@new) (check removal)');
is_deeply($added, [qw(delta)], 'diff_array(\@old, \@new) (check addition)');
diff --git a/t/008filter.t b/t/008filter.t
index f0a26d13f..b60a97579 100644
--- a/t/008filter.t
+++ b/t/008filter.t
@@ -11,7 +11,7 @@
# This test scans all our templates for every directive. Having eliminated
# those which cannot possibly cause XSS problems, it then checks the rest
-# against the safe list stored in the filterexceptions.pl file.
+# against the safe list stored in the filterexceptions.pl file.
# Sample exploit code: '>"><script>alert('Oh dear...')</script>
@@ -29,192 +29,196 @@ use Cwd;
# Undefine the record separator so we can read in whole files at once
my $oldrecsep = $/;
-my $topdir = cwd;
+my $topdir = cwd;
$/ = undef;
our %safe;
foreach my $path (@Support::Templates::include_paths) {
- $path =~ s|\\|/|g if ON_WINDOWS; # convert \ to / in path if on windows
- $path =~ m|template/([^/]+)/([^/]+)|;
- my $lang = $1;
- my $flavor = $2;
-
- chdir $topdir; # absolute path
- my @testitems = Support::Templates::find_actual_files($path);
- chdir $topdir; # absolute path
-
- next unless @testitems;
-
- # Some people require this, others don't. No-one knows why.
- chdir $path; # relative path
-
- # We load a %safe list of acceptable exceptions.
- if (-r "filterexceptions.pl") {
- do "filterexceptions.pl";
- if (ON_WINDOWS) {
- # filterexceptions.pl uses / separated paths, while
- # find_actual_files returns \ separated ones on Windows.
- # Here, we convert the filter exception hash to use \.
- foreach my $file (keys %safe) {
- my $orig_file = $file;
- $file =~ s|/|\\|g;
- if ($file ne $orig_file) {
- $safe{$file} = $safe{$orig_file};
- delete $safe{$orig_file};
- }
- }
+ $path =~ s|\\|/|g if ON_WINDOWS; # convert \ to / in path if on windows
+ $path =~ m|template/([^/]+)/([^/]+)|;
+ my $lang = $1;
+ my $flavor = $2;
+
+ chdir $topdir; # absolute path
+ my @testitems = Support::Templates::find_actual_files($path);
+ chdir $topdir; # absolute path
+
+ next unless @testitems;
+
+ # Some people require this, others don't. No-one knows why.
+ chdir $path; # relative path
+
+ # We load a %safe list of acceptable exceptions.
+ if (-r "filterexceptions.pl") {
+ do "filterexceptions.pl";
+ if (ON_WINDOWS) {
+
+ # filterexceptions.pl uses / separated paths, while
+ # find_actual_files returns \ separated ones on Windows.
+ # Here, we convert the filter exception hash to use \.
+ foreach my $file (keys %safe) {
+ my $orig_file = $file;
+ $file =~ s|/|\\|g;
+ if ($file ne $orig_file) {
+ $safe{$file} = $safe{$orig_file};
+ delete $safe{$orig_file};
}
+ }
}
-
- # We preprocess the %safe hash of lists into a hash of hashes. This allows
- # us to flag which members were not found, and report that as a warning,
- # thereby keeping the lists clean.
- foreach my $file (keys %safe) {
- if (ref $safe{$file} eq 'ARRAY') {
- my $list = $safe{$file};
- $safe{$file} = {};
- foreach my $directive (@$list) {
- $safe{$file}{$directive} = 0;
- }
- }
+ }
+
+ # We preprocess the %safe hash of lists into a hash of hashes. This allows
+ # us to flag which members were not found, and report that as a warning,
+ # thereby keeping the lists clean.
+ foreach my $file (keys %safe) {
+ if (ref $safe{$file} eq 'ARRAY') {
+ my $list = $safe{$file};
+ $safe{$file} = {};
+ foreach my $directive (@$list) {
+ $safe{$file}{$directive} = 0;
+ }
}
+ }
- foreach my $file (@testitems) {
- # There are some files we don't check, because there is no need to
- # filter their contents due to their content-type.
- if ($file =~ /\.(pm|txt|rst|png)\.tmpl$/) {
- ok(1, "($lang/$flavor) $file is filter-safe");
- next;
- }
+ foreach my $file (@testitems) {
- # Read the entire file into a string
- open (FILE, "<$file") || die "Can't open $file: $!\n";
- my $slurp = <FILE>;
- close (FILE);
+ # There are some files we don't check, because there is no need to
+ # filter their contents due to their content-type.
+ if ($file =~ /\.(pm|txt|rst|png)\.tmpl$/) {
+ ok(1, "($lang/$flavor) $file is filter-safe");
+ next;
+ }
- my @unfiltered;
+ # Read the entire file into a string
+ open(FILE, "<$file") || die "Can't open $file: $!\n";
+ my $slurp = <FILE>;
+ close(FILE);
- # /g means we execute this loop for every match
- # /s means we ignore linefeeds in the regexp matches
- while ($slurp =~ /\[%(?:-|\+|~|=)?(.*?)(?:-|\+|~|=)?%\]/gs) {
- my $directive = $1;
+ my @unfiltered;
- my @lineno = ($` =~ m/\n/gs);
- my $lineno = scalar(@lineno) + 1;
+ # /g means we execute this loop for every match
+ # /s means we ignore linefeeds in the regexp matches
+ while ($slurp =~ /\[%(?:-|\+|~|=)?(.*?)(?:-|\+|~|=)?%\]/gs) {
+ my $directive = $1;
- if (!directive_ok($file, $directive)) {
+ my @lineno = ($` =~ m/\n/gs);
+ my $lineno = scalar(@lineno) + 1;
- # This intentionally makes no effort to eliminate duplicates; to do
- # so would merely make it more likely that the user would not
- # escape all instances when attempting to correct an error.
- push(@unfiltered, "$lineno:$directive");
- }
- }
+ if (!directive_ok($file, $directive)) {
- my $fullpath = File::Spec->catfile($path, $file);
-
- if (@unfiltered) {
- my $uflist = join("\n ", @unfiltered);
- ok(0, "($lang/$flavor) $fullpath has unfiltered directives:\n $uflist\n--ERROR");
- }
- else {
- # Find any members of the exclusion list which were not found
- my @notfound;
- foreach my $directive (keys %{$safe{$file}}) {
- push(@notfound, $directive) if ($safe{$file}{$directive} == 0);
- }
-
- if (@notfound) {
- my $nflist = join("\n ", @notfound);
- ok(0, "($lang/$flavor) $fullpath - filterexceptions.pl has extra members:\n $nflist\n" .
- "--WARNING");
- }
- else {
- # Don't use the full path here - it's too long and unwieldy.
- ok(1, "($lang/$flavor) $file is filter-safe");
- }
- }
+ # This intentionally makes no effort to eliminate duplicates; to do
+ # so would merely make it more likely that the user would not
+ # escape all instances when attempting to correct an error.
+ push(@unfiltered, "$lineno:$directive");
+ }
}
+
+ my $fullpath = File::Spec->catfile($path, $file);
+
+ if (@unfiltered) {
+ my $uflist = join("\n ", @unfiltered);
+ ok(0,
+ "($lang/$flavor) $fullpath has unfiltered directives:\n $uflist\n--ERROR");
+ }
+ else {
+ # Find any members of the exclusion list which were not found
+ my @notfound;
+ foreach my $directive (keys %{$safe{$file}}) {
+ push(@notfound, $directive) if ($safe{$file}{$directive} == 0);
+ }
+
+ if (@notfound) {
+ my $nflist = join("\n ", @notfound);
+ ok(0,
+ "($lang/$flavor) $fullpath - filterexceptions.pl has extra members:\n $nflist\n"
+ . "--WARNING");
+ }
+ else {
+ # Don't use the full path here - it's too long and unwieldy.
+ ok(1, "($lang/$flavor) $file is filter-safe");
+ }
+ }
+ }
}
sub directive_ok {
- my ($file, $directive) = @_;
+ my ($file, $directive) = @_;
- # Comments
- return 1 if $directive =~ /^#/;
+ # Comments
+ return 1 if $directive =~ /^#/;
- # Remove any leading/trailing whitespace.
- $directive =~ s/^\s*//;
- $directive =~ s/\s*$//;
+ # Remove any leading/trailing whitespace.
+ $directive =~ s/^\s*//;
+ $directive =~ s/\s*$//;
- # Empty directives are ok; they are usually line break helpers
- return 1 if $directive eq '';
+ # Empty directives are ok; they are usually line break helpers
+ return 1 if $directive eq '';
- # Make sure we're not looking for ./ in the $safe hash
- $file =~ s#^\./##;
+ # Make sure we're not looking for ./ in the $safe hash
+ $file =~ s#^\./##;
- # Exclude those on the nofilter list
- if (defined($safe{$file}{$directive})) {
- $safe{$file}{$directive}++;
- return 1;
- };
+ # Exclude those on the nofilter list
+ if (defined($safe{$file}{$directive})) {
+ $safe{$file}{$directive}++;
+ return 1;
+ }
- # Directives
- return 1 if $directive =~ /^(IF|END|UNLESS|FOREACH|PROCESS|INCLUDE|
+ # Directives
+ return 1 if $directive =~ /^(IF|END|UNLESS|FOREACH|PROCESS|INCLUDE|
BLOCK|USE|ELSE|NEXT|LAST|DEFAULT|
ELSIF|SET|SWITCH|CASE|WHILE|RETURN|STOP|
TRY|CATCH|FINAL|THROW|CLEAR|MACRO|FILTER)/x;
- # ? :
- if ($directive =~ /.+\?(.+):(.+)/) {
- return 1 if directive_ok($file, $1) && directive_ok($file, $2);
- }
+ # ? :
+ if ($directive =~ /.+\?(.+):(.+)/) {
+ return 1 if directive_ok($file, $1) && directive_ok($file, $2);
+ }
+
+ # + - * /
+ return 1 if $directive =~ /[+\-*\/]/;
+
+ # Numbers
+ return 1 if $directive =~ /^[0-9]+$/;
+
+ # Simple assignments
+ return 1 if $directive =~ /^[\w\.\$\{\}]+\s+=\s+/;
+
+ # Conditional literals with either sort of quotes
+ # There must be no $ in the string for it to be a literal
+ return 1 if $directive =~ /^(["'])[^\$]*[^\\]\1/;
+ return 1 if $directive =~ /^(["'])\1/;
+
+ # Special values always used for numbers
+ return 1 if $directive =~ /^[ijkn]$/;
+ return 1 if $directive =~ /^count$/;
+
+ # Params
+ return 1 if $directive =~ /^Param\(/;
+
+ # Hooks
+ return 1 if $directive =~ /^Hook.process\(/;
+
+ # Other functions guaranteed to return OK output
+ return 1 if $directive =~ /^(time2str|url)\(/;
+
+ # Safe Template Toolkit virtual methods
+ return 1 if $directive =~ /\.(length$|size$|push\(|unshift\(|delete\()/;
+
+ # Special Template Toolkit loop variable
+ return 1 if $directive =~ /^loop\.(index|count)$/;
+
+ # Branding terms
+ return 1 if $directive =~ /^terms\./;
- # + - * /
- return 1 if $directive =~ /[+\-*\/]/;
-
- # Numbers
- return 1 if $directive =~ /^[0-9]+$/;
-
- # Simple assignments
- return 1 if $directive =~ /^[\w\.\$\{\}]+\s+=\s+/;
-
- # Conditional literals with either sort of quotes
- # There must be no $ in the string for it to be a literal
- return 1 if $directive =~ /^(["'])[^\$]*[^\\]\1/;
- return 1 if $directive =~ /^(["'])\1/;
-
- # Special values always used for numbers
- return 1 if $directive =~ /^[ijkn]$/;
- return 1 if $directive =~ /^count$/;
-
- # Params
- return 1 if $directive =~ /^Param\(/;
-
- # Hooks
- return 1 if $directive =~ /^Hook.process\(/;
-
- # Other functions guaranteed to return OK output
- return 1 if $directive =~ /^(time2str|url)\(/;
-
- # Safe Template Toolkit virtual methods
- return 1 if $directive =~ /\.(length$|size$|push\(|unshift\(|delete\()/;
-
- # Special Template Toolkit loop variable
- return 1 if $directive =~ /^loop\.(index|count)$/;
-
- # Branding terms
- return 1 if $directive =~ /^terms\./;
-
- # Things which are already filtered
- # Note: If a single directive prints two things, and only one is
- # filtered, we may not catch that case.
- return 1 if $directive =~ /FILTER\ (html|csv|js|base64|css_class_quote|ics|
+ # Things which are already filtered
+ # Note: If a single directive prints two things, and only one is
+ # filtered, we may not catch that case.
+ return 1 if $directive =~ /FILTER\ (html|csv|js|base64|css_class_quote|ics|
quoteUrls|time|uri|xml|lower|html_light|
obsolete|inactive|closed|unitconvert|
txt|html_linebreak|none)\b/x;
- return 0;
+ return 0;
}
$/ = $oldrecsep;
diff --git a/t/009bugwords.t b/t/009bugwords.t
index e36651edb..da432e114 100644
--- a/t/009bugwords.t
+++ b/t/009bugwords.t
@@ -9,9 +9,9 @@
#Bugzilla Test 9#
####bugwords#####
-# Bugzilla has a mechanism for taking various words, including "bug", "bugs",
+# Bugzilla has a mechanism for taking various words, including "bug", "bugs",
# and "a bug" and automatically replacing them in the templates with the local
-# terminology. It does this by using the 'terms' hash, so "bug" becomes
+# terminology. It does this by using the 'terms' hash, so "bug" becomes
# "[% terms.bug %]". This test makes sure the relevant words aren't used
# bare.
@@ -27,53 +27,57 @@ use Bugzilla::Util;
use File::Spec;
-use Test::More tests => ($Support::Templates::num_actual_files);
+use Test::More tests => ($Support::Templates::num_actual_files);
# Find all the templates
my @testitems;
for my $path (@Support::Templates::include_paths) {
- push(@testitems, map(File::Spec->catfile($path, $_),
- Support::Templates::find_actual_files($path)));
+ push(
+ @testitems,
+ map(File::Spec->catfile($path, $_),
+ Support::Templates::find_actual_files($path))
+ );
}
foreach my $file (@testitems) {
- my @errors;
-
- # Read the entire file into a string
- local $/;
- open (FILE, "<$file") || die "Can't open $file: $!\n";
- my $slurp = <FILE>;
- close (FILE);
-
- # /g means we execute this loop for every match
- # /s means we ignore linefeeds in the regexp matches
- # This extracts everything which is _not_ a directive.
- while ($slurp =~ /%\](.*?)(\[%|$)/gs) {
- my $text = $1;
-
- my @lineno = ($` =~ m/\n/gs);
- my $lineno = scalar(@lineno) + 1;
-
- # "a bug", "bug", "bugs"
- if (grep /(a?[\s>]bugs?[\s.:;,<])/i, $text) {
- # Exclude variable assignment.
- unless (grep /bugs =/, $text) {
- push(@errors, [$lineno, $text]);
- next;
- }
- }
- }
-
- if (scalar(@errors)) {
- ok(0, "$file contains invalid bare words (e.g. 'bug') --WARNING");
-
- foreach my $error (@errors) {
- print "$error->[0]: $error->[1]\n";
+ my @errors;
+
+ # Read the entire file into a string
+ local $/;
+ open(FILE, "<$file") || die "Can't open $file: $!\n";
+ my $slurp = <FILE>;
+ close(FILE);
+
+ # /g means we execute this loop for every match
+ # /s means we ignore linefeeds in the regexp matches
+ # This extracts everything which is _not_ a directive.
+ while ($slurp =~ /%\](.*?)(\[%|$)/gs) {
+ my $text = $1;
+
+ my @lineno = ($` =~ m/\n/gs);
+ my $lineno = scalar(@lineno) + 1;
+
+ # "a bug", "bug", "bugs"
+ if (grep /(a?[\s>]bugs?[\s.:;,<])/i, $text) {
+
+ # Exclude variable assignment.
+ unless (grep /bugs =/, $text) {
+ push(@errors, [$lineno, $text]);
+ next;
}
- }
- else {
- ok(1, "$file has no invalid barewords");
}
+ }
+
+ if (scalar(@errors)) {
+ ok(0, "$file contains invalid bare words (e.g. 'bug') --WARNING");
+
+ foreach my $error (@errors) {
+ print "$error->[0]: $error->[1]\n";
+ }
+ }
+ else {
+ ok(1, "$file has no invalid barewords");
+ }
}
exit 0;
diff --git a/t/010dependencies.t b/t/010dependencies.t
index afd29a652..b600766b6 100644
--- a/t/010dependencies.t
+++ b/t/010dependencies.t
@@ -30,7 +30,8 @@ use constant MODULE_REGEX => qr/
['"]?
([\w:\.\\]+)
/x;
-use constant BASE_REGEX => qr/^use (?:base|parent) (?:-norequire, )?qw\(([^\)]+)/;
+use constant BASE_REGEX =>
+ qr/^use (?:base|parent) (?:-norequire, )?qw\(([^\)]+)/;
# Extract all Perl modules.
foreach my $file (@Support::Files::testitems) {
@@ -42,46 +43,47 @@ foreach my $file (@Support::Files::testitems) {
}
foreach my $module (keys %mods) {
- my $reading = 1;
- my @use;
-
- open(SOURCE, $mods{$module});
- while (my $line = <SOURCE>) {
- last if ($line =~ /^__END__/);
- if ($line =~ /^=cut/) {
- $reading = 1;
- next;
- }
- next unless $reading;
- if ($line =~ /^=(head|over|item|back|pod|begin|end|for)/) {
- $reading = 0;
- next;
- }
- if ($line =~ /^package\s+([^;]);/) {
- $module = $1;
- }
- elsif ($line =~ BASE_REGEX or $line =~ MODULE_REGEX) {
- my $used_string = $1;
- # "use base"/"use parent" can have multiple modules
- my @used_array = split(/\s+/, $used_string);
- foreach my $used (@used_array) {
- next if $used !~ /^Bugzilla/;
- $used =~ s#/#::#g;
- $used =~ s#\.pm$##;
- $used =~ s#\$module#[^:]+#;
- $used =~ s#\${[^}]+}#[^:]+#;
- $used =~ s#[" ]##g;
- push(@use, grep(/^\Q$used\E$/, keys %mods));
- }
- }
+ my $reading = 1;
+ my @use;
+
+ open(SOURCE, $mods{$module});
+ while (my $line = <SOURCE>) {
+ last if ($line =~ /^__END__/);
+ if ($line =~ /^=cut/) {
+ $reading = 1;
+ next;
+ }
+ next unless $reading;
+ if ($line =~ /^=(head|over|item|back|pod|begin|end|for)/) {
+ $reading = 0;
+ next;
}
- close (SOURCE);
+ if ($line =~ /^package\s+([^;]);/) {
+ $module = $1;
+ }
+ elsif ($line =~ BASE_REGEX or $line =~ MODULE_REGEX) {
+ my $used_string = $1;
- foreach my $u (@use) {
- if (!grep {$_ eq $u} @{$deps{$module}}) {
- push(@{$deps{$module}}, $u);
+ # "use base"/"use parent" can have multiple modules
+ my @used_array = split(/\s+/, $used_string);
+ foreach my $used (@used_array) {
+ next if $used !~ /^Bugzilla/;
+ $used =~ s#/#::#g;
+ $used =~ s#\.pm$##;
+ $used =~ s#\$module#[^:]+#;
+ $used =~ s#\${[^}]+}#[^:]+#;
+ $used =~ s#[" ]##g;
+ push(@use, grep(/^\Q$used\E$/, keys %mods));
}
}
+ }
+ close(SOURCE);
+
+ foreach my $u (@use) {
+ if (!grep { $_ eq $u } @{$deps{$module}}) {
+ push(@{$deps{$module}}, $u);
+ }
+ }
}
sub creates_loop {
diff --git a/t/011pod.t b/t/011pod.t
index 8a7f374ce..2930ea112 100644
--- a/t/011pod.t
+++ b/t/011pod.t
@@ -21,111 +21,123 @@ use Pod::Checker;
use Pod::Coverage;
use Test::More tests => scalar(@Support::Files::testitems)
- + scalar(@Support::Files::module_files);
+ + scalar(@Support::Files::module_files);
# These methods do not need to be documented by default.
-use constant DEFAULT_WHITELIST => qr/^(?:new|new_from_list|check|run_create_validators)$/;
+use constant DEFAULT_WHITELIST =>
+ qr/^(?:new|new_from_list|check|run_create_validators)$/;
# These subroutines do not need to be documented, generally because
# you shouldn't call them yourself. No need to include subroutines
# of the form _foo(); they are already treated as private.
use constant SUB_WHITELIST => (
- 'Bugzilla::Flag' => qr/^(?:(force_)?retarget|force_cleanup)$/,
- 'Bugzilla::FlagType' => qr/^sqlify_criteria$/,
- 'Bugzilla::JobQueue' => qr/(?:^work_once|work_until_done|subprocess_worker)$/,
- 'Bugzilla::Search' => qr/^SPECIAL_PARSING$/,
- 'Bugzilla::Template' => qr/^field_name$/,
- 'Bugzilla::MIME' => qr/^as_string$/,
+ 'Bugzilla::Flag' => qr/^(?:(force_)?retarget|force_cleanup)$/,
+ 'Bugzilla::FlagType' => qr/^sqlify_criteria$/,
+ 'Bugzilla::JobQueue' => qr/(?:^work_once|work_until_done|subprocess_worker)$/,
+ 'Bugzilla::Search' => qr/^SPECIAL_PARSING$/,
+ 'Bugzilla::Template' => qr/^field_name$/,
+ 'Bugzilla::MIME' => qr/^as_string$/,
);
# These modules do not need to be documented, generally because they
# are subclasses of another module which already has all the relevant
# documentation. Partial names are allowed.
use constant MODULE_WHITELIST => qw(
- Bugzilla::Auth::Login::
- Bugzilla::Auth::Persist::
- Bugzilla::Auth::Verify::
- Bugzilla::BugUrl::
- Bugzilla::Config::
- Bugzilla::Extension::
- Bugzilla::Job::
- Bugzilla::Migrate::
- docs::lib::Pod::Simple::
+ Bugzilla::Auth::Login::
+ Bugzilla::Auth::Persist::
+ Bugzilla::Auth::Verify::
+ Bugzilla::BugUrl::
+ Bugzilla::Config::
+ Bugzilla::Extension::
+ Bugzilla::Job::
+ Bugzilla::Migrate::
+ docs::lib::Pod::Simple::
);
# Capture the TESTOUT from Test::More or Test::Builder for printing errors.
# This will handle verbosity for us automatically.
my $fh;
{
- no warnings qw(unopened); # Don't complain about non-existent filehandles
- if (-e \*Test::More::TESTOUT) {
- $fh = \*Test::More::TESTOUT;
- } elsif (-e \*Test::Builder::TESTOUT) {
- $fh = \*Test::Builder::TESTOUT;
- } else {
- $fh = \*STDOUT;
- }
+ no warnings qw(unopened); # Don't complain about non-existent filehandles
+ if (-e \*Test::More::TESTOUT) {
+ $fh = \*Test::More::TESTOUT;
+ }
+ elsif (-e \*Test::Builder::TESTOUT) {
+ $fh = \*Test::Builder::TESTOUT;
+ }
+ else {
+ $fh = \*STDOUT;
+ }
}
my @testitems = @Support::Files::testitems;
foreach my $file (@testitems) {
- $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
- next if (!$file); # skip null entries
- my $error_count = podchecker($file, $fh);
- if ($error_count < 0) {
- ok(1,"$file does not contain any POD");
- } elsif ($error_count == 0) {
- ok(1,"$file has correct POD syntax");
- } else {
- ok(0,"$file has incorrect POD syntax --ERROR");
- }
+ $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
+ next if (!$file); # skip null entries
+ my $error_count = podchecker($file, $fh);
+ if ($error_count < 0) {
+ ok(1, "$file does not contain any POD");
+ }
+ elsif ($error_count == 0) {
+ ok(1, "$file has correct POD syntax");
+ }
+ else {
+ ok(0, "$file has incorrect POD syntax --ERROR");
+ }
}
my %sub_whitelist = SUB_WHITELIST;
-my @module_files = sort @Support::Files::module_files;
+my @module_files = sort @Support::Files::module_files;
foreach my $file (@module_files) {
- my $module = $file;
- $module =~ s/\.pm$//;
- $module =~ s#/#::#g;
- $module =~ s/^extensions/Bugzilla::Extension/;
-
- my @whitelist = (DEFAULT_WHITELIST);
- push(@whitelist, $sub_whitelist{$module}) if $sub_whitelist{$module};
-
- # XXX Once all methods are correctly documented, nonwhitespace should
- # be set to 1.
- my $cover = Pod::Coverage->new(package => $module, nonwhitespace => 0,
- trustme => \@whitelist);
- my $coverage = $cover->coverage;
- my $reason = $cover->why_unrated;
-
- if (defined $coverage) {
- if ($coverage == 1) {
- ok(1, "$file has POD for all methods");
- }
- else {
- ok(0, "$file POD coverage is " . sprintf("%u%%", 100 * $coverage) .
- ". Undocumented methods: " . join(', ', $cover->uncovered));
- }
+ my $module = $file;
+ $module =~ s/\.pm$//;
+ $module =~ s#/#::#g;
+ $module =~ s/^extensions/Bugzilla::Extension/;
+
+ my @whitelist = (DEFAULT_WHITELIST);
+ push(@whitelist, $sub_whitelist{$module}) if $sub_whitelist{$module};
+
+ # XXX Once all methods are correctly documented, nonwhitespace should
+ # be set to 1.
+ my $cover = Pod::Coverage->new(
+ package => $module,
+ nonwhitespace => 0,
+ trustme => \@whitelist
+ );
+ my $coverage = $cover->coverage;
+ my $reason = $cover->why_unrated;
+
+ if (defined $coverage) {
+ if ($coverage == 1) {
+ ok(1, "$file has POD for all methods");
}
- # These errors are thrown when the module couldn't be loaded due to
- # a missing dependency.
- elsif ($reason =~ /^(?:no public symbols defined|requiring '[^']+' failed)$/) {
- ok(1, "$file cannot be loaded");
+ else {
+ ok(0,
+ "$file POD coverage is "
+ . sprintf("%u%%", 100 * $coverage)
+ . ". Undocumented methods: "
+ . join(', ', $cover->uncovered));
}
- elsif ($reason eq "couldn't find pod") {
- if (grep { $module =~ /^\Q$_\E/ } MODULE_WHITELIST) {
- ok(1, "$file does not contain any POD (whitelisted)");
- }
- else {
- ok(0, "$file POD coverage is 0%");
- }
+ }
+
+ # These errors are thrown when the module couldn't be loaded due to
+ # a missing dependency.
+ elsif ($reason =~ /^(?:no public symbols defined|requiring '[^']+' failed)$/) {
+ ok(1, "$file cannot be loaded");
+ }
+ elsif ($reason eq "couldn't find pod") {
+ if (grep { $module =~ /^\Q$_\E/ } MODULE_WHITELIST) {
+ ok(1, "$file does not contain any POD (whitelisted)");
}
else {
- ok(0, "$file: $reason");
+ ok(0, "$file POD coverage is 0%");
}
+ }
+ else {
+ ok(0, "$file: $reason");
+ }
}
exit 0;
diff --git a/t/012throwables.t b/t/012throwables.t
index 0ef043fa5..6b092d8c2 100644
--- a/t/012throwables.t
+++ b/t/012throwables.t
@@ -6,7 +6,6 @@
# defined by the Mozilla Public License, v. 2.0.
-
##################
#Bugzilla Test 12#
######Errors######
@@ -32,11 +31,11 @@ push @{$Errors{code}{template_error}{used_in}{'Bugzilla/Error.pm'}}, 0;
# Define files to test. Each file would have a list of error messages, if any.
my %test_templates = ();
-my %test_modules = ();
+my %test_modules = ();
# Find all modules
foreach my $module (@Support::Files::testitems) {
- $test_modules{$module} = ();
+ $test_modules{$module} = ();
}
# Find all error templates
@@ -44,20 +43,19 @@ foreach my $module (@Support::Files::testitems) {
# hairy. But let us do it only once.
foreach my $include_path (@include_paths) {
- foreach my $path (@{$actual_files{$include_path}}) {
- my $file = File::Spec->catfile($include_path, $path);
- $file =~ s/\s.*$//; # nuke everything after the first space
- $file =~ s|\\|/|g if ON_WINDOWS; # convert \ to / in path if on windows
- $test_templates{$file} = ()
- if $file =~ m#global/(code|user)-error\.html\.tmpl#;
-
- # Make sure the extension is not disabled
- if ($file =~ m#^(extensions/[^/]+/)#) {
- $test_templates{$file} = ()
- if ! -e "${1}disabled"
- && $file =~ m#global/(code|user)-error-errors\.html\.tmpl#;
- }
+ foreach my $path (@{$actual_files{$include_path}}) {
+ my $file = File::Spec->catfile($include_path, $path);
+ $file =~ s/\s.*$//; # nuke everything after the first space
+ $file =~ s|\\|/|g if ON_WINDOWS; # convert \ to / in path if on windows
+ $test_templates{$file} = () if $file =~ m#global/(code|user)-error\.html\.tmpl#;
+
+ # Make sure the extension is not disabled
+ if ($file =~ m#^(extensions/[^/]+/)#) {
+ $test_templates{$file} = ()
+ if !-e "${1}disabled"
+ && $file =~ m#global/(code|user)-error-errors\.html\.tmpl#;
}
+ }
}
# Count the tests. The +1 is for checking the WS_ERROR_CODE errors.
@@ -69,112 +67,115 @@ plan tests => $tests;
# Collect all errors defined in templates
foreach my $file (keys %test_templates) {
- $file =~ m|template/([^/]+).*/global/([^/]+)-error(?:-errors)?\.html\.tmpl|;
- my $lang = $1;
- my $errtype = $2;
-
- if (! open (TMPL, $file)) {
- Register(\%test_templates, $file, "could not open file --WARNING");
- next;
+ $file =~ m|template/([^/]+).*/global/([^/]+)-error(?:-errors)?\.html\.tmpl|;
+ my $lang = $1;
+ my $errtype = $2;
+
+ if (!open(TMPL, $file)) {
+ Register(\%test_templates, $file, "could not open file --WARNING");
+ next;
+ }
+
+ my $lineno = 0;
+ while (my $line = <TMPL>) {
+ $lineno++;
+ if ($line =~ /\[%\s[A-Z]+\s*error\s*==\s*"(.+)"\s*%\]/) {
+ my $errtag = $1;
+ if ($errtag =~ /\s/) {
+ Register(\%test_templates, $file,
+ "has an error definition \"$errtag\" at line $lineno with "
+ . "space(s) embedded --ERROR");
+ }
+ else {
+ push @{$Errors{$errtype}{$errtag}{defined_in}{$lang}{$file}}, $lineno;
+ }
}
-
- my $lineno=0;
- while (my $line = <TMPL>) {
- $lineno++;
- if ($line =~ /\[%\s[A-Z]+\s*error\s*==\s*"(.+)"\s*%\]/) {
- my $errtag = $1;
- if ($errtag =~ /\s/) {
- Register(\%test_templates, $file,
- "has an error definition \"$errtag\" at line $lineno with "
- . "space(s) embedded --ERROR");
- }
- else {
- push @{$Errors{$errtype}{$errtag}{defined_in}{$lang}{$file}}, $lineno;
- }
- }
- }
- close(TMPL);
+ }
+ close(TMPL);
}
# Collect all used errors from cgi/pm files
foreach my $file (keys %test_modules) {
- $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
- next if (!$file); # skip null entries
- if (! open (TMPL, $file)) {
- Register(\%test_modules, $file, "could not open file --WARNING");
- next;
+ $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
+ next if (!$file); # skip null entries
+ if (!open(TMPL, $file)) {
+ Register(\%test_modules, $file, "could not open file --WARNING");
+ next;
+ }
+
+ my $lineno = 0;
+ while (my $line = <TMPL>) {
+ last if $line =~ /^__END__/; # skip the POD (at least in
+ # Bugzilla/Error.pm)
+ $lineno++;
+ if ($line
+ =~ /^[^#]*\b(Throw(Code|User)Error|(user_)?error\s+=>)\s*\(?\s*["'](.*?)['"]/)
+ {
+ my $errtype;
+
+ # If it's a normal ThrowCode/UserError
+ if ($2) {
+ $errtype = lc($2);
+ }
+
+ # If it's an AUTH_ERROR tag
+ else {
+ $errtype = $3 ? 'user' : 'code';
+ }
+ my $errtag = $4;
+ push @{$Errors{$errtype}{$errtag}{used_in}{$file}}, $lineno;
}
+ }
- my $lineno = 0;
- while (my $line = <TMPL>) {
- last if $line =~ /^__END__/; # skip the POD (at least in
- # Bugzilla/Error.pm)
- $lineno++;
- if ($line =~
-/^[^#]*\b(Throw(Code|User)Error|(user_)?error\s+=>)\s*\(?\s*["'](.*?)['"]/) {
- my $errtype;
- # If it's a normal ThrowCode/UserError
- if ($2) {
- $errtype = lc($2);
- }
- # If it's an AUTH_ERROR tag
- else {
- $errtype = $3 ? 'user' : 'code';
- }
- my $errtag = $4;
- push @{$Errors{$errtype}{$errtag}{used_in}{$file}}, $lineno;
- }
- }
-
- close(TMPL);
+ close(TMPL);
}
# Now let us start the checks
foreach my $errtype (keys %Errors) {
- foreach my $errtag (keys %{$Errors{$errtype}}) {
- # Check for undefined tags
- if (!defined $Errors{$errtype}{$errtag}{defined_in}) {
- UsedIn($errtype, $errtag, "any");
+ foreach my $errtag (keys %{$Errors{$errtype}}) {
+
+ # Check for undefined tags
+ if (!defined $Errors{$errtype}{$errtag}{defined_in}) {
+ UsedIn($errtype, $errtag, "any");
+ }
+ else {
+ # Check for all languages!!!
+ my @langs = ();
+ foreach my $lang (@languages) {
+ if (!defined $Errors{$errtype}{$errtag}{defined_in}{$lang}) {
+ push @langs, $lang;
}
- else {
- # Check for all languages!!!
- my @langs = ();
- foreach my $lang (@languages) {
- if (!defined $Errors{$errtype}{$errtag}{defined_in}{$lang}) {
- push @langs, $lang;
- }
- }
- if (scalar @langs) {
- UsedIn($errtype, $errtag, join(', ',@langs));
- }
-
- # Now check for tag usage in all DEFINED languages
- foreach my $lang (keys %{$Errors{$errtype}{$errtag}{defined_in}}) {
- if (!defined $Errors{$errtype}{$errtag}{used_in}) {
- DefinedIn($errtype, $errtag, $lang);
- }
- }
+ }
+ if (scalar @langs) {
+ UsedIn($errtype, $errtag, join(', ', @langs));
+ }
+
+ # Now check for tag usage in all DEFINED languages
+ foreach my $lang (keys %{$Errors{$errtype}{$errtag}{defined_in}}) {
+ if (!defined $Errors{$errtype}{$errtag}{used_in}) {
+ DefinedIn($errtype, $errtag, $lang);
}
+ }
}
+ }
}
# And make sure that everything defined in WS_ERROR_CODE
# is actually a valid error.
foreach my $err_name (keys %{WS_ERROR_CODE()}) {
- if (!defined $Errors{'code'}{$err_name}
- && !defined $Errors{'user'}{$err_name})
- {
- Register(\%test_modules, 'WS_ERROR_CODE',
- "Error tag '$err_name' is used in WS_ERROR_CODE in"
- . " Bugzilla/WebService/Constants.pm"
- . " but not defined in any template, and not used in any code.");
- }
+ if (!defined $Errors{'code'}{$err_name} && !defined $Errors{'user'}{$err_name})
+ {
+ Register(\%test_modules, 'WS_ERROR_CODE',
+ "Error tag '$err_name' is used in WS_ERROR_CODE in"
+ . " Bugzilla/WebService/Constants.pm"
+ . " but not defined in any template, and not used in any code.");
+ }
}
# Now report modules results
foreach my $file (sort keys %test_modules) {
- Report($file, @{$test_modules{$file}});
+ Report($file, @{$test_modules{$file}});
}
# And report WS_ERROR_CODE results
@@ -182,56 +183,67 @@ Report('WS_ERROR_CODE', @{$test_modules{'WS_ERROR_CODE'}});
# Now report templates results
foreach my $file (sort keys %test_templates) {
- Report($file, @{$test_templates{$file}});
+ Report($file, @{$test_templates{$file}});
}
sub Register {
- my ($hash, $file, $message, $warning) = @_;
- # If set to 1, $warning will avoid the test to fail.
- $warning ||= 0;
- push(@{$hash->{$file}}, {'message' => $message, 'warning' => $warning});
+ my ($hash, $file, $message, $warning) = @_;
+
+ # If set to 1, $warning will avoid the test to fail.
+ $warning ||= 0;
+ push(@{$hash->{$file}}, {'message' => $message, 'warning' => $warning});
}
sub Report {
- my ($file, @errors) = @_;
- if (scalar @errors) {
- # Do we only have warnings to report or also real errors?
- my @real_errors = grep {$_->{'warning'} == 0} @errors;
- # Extract error messages.
- @errors = map {$_->{'message'}} @errors;
- if (scalar(@real_errors)) {
- ok(0, "$file has ". scalar(@errors) ." error(s):\n" . join("\n", @errors));
- }
- else {
- ok(1, "--WARNING $file has " . scalar(@errors) .
- " unused error tag(s):\n" . join("\n", @errors));
- }
+ my ($file, @errors) = @_;
+ if (scalar @errors) {
+
+ # Do we only have warnings to report or also real errors?
+ my @real_errors = grep { $_->{'warning'} == 0 } @errors;
+
+ # Extract error messages.
+ @errors = map { $_->{'message'} } @errors;
+ if (scalar(@real_errors)) {
+ ok(0, "$file has " . scalar(@errors) . " error(s):\n" . join("\n", @errors));
}
else {
- # This is used for both code and template files, so let's use
- # file-independent phrase
- ok(1, "$file uses error tags correctly");
+ ok(1,
+ "--WARNING $file has "
+ . scalar(@errors)
+ . " unused error tag(s):\n"
+ . join("\n", @errors));
}
+ }
+ else {
+ # This is used for both code and template files, so let's use
+ # file-independent phrase
+ ok(1, "$file uses error tags correctly");
+ }
}
sub UsedIn {
- my ($errtype, $errtag, $lang) = @_;
- $lang = $lang || "any";
- foreach my $file (keys %{$Errors{$errtype}{$errtag}{used_in}}) {
- Register(\%test_modules, $file,
- "$errtype error tag '$errtag' is used at line(s) ("
- . join (',', @{$Errors{$errtype}{$errtag}{used_in}{$file}})
- . ") but not defined for language(s): $lang");
- }
+ my ($errtype, $errtag, $lang) = @_;
+ $lang = $lang || "any";
+ foreach my $file (keys %{$Errors{$errtype}{$errtag}{used_in}}) {
+ Register(\%test_modules, $file,
+ "$errtype error tag '$errtag' is used at line(s) ("
+ . join(',', @{$Errors{$errtype}{$errtag}{used_in}{$file}})
+ . ") but not defined for language(s): $lang");
+ }
}
+
sub DefinedIn {
- my ($errtype, $errtag, $lang) = @_;
- foreach my $file (keys %{$Errors{$errtype}{$errtag}{defined_in}{$lang}}) {
- Register(\%test_templates, $file,
- "$errtype error tag '$errtag' is defined at line(s) ("
- . join (',', @{$Errors{$errtype}{$errtag}{defined_in}{$lang}{$file}})
- . ") but is not used anywhere", 1);
- }
+ my ($errtype, $errtag, $lang) = @_;
+ foreach my $file (keys %{$Errors{$errtype}{$errtag}{defined_in}{$lang}}) {
+ Register(
+ \%test_templates,
+ $file,
+ "$errtype error tag '$errtag' is defined at line(s) ("
+ . join(',', @{$Errors{$errtype}{$errtag}{defined_in}{$lang}{$file}})
+ . ") but is not used anywhere",
+ 1
+ );
+ }
}
exit 0;
diff --git a/t/013dbschema.t b/t/013dbschema.t
index 217176ff2..062a22992 100644
--- a/t/013dbschema.t
+++ b/t/013dbschema.t
@@ -23,30 +23,30 @@ use Bugzilla::DB::Schema;
# SQL reserved words
use constant RESERVED_WORDS => qw(
- ABSOLUTE ACTION ACTOR ADD AFTER ALL ALLOCATE ALTER ANY AND ARE AS ASC ASSERTION ASYNC AT
- ATTRIBUTES BEFORE BEGIN BETWEEN BIT BIT_LENGTH BOOLEAN BOTH BREADTH BY CALL CASCADE
- CASCADED CASE CAST CATALOG CHAR CHARACTER_LENGTH CHAR_LENGTH COLLATE
- COLLATION COLUMN COMPLETION CONNECT CONNECTION CONSTRAINT CONSTRAINTS
- CONVERT CORRESPONDING CREATE CROSS CURRENT_DATE CURRENT_PATH CURRENT_TIME
- CURRENT_TIMESTAMP CURRENT_USER CYCLE DATA DATE DAY DEALLOCATE DECLARE DEFAULT DEFERRABLE
- DEFERRED DELETE DEPTH DESC DESCRIBE DESCRIPTOR DESTROY DIAGNOSTICS DICTIONARY
- DISCONNECT DISTINCT DO DOMAIN DROP EACH ELEMENT ELSE ELSEIF END END-EXEC EQUALS EXCEPT
- EXCEPTION EXECUTE EXTERNAL EXTRACT FACTOR FALSE FIRST FOR FROM FULL GENERAL GET
- GLOBAL GRANT GROUP HAVING HOLD HOUR IDENTITY IF IGNORE IMMEDIATE IN INITIALLY INNER INPUT
- INSENSITIVE INSERT INSTEAD INTERSECT INTERVAL IS ISOLATION JOIN LAST LEADING LEAVE
- LEFT LESS LEVEL LIMIT LIST LOCAL LOOP LOWER MATCH MINUTE MODIFY MONTH NAMES
- NATIONAL NATURAL NCHAR NEW NEW_TABLE NEXT NO NONE NOT NULL NULLIF OBJECT
- OCTET_LENGTH OFF OID OLD OLD_TABLE ONLY OPERATION OPERATOR OPERATORS OR ORDER OTHERS
- OUTER OUTPUT OVERLAPS PAD PARAMETERS PARTIAL PATH PENDANT POSITION POSTFIX
- PREFIX PREORDER PREPARE PRESERVE PRIOR PRIVATE PROTECTED READ RECURSIVE REF
- REFERENCING RELATIVE REPLACE RESIGNAL RESTRICT RETURN RETURNS REVOKE RIGHT
- ROLE ROUTINE ROW ROWS SAVEPOINT SCROLL SEARCH SECOND SELECT SENSITIVE SEQUENCE
- SESSION SESSION_USER SIGNAL SIMILAR SIZE SPACE SQLEXCEPTION SQLSTATE
- SQLWARNING START STATE STRUCTURE SUBSTRING SYMBOL SYSTEM_USER TABLE TEMPORARY
- TERM TEST THEN THERE TIME TIMESTAMP TIMEZONE_HOUR TIMEZONE_MINUTE TRAILING
- TRANSACTION TRANSLATE TRANSLATION TRIGGER TRIM TRUE TUPLE UNDER
- UNKNOWN UNION UNIQUE UPDATE UPPER USAGE USING VARCHAR VARIABLE VARYING VIEW VIRTUAL VISIBLE
- WAIT WHEN WHERE WHILE WITH WITHOUT WRITE YEAR ZONE
+ ABSOLUTE ACTION ACTOR ADD AFTER ALL ALLOCATE ALTER ANY AND ARE AS ASC ASSERTION ASYNC AT
+ ATTRIBUTES BEFORE BEGIN BETWEEN BIT BIT_LENGTH BOOLEAN BOTH BREADTH BY CALL CASCADE
+ CASCADED CASE CAST CATALOG CHAR CHARACTER_LENGTH CHAR_LENGTH COLLATE
+ COLLATION COLUMN COMPLETION CONNECT CONNECTION CONSTRAINT CONSTRAINTS
+ CONVERT CORRESPONDING CREATE CROSS CURRENT_DATE CURRENT_PATH CURRENT_TIME
+ CURRENT_TIMESTAMP CURRENT_USER CYCLE DATA DATE DAY DEALLOCATE DECLARE DEFAULT DEFERRABLE
+ DEFERRED DELETE DEPTH DESC DESCRIBE DESCRIPTOR DESTROY DIAGNOSTICS DICTIONARY
+ DISCONNECT DISTINCT DO DOMAIN DROP EACH ELEMENT ELSE ELSEIF END END-EXEC EQUALS EXCEPT
+ EXCEPTION EXECUTE EXTERNAL EXTRACT FACTOR FALSE FIRST FOR FROM FULL GENERAL GET
+ GLOBAL GRANT GROUP HAVING HOLD HOUR IDENTITY IF IGNORE IMMEDIATE IN INITIALLY INNER INPUT
+ INSENSITIVE INSERT INSTEAD INTERSECT INTERVAL IS ISOLATION JOIN LAST LEADING LEAVE
+ LEFT LESS LEVEL LIMIT LIST LOCAL LOOP LOWER MATCH MINUTE MODIFY MONTH NAMES
+ NATIONAL NATURAL NCHAR NEW NEW_TABLE NEXT NO NONE NOT NULL NULLIF OBJECT
+ OCTET_LENGTH OFF OID OLD OLD_TABLE ONLY OPERATION OPERATOR OPERATORS OR ORDER OTHERS
+ OUTER OUTPUT OVERLAPS PAD PARAMETERS PARTIAL PATH PENDANT POSITION POSTFIX
+ PREFIX PREORDER PREPARE PRESERVE PRIOR PRIVATE PROTECTED READ RECURSIVE REF
+ REFERENCING RELATIVE REPLACE RESIGNAL RESTRICT RETURN RETURNS REVOKE RIGHT
+ ROLE ROUTINE ROW ROWS SAVEPOINT SCROLL SEARCH SECOND SELECT SENSITIVE SEQUENCE
+ SESSION SESSION_USER SIGNAL SIMILAR SIZE SPACE SQLEXCEPTION SQLSTATE
+ SQLWARNING START STATE STRUCTURE SUBSTRING SYMBOL SYSTEM_USER TABLE TEMPORARY
+ TERM TEST THEN THERE TIME TIMESTAMP TIMEZONE_HOUR TIMEZONE_MINUTE TRAILING
+ TRANSACTION TRANSLATE TRANSLATION TRIGGER TRIM TRUE TUPLE UNDER
+ UNKNOWN UNION UNIQUE UPDATE UPPER USAGE USING VARCHAR VARIABLE VARYING VIEW VIRTUAL VISIBLE
+ WAIT WHEN WHERE WHILE WITH WITHOUT WRITE YEAR ZONE
);
# Few Exceptions are removed from the above list
@@ -57,31 +57,31 @@ our $schema;
our @tables;
BEGIN {
- $schema = Bugzilla::DB::Schema->new("Mysql");
- @tables = $schema->get_table_list();
+ $schema = Bugzilla::DB::Schema->new("Mysql");
+ @tables = $schema->get_table_list();
}
use Test::More tests => scalar(@tables);
foreach my $table (@tables) {
- my @reserved;
+ my @reserved;
- if (grep { uc($table) eq $_ } RESERVED_WORDS) {
- push(@reserved, $table);
- }
+ if (grep { uc($table) eq $_ } RESERVED_WORDS) {
+ push(@reserved, $table);
+ }
- foreach my $column ($schema->get_table_columns($table)) {
- if (grep { uc($column) eq $_ } RESERVED_WORDS) {
- push(@reserved, $column);
- }
+ foreach my $column ($schema->get_table_columns($table)) {
+ if (grep { uc($column) eq $_ } RESERVED_WORDS) {
+ push(@reserved, $column);
}
+ }
- if (scalar @reserved) {
- ok(0, "Table $table use reserved words: " . join(", ", @reserved));
- }
- else {
- ok(1, "Table $table does not use reserved words");
- }
+ if (scalar @reserved) {
+ ok(0, "Table $table use reserved words: " . join(", ", @reserved));
+ }
+ else {
+ ok(1, "Table $table does not use reserved words");
+ }
}
exit 0;
diff --git a/t/Support/Files.pm b/t/Support/Files.pm
index f3fae58fc..72737ac1f 100644
--- a/t/Support/Files.pm
+++ b/t/Support/Files.pm
@@ -17,40 +17,40 @@ use File::Find;
our @additional_files = ();
our @files = glob('*');
-find(sub { push(@files, $File::Find::name) if $_ =~ /\.pm$/;}, qw(Bugzilla docs));
+find(sub { push(@files, $File::Find::name) if $_ =~ /\.pm$/; },
+ qw(Bugzilla docs));
push(@files, 'extensions/create.pl', 'docs/makedocs.pl');
-our @extensions =
- grep { $_ ne 'extensions/create.pl' && ! -e "$_/disabled" }
- glob('extensions/*');
+our @extensions = grep { $_ ne 'extensions/create.pl' && !-e "$_/disabled" }
+ glob('extensions/*');
foreach my $extension (@extensions) {
- find(sub { push(@files, $File::Find::name) if $_ =~ /\.pm$/;}, $extension);
+ find(sub { push(@files, $File::Find::name) if $_ =~ /\.pm$/; }, $extension);
}
our @test_files = glob('t/*.t');
sub isTestingFile {
- my ($file) = @_;
- my $exclude;
-
- if ($file =~ /\.cgi$|\.pl$|\.pm$/) {
- return 1;
- }
- my $additional;
- foreach $additional (@additional_files) {
- if ($file eq $additional) { return 1; }
- }
- return undef;
+ my ($file) = @_;
+ my $exclude;
+
+ if ($file =~ /\.cgi$|\.pl$|\.pm$/) {
+ return 1;
+ }
+ my $additional;
+ foreach $additional (@additional_files) {
+ if ($file eq $additional) { return 1; }
+ }
+ return undef;
}
our (@testitems, @module_files);
foreach my $currentfile (@files) {
- if (isTestingFile($currentfile)) {
- push(@testitems, $currentfile);
- }
- push(@module_files, $currentfile) if $currentfile =~ /\.pm$/;
+ if (isTestingFile($currentfile)) {
+ push(@testitems, $currentfile);
+ }
+ push(@module_files, $currentfile) if $currentfile =~ /\.pm$/;
}
diff --git a/t/Support/Templates.pm b/t/Support/Templates.pm
index d17c7334b..e08686e0f 100644
--- a/t/Support/Templates.pm
+++ b/t/Support/Templates.pm
@@ -13,9 +13,9 @@ use warnings;
use lib 't';
use parent qw(Exporter);
-@Support::Templates::EXPORT =
- qw(@languages @include_paths @english_default_include_paths
- @referenced_files %actual_files $num_actual_files);
+@Support::Templates::EXPORT
+ = qw(@languages @include_paths @english_default_include_paths
+ @referenced_files %actual_files $num_actual_files);
use Bugzilla;
use Bugzilla::Constants;
@@ -26,15 +26,15 @@ use File::Find;
use File::Spec;
# English default include paths
-our @english_default_include_paths =
- (File::Spec->catdir(bz_locations()->{'templatedir'}, 'en', 'default'));
+our @english_default_include_paths
+ = (File::Spec->catdir(bz_locations()->{'templatedir'}, 'en', 'default'));
# And the extensions too
foreach my $extension (@Support::Files::extensions) {
- my $dir = File::Spec->catdir($extension, 'template', 'en', 'default');
- if (-e $dir) {
- push @english_default_include_paths, $dir;
- }
+ my $dir = File::Spec->catdir($extension, 'template', 'en', 'default');
+ if (-e $dir) {
+ push @english_default_include_paths, $dir;
+ }
}
# Files which are referenced in the cgi files
@@ -47,37 +47,39 @@ our %actual_files = ();
our $num_actual_files = 0;
# Set the template available languages and include paths
-our @languages = @{ Bugzilla->languages };
-our @include_paths = @{ template_include_path({ language => Bugzilla->languages }) };
+our @languages = @{Bugzilla->languages};
+our @include_paths
+ = @{template_include_path({language => Bugzilla->languages})};
our @files;
# Local subroutine used with File::Find
sub find_templates {
- # Prune CVS directories
- if (-d $_ && $_ eq 'CVS') {
- $File::Find::prune = 1;
- return;
- }
- # Only include files ending in '.tmpl'
- if (-f $_ && $_ =~ m/\.tmpl$/i) {
- my $filename;
- my $local_dir = File::Spec->abs2rel($File::Find::dir,
- $File::Find::topdir);
+ # Prune CVS directories
+ if (-d $_ && $_ eq 'CVS') {
+ $File::Find::prune = 1;
+ return;
+ }
- # File::Spec 3.13 and newer return "." instead of "" if both
- # arguments of abs2rel() are identical.
- $local_dir = "" if ($local_dir eq ".");
+ # Only include files ending in '.tmpl'
+ if (-f $_ && $_ =~ m/\.tmpl$/i) {
+ my $filename;
+ my $local_dir = File::Spec->abs2rel($File::Find::dir, $File::Find::topdir);
- if ($local_dir) {
- $filename = File::Spec->catfile($local_dir, $_);
- } else {
- $filename = $_;
- }
+ # File::Spec 3.13 and newer return "." instead of "" if both
+ # arguments of abs2rel() are identical.
+ $local_dir = "" if ($local_dir eq ".");
- push(@files, $filename);
+ if ($local_dir) {
+ $filename = File::Spec->catfile($local_dir, $_);
}
+ else {
+ $filename = $_;
+ }
+
+ push(@files, $filename);
+ }
}
# Scan the given template include path for templates
@@ -90,7 +92,7 @@ sub find_actual_files {
foreach my $include_path (@include_paths) {
- $actual_files{$include_path} = [ find_actual_files($include_path) ];
+ $actual_files{$include_path} = [find_actual_files($include_path)];
$num_actual_files += scalar(@{$actual_files{$include_path}});
}
@@ -99,20 +101,21 @@ foreach my $include_path (@include_paths) {
my %seen;
foreach my $file (@Support::Files::testitems) {
- open (FILE, $file);
- my @lines = <FILE>;
- close (FILE);
- foreach my $line (@lines) {
- if ($line =~ m/template->process\(\"(.+?)\", .+?\)/) {
- my $template = $1;
- # Ignore templates with $ in the name, since they're
- # probably vars, not real files
- next if $template =~ m/\$/;
- next if $seen{$template};
- push (@referenced_files, $template);
- $seen{$template} = 1;
- }
+ open(FILE, $file);
+ my @lines = <FILE>;
+ close(FILE);
+ foreach my $line (@lines) {
+ if ($line =~ m/template->process\(\"(.+?)\", .+?\)/) {
+ my $template = $1;
+
+ # Ignore templates with $ in the name, since they're
+ # probably vars, not real files
+ next if $template =~ m/\$/;
+ next if $seen{$template};
+ push(@referenced_files, $template);
+ $seen{$template} = 1;
}
+ }
}
1;
diff --git a/template/en/default/filterexceptions.pl b/template/en/default/filterexceptions.pl
index 6adbbcb95..b9ede2001 100644
--- a/template/en/default/filterexceptions.pl
+++ b/template/en/default/filterexceptions.pl
@@ -24,431 +24,234 @@
%::safe = (
-'whine/schedule.html.tmpl' => [
- 'event.key',
- 'query.id',
- 'query.sort',
- 'schedule.id',
- 'option.0',
- 'option.1',
-],
-
-'whine/mail.html.tmpl' => [
- 'bug.bug_id',
-],
-
-'flag/list.html.tmpl' => [
- 'flag.status',
- 'type.id',
-],
-
-'search/form.html.tmpl' => [
- 'qv.name',
- 'qv.description',
-],
-
-'search/search-specific.html.tmpl' => [
- 'status.name',
-],
-
-'search/tabs.html.tmpl' => [
- 'content',
-],
-
-'request/queue.html.tmpl' => [
- 'column_headers.$group_field',
- 'column_headers.$column',
- 'request.status',
- 'request.bug_id',
- 'request.attach_id',
-],
-
-'reports/keywords.html.tmpl' => [
- 'keyword.bug_count',
-],
-
-'reports/report-table.csv.tmpl' => [
- 'data.$tbl.$col.$row',
- 'colsepchar',
-],
-
-'reports/report-table.html.tmpl' => [
- '"&amp;$col_vals" IF col_vals',
- '"&amp;$row_vals" IF row_vals',
- 'classes.$row_idx.$col_idx',
- 'urlbase',
- 'data.$tbl.$col.$row',
-],
-
-'reports/report.html.tmpl' => [
- 'width',
- 'height',
- 'imageurl',
- 'formaturl',
- 'other_format.name',
- 'switchbase',
- 'cumulate',
-],
-
-'reports/chart.html.tmpl' => [
- 'width',
- 'height',
- 'imageurl',
- 'sizeurl',
- 'height + 100',
- 'height - 100',
- 'width + 100',
- 'width - 100',
-],
-
-'reports/series-common.html.tmpl' => [
- 'sel.name',
- '"onchange=\"$sel.onchange\"" IF sel.onchange',
-],
-
-'reports/chart.csv.tmpl' => [
- 'data.$j.$i',
- 'colsepchar',
-],
-
-'reports/create-chart.html.tmpl' => [
- 'series.series_id',
- 'newidx',
-],
-
-'reports/edit-series.html.tmpl' => [
- 'default.series_id',
-],
-
-'list/edit-multiple.html.tmpl' => [
- 'group.id',
- 'menuname',
-],
-
-'list/list.rdf.tmpl' => [
- 'template_version',
- 'bug.bug_id',
- 'column',
-],
-
-'list/table.html.tmpl' => [
- 'tableheader',
- 'bug.bug_id',
-],
-
-'list/list.csv.tmpl' => [
- 'bug.bug_id',
- 'colsepchar',
-],
-
-'list/list.js.tmpl' => [
- 'bug.bug_id',
-],
-
-'global/choose-product.html.tmpl' => [
- 'target',
-],
-
-# You are not permitted to add any values here. Everything in this file should
+ 'whine/schedule.html.tmpl' => [
+ 'event.key', 'query.id', 'query.sort', 'schedule.id', 'option.0', 'option.1',
+ ],
+
+ 'whine/mail.html.tmpl' => ['bug.bug_id',],
+
+ 'flag/list.html.tmpl' => ['flag.status', 'type.id',],
+
+ 'search/form.html.tmpl' => ['qv.name', 'qv.description',],
+
+ 'search/search-specific.html.tmpl' => ['status.name',],
+
+ 'search/tabs.html.tmpl' => ['content',],
+
+ 'request/queue.html.tmpl' => [
+ 'column_headers.$group_field', 'column_headers.$column',
+ 'request.status', 'request.bug_id',
+ 'request.attach_id',
+ ],
+
+ 'reports/keywords.html.tmpl' => ['keyword.bug_count',],
+
+ 'reports/report-table.csv.tmpl' => ['data.$tbl.$col.$row', 'colsepchar',],
+
+ 'reports/report-table.html.tmpl' => [
+ '"&amp;$col_vals" IF col_vals', '"&amp;$row_vals" IF row_vals',
+ 'classes.$row_idx.$col_idx', 'urlbase',
+ 'data.$tbl.$col.$row',
+ ],
+
+ 'reports/report.html.tmpl' => [
+ 'width', 'height', 'imageurl', 'formaturl',
+ 'other_format.name', 'switchbase', 'cumulate',
+ ],
+
+ 'reports/chart.html.tmpl' => [
+ 'width', 'height', 'imageurl', 'sizeurl',
+ 'height + 100', 'height - 100', 'width + 100', 'width - 100',
+ ],
+
+ 'reports/series-common.html.tmpl' =>
+ ['sel.name', '"onchange=\"$sel.onchange\"" IF sel.onchange',],
+
+ 'reports/chart.csv.tmpl' => ['data.$j.$i', 'colsepchar',],
+
+ 'reports/create-chart.html.tmpl' => ['series.series_id', 'newidx',],
+
+ 'reports/edit-series.html.tmpl' => ['default.series_id',],
+
+ 'list/edit-multiple.html.tmpl' => ['group.id', 'menuname',],
+
+ 'list/list.rdf.tmpl' => ['template_version', 'bug.bug_id', 'column',],
+
+ 'list/table.html.tmpl' => ['tableheader', 'bug.bug_id',],
+
+ 'list/list.csv.tmpl' => ['bug.bug_id', 'colsepchar',],
+
+ 'list/list.js.tmpl' => ['bug.bug_id',],
+
+ 'global/choose-product.html.tmpl' => ['target',],
+
+# You are not permitted to add any values here. Everything in this file should
# be filtered unless there's an extremely good reason why not, in which case,
# use the "none" dummy filter.
-'global/code-error.html.tmpl' => [
-],
-
-'global/header.html.tmpl' => [
- 'javascript',
- 'style',
- 'onload',
- 'title',
- '" &ndash; $header" IF header',
- 'subheader',
- 'header_addl_info',
- 'message',
-],
-
-'global/messages.html.tmpl' => [
- 'series.frequency * 2',
-],
-
-'global/select-menu.html.tmpl' => [
- 'options',
- 'size',
-],
-
-'global/tabs.html.tmpl' => [
- 'content',
-],
-
-# You are not permitted to add any values here. Everything in this file should
+ 'global/code-error.html.tmpl' => [],
+
+ 'global/header.html.tmpl' => [
+ 'javascript', 'style',
+ 'onload', 'title',
+ '" &ndash; $header" IF header', 'subheader',
+ 'header_addl_info', 'message',
+ ],
+
+ 'global/messages.html.tmpl' => ['series.frequency * 2',],
+
+ 'global/select-menu.html.tmpl' => ['options', 'size',],
+
+ 'global/tabs.html.tmpl' => ['content',],
+
+# You are not permitted to add any values here. Everything in this file should
# be filtered unless there's an extremely good reason why not, in which case,
# use the "none" dummy filter.
-'global/user-error.html.tmpl' => [
-],
-
-'global/confirm-user-match.html.tmpl' => [
- 'script',
-],
-
-'bug/comments.html.tmpl' => [
- 'comment.id',
- 'comment.count',
- 'bug.bug_id',
-],
-
-'bug/dependency-graph.html.tmpl' => [
- 'image_map', # We need to continue to make sure this is safe in the CGI
- 'image_url',
- 'map_url',
- 'bug_id',
-],
-
-'bug/dependency-tree.html.tmpl' => [
- 'bugid',
- 'maxdepth',
- 'hide_resolved',
- 'ids.join(",")',
- 'maxdepth + 1',
- 'maxdepth > 0 && maxdepth <= realdepth ? maxdepth : ""',
- 'maxdepth == 1 ? 1
+ 'global/user-error.html.tmpl' => [],
+
+ 'global/confirm-user-match.html.tmpl' => ['script',],
+
+ 'bug/comments.html.tmpl' => ['comment.id', 'comment.count', 'bug.bug_id',],
+
+ 'bug/dependency-graph.html.tmpl' => [
+ 'image_map', # We need to continue to make sure this is safe in the CGI
+ 'image_url', 'map_url', 'bug_id',
+ ],
+
+ 'bug/dependency-tree.html.tmpl' => [
+ 'bugid', 'maxdepth', 'hide_resolved', 'ids.join(",")', 'maxdepth + 1',
+ 'maxdepth > 0 && maxdepth <= realdepth ? maxdepth : ""', 'maxdepth == 1 ? 1
: ( maxdepth ? maxdepth - 1 : realdepth - 1 )',
-],
-
-'bug/edit.html.tmpl' => [
- 'bug.remaining_time',
- 'bug.delta_ts',
- 'bug.bug_id',
- 'group.bit',
- 'selname',
- 'inputname',
- '" colspan=\"$colspan\"" IF colspan',
- '" size=\"$size\"" IF size',
- '" maxlength=\"$maxlength\"" IF maxlength',
- '" spellcheck=\"$spellcheck\"" IF spellcheck',
-],
-
-'bug/show-multiple.html.tmpl' => [
- 'attachment.id',
- 'flag.status',
-],
-
-'bug/show.html.tmpl' => [
- 'bug.bug_id',
-],
-
-'bug/show.xml.tmpl' => [
- 'constants.BUGZILLA_VERSION',
- 'a.id',
- 'field',
-],
-
-'bug/summarize-time.html.tmpl' => [
- 'global.grand_total FILTER format("%.2f")',
- 'subtotal FILTER format("%.2f")',
- 'work_time FILTER format("%.2f")',
- 'global.total FILTER format("%.2f")',
- 'global.remaining FILTER format("%.2f")',
- 'global.estimated FILTER format("%.2f")',
- 'bugs.$id.remaining_time FILTER format("%.2f")',
- 'bugs.$id.estimated_time FILTER format("%.2f")',
-],
-
-
-'bug/time.html.tmpl' => [
- "time_unit.replace('0\\Z', '')",
- '(act / (act + rem)) * 100
- FILTER format("%d")',
-],
-
-'bug/process/results.html.tmpl' => [
- 'title.$type.ucfirst',
-],
-
-'bug/create/create.html.tmpl' => [
- 'cloned_bug_id',
-],
-
-'bug/create/create-guided.html.tmpl' => [
- 'sel',
-],
-
-'bug/activity/table.html.tmpl' => [
- 'change.attachid',
-],
-
-'attachment/create.html.tmpl' => [
- 'bug.bug_id',
- 'attachment.id',
-],
-
-'attachment/created.html.tmpl' => [
- 'attachment.id',
- 'attachment.bug_id',
-],
-
-'attachment/edit.html.tmpl' => [
- 'attachment.id',
- 'attachment.bug_id',
- 'editable_or_hide',
- 'use_patchviewer',
-],
-
-'attachment/list.html.tmpl' => [
- 'attachment.id',
- 'flag.status',
- 'bugid',
- 'obsolete_attachments',
-],
-
-'attachment/midair.html.tmpl' => [
- 'attachment.id',
-],
-
-'attachment/show-multiple.html.tmpl' => [
- 'a.id',
- 'flag.status'
-],
-
-'attachment/updated.html.tmpl' => [
- 'attachment.id',
-],
-
-'attachment/diff-header.html.tmpl' => [
- 'attachid',
- 'id',
- 'bugid',
- 'oldid',
- 'newid',
- 'patch.id',
-],
-
-'attachment/diff-file.html.tmpl' => [
- 'file.minus_lines',
- 'file.plus_lines',
- 'section.old_start',
- 'section_num',
- 'current_line_old',
- 'current_line_new',
-],
-
-'admin/admin.html.tmpl' => [
- 'class'
-],
-
-'admin/table.html.tmpl' => [
- 'contentlink'
-],
-
-'admin/custom_fields/cf-js.js.tmpl' => [
- 'constants.FIELD_TYPE_SINGLE_SELECT',
- 'constants.FIELD_TYPE_MULTI_SELECT',
- 'constants.FIELD_TYPE_BUG_ID',
-],
-
-'admin/params/common.html.tmpl' => [
- 'sortlist_separator',
-],
-
-'admin/products/groupcontrol/confirm-edit.html.tmpl' => [
- 'group.count',
-],
-
-'admin/products/groupcontrol/edit.html.tmpl' => [
- 'group.id',
- 'constants.CONTROLMAPNA',
- 'constants.CONTROLMAPSHOWN',
- 'constants.CONTROLMAPDEFAULT',
- 'constants.CONTROLMAPMANDATORY',
-],
-
-'admin/products/list.html.tmpl' => [
- 'classification_url_part',
-],
-
-'admin/products/footer.html.tmpl' => [
- 'classification_url_part',
- 'classification_text',
-],
-
-'admin/flag-type/confirm-delete.html.tmpl' => [
- 'flag_type.flag_count',
- 'flag_type.id',
-],
-
-'admin/flag-type/edit.html.tmpl' => [
- 'selname',
-],
-
-'admin/flag-type/list.html.tmpl' => [
- 'type.id',
-],
-
-
-'admin/components/confirm-delete.html.tmpl' => [
- 'comp.bug_count'
-],
-
-'admin/groups/delete.html.tmpl' => [
- 'shared_queries'
-],
-
-'admin/users/confirm-delete.html.tmpl' => [
- 'attachments',
- 'reporter',
- 'assignee_or_qa',
- 'cc',
- 'component_cc',
- 'flags.requestee',
- 'flags.setter',
- 'longdescs',
- 'quips',
- 'series',
- 'watch.watched',
- 'watch.watcher',
- 'whine_events',
- 'whine_schedules',
- 'otheruser.id'
-],
-
-'admin/users/edit.html.tmpl' => [
- 'otheruser.id',
- 'group.id',
-],
-
-'admin/components/edit.html.tmpl' => [
- 'comp.bug_count'
-],
-
-'admin/workflow/edit.html.tmpl' => [
- 'status.id',
- 'new_status.id',
-],
-
-'admin/workflow/comment.html.tmpl' => [
- 'status.id',
- 'new_status.id',
-],
-
-'account/auth/login-small.html.tmpl' => [
- 'qs_suffix',
-],
-
-'account/prefs/email.html.tmpl' => [
- 'relationship.id',
- 'event.id',
- 'prefname',
-],
-
-'account/prefs/prefs.html.tmpl' => [
- 'current_tab.label',
- 'current_tab.name',
-],
-
-'account/prefs/saved-searches.html.tmpl' => [
- 'group.id',
-],
-
-'config.rdf.tmpl' => [
- 'escaped_urlbase',
-],
+ ],
+
+ 'bug/edit.html.tmpl' => [
+ 'bug.remaining_time',
+ 'bug.delta_ts',
+ 'bug.bug_id',
+ 'group.bit',
+ 'selname',
+ 'inputname',
+ '" colspan=\"$colspan\"" IF colspan',
+ '" size=\"$size\"" IF size',
+ '" maxlength=\"$maxlength\"" IF maxlength',
+ '" spellcheck=\"$spellcheck\"" IF spellcheck',
+ ],
+
+ 'bug/show-multiple.html.tmpl' => ['attachment.id', 'flag.status',],
+
+ 'bug/show.html.tmpl' => ['bug.bug_id',],
+
+ 'bug/show.xml.tmpl' => ['constants.BUGZILLA_VERSION', 'a.id', 'field',],
+
+ 'bug/summarize-time.html.tmpl' => [
+ 'global.grand_total FILTER format("%.2f")',
+ 'subtotal FILTER format("%.2f")',
+ 'work_time FILTER format("%.2f")',
+ 'global.total FILTER format("%.2f")',
+ 'global.remaining FILTER format("%.2f")',
+ 'global.estimated FILTER format("%.2f")',
+ 'bugs.$id.remaining_time FILTER format("%.2f")',
+ 'bugs.$id.estimated_time FILTER format("%.2f")',
+ ],
+
+
+ 'bug/time.html.tmpl' => [
+ "time_unit.replace('0\\Z', '')", '(act / (act + rem)) * 100
+ FILTER format("%d")',
+ ],
+
+ 'bug/process/results.html.tmpl' => ['title.$type.ucfirst',],
+
+ 'bug/create/create.html.tmpl' => ['cloned_bug_id',],
+
+ 'bug/create/create-guided.html.tmpl' => ['sel',],
+
+ 'bug/activity/table.html.tmpl' => ['change.attachid',],
+
+ 'attachment/create.html.tmpl' => ['bug.bug_id', 'attachment.id',],
+
+ 'attachment/created.html.tmpl' => ['attachment.id', 'attachment.bug_id',],
+
+ 'attachment/edit.html.tmpl' => [
+ 'attachment.id', 'attachment.bug_id', 'editable_or_hide', 'use_patchviewer',
+ ],
+
+ 'attachment/list.html.tmpl' =>
+ ['attachment.id', 'flag.status', 'bugid', 'obsolete_attachments',],
+
+ 'attachment/midair.html.tmpl' => ['attachment.id',],
+
+ 'attachment/show-multiple.html.tmpl' => ['a.id', 'flag.status'],
+
+ 'attachment/updated.html.tmpl' => ['attachment.id',],
+
+ 'attachment/diff-header.html.tmpl' =>
+ ['attachid', 'id', 'bugid', 'oldid', 'newid', 'patch.id',],
+
+ 'attachment/diff-file.html.tmpl' => [
+ 'file.minus_lines', 'file.plus_lines',
+ 'section.old_start', 'section_num',
+ 'current_line_old', 'current_line_new',
+ ],
+
+ 'admin/admin.html.tmpl' => ['class'],
+
+ 'admin/table.html.tmpl' => ['contentlink'],
+
+ 'admin/custom_fields/cf-js.js.tmpl' => [
+ 'constants.FIELD_TYPE_SINGLE_SELECT', 'constants.FIELD_TYPE_MULTI_SELECT',
+ 'constants.FIELD_TYPE_BUG_ID',
+ ],
+
+ 'admin/params/common.html.tmpl' => ['sortlist_separator',],
+
+ 'admin/products/groupcontrol/confirm-edit.html.tmpl' => ['group.count',],
+
+ 'admin/products/groupcontrol/edit.html.tmpl' => [
+ 'group.id', 'constants.CONTROLMAPNA',
+ 'constants.CONTROLMAPSHOWN', 'constants.CONTROLMAPDEFAULT',
+ 'constants.CONTROLMAPMANDATORY',
+ ],
+
+ 'admin/products/list.html.tmpl' => ['classification_url_part',],
+
+ 'admin/products/footer.html.tmpl' =>
+ ['classification_url_part', 'classification_text',],
+
+ 'admin/flag-type/confirm-delete.html.tmpl' =>
+ ['flag_type.flag_count', 'flag_type.id',],
+
+ 'admin/flag-type/edit.html.tmpl' => ['selname',],
+
+ 'admin/flag-type/list.html.tmpl' => ['type.id',],
+
+
+ 'admin/components/confirm-delete.html.tmpl' => ['comp.bug_count'],
+
+ 'admin/groups/delete.html.tmpl' => ['shared_queries'],
+
+ 'admin/users/confirm-delete.html.tmpl' => [
+ 'attachments', 'reporter', 'assignee_or_qa', 'cc',
+ 'component_cc', 'flags.requestee', 'flags.setter', 'longdescs',
+ 'quips', 'series', 'watch.watched', 'watch.watcher',
+ 'whine_events', 'whine_schedules', 'otheruser.id'
+ ],
+
+ 'admin/users/edit.html.tmpl' => ['otheruser.id', 'group.id',],
+
+ 'admin/components/edit.html.tmpl' => ['comp.bug_count'],
+
+ 'admin/workflow/edit.html.tmpl' => ['status.id', 'new_status.id',],
+
+ 'admin/workflow/comment.html.tmpl' => ['status.id', 'new_status.id',],
+
+ 'account/auth/login-small.html.tmpl' => ['qs_suffix',],
+
+ 'account/prefs/email.html.tmpl' => ['relationship.id', 'event.id', 'prefname',],
+
+ 'account/prefs/prefs.html.tmpl' => ['current_tab.label', 'current_tab.name',],
+
+ 'account/prefs/saved-searches.html.tmpl' => ['group.id',],
+
+ 'config.rdf.tmpl' => ['escaped_urlbase',],
);
diff --git a/template/en/default/list/list.html.tmpl b/template/en/default/list/list.html.tmpl
index 368cd9c08..d34a454e0 100644
--- a/template/en/default/list/list.html.tmpl
+++ b/template/en/default/list/list.html.tmpl
@@ -250,7 +250,7 @@
[% IF bugowners && user.id %]
<button type="button" id="email_assignees"
- onclick="document.location='mailto:[% bugowners FILTER html %]'">
+ onclick="document.location='mailto:[% bugowners FILTER html FILTER js %]'">
Send Mail to [% terms.Bug %] Assignees</button>
[% END %]
diff --git a/template/en/default/pages/release-notes.html.tmpl b/template/en/default/pages/release-notes.html.tmpl
index b89e3a61d..fd6c9e00d 100644
--- a/template/en/default/pages/release-notes.html.tmpl
+++ b/template/en/default/pages/release-notes.html.tmpl
@@ -43,6 +43,19 @@
<h2 id="point">Updates in this 5.0.x Release</h2>
+<h3>5.0.6</h3>
+<p>This release contains a schema change to the flagtypes table, allowing for many more flagtypes.</p>.
+<p>The flagtypes table should have been using a mediumint for several releases, but due to a bug in the schema migration code this never happened.</p>
+
+<h3>5.0.5</h3>
+
+<p>This release reformats the code according to the same conventions as the popular Mojolicious product and includes a <code>.perltidyrc</code> to do the same.
+You may use whatever coding style you want, but all files commited to the repo must be reformatted according to those rules.</p>
+
+<p>Additionally, we no longer follow the same release process as before. Releases will be more frequent.</p>
+
+<p>As it is now 2019, the bugs_fulltext table is now InnoDB instead of MyISAM. This may cause upgrade headaches.</p>
+
<h3>5.0.4</h3>
<p>This release fixes one security issue. See the
diff --git a/template/en/default/setup/strings.txt.pl b/template/en/default/setup/strings.txt.pl
index 78c4d861b..0efc378bb 100644
--- a/template/en/default/setup/strings.txt.pl
+++ b/template/en/default/setup/strings.txt.pl
@@ -16,15 +16,15 @@
# Please keep the strings in alphabetical order by their name.
%strings = (
- any => 'any',
- apachectl_failed => <<END,
+ any => 'any',
+ apachectl_failed => <<END,
WARNING: We could not check the configuration of Apache. This sometimes
happens when you are not running checksetup.pl as ##root##. To see the
problem we ran into, run: ##command##
END
- bad_executable => 'not a valid executable: ##bin##',
- blacklisted => '(blacklisted)',
- bz_schema_exists_before_220 => <<'END',
+ bad_executable => 'not a valid executable: ##bin##',
+ blacklisted => '(blacklisted)',
+ bz_schema_exists_before_220 => <<'END',
You are upgrading from a version before 2.20, but the bz_schema table
already exists. This means that you restored a mysqldump into the Bugzilla
database without first dropping the already-existing Bugzilla database,
@@ -36,34 +36,33 @@ not contain the bz_schema table. If for some reason you cannot do this, you
can connect to your MySQL database and drop the bz_schema table, as a last
resort.
END
- checking_for => 'Checking for',
- checking_dbd => 'Checking available perl DBD modules...',
- checking_optional => 'The following Perl modules are optional:',
- checking_modules => 'Checking perl modules...',
- chmod_failed => '##path##: Failed to change permissions: ##error##',
- chown_failed => '##path##: Failed to change ownership: ##error##',
- commands_dbd => <<EOT,
+ checking_for => 'Checking for',
+ checking_dbd => 'Checking available perl DBD modules...',
+ checking_optional => 'The following Perl modules are optional:',
+ checking_modules => 'Checking perl modules...',
+ chmod_failed => '##path##: Failed to change permissions: ##error##',
+ chown_failed => '##path##: Failed to change ownership: ##error##',
+ commands_dbd => <<EOT,
YOU MUST RUN ONE OF THE FOLLOWING COMMANDS (depending on which database
you use):
EOT
- commands_optional => 'COMMANDS TO INSTALL OPTIONAL MODULES:',
- commands_required => <<EOT,
+ commands_optional => 'COMMANDS TO INSTALL OPTIONAL MODULES:',
+ commands_required => <<EOT,
COMMANDS TO INSTALL REQUIRED MODULES (You *must* run all these commands
and then re-run checksetup.pl):
EOT
- continue_without_answers => <<'END',
+ continue_without_answers => <<'END',
Re-run checksetup.pl in interactive mode (without an 'answers' file)
to continue.
END
- cpan_bugzilla_home =>
- "WARNING: Using the Bugzilla directory as the CPAN home.",
- db_enum_setup => "Setting up choices for standard drop-down fields:",
- db_schema_init => "Initializing bz_schema...",
- db_table_new => "Adding new table ##table##...",
- db_table_setup => "Creating tables...",
- done => 'done.',
- enter_or_ctrl_c => "Press Enter to continue or Ctrl-C to exit...",
- error_localconfig_read => <<'END',
+ cpan_bugzilla_home => "WARNING: Using the Bugzilla directory as the CPAN home.",
+ db_enum_setup => "Setting up choices for standard drop-down fields:",
+ db_schema_init => "Initializing bz_schema...",
+ db_table_new => "Adding new table ##table##...",
+ db_table_setup => "Creating tables...",
+ done => 'done.',
+ enter_or_ctrl_c => "Press Enter to continue or Ctrl-C to exit...",
+ error_localconfig_read => <<'END',
An error has occurred while reading the ##localconfig## file. The text of
the error message is:
@@ -76,38 +75,38 @@ localconfig file:
$ mv -f localconfig localconfig.old
$ ./checksetup.pl
END
- extension_must_return_name => <<END,
+ extension_must_return_name => <<END,
##file## returned ##returned##, which is not a valid name for an extension.
Extensions must return their name, not <code>1</code> or a number. See
the documentation of Bugzilla::Extension for details.
END
- feature_auth_ldap => 'LDAP Authentication',
- feature_auth_radius => 'RADIUS Authentication',
- feature_documentation => 'Documentation',
- feature_graphical_reports => 'Graphical Reports',
- feature_html_desc => 'More HTML in Product/Group Descriptions',
- feature_inbound_email => 'Inbound Email',
- feature_jobqueue => 'Mail Queueing',
- feature_jsonrpc => 'JSON-RPC Interface',
- feature_new_charts => 'New Charts',
- feature_old_charts => 'Old Charts',
- feature_memcached => 'Memcached Support',
- feature_mod_perl => 'mod_perl',
- feature_moving => 'Move Bugs Between Installations',
- feature_patch_viewer => 'Patch Viewer',
- feature_rest => 'REST Interface',
- feature_smtp_auth => 'SMTP Authentication',
- feature_smtp_ssl => 'SSL Support for SMTP',
- feature_updates => 'Automatic Update Notifications',
- feature_xmlrpc => 'XML-RPC Interface',
- feature_detect_charset => 'Automatic charset detection for text attachments',
- feature_typesniffer => 'Sniff MIME type of attachments',
+ feature_auth_ldap => 'LDAP Authentication',
+ feature_auth_radius => 'RADIUS Authentication',
+ feature_documentation => 'Documentation',
+ feature_graphical_reports => 'Graphical Reports',
+ feature_html_desc => 'More HTML in Product/Group Descriptions',
+ feature_inbound_email => 'Inbound Email',
+ feature_jobqueue => 'Mail Queueing',
+ feature_jsonrpc => 'JSON-RPC Interface',
+ feature_new_charts => 'New Charts',
+ feature_old_charts => 'Old Charts',
+ feature_memcached => 'Memcached Support',
+ feature_mod_perl => 'mod_perl',
+ feature_moving => 'Move Bugs Between Installations',
+ feature_patch_viewer => 'Patch Viewer',
+ feature_rest => 'REST Interface',
+ feature_smtp_auth => 'SMTP Authentication',
+ feature_smtp_ssl => 'SSL Support for SMTP',
+ feature_updates => 'Automatic Update Notifications',
+ feature_xmlrpc => 'XML-RPC Interface',
+ feature_detect_charset => 'Automatic charset detection for text attachments',
+ feature_typesniffer => 'Sniff MIME type of attachments',
- file_remove => 'Removing ##name##...',
- file_rename => 'Renaming ##from## to ##to##...',
- header => "* This is Bugzilla ##bz_ver## on perl ##perl_ver##\n"
- . "* Running on ##os_name## ##os_ver##",
- install_all => <<EOT,
+ file_remove => 'Removing ##name##...',
+ file_rename => 'Renaming ##from## to ##to##...',
+ header => "* This is Bugzilla ##bz_ver## on perl ##perl_ver##\n"
+ . "* Running on ##os_name## ##os_ver##",
+ install_all => <<EOT,
To attempt an automatic install of every required and optional module
with one command, do:
@@ -115,23 +114,23 @@ with one command, do:
##perl## install-module.pl --all
EOT
- install_data_too_long => <<EOT,
+ install_data_too_long => <<EOT,
WARNING: Some of the data in the ##table##.##column## column is longer than
its new length limit of ##max_length## characters. The data that needs to be
fixed is printed below with the value of the ##id_column## column first and
then the value of the ##column## column that needs to be fixed:
EOT
- install_module => 'Installing ##module## version ##version##...',
- installation_failed => '*** Installation aborted. Read the messages above. ***',
- install_no_compiler => <<END,
+ install_module => 'Installing ##module## version ##version##...',
+ installation_failed => '*** Installation aborted. Read the messages above. ***',
+ install_no_compiler => <<END,
ERROR: Using install-module.pl requires that you install a compiler, such as
gcc.
END
- install_no_make => <<END,
+ install_no_make => <<END,
ERROR: Using install-module.pl requires that you install "make".
END
- lc_new_vars => <<'END',
+ lc_new_vars => <<'END',
This version of Bugzilla contains some variables that you may want to
change and adapt to your local settings. The following variables are
new to ##localconfig## since you last ran checksetup.pl:
@@ -141,11 +140,11 @@ new to ##localconfig## since you last ran checksetup.pl:
Please edit the file ##localconfig## and then re-run checksetup.pl
to complete your installation.
END
- lc_old_vars => <<'END',
+ lc_old_vars => <<'END',
The following variables are no longer used in ##localconfig##, and
have been moved to ##old_file##: ##vars##
END
- localconfig_create_htaccess => <<'END',
+ localconfig_create_htaccess => <<'END',
If you are using Apache as your web server, Bugzilla can create .htaccess
files for you, which will keep this file (localconfig) and other
confidential files from being read over the web.
@@ -155,66 +154,66 @@ they don't exist.
If this is set to 0, checksetup.pl will not create .htaccess files.
END
- localconfig_db_check => <<'END',
+ localconfig_db_check => <<'END',
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, and so you can try setting this to 0 to make checksetup.pl
run.
END
- localconfig_db_driver => <<'END',
+ localconfig_db_driver => <<'END',
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 of the module (before ".pm")
corresponds to a valid value for this variable.
END
- localconfig_db_host => <<'END',
+ localconfig_db_host => <<'END',
The DNS name or IP address of the host that the database server runs on.
END
- localconfig_db_name => <<'END',
+ localconfig_db_name => <<'END',
The name of the database. For Oracle, this is the database's SID. For
SQLite, this is a name (or path) for the DB file.
END
- localconfig_db_pass => <<'END',
+ localconfig_db_pass => <<'END',
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 (\)
(It is far simpler to just not use those characters.)
END
- localconfig_db_port => <<'END',
+ localconfig_db_port => <<'END',
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."
END
- localconfig_db_sock => <<'END',
+ localconfig_db_sock => <<'END',
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.
END
- localconfig_db_user => "Who we connect to the database as.",
- localconfig_db_mysql_ssl_ca_file => <<'END',
+ localconfig_db_user => "Who we connect to the database as.",
+ localconfig_db_mysql_ssl_ca_file => <<'END',
Path to a PEM file with a list of trusted SSL CA certificates.
The file must be readable by web server user.
END
- localconfig_db_mysql_ssl_ca_path => <<'END',
+ localconfig_db_mysql_ssl_ca_path => <<'END',
Path to a directory containing trusted SSL CA certificates in PEM format.
Directory and files inside must be readable by the web server user.
END
- localconfig_db_mysql_ssl_client_cert => <<'END',
+ localconfig_db_mysql_ssl_client_cert => <<'END',
Full path to the client SSL certificate in PEM format we will present to the DB server.
The file must be readable by web server user.
END
- localconfig_db_mysql_ssl_client_key => <<'END',
+ localconfig_db_mysql_ssl_client_key => <<'END',
Full path to the private key corresponding to the client SSL certificate.
The file must not be password-protected and must be readable by web server user.
END
- localconfig_diffpath => <<'END',
+ localconfig_diffpath => <<'END',
For the "Difference Between Two Patches" feature to work, we need to know
what directory the "diff" bin is in. (You only need to set this if you
are using that feature of the Patch Viewer.)
END
- localconfig_index_html => <<'END',
+ localconfig_index_html => <<'END',
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
@@ -224,19 +223,19 @@ 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.
END
- localconfig_interdiffbin => <<'END',
+ localconfig_interdiffbin => <<'END',
If you want to use the "Difference Between Two Patches" feature of the
Patch Viewer, please specify the full path to the "interdiff" executable
here.
END
- localconfig_site_wide_secret => <<'END',
+ localconfig_site_wide_secret => <<'END',
This secret key is used by your installation for the creation and
validation of encrypted tokens. These tokens are used to implement
security features in Bugzilla, to protect against certain types of attacks.
A random string is generated by default. It's very important that this key
is kept secret. It also must be very long.
END
- localconfig_use_suexec => <<'END',
+ localconfig_use_suexec => <<'END',
Set this to 1 if Bugzilla runs in an Apache SuexecUserGroup environment.
If your web server runs control panel software (cPanel, Plesk or similar),
@@ -251,7 +250,7 @@ a normal webserver environment.
If set to 1, checksetup.pl will set file permissions so that Bugzilla
works in a SuexecUserGroup environment.
END
- localconfig_webservergroup => <<'END',
+ localconfig_webservergroup => <<'END',
The name of the group that your web server runs as. On Red Hat
distributions, this is usually "apache". On Debian/Ubuntu, it is
usually "www-data".
@@ -271,18 +270,18 @@ 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## or as a user who is a member of the specified group.
END
- max_allowed_packet => <<EOT,
+ max_allowed_packet => <<EOT,
WARNING: You need to set the max_allowed_packet parameter in your MySQL
configuration to at least ##needed##. Currently it is set to ##current##.
You can set this parameter in the [mysqld] section of your MySQL
configuration file.
EOT
- min_version_required => "Minimum version required: ",
+ min_version_required => "Minimum version required: ",
# Note: When translating these "modules" messages, don't change the formatting
-# if possible, because there is hardcoded formatting in
+# if possible, because there is hardcoded formatting in
# Bugzilla::Install::Requirements to match the box formatting.
- modules_message_apache => <<END,
+ modules_message_apache => <<END,
***********************************************************************
* APACHE MODULES *
***********************************************************************
@@ -297,7 +296,7 @@ EOT
* The modules you need to enable are: *
* *
END
- modules_message_db => <<EOT,
+ modules_message_db => <<EOT,
***********************************************************************
* DATABASE ACCESS *
***********************************************************************
@@ -306,7 +305,7 @@ END
* running. See below for the correct command to run to install the *
* appropriate module for your database. *
EOT
- modules_message_optional => <<EOT,
+ modules_message_optional => <<EOT,
***********************************************************************
* OPTIONAL MODULES *
***********************************************************************
@@ -318,7 +317,7 @@ EOT
* with the name of the feature they enable. Below that table are the *
* commands to install each module. *
EOT
- modules_message_required => <<EOT,
+ modules_message_required => <<EOT,
***********************************************************************
* REQUIRED MODULES *
***********************************************************************
@@ -327,23 +326,23 @@ EOT
* See below for commands to install these modules. *
EOT
- module_found => "found v##ver##",
- module_not_found => "not found",
- module_ok => 'ok',
- module_unknown_version => "found unknown version",
- no_such_module => "There is no Perl module on CPAN named ##module##.",
- mysql_innodb_disabled => <<'END',
+ module_found => "found v##ver##",
+ module_not_found => "not found",
+ module_ok => 'ok',
+ module_unknown_version => "found unknown version",
+ no_such_module => "There is no Perl module on CPAN named ##module##.",
+ mysql_innodb_disabled => <<'END',
InnoDB is disabled in your MySQL installation.
Bugzilla requires InnoDB to be enabled.
Please enable it and then re-run checksetup.pl.
END
- mysql_index_renaming => <<'END',
+ mysql_index_renaming => <<'END',
We are about to rename old indexes. The estimated time to complete
renaming is ##minutes## minutes. You cannot interrupt this action once
it has begun. If you would like to cancel, press Ctrl-C now...
(Waiting 45 seconds...)
END
- mysql_utf8_conversion => <<'END',
+ mysql_utf8_conversion => <<'END',
WARNING: We are about to convert your table storage format to UTF-8. This
allows Bugzilla to correctly store and sort international characters.
However, if you have any non-UTF-8 data in your database,
@@ -358,7 +357,7 @@ WARNING: We are about to convert your table storage format to UTF-8. This
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.
END
- no_checksetup_from_cgi => <<END,
+ no_checksetup_from_cgi => <<END,
<!DOCTYPE html>
<html>
<head>
@@ -382,49 +381,49 @@ END
</body>
</html>
END
- patchutils_missing => <<'END',
+ patchutils_missing => <<'END',
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/software/patchutils/
END
- template_precompile => "Precompiling templates...",
- template_removal_failed => <<END,
+ template_precompile => "Precompiling templates...",
+ template_removal_failed => <<END,
WARNING: The directory '##template_cache##' could not be removed.
It has been moved into '##deleteme##', which should be
deleted manually to conserve disk space.
END
- template_removing_dir => "Removing existing compiled templates...",
- update_cf_invalid_name =>
- "Removing custom field '##field##', because it has an invalid name...",
- update_flags_bad_name => <<'END',
+ template_removing_dir => "Removing existing compiled templates...",
+ update_cf_invalid_name =>
+ "Removing custom field '##field##', because it has an invalid name...",
+ update_flags_bad_name => <<'END',
"##flag##" is not a valid name for a flag. Rename it to not have any spaces
or commas.
END
- update_nomail_bad => <<'END',
+ update_nomail_bad => <<'END',
WARNING: The following users were listed in ##data##/nomail, but do
not have an account here. The unmatched entries have been moved to
##data##/nomail.bad:
END
- update_summary_truncate_comment =>
- "The original value of the Summary field was longer than 255"
- . " characters, and so it was truncated during an upgrade."
- . " The original summary was:\n\n##summary##",
- update_summary_truncated => <<'END',
+ update_summary_truncate_comment =>
+ "The original value of the Summary field was longer than 255"
+ . " characters, and so it was truncated during an upgrade."
+ . " The original summary was:\n\n##summary##",
+ update_summary_truncated => <<'END',
WARNING: Some of your bugs had summaries longer than 255 characters.
They have had their original summary copied into a comment, and then
the summary was truncated to 255 characters. The affected bug numbers were:
END
- update_quips => <<'END',
+ update_quips => <<'END',
Quips are now stored in the database, rather than in an external file.
The quips previously stored in ##data##/comments have been copied into
the database, and that file has been renamed to ##data##/comments.bak
You may delete the renamed file once you have confirmed that all your
quips were moved successfully.
END
- update_queries_to_tags => "Populating the new 'tag' table:",
- webdot_bad_htaccess => <<END,
+ update_queries_to_tags => "Populating the new 'tag' table:",
+ webdot_bad_htaccess => <<END,
WARNING: Dependency graph images are not accessible.
Delete ##dir##/.htaccess and re-run checksetup.pl.
END
diff --git a/testserver.pl b/testserver.pl
index d827c80ea..3c01a550e 100755
--- a/testserver.pl
+++ b/testserver.pl
@@ -27,61 +27,59 @@ my $datadir = bz_locations()->{'datadir'};
eval "require LWP; require LWP::UserAgent;";
my $lwp = $@ ? 0 : 1;
-if ((@ARGV != 1) || ($ARGV[0] !~ /^https?:/i))
-{
- say "Usage: $0 <URL to this Bugzilla installation>";
- say "e.g.: $0 http://www.mycompany.com/bugzilla";
- exit(1);
+if ((@ARGV != 1) || ($ARGV[0] !~ /^https?:/i)) {
+ say "Usage: $0 <URL to this Bugzilla installation>";
+ say "e.g.: $0 http://www.mycompany.com/bugzilla";
+ exit(1);
}
# Try to determine the GID used by the web server.
-my @pscmds = ('ps -eo comm,gid', 'ps -acxo command,gid', 'ps -acxo command,rgid');
+my @pscmds
+ = ('ps -eo comm,gid', 'ps -acxo command,gid', 'ps -acxo command,rgid');
my $sgid = 0;
if (!ON_WINDOWS) {
- foreach my $pscmd (@pscmds) {
- open PH, '-|', "$pscmd 2>/dev/null";
- while (my $line = <PH>) {
- if ($line =~ /^(?:\S*\/)?(?:httpd|apache?)2?\s+(\d+)$/) {
- $sgid = $1 if $1 > $sgid;
- }
- }
- close(PH);
+ foreach my $pscmd (@pscmds) {
+ open PH, '-|', "$pscmd 2>/dev/null";
+ while (my $line = <PH>) {
+ if ($line =~ /^(?:\S*\/)?(?:httpd|apache?)2?\s+(\d+)$/) {
+ $sgid = $1 if $1 > $sgid;
+ }
}
+ close(PH);
+ }
}
# Determine the numeric GID of $webservergroup
-my $webgroupnum = 0;
+my $webgroupnum = 0;
my $webservergroup = Bugzilla->localconfig->{webservergroup};
if ($webservergroup =~ /^(\d+)$/) {
- $webgroupnum = $1;
+ $webgroupnum = $1;
}
else {
- eval { $webgroupnum = (getgrnam $webservergroup) || 0; };
+ eval { $webgroupnum = (getgrnam $webservergroup) || 0; };
}
# Check $webservergroup against the server's GID
if ($sgid > 0) {
- if ($webservergroup eq "") {
- say
-"WARNING \$webservergroup is set to an empty string.
+ if ($webservergroup eq "") {
+ say "WARNING \$webservergroup is set to an empty string.
That is a very insecure practice. Please refer to the
Bugzilla documentation.";
- }
- elsif ($webgroupnum == $sgid || Bugzilla->localconfig->{use_suexec}) {
- say "TEST-OK Webserver is running under group id in \$webservergroup.";
- }
- else {
- say
-"TEST-WARNING Webserver is running under group id not matching \$webservergroup.
+ }
+ elsif ($webgroupnum == $sgid || Bugzilla->localconfig->{use_suexec}) {
+ say "TEST-OK Webserver is running under group id in \$webservergroup.";
+ }
+ else {
+ say
+ "TEST-WARNING Webserver is running under group id not matching \$webservergroup.
This if the tests below fail, this is probably the problem.
Please refer to the web server configuration section of the Bugzilla guide.
If you are using virtual hosts or suexec, this warning may not apply.";
- }
+ }
}
elsif (!ON_WINDOWS) {
- say
-"TEST-WARNING Failed to find the GID for the 'httpd' process, unable
+ say "TEST-WARNING Failed to find the GID for the 'httpd' process, unable
to validate webservergroup.";
}
@@ -90,196 +88,202 @@ to validate webservergroup.";
$ARGV[0] =~ s/\/$//;
my $url = $ARGV[0] . "/images/padlock.png";
if (fetch($url)) {
- say "TEST-OK Got padlock picture.";
-} else {
- say
-"TEST-FAILED Fetch of images/padlock.png failed
+ say "TEST-OK Got padlock picture.";
+}
+else {
+ say "TEST-FAILED Fetch of images/padlock.png failed
Your web server could not fetch $url.
Check your web server configuration and try again.";
- exit(1);
+ exit(1);
}
# Try to execute a cgi script
my $response = clean_text(fetch($ARGV[0] . "/testagent.cgi"));
if ($response =~ /^OK (.*)$/) {
- say "TEST-OK Webserver is executing CGIs via $1.";
-} elsif ($response =~ /^#!/) {
- say
-"TEST-FAILED Webserver is fetching rather than executing CGI files.
+ say "TEST-OK Webserver is executing CGIs via $1.";
+}
+elsif ($response =~ /^#!/) {
+ say "TEST-FAILED Webserver is fetching rather than executing CGI files.
Check the AddHandler statement in your httpd.conf file.";
- exit(1);
-} else {
- say "TEST-FAILED Webserver is not executing CGI files.";
+ exit(1);
+}
+else {
+ say "TEST-FAILED Webserver is not executing CGI files.";
}
# Make sure that the web server is honoring .htaccess files
my $localconfig = bz_locations()->{'localconfig'};
$localconfig =~ s~^\./~~;
-$url = $ARGV[0] . "/$localconfig";
+$url = $ARGV[0] . "/$localconfig";
$response = fetch($url);
if ($response) {
- say
-"TEST-FAILED Webserver is permitting fetch of $url.
+ say "TEST-FAILED Webserver is permitting fetch of $url.
This is a serious security problem.
Check your web server configuration.";
- exit(1);
-} else {
- say "TEST-OK Webserver is preventing fetch of $url.";
+ exit(1);
+}
+else {
+ say "TEST-OK Webserver is preventing fetch of $url.";
}
# Test chart generation
eval 'use GD';
if ($@ eq '') {
- undef $/;
-
- # Ensure major versions of GD and libgd match
- # Windows's GD module include libgd.dll, guaranteed to match
- if (!ON_WINDOWS) {
- my $gdlib = `gdlib-config --version 2>&1` || "";
- $gdlib =~ s/\n$//;
- if (!$gdlib) {
- say "TEST-WARNING Failed to run gdlib-config; can't compare " .
- "GD versions.";
- }
- else {
- my $gd = $GD::VERSION;
-
- my $verstring = "GD version $gd, libgd version $gdlib";
-
- $gdlib =~ s/^([^\.]+)\..*/$1/;
- $gd =~ s/^([^\.]+)\..*/$1/;
-
- if ($gdlib == $gd) {
- say "TEST-OK $verstring; Major versions match.";
- } else {
- say "TEST-FAILED $verstring; Major versions do not match.";
- }
- }
+ undef $/;
+
+ # Ensure major versions of GD and libgd match
+ # Windows's GD module include libgd.dll, guaranteed to match
+ if (!ON_WINDOWS) {
+ my $gdlib = `gdlib-config --version 2>&1` || "";
+ $gdlib =~ s/\n$//;
+ if (!$gdlib) {
+ say "TEST-WARNING Failed to run gdlib-config; can't compare " . "GD versions.";
+ }
+ else {
+ my $gd = $GD::VERSION;
+
+ my $verstring = "GD version $gd, libgd version $gdlib";
+
+ $gdlib =~ s/^([^\.]+)\..*/$1/;
+ $gd =~ s/^([^\.]+)\..*/$1/;
+
+ if ($gdlib == $gd) {
+ say "TEST-OK $verstring; Major versions match.";
+ }
+ else {
+ say "TEST-FAILED $verstring; Major versions do not match.";
+ }
}
+ }
+
+ # Test GD
+ eval {
+ my $image = new GD::Image(100, 100);
+ my $black = $image->colorAllocate(0, 0, 0);
+ my $white = $image->colorAllocate(255, 255, 255);
+ my $red = $image->colorAllocate(255, 0, 0);
+ my $blue = $image->colorAllocate(0, 0, 255);
+ $image->transparent($white);
+ $image->rectangle(0, 0, 99, 99, $black);
+ $image->arc(50, 50, 95, 75, 0, 360, $blue);
+ $image->fill(50, 50, $red);
- # Test GD
+ if ($image->can('png')) {
+ create_file("$datadir/testgd-local.png", $image->png);
+ check_image("$datadir/testgd-local.png", 'GD');
+ }
+ else {
+ say "TEST-FAILED GD doesn't support PNG generation.";
+ }
+ };
+ if ($@ ne '') {
+ say "TEST-FAILED GD returned: $@";
+ }
+
+ # Test Chart
+ eval 'use Chart::Lines';
+ if ($@) {
+ say "TEST-FAILED Chart::Lines is not installed.";
+ }
+ else {
eval {
- my $image = new GD::Image(100, 100);
- my $black = $image->colorAllocate(0, 0, 0);
- my $white = $image->colorAllocate(255, 255, 255);
- my $red = $image->colorAllocate(255, 0, 0);
- my $blue = $image->colorAllocate(0, 0, 255);
- $image->transparent($white);
- $image->rectangle(0, 0, 99, 99, $black);
- $image->arc(50, 50, 95, 75, 0, 360, $blue);
- $image->fill(50, 50, $red);
-
- if ($image->can('png')) {
- create_file("$datadir/testgd-local.png", $image->png);
- check_image("$datadir/testgd-local.png", 'GD');
- } else {
- say "TEST-FAILED GD doesn't support PNG generation.";
- }
+ my $chart = Chart::Lines->new(400, 400);
+
+ $chart->add_pt('foo', 30, 25);
+ $chart->add_pt('bar', 16, 32);
+
+ $chart->png("$datadir/testchart-local.png");
+ check_image("$datadir/testchart-local.png", "Chart");
};
if ($@ ne '') {
- say "TEST-FAILED GD returned: $@";
+ say "TEST-FAILED Chart returned: $@";
}
+ }
- # Test Chart
- eval 'use Chart::Lines';
- if ($@) {
- say "TEST-FAILED Chart::Lines is not installed.";
- } else {
- eval {
- my $chart = Chart::Lines->new(400, 400);
-
- $chart->add_pt('foo', 30, 25);
- $chart->add_pt('bar', 16, 32);
-
- $chart->png("$datadir/testchart-local.png");
- check_image("$datadir/testchart-local.png", "Chart");
- };
- if ($@ ne '') {
- say "TEST-FAILED Chart returned: $@";
- }
- }
+ eval 'use Template::Plugin::GD::Image';
+ if ($@) {
+ say "TEST-FAILED Template::Plugin::GD is not installed.";
+ }
+ else {
+ say "TEST-OK Template::Plugin::GD is installed.";
+ }
+}
- eval 'use Template::Plugin::GD::Image';
- if ($@) {
- say "TEST-FAILED Template::Plugin::GD is not installed.";
+sub fetch {
+ my $url = shift;
+ my $rtn;
+ if ($lwp) {
+ my $req = HTTP::Request->new(GET => $url);
+ my $ua = LWP::UserAgent->new;
+ my $res = $ua->request($req);
+ $rtn = ($res->is_success ? $res->content : undef);
+ }
+ elsif ($url =~ /^https:/i) {
+ die("You need LWP installed to use https with testserver.pl");
+ }
+ else {
+ my ($host, $port, $file) = ('', 80, '');
+ if ($url =~ m#^http://([^:]+):(\d+)(/.*)#i) {
+ ($host, $port, $file) = ($1, $2, $3);
+ }
+ elsif ($url =~ m#^http://([^/]+)(/.*)#i) {
+ ($host, $file) = ($1, $2);
}
else {
- say "TEST-OK Template::Plugin::GD is installed.";
+ die("Cannot parse url");
}
-}
-sub fetch {
- my $url = shift;
- my $rtn;
- if ($lwp) {
- my $req = HTTP::Request->new(GET => $url);
- my $ua = LWP::UserAgent->new;
- my $res = $ua->request($req);
- $rtn = ($res->is_success ? $res->content : undef);
- } elsif ($url =~ /^https:/i) {
- die("You need LWP installed to use https with testserver.pl");
- } else {
- my($host, $port, $file) = ('', 80, '');
- if ($url =~ m#^http://([^:]+):(\d+)(/.*)#i) {
- ($host, $port, $file) = ($1, $2, $3);
- } elsif ($url =~ m#^http://([^/]+)(/.*)#i) {
- ($host, $file) = ($1, $2);
- } else {
- die("Cannot parse url");
- }
-
- my $proto = getprotobyname('tcp');
- socket(SOCK, PF_INET, SOCK_STREAM, $proto);
- my $sin = sockaddr_in($port, inet_aton($host));
- if (connect(SOCK, $sin)) {
- binmode SOCK;
- select((select(SOCK), $| = 1)[0]);
-
- # get content
- print SOCK "GET $file HTTP/1.0\015\012host: $host:$port\015\012\015\012";
- my $header = '';
- while (defined(my $line = <SOCK>)) {
- last if $line eq "\015\012";
- $header .= $line;
- }
- my $content = '';
- while (defined(my $line = <SOCK>)) {
- $content .= $line;
- }
-
- my ($status) = $header =~ m#^HTTP/\d+\.\d+ (\d+)#;
- $rtn = (($status =~ /^2\d\d/) ? $content : undef);
- }
+ my $proto = getprotobyname('tcp');
+ socket(SOCK, PF_INET, SOCK_STREAM, $proto);
+ my $sin = sockaddr_in($port, inet_aton($host));
+ if (connect(SOCK, $sin)) {
+ binmode SOCK;
+ select((select(SOCK), $| = 1)[0]);
+
+ # get content
+ print SOCK "GET $file HTTP/1.0\015\012host: $host:$port\015\012\015\012";
+ my $header = '';
+ while (defined(my $line = <SOCK>)) {
+ last if $line eq "\015\012";
+ $header .= $line;
+ }
+ my $content = '';
+ while (defined(my $line = <SOCK>)) {
+ $content .= $line;
+ }
+
+ my ($status) = $header =~ m#^HTTP/\d+\.\d+ (\d+)#;
+ $rtn = (($status =~ /^2\d\d/) ? $content : undef);
}
- return($rtn);
+ }
+ return ($rtn);
}
sub check_image {
- my ($local_file, $library) = @_;
- my $filedata = read_file($local_file);
- if ($filedata =~ /^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A/) {
- say "TEST-OK $library library generated a good PNG image.";
- unlink $local_file;
- } else {
- say "TEST-WARNING $library library did not generate a good PNG.";
- }
+ my ($local_file, $library) = @_;
+ my $filedata = read_file($local_file);
+ if ($filedata =~ /^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A/) {
+ say "TEST-OK $library library generated a good PNG image.";
+ unlink $local_file;
+ }
+ else {
+ say "TEST-WARNING $library library did not generate a good PNG.";
+ }
}
sub create_file {
- my ($filename, $content) = @_;
- open(FH, ">", $filename)
- or die "Failed to create $filename: $!\n";
- binmode FH;
- print FH $content;
- close FH;
+ my ($filename, $content) = @_;
+ open(FH, ">", $filename) or die "Failed to create $filename: $!\n";
+ binmode FH;
+ print FH $content;
+ close FH;
}
sub read_file {
- my ($filename) = @_;
- open(FH, '<', $filename)
- or die "Failed to open $filename: $!\n";
- binmode FH;
- my $content = <FH>;
- close FH;
- return $content;
+ my ($filename) = @_;
+ open(FH, '<', $filename) or die "Failed to open $filename: $!\n";
+ binmode FH;
+ my $content = <FH>;
+ close FH;
+ return $content;
}
diff --git a/token.cgi b/token.cgi
index 830ecfccb..cb52479b2 100755
--- a/token.cgi
+++ b/token.cgi
@@ -22,12 +22,12 @@ use Bugzilla::User;
use Date::Format;
use Date::Parse;
-local our $cgi = Bugzilla->cgi;
+local our $cgi = Bugzilla->cgi;
local our $template = Bugzilla->template;
-local our $vars = {};
+local our $vars = {};
my $action = $cgi->param('a');
-my $token = $cgi->param('t');
+my $token = $cgi->param('t');
Bugzilla->login(LOGIN_OPTIONAL);
@@ -41,66 +41,66 @@ my ($user_id, $date, $data, $tokentype) = Bugzilla::Token::GetTokenData($token);
# Requesting a new password is the single action which doesn't require a token.
# XXX Ideally, these checks should be done inside the subroutines themselves.
unless ($action eq 'reqpw') {
- $tokentype || ThrowUserError("token_does_not_exist");
-
- # Make sure the token is the correct type for the action being taken.
- # The { user_error => 'wrong_token_for_*' } trick is to make 012throwables.t happy.
- my $error = {};
- if (grep($action eq $_ , qw(cfmpw cxlpw chgpw)) && $tokentype ne 'password') {
- $error = { user_error => 'wrong_token_for_changing_passwd' };
- }
- elsif ($action eq 'cxlem'
- && ($tokentype ne 'emailold' && $tokentype ne 'emailnew'))
- {
- $error = { user_error => 'wrong_token_for_cancelling_email_change' };
- }
- elsif (grep($action eq $_ , qw(cfmem chgem)) && $tokentype ne 'emailnew') {
- $error = { user_error => 'wrong_token_for_confirming_email_change' };
- }
- elsif ($action =~ /^(request|confirm|cancel)_new_account$/
- && $tokentype ne 'account')
- {
- $error = { user_error => 'wrong_token_for_creating_account' };
- }
-
- if (my $user_error = $error->{user_error}) {
- Bugzilla::Token::Cancel($token, $user_error);
- ThrowUserError($user_error);
- }
+ $tokentype || ThrowUserError("token_does_not_exist");
+
+# Make sure the token is the correct type for the action being taken.
+# The { user_error => 'wrong_token_for_*' } trick is to make 012throwables.t happy.
+ my $error = {};
+ if (grep($action eq $_, qw(cfmpw cxlpw chgpw)) && $tokentype ne 'password') {
+ $error = {user_error => 'wrong_token_for_changing_passwd'};
+ }
+ elsif ($action eq 'cxlem'
+ && ($tokentype ne 'emailold' && $tokentype ne 'emailnew'))
+ {
+ $error = {user_error => 'wrong_token_for_cancelling_email_change'};
+ }
+ elsif (grep($action eq $_, qw(cfmem chgem)) && $tokentype ne 'emailnew') {
+ $error = {user_error => 'wrong_token_for_confirming_email_change'};
+ }
+ elsif ($action =~ /^(request|confirm|cancel)_new_account$/
+ && $tokentype ne 'account')
+ {
+ $error = {user_error => 'wrong_token_for_creating_account'};
+ }
+
+ if (my $user_error = $error->{user_error}) {
+ Bugzilla::Token::Cancel($token, $user_error);
+ ThrowUserError($user_error);
+ }
}
if ($action eq 'reqpw') {
- requestChangePassword();
+ requestChangePassword();
}
elsif ($action eq 'cfmpw') {
- confirmChangePassword($token);
+ confirmChangePassword($token);
}
elsif ($action eq 'cxlpw') {
- cancelChangePassword($token);
+ cancelChangePassword($token);
}
elsif ($action eq 'chgpw') {
- changePassword($user_id, $token);
+ changePassword($user_id, $token);
}
elsif ($action eq 'cfmem') {
- confirmChangeEmail($token);
+ confirmChangeEmail($token);
}
elsif ($action eq 'cxlem') {
- cancelChangeEmail($user_id, $data, $tokentype, $token);
+ cancelChangeEmail($user_id, $data, $tokentype, $token);
}
elsif ($action eq 'chgem') {
- changeEmail($user_id, $data, $token);
+ changeEmail($user_id, $data, $token);
}
elsif ($action eq 'request_new_account') {
- request_create_account($date, $data, $token);
+ request_create_account($date, $data, $token);
}
elsif ($action eq 'confirm_new_account') {
- confirm_create_account($data, $token);
+ confirm_create_account($data, $token);
}
elsif ($action eq 'cancel_new_account') {
- cancel_create_account($data, $token);
+ cancel_create_account($data, $token);
}
else {
- ThrowUserError('unknown_action', {action => $action});
+ ThrowUserError('unknown_action', {action => $action});
}
exit;
@@ -113,235 +113,244 @@ exit;
# their login name and it exists in the database, and that the DB module is in
# the list of allowed verification methods.
sub requestChangePassword {
- # check verification methods
- Bugzilla->user->authorizer->can_change_password
- || ThrowUserError("password_change_requests_not_allowed");
- # Check the hash token to make sure this user actually submitted
- # the forgotten password form.
- my $token = $cgi->param('token');
- check_hash_token($token, ['reqpw']);
+ # check verification methods
+ Bugzilla->user->authorizer->can_change_password
+ || ThrowUserError("password_change_requests_not_allowed");
- my $login_name = $cgi->param('loginname')
- or ThrowUserError("login_needed_for_password_change");
+ # Check the hash token to make sure this user actually submitted
+ # the forgotten password form.
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['reqpw']);
- check_email_syntax($login_name);
- my $user = new Bugzilla::User({ name => $login_name });
+ my $login_name = $cgi->param('loginname')
+ or ThrowUserError("login_needed_for_password_change");
- # Make sure the user account is active.
- if ($user && !$user->is_enabled) {
- ThrowUserError('account_disabled',
- {disabled_reason => get_text('account_disabled', {account => $login_name})});
- }
+ check_email_syntax($login_name);
+ my $user = new Bugzilla::User({name => $login_name});
- Bugzilla::Token::IssuePasswordToken($user) if $user;
+ # Make sure the user account is active.
+ if ($user && !$user->is_enabled) {
+ ThrowUserError('account_disabled',
+ {disabled_reason => get_text('account_disabled', {account => $login_name})});
+ }
- $vars->{'message'} = "password_change_request";
- $vars->{'login_name'} = $login_name;
+ Bugzilla::Token::IssuePasswordToken($user) if $user;
- print $cgi->header();
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $vars->{'message'} = "password_change_request";
+ $vars->{'login_name'} = $login_name;
+
+ print $cgi->header();
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
sub confirmChangePassword {
- my $token = shift;
- $vars->{'token'} = $token;
+ my $token = shift;
+ $vars->{'token'} = $token;
- print $cgi->header();
- $template->process("account/password/set-forgotten-password.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ print $cgi->header();
+ $template->process("account/password/set-forgotten-password.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
sub cancelChangePassword {
- my $token = shift;
- $vars->{'message'} = "password_change_canceled";
- Bugzilla::Token::Cancel($token, $vars->{'message'});
+ my $token = shift;
+ $vars->{'message'} = "password_change_canceled";
+ Bugzilla::Token::Cancel($token, $vars->{'message'});
- print $cgi->header();
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ print $cgi->header();
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
# If the user is changing their password, make sure they submitted a new
# password and that the new password is valid.
sub changePassword {
- my ($user_id, $token) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($user_id, $token) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $password = $cgi->param('password');
+ (defined $password && defined $cgi->param('matchpassword'))
+ || ThrowUserError("require_new_password");
- my $password = $cgi->param('password');
- (defined $password && defined $cgi->param('matchpassword'))
- || ThrowUserError("require_new_password");
+ validate_password($password, $cgi->param('matchpassword'));
- validate_password($password, $cgi->param('matchpassword'));
- # Make sure that these never show up in the UI under any circumstances.
- $cgi->delete('password', 'matchpassword');
+ # Make sure that these never show up in the UI under any circumstances.
+ $cgi->delete('password', 'matchpassword');
- my $user = Bugzilla::User->check({ id => $user_id });
- $user->set_password($password);
- $user->update();
- delete_token($token);
- $dbh->do(q{DELETE FROM tokens WHERE userid = ?
- AND tokentype = 'password'}, undef, $user_id);
+ my $user = Bugzilla::User->check({id => $user_id});
+ $user->set_password($password);
+ $user->update();
+ delete_token($token);
+ $dbh->do(
+ q{DELETE FROM tokens WHERE userid = ?
+ AND tokentype = 'password'}, undef, $user_id
+ );
- Bugzilla->logout_user_by_id($user_id);
+ Bugzilla->logout_user_by_id($user_id);
- $vars->{'message'} = "password_changed";
+ $vars->{'message'} = "password_changed";
- print $cgi->header();
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ print $cgi->header();
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
sub confirmChangeEmail {
- my $token = shift;
- $vars->{'token'} = $token;
+ my $token = shift;
+ $vars->{'token'} = $token;
- print $cgi->header();
- $template->process("account/email/confirm.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ print $cgi->header();
+ $template->process("account/email/confirm.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
sub changeEmail {
- my ($userid, $eventdata, $token) = @_;
- my $dbh = Bugzilla->dbh;
- my ($old_email, $new_email) = split(/:/,$eventdata);
-
- $dbh->bz_start_transaction();
-
- my $user = Bugzilla::User->check({ id => $userid });
- my $realpassword = $user->cryptpassword;
- my $cgipassword = $cgi->param('password');
-
- # Make sure the user who wants to change the email address
- # is the real account owner.
- if (bz_crypt($cgipassword, $realpassword) ne $realpassword) {
- ThrowUserError("old_password_incorrect");
- }
-
- # The new email address should be available as this was
- # confirmed initially so cancel token if it is not still available
- if (! is_available_username($new_email,$old_email)) {
- $vars->{'email'} = $new_email; # Needed for Bugzilla::Token::Cancel's mail
- Bugzilla::Token::Cancel($token, "account_exists", $vars);
- ThrowUserError("account_exists", { email => $new_email } );
- }
-
- # Update the user's login name in the profiles table.
- $user->set_login($new_email);
- $user->update({ keep_session => 1, keep_tokens => 1 });
- delete_token($token);
- $dbh->do(q{DELETE FROM tokens WHERE userid = ?
- AND tokentype = 'emailnew'}, undef, $userid);
-
- $dbh->bz_commit_transaction();
-
- # Return HTTP response headers.
- print $cgi->header();
-
- # Let the user know their email address has been changed.
- $vars->{'message'} = "login_changed";
-
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ my ($userid, $eventdata, $token) = @_;
+ my $dbh = Bugzilla->dbh;
+ my ($old_email, $new_email) = split(/:/, $eventdata);
+
+ $dbh->bz_start_transaction();
+
+ my $user = Bugzilla::User->check({id => $userid});
+ my $realpassword = $user->cryptpassword;
+ my $cgipassword = $cgi->param('password');
+
+ # Make sure the user who wants to change the email address
+ # is the real account owner.
+ if (bz_crypt($cgipassword, $realpassword) ne $realpassword) {
+ ThrowUserError("old_password_incorrect");
+ }
+
+ # The new email address should be available as this was
+ # confirmed initially so cancel token if it is not still available
+ if (!is_available_username($new_email, $old_email)) {
+ $vars->{'email'} = $new_email; # Needed for Bugzilla::Token::Cancel's mail
+ Bugzilla::Token::Cancel($token, "account_exists", $vars);
+ ThrowUserError("account_exists", {email => $new_email});
+ }
+
+ # Update the user's login name in the profiles table.
+ $user->set_login($new_email);
+ $user->update({keep_session => 1, keep_tokens => 1});
+ delete_token($token);
+ $dbh->do(
+ q{DELETE FROM tokens WHERE userid = ?
+ AND tokentype = 'emailnew'}, undef, $userid
+ );
+
+ $dbh->bz_commit_transaction();
+
+ # Return HTTP response headers.
+ print $cgi->header();
+
+ # Let the user know their email address has been changed.
+ $vars->{'message'} = "login_changed";
+
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
sub cancelChangeEmail {
- my ($userid, $eventdata, $tokentype, $token) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($userid, $eventdata, $tokentype, $token) = @_;
+ my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- my ($old_email, $new_email) = split(/:/,$eventdata);
+ my ($old_email, $new_email) = split(/:/, $eventdata);
- if ($tokentype eq "emailold") {
- $vars->{'message'} = "emailold_change_canceled";
- my $user = Bugzilla::User->check({ id => $userid });
+ if ($tokentype eq "emailold") {
+ $vars->{'message'} = "emailold_change_canceled";
+ my $user = Bugzilla::User->check({id => $userid});
- # check to see if it has been altered
- if ($user->login ne $old_email) {
- $user->set_login($old_email);
- $user->update({ keep_tokens => 1 });
+ # check to see if it has been altered
+ if ($user->login ne $old_email) {
+ $user->set_login($old_email);
+ $user->update({keep_tokens => 1});
- $vars->{'message'} = "email_change_canceled_reinstated";
- }
- }
- else {
- $vars->{'message'} = 'email_change_canceled'
- }
+ $vars->{'message'} = "email_change_canceled_reinstated";
+ }
+ }
+ else {
+ $vars->{'message'} = 'email_change_canceled';
+ }
- $vars->{'old_email'} = $old_email;
- $vars->{'new_email'} = $new_email;
- Bugzilla::Token::Cancel($token, $vars->{'message'}, $vars);
+ $vars->{'old_email'} = $old_email;
+ $vars->{'new_email'} = $new_email;
+ Bugzilla::Token::Cancel($token, $vars->{'message'}, $vars);
- $dbh->do(q{DELETE FROM tokens WHERE userid = ?
- AND tokentype = 'emailold' OR tokentype = 'emailnew'},
- undef, $userid);
+ $dbh->do(
+ q{DELETE FROM tokens WHERE userid = ?
+ AND tokentype = 'emailold' OR tokentype = 'emailnew'}, undef, $userid
+ );
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
- # Return HTTP response headers.
- print $cgi->header();
+ # Return HTTP response headers.
+ print $cgi->header();
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
sub request_create_account {
- my ($date, $login_name, $token) = @_;
+ my ($date, $login_name, $token) = @_;
- Bugzilla->user->check_account_creation_enabled;
+ Bugzilla->user->check_account_creation_enabled;
- $vars->{'token'} = $token;
- $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
- $vars->{'expiration_ts'} = ctime(str2time($date) + MAX_TOKEN_AGE * 86400);
+ $vars->{'token'} = $token;
+ $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
+ $vars->{'expiration_ts'} = ctime(str2time($date) + MAX_TOKEN_AGE * 86400);
- print $cgi->header();
- $template->process('account/email/confirm-new.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ print $cgi->header();
+ $template->process('account/email/confirm-new.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
sub confirm_create_account {
- my ($login_name, $token) = @_;
+ my ($login_name, $token) = @_;
+
+ Bugzilla->user->check_account_creation_enabled;
- Bugzilla->user->check_account_creation_enabled;
+ my $password = $cgi->param('passwd1') || '';
+ validate_password($password, $cgi->param('passwd2') || '');
- my $password = $cgi->param('passwd1') || '';
- validate_password($password, $cgi->param('passwd2') || '');
- # Make sure that these never show up anywhere in the UI.
- $cgi->delete('passwd1', 'passwd2');
+ # Make sure that these never show up anywhere in the UI.
+ $cgi->delete('passwd1', 'passwd2');
- my $otheruser = Bugzilla::User->create({
- login_name => $login_name,
- realname => scalar $cgi->param('realname'),
- cryptpassword => $password});
+ my $otheruser = Bugzilla::User->create({
+ login_name => $login_name,
+ realname => scalar $cgi->param('realname'),
+ cryptpassword => $password
+ });
- # Now delete this token.
- delete_token($token);
+ # Now delete this token.
+ delete_token($token);
- # Let the user know that their user account has been successfully created.
- $vars->{'message'} = 'account_created';
- $vars->{'otheruser'} = $otheruser;
+ # Let the user know that their user account has been successfully created.
+ $vars->{'message'} = 'account_created';
+ $vars->{'otheruser'} = $otheruser;
- # Log in the new user using credentials they just gave.
- $cgi->param('Bugzilla_login', $otheruser->login);
- $cgi->param('Bugzilla_password', $password);
- Bugzilla->login(LOGIN_OPTIONAL);
+ # Log in the new user using credentials they just gave.
+ $cgi->param('Bugzilla_login', $otheruser->login);
+ $cgi->param('Bugzilla_password', $password);
+ Bugzilla->login(LOGIN_OPTIONAL);
- print $cgi->header();
+ print $cgi->header();
- $template->process('index.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ $template->process('index.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
sub cancel_create_account {
- my ($login_name, $token) = @_;
+ my ($login_name, $token) = @_;
- $vars->{'message'} = 'account_creation_canceled';
- $vars->{'account'} = $login_name;
- Bugzilla::Token::Cancel($token, $vars->{'message'});
+ $vars->{'message'} = 'account_creation_canceled';
+ $vars->{'account'} = $login_name;
+ Bugzilla::Token::Cancel($token, $vars->{'message'});
- print $cgi->header();
- $template->process('global/message.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
+ print $cgi->header();
+ $template->process('global/message.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
}
diff --git a/userprefs.cgi b/userprefs.cgi
index 10c62657b..6f79160c0 100755
--- a/userprefs.cgi
+++ b/userprefs.cgi
@@ -28,538 +28,564 @@ my $template = Bugzilla->template;
local our $vars = {};
###############################################################################
-# Each panel has two functions - panel Foo has a DoFoo, to get the data
-# necessary for displaying the panel, and a SaveFoo, to save the panel's
-# contents from the form data (if appropriate).
-# SaveFoo may be called before DoFoo.
+# Each panel has two functions - panel Foo has a DoFoo, to get the data
+# necessary for displaying the panel, and a SaveFoo, to save the panel's
+# contents from the form data (if appropriate).
+# SaveFoo may be called before DoFoo.
###############################################################################
sub DoAccount {
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
- $vars->{'realname'} = $user->name;
+ $vars->{'realname'} = $user->name;
- if (Bugzilla->params->{'allowemailchange'}
- && $user->authorizer->can_change_email)
- {
- # First delete old tokens.
- Bugzilla::Token::CleanTokenTable();
+ if (Bugzilla->params->{'allowemailchange'}
+ && $user->authorizer->can_change_email)
+ {
+ # First delete old tokens.
+ Bugzilla::Token::CleanTokenTable();
- my @token = $dbh->selectrow_array(
- "SELECT tokentype, " .
- $dbh->sql_date_math('issuedate', '+', MAX_TOKEN_AGE, 'DAY')
- . ", eventdata
+ my @token = $dbh->selectrow_array(
+ "SELECT tokentype, "
+ . $dbh->sql_date_math('issuedate', '+', MAX_TOKEN_AGE, 'DAY')
+ . ", eventdata
FROM tokens
WHERE userid = ?
AND tokentype LIKE 'email%'
- ORDER BY tokentype ASC " . $dbh->sql_limit(1), undef, $user->id);
- if (scalar(@token) > 0) {
- my ($tokentype, $change_date, $eventdata) = @token;
- $vars->{'login_change_date'} = $change_date;
-
- if($tokentype eq 'emailnew') {
- my ($oldemail,$newemail) = split(/:/,$eventdata);
- $vars->{'new_login_name'} = $newemail;
- }
- }
+ ORDER BY tokentype ASC " . $dbh->sql_limit(1), undef, $user->id
+ );
+ if (scalar(@token) > 0) {
+ my ($tokentype, $change_date, $eventdata) = @token;
+ $vars->{'login_change_date'} = $change_date;
+
+ if ($tokentype eq 'emailnew') {
+ my ($oldemail, $newemail) = split(/:/, $eventdata);
+ $vars->{'new_login_name'} = $newemail;
+ }
}
+ }
}
sub SaveAccount {
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
-
- $dbh->bz_start_transaction;
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
+ $dbh->bz_start_transaction;
- my $oldpassword = $cgi->param('old_password');
- my $pwd1 = $cgi->param('new_password1');
- my $pwd2 = $cgi->param('new_password2');
- my $new_login_name = trim($cgi->param('new_login_name'));
+ my $user = Bugzilla->user;
- if ($user->authorizer->can_change_password
- && ($oldpassword ne "" || $pwd1 ne "" || $pwd2 ne ""))
- {
- my $oldcryptedpwd = $user->cryptpassword;
- $oldcryptedpwd || ThrowCodeError("unable_to_retrieve_password");
-
- if (bz_crypt($oldpassword, $oldcryptedpwd) ne $oldcryptedpwd) {
- ThrowUserError("old_password_incorrect");
- }
+ my $oldpassword = $cgi->param('old_password');
+ my $pwd1 = $cgi->param('new_password1');
+ my $pwd2 = $cgi->param('new_password2');
+ my $new_login_name = trim($cgi->param('new_login_name'));
- if ($pwd1 ne "" || $pwd2 ne "") {
- $pwd1 || ThrowUserError("new_password_missing");
- validate_password($pwd1, $pwd2);
+ if ($user->authorizer->can_change_password
+ && ($oldpassword ne "" || $pwd1 ne "" || $pwd2 ne ""))
+ {
+ my $oldcryptedpwd = $user->cryptpassword;
+ $oldcryptedpwd || ThrowCodeError("unable_to_retrieve_password");
- if ($oldpassword ne $pwd1) {
- $user->set_password($pwd1);
- # Invalidate all logins except for the current one
- Bugzilla->logout(LOGOUT_KEEP_CURRENT);
- }
- }
+ if (bz_crypt($oldpassword, $oldcryptedpwd) ne $oldcryptedpwd) {
+ ThrowUserError("old_password_incorrect");
}
- if ($user->authorizer->can_change_email
- && Bugzilla->params->{"allowemailchange"}
- && $new_login_name)
- {
- if ($user->login ne $new_login_name) {
- $oldpassword || ThrowUserError("old_password_required");
-
- # Block multiple email changes for the same user.
- if (Bugzilla::Token::HasEmailChangeToken($user->id)) {
- ThrowUserError("email_change_in_progress");
- }
-
- # Before changing an email address, confirm one does not exist.
- check_email_syntax($new_login_name);
- is_available_username($new_login_name)
- || ThrowUserError("account_exists", {email => $new_login_name});
- if(!$user->in_group("editusers")) {
- ThrowUserError('restricted_email_address', {addr => $new_login_name}) if $new_login_name =~ m/[^\@]+\@gentoo\.org$/ or $user->login =~ m/[^\@]+\@gentoo\.org$/;
- }
-
- $vars->{'email_token'} = Bugzilla::Token::IssueEmailChangeToken($new_login_name);
- $vars->{'email_changes_saved'} = 1;
- }
+ if ($pwd1 ne "" || $pwd2 ne "") {
+ $pwd1 || ThrowUserError("new_password_missing");
+ validate_password($pwd1, $pwd2);
+
+ if ($oldpassword ne $pwd1) {
+ $user->set_password($pwd1);
+
+ # Invalidate all logins except for the current one
+ Bugzilla->logout(LOGOUT_KEEP_CURRENT);
+ }
+ }
+ }
+
+ if ( $user->authorizer->can_change_email
+ && Bugzilla->params->{"allowemailchange"}
+ && $new_login_name)
+ {
+ if ($user->login ne $new_login_name) {
+ $oldpassword || ThrowUserError("old_password_required");
+
+ # Block multiple email changes for the same user.
+ if (Bugzilla::Token::HasEmailChangeToken($user->id)) {
+ ThrowUserError("email_change_in_progress");
+ }
+
+ # Before changing an email address, confirm one does not exist.
+ check_email_syntax($new_login_name);
+ is_available_username($new_login_name)
+ || ThrowUserError("account_exists", {email => $new_login_name});
+ if (!$user->in_group("editusers")) {
+ ThrowUserError('restricted_email_address', {addr => $new_login_name})
+ if $new_login_name =~ m/[^\@]+\@gentoo\.org$/
+ or $user->login =~ m/[^\@]+\@gentoo\.org$/;
+ }
+
+ $vars->{'email_token'}
+ = Bugzilla::Token::IssueEmailChangeToken($new_login_name);
+ $vars->{'email_changes_saved'} = 1;
}
+ }
- $user->set_name($cgi->param('realname'));
- $user->update({ keep_session => 1, keep_tokens => 1 });
- $dbh->bz_commit_transaction;
+ $user->set_name($cgi->param('realname'));
+ $user->update({keep_session => 1, keep_tokens => 1});
+ $dbh->bz_commit_transaction;
}
sub DoSettings {
- my $user = Bugzilla->user;
+ my $user = Bugzilla->user;
- my $settings = $user->settings;
- $vars->{'settings'} = $settings;
+ my $settings = $user->settings;
+ $vars->{'settings'} = $settings;
- my @setting_list = sort keys %$settings;
- $vars->{'setting_names'} = \@setting_list;
+ my @setting_list = sort keys %$settings;
+ $vars->{'setting_names'} = \@setting_list;
- $vars->{'has_settings_enabled'} = 0;
- # Is there at least one user setting enabled?
- foreach my $setting_name (@setting_list) {
- if ($settings->{"$setting_name"}->{'is_enabled'}) {
- $vars->{'has_settings_enabled'} = 1;
- last;
- }
+ $vars->{'has_settings_enabled'} = 0;
+
+ # Is there at least one user setting enabled?
+ foreach my $setting_name (@setting_list) {
+ if ($settings->{"$setting_name"}->{'is_enabled'}) {
+ $vars->{'has_settings_enabled'} = 1;
+ last;
}
- $vars->{'dont_show_button'} = !$vars->{'has_settings_enabled'};
+ }
+ $vars->{'dont_show_button'} = !$vars->{'has_settings_enabled'};
}
sub SaveSettings {
- my $cgi = Bugzilla->cgi;
- my $user = Bugzilla->user;
-
- my $settings = $user->settings;
- my @setting_list = keys %$settings;
-
- foreach my $name (@setting_list) {
- next if ! ($settings->{$name}->{'is_enabled'});
- my $value = $cgi->param($name);
- next unless defined $value;
- my $setting = new Bugzilla::User::Setting($name);
-
- if ($value eq "${name}-isdefault" ) {
- if (! $settings->{$name}->{'is_default'}) {
- $settings->{$name}->reset_to_default;
- }
- }
- else {
- $setting->validate_value($value);
- $settings->{$name}->set($value);
- }
+ my $cgi = Bugzilla->cgi;
+ my $user = Bugzilla->user;
+
+ my $settings = $user->settings;
+ my @setting_list = keys %$settings;
+
+ foreach my $name (@setting_list) {
+ next if !($settings->{$name}->{'is_enabled'});
+ my $value = $cgi->param($name);
+ next unless defined $value;
+ my $setting = new Bugzilla::User::Setting($name);
+
+ if ($value eq "${name}-isdefault") {
+ if (!$settings->{$name}->{'is_default'}) {
+ $settings->{$name}->reset_to_default;
+ }
+ }
+ else {
+ $setting->validate_value($value);
+ $settings->{$name}->set($value);
}
- $vars->{'settings'} = $user->settings(1);
- clear_settings_cache($user->id);
+ }
+ $vars->{'settings'} = $user->settings(1);
+ clear_settings_cache($user->id);
}
sub DoEmail {
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
-
- ###########################################################################
- # User watching
- ###########################################################################
- my $watched_ref = $dbh->selectcol_arrayref(
- "SELECT profiles.login_name FROM watch INNER JOIN profiles" .
- " ON watch.watched = profiles.userid" .
- " WHERE watcher = ?" .
- " ORDER BY profiles.login_name",
- undef, $user->id);
- $vars->{'watchedusers'} = $watched_ref;
-
- my $watcher_ids = $dbh->selectcol_arrayref(
- "SELECT watcher FROM watch WHERE watched = ?",
- undef, $user->id);
-
- my @watchers;
- foreach my $watcher_id (@$watcher_ids) {
- my $watcher = new Bugzilla::User($watcher_id);
- push(@watchers, Bugzilla::User::identity($watcher));
- }
-
- @watchers = sort { lc($a) cmp lc($b) } @watchers;
- $vars->{'watchers'} = \@watchers;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ ###########################################################################
+ # User watching
+ ###########################################################################
+ my $watched_ref = $dbh->selectcol_arrayref(
+ "SELECT profiles.login_name FROM watch INNER JOIN profiles"
+ . " ON watch.watched = profiles.userid"
+ . " WHERE watcher = ?"
+ . " ORDER BY profiles.login_name",
+ undef, $user->id
+ );
+ $vars->{'watchedusers'} = $watched_ref;
+
+ my $watcher_ids
+ = $dbh->selectcol_arrayref("SELECT watcher FROM watch WHERE watched = ?",
+ undef, $user->id);
+
+ my @watchers;
+ foreach my $watcher_id (@$watcher_ids) {
+ my $watcher = new Bugzilla::User($watcher_id);
+ push(@watchers, Bugzilla::User::identity($watcher));
+ }
+
+ @watchers = sort { lc($a) cmp lc($b) } @watchers;
+ $vars->{'watchers'} = \@watchers;
}
sub SaveEmail {
- my $dbh = Bugzilla->dbh;
- my $cgi = Bugzilla->cgi;
- my $user = Bugzilla->user;
-
- Bugzilla::User::match_field({ 'new_watchedusers' => {'type' => 'multi'} });
-
- ###########################################################################
- # Role-based preferences
- ###########################################################################
- $dbh->bz_start_transaction();
-
- my $sth_insert = $dbh->prepare('INSERT INTO email_setting
- (user_id, relationship, event) VALUES (?, ?, ?)');
-
- my $sth_delete = $dbh->prepare('DELETE FROM email_setting
- WHERE user_id = ? AND relationship = ? AND event = ?');
- # Load current email preferences into memory before updating them.
- my $settings = $user->mail_settings;
-
- # Update the table - first, with normal events in the
- # relationship/event matrix.
- my %relationships = Bugzilla::BugMail::relationships();
- foreach my $rel (keys %relationships) {
- next if ($rel == REL_QA && !Bugzilla->params->{'useqacontact'});
- # Positive events: a ticked box means "send me mail."
- foreach my $event (POS_EVENTS) {
- my $is_set = $cgi->param("email-$rel-$event");
- if ($is_set xor $settings->{$rel}{$event}) {
- if ($is_set) {
- $sth_insert->execute($user->id, $rel, $event);
- }
- else {
- $sth_delete->execute($user->id, $rel, $event);
- }
- }
+ my $dbh = Bugzilla->dbh;
+ my $cgi = Bugzilla->cgi;
+ my $user = Bugzilla->user;
+
+ Bugzilla::User::match_field({'new_watchedusers' => {'type' => 'multi'}});
+
+ ###########################################################################
+ # Role-based preferences
+ ###########################################################################
+ $dbh->bz_start_transaction();
+
+ my $sth_insert = $dbh->prepare(
+ 'INSERT INTO email_setting
+ (user_id, relationship, event) VALUES (?, ?, ?)'
+ );
+
+ my $sth_delete = $dbh->prepare(
+ 'DELETE FROM email_setting
+ WHERE user_id = ? AND relationship = ? AND event = ?'
+ );
+
+ # Load current email preferences into memory before updating them.
+ my $settings = $user->mail_settings;
+
+ # Update the table - first, with normal events in the
+ # relationship/event matrix.
+ my %relationships = Bugzilla::BugMail::relationships();
+ foreach my $rel (keys %relationships) {
+ next if ($rel == REL_QA && !Bugzilla->params->{'useqacontact'});
+
+ # Positive events: a ticked box means "send me mail."
+ foreach my $event (POS_EVENTS) {
+ my $is_set = $cgi->param("email-$rel-$event");
+ if ($is_set xor $settings->{$rel}{$event}) {
+ if ($is_set) {
+ $sth_insert->execute($user->id, $rel, $event);
}
-
- # Negative events: a ticked box means "don't send me mail."
- foreach my $event (NEG_EVENTS) {
- my $is_set = $cgi->param("neg-email-$rel-$event");
- if (!$is_set xor $settings->{$rel}{$event}) {
- if (!$is_set) {
- $sth_insert->execute($user->id, $rel, $event);
- }
- else {
- $sth_delete->execute($user->id, $rel, $event);
- }
- }
+ else {
+ $sth_delete->execute($user->id, $rel, $event);
}
+ }
}
- # Global positive events: a ticked box means "send me mail."
- foreach my $event (GLOBAL_EVENTS) {
- my $is_set = $cgi->param("email-" . REL_ANY . "-$event");
- if ($is_set xor $settings->{+REL_ANY}{$event}) {
- if ($is_set) {
- $sth_insert->execute($user->id, REL_ANY, $event);
- }
- else {
- $sth_delete->execute($user->id, REL_ANY, $event);
- }
+ # Negative events: a ticked box means "don't send me mail."
+ foreach my $event (NEG_EVENTS) {
+ my $is_set = $cgi->param("neg-email-$rel-$event");
+ if (!$is_set xor $settings->{$rel}{$event}) {
+ if (!$is_set) {
+ $sth_insert->execute($user->id, $rel, $event);
+ }
+ else {
+ $sth_delete->execute($user->id, $rel, $event);
}
+ }
}
+ }
+
+ # Global positive events: a ticked box means "send me mail."
+ foreach my $event (GLOBAL_EVENTS) {
+ my $is_set = $cgi->param("email-" . REL_ANY . "-$event");
+ if ($is_set xor $settings->{+REL_ANY}{$event}) {
+ if ($is_set) {
+ $sth_insert->execute($user->id, REL_ANY, $event);
+ }
+ else {
+ $sth_delete->execute($user->id, REL_ANY, $event);
+ }
+ }
+ }
- $dbh->bz_commit_transaction();
-
- # We have to clear the cache about email preferences.
- delete $user->{'mail_settings'};
-
- ###########################################################################
- # User watching
- ###########################################################################
- if (defined $cgi->param('new_watchedusers')
- || defined $cgi->param('remove_watched_users'))
- {
- $dbh->bz_start_transaction();
-
- # Use this to protect error messages on duplicate submissions
- my $old_watch_ids =
- $dbh->selectcol_arrayref("SELECT watched FROM watch"
- . " WHERE watcher = ?", undef, $user->id);
+ $dbh->bz_commit_transaction();
- # The new information given to us by the user.
- my $new_watched_users = join(',', $cgi->param('new_watchedusers')) || '';
- my @new_watch_names = split(/[,\s]+/, $new_watched_users);
- my %new_watch_ids;
+ # We have to clear the cache about email preferences.
+ delete $user->{'mail_settings'};
- foreach my $username (@new_watch_names) {
- my $watched_userid = login_to_id(trim($username), THROW_ERROR);
- $new_watch_ids{$watched_userid} = 1;
- }
+ ###########################################################################
+ # User watching
+ ###########################################################################
+ if ( defined $cgi->param('new_watchedusers')
+ || defined $cgi->param('remove_watched_users'))
+ {
+ $dbh->bz_start_transaction();
- # Add people who were added.
- my $insert_sth = $dbh->prepare('INSERT INTO watch (watched, watcher)'
- . ' VALUES (?, ?)');
- foreach my $add_me (keys(%new_watch_ids)) {
- next if grep($_ == $add_me, @$old_watch_ids);
- $insert_sth->execute($add_me, $user->id);
- }
+ # Use this to protect error messages on duplicate submissions
+ my $old_watch_ids
+ = $dbh->selectcol_arrayref("SELECT watched FROM watch" . " WHERE watcher = ?",
+ undef, $user->id);
- if (defined $cgi->param('remove_watched_users')) {
- my @removed = $cgi->param('watched_by_you');
- # Remove people who were removed.
- my $delete_sth = $dbh->prepare('DELETE FROM watch WHERE watched = ?'
- . ' AND watcher = ?');
-
- my %remove_watch_ids;
- foreach my $username (@removed) {
- my $watched_userid = login_to_id(trim($username), THROW_ERROR);
- $remove_watch_ids{$watched_userid} = 1;
- }
- foreach my $remove_me (keys(%remove_watch_ids)) {
- $delete_sth->execute($remove_me, $user->id);
- }
- }
+ # The new information given to us by the user.
+ my $new_watched_users = join(',', $cgi->param('new_watchedusers')) || '';
+ my @new_watch_names = split(/[,\s]+/, $new_watched_users);
+ my %new_watch_ids;
- $dbh->bz_commit_transaction();
+ foreach my $username (@new_watch_names) {
+ my $watched_userid = login_to_id(trim($username), THROW_ERROR);
+ $new_watch_ids{$watched_userid} = 1;
}
- ###########################################################################
- # Ignore Bugs
- ###########################################################################
- my %ignored_bugs = map { $_->{'id'} => 1 } @{$user->bugs_ignored};
-
- # Validate the new bugs to ignore by checking that they exist and also
- # if the user gave an alias
- my @add_ignored = split(/[\s,]+/, $cgi->param('add_ignored_bugs'));
- @add_ignored = map { Bugzilla::Bug->check($_)->id } @add_ignored;
- map { $ignored_bugs{$_} = 1 } @add_ignored;
-
- # Remove any bug ids the user no longer wants to ignore
- foreach my $key (grep(/^remove_ignored_bug_/, $cgi->param)) {
- my ($bug_id) = $key =~ /(\d+)$/;
- delete $ignored_bugs{$bug_id};
+ # Add people who were added.
+ my $insert_sth
+ = $dbh->prepare('INSERT INTO watch (watched, watcher)' . ' VALUES (?, ?)');
+ foreach my $add_me (keys(%new_watch_ids)) {
+ next if grep($_ == $add_me, @$old_watch_ids);
+ $insert_sth->execute($add_me, $user->id);
}
- # Update the database with any changes made
- my ($removed, $added) = diff_arrays([ map { $_->{'id'} } @{$user->bugs_ignored} ],
- [ keys %ignored_bugs ]);
+ if (defined $cgi->param('remove_watched_users')) {
+ my @removed = $cgi->param('watched_by_you');
+
+ # Remove people who were removed.
+ my $delete_sth
+ = $dbh->prepare('DELETE FROM watch WHERE watched = ?' . ' AND watcher = ?');
+
+ my %remove_watch_ids;
+ foreach my $username (@removed) {
+ my $watched_userid = login_to_id(trim($username), THROW_ERROR);
+ $remove_watch_ids{$watched_userid} = 1;
+ }
+ foreach my $remove_me (keys(%remove_watch_ids)) {
+ $delete_sth->execute($remove_me, $user->id);
+ }
+ }
- if (scalar @$removed || scalar @$added) {
- $dbh->bz_start_transaction();
+ $dbh->bz_commit_transaction();
+ }
+
+ ###########################################################################
+ # Ignore Bugs
+ ###########################################################################
+ my %ignored_bugs = map { $_->{'id'} => 1 } @{$user->bugs_ignored};
+
+ # Validate the new bugs to ignore by checking that they exist and also
+ # if the user gave an alias
+ my @add_ignored = split(/[\s,]+/, $cgi->param('add_ignored_bugs'));
+ @add_ignored = map { Bugzilla::Bug->check($_)->id } @add_ignored;
+ map { $ignored_bugs{$_} = 1 } @add_ignored;
+
+ # Remove any bug ids the user no longer wants to ignore
+ foreach my $key (grep(/^remove_ignored_bug_/, $cgi->param)) {
+ my ($bug_id) = $key =~ /(\d+)$/;
+ delete $ignored_bugs{$bug_id};
+ }
+
+ # Update the database with any changes made
+ my ($removed, $added)
+ = diff_arrays([map { $_->{'id'} } @{$user->bugs_ignored}],
+ [keys %ignored_bugs]);
+
+ if (scalar @$removed || scalar @$added) {
+ $dbh->bz_start_transaction();
- if (scalar @$removed) {
- $dbh->do('DELETE FROM email_bug_ignore WHERE user_id = ? AND ' .
- $dbh->sql_in('bug_id', $removed),
- undef, $user->id);
- }
- if (scalar @$added) {
- my $sth = $dbh->prepare('INSERT INTO email_bug_ignore
- (user_id, bug_id) VALUES (?, ?)');
- $sth->execute($user->id, $_) foreach @$added;
- }
+ if (scalar @$removed) {
+ $dbh->do(
+ 'DELETE FROM email_bug_ignore WHERE user_id = ? AND '
+ . $dbh->sql_in('bug_id', $removed),
+ undef, $user->id
+ );
+ }
+ if (scalar @$added) {
+ my $sth = $dbh->prepare(
+ 'INSERT INTO email_bug_ignore
+ (user_id, bug_id) VALUES (?, ?)'
+ );
+ $sth->execute($user->id, $_) foreach @$added;
+ }
- # Reset the cache of ignored bugs if the list changed.
- delete $user->{bugs_ignored};
+ # Reset the cache of ignored bugs if the list changed.
+ delete $user->{bugs_ignored};
- $dbh->bz_commit_transaction();
- }
+ $dbh->bz_commit_transaction();
+ }
}
sub DoPermissions {
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
- my (@has_bits, @set_bits);
-
- my $groups = $dbh->selectall_arrayref(
- "SELECT DISTINCT name, description FROM groups WHERE id IN (" .
- $user->groups_as_string . ") ORDER BY name");
- foreach my $group (@$groups) {
- my ($nam, $desc) = @$group;
- push(@has_bits, {"desc" => $desc, "name" => $nam});
- }
- $groups = $dbh->selectall_arrayref('SELECT DISTINCT id, name, description
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ my (@has_bits, @set_bits);
+
+ my $groups
+ = $dbh->selectall_arrayref(
+ "SELECT DISTINCT name, description FROM groups WHERE id IN ("
+ . $user->groups_as_string
+ . ") ORDER BY name");
+ foreach my $group (@$groups) {
+ my ($nam, $desc) = @$group;
+ push(@has_bits, {"desc" => $desc, "name" => $nam});
+ }
+ $groups = $dbh->selectall_arrayref(
+ 'SELECT DISTINCT id, name, description
FROM groups
- ORDER BY name');
- foreach my $group (@$groups) {
- my ($group_id, $nam, $desc) = @$group;
- if ($user->can_bless($group_id)) {
- push(@set_bits, {"desc" => $desc, "name" => $nam});
- }
+ ORDER BY name'
+ );
+ foreach my $group (@$groups) {
+ my ($group_id, $nam, $desc) = @$group;
+ if ($user->can_bless($group_id)) {
+ push(@set_bits, {"desc" => $desc, "name" => $nam});
}
+ }
- # If the user has product specific privileges, inform them about that.
- foreach my $privs (PER_PRODUCT_PRIVILEGES) {
- next if $user->in_group($privs);
- $vars->{"local_$privs"} = $user->get_products_by_permission($privs);
- }
+ # If the user has product specific privileges, inform them about that.
+ foreach my $privs (PER_PRODUCT_PRIVILEGES) {
+ next if $user->in_group($privs);
+ $vars->{"local_$privs"} = $user->get_products_by_permission($privs);
+ }
- $vars->{'has_bits'} = \@has_bits;
- $vars->{'set_bits'} = \@set_bits;
+ $vars->{'has_bits'} = \@has_bits;
+ $vars->{'set_bits'} = \@set_bits;
}
# No SavePermissions() because this panel has no changeable fields.
sub DoSavedSearches {
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
-
- if ($user->queryshare_groups_as_string) {
- $vars->{'queryshare_groups'} =
- Bugzilla::Group->new_from_list($user->queryshare_groups);
- }
- $vars->{'bless_group_ids'} = [map { $_->id } @{$user->bless_groups}];
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ if ($user->queryshare_groups_as_string) {
+ $vars->{'queryshare_groups'}
+ = Bugzilla::Group->new_from_list($user->queryshare_groups);
+ }
+ $vars->{'bless_group_ids'} = [map { $_->id } @{$user->bless_groups}];
}
sub SaveSavedSearches {
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
- # We'll need this in a loop, so do the call once.
- my $user_id = $user->id;
+ # We'll need this in a loop, so do the call once.
+ my $user_id = $user->id;
- my $sth_insert_nl = $dbh->prepare('INSERT INTO namedqueries_link_in_footer
+ my $sth_insert_nl = $dbh->prepare(
+ 'INSERT INTO namedqueries_link_in_footer
(namedquery_id, user_id)
- VALUES (?, ?)');
- my $sth_delete_nl = $dbh->prepare('DELETE FROM namedqueries_link_in_footer
+ VALUES (?, ?)'
+ );
+ my $sth_delete_nl = $dbh->prepare(
+ 'DELETE FROM namedqueries_link_in_footer
WHERE namedquery_id = ?
- AND user_id = ?');
- my $sth_insert_ngm = $dbh->prepare('INSERT INTO namedquery_group_map
+ AND user_id = ?'
+ );
+ my $sth_insert_ngm = $dbh->prepare(
+ 'INSERT INTO namedquery_group_map
(namedquery_id, group_id)
- VALUES (?, ?)');
- my $sth_update_ngm = $dbh->prepare('UPDATE namedquery_group_map
+ VALUES (?, ?)'
+ );
+ my $sth_update_ngm = $dbh->prepare(
+ 'UPDATE namedquery_group_map
SET group_id = ?
- WHERE namedquery_id = ?');
- my $sth_delete_ngm = $dbh->prepare('DELETE FROM namedquery_group_map
- WHERE namedquery_id = ?');
-
- # Update namedqueries_link_in_footer for this user.
- foreach my $q (@{$user->queries}, @{$user->queries_available}) {
- if (defined $cgi->param("link_in_footer_" . $q->id)) {
- $sth_insert_nl->execute($q->id, $user_id) if !$q->link_in_footer;
- }
- else {
- $sth_delete_nl->execute($q->id, $user_id) if $q->link_in_footer;
- }
+ WHERE namedquery_id = ?'
+ );
+ my $sth_delete_ngm = $dbh->prepare(
+ 'DELETE FROM namedquery_group_map
+ WHERE namedquery_id = ?'
+ );
+
+ # Update namedqueries_link_in_footer for this user.
+ foreach my $q (@{$user->queries}, @{$user->queries_available}) {
+ if (defined $cgi->param("link_in_footer_" . $q->id)) {
+ $sth_insert_nl->execute($q->id, $user_id) if !$q->link_in_footer;
+ }
+ else {
+ $sth_delete_nl->execute($q->id, $user_id) if $q->link_in_footer;
}
+ }
- # For user's own queries, update namedquery_group_map.
- foreach my $q (@{$user->queries}) {
- my $group_id;
+ # For user's own queries, update namedquery_group_map.
+ foreach my $q (@{$user->queries}) {
+ my $group_id;
- if ($user->in_group(Bugzilla->params->{'querysharegroup'})) {
- $group_id = $cgi->param("share_" . $q->id) || '';
- }
+ if ($user->in_group(Bugzilla->params->{'querysharegroup'})) {
+ $group_id = $cgi->param("share_" . $q->id) || '';
+ }
- if ($group_id) {
- # Don't allow the user to share queries with groups they're not
- # allowed to.
- next unless grep($_ eq $group_id, @{$user->queryshare_groups});
-
- # $group_id is now definitely a valid ID of a group the
- # user can share queries with, so we can trick_taint.
- detaint_natural($group_id);
- if ($q->shared_with_group) {
- $sth_update_ngm->execute($group_id, $q->id);
- }
- else {
- $sth_insert_ngm->execute($q->id, $group_id);
- }
-
- # If we're sharing our query with a group we can bless, we
- # have the ability to add link to our search to the footer of
- # direct group members automatically.
- if ($user->can_bless($group_id) && $cgi->param('force_' . $q->id)) {
- my $group = new Bugzilla::Group($group_id);
- my $members = $group->members_non_inherited;
- foreach my $member (@$members) {
- next if $member->id == $user->id;
- $sth_insert_nl->execute($q->id, $member->id)
- if !$q->link_in_footer($member);
- }
- }
- }
- else {
- # They have unshared that query.
- if ($q->shared_with_group) {
- $sth_delete_ngm->execute($q->id);
- }
-
- # Don't remove namedqueries_link_in_footer entries for users
- # subscribing to the shared query. The idea is that they will
- # probably want to be subscribers again should the sharing
- # user choose to share the query again.
+ if ($group_id) {
+
+ # Don't allow the user to share queries with groups they're not
+ # allowed to.
+ next unless grep($_ eq $group_id, @{$user->queryshare_groups});
+
+ # $group_id is now definitely a valid ID of a group the
+ # user can share queries with, so we can trick_taint.
+ detaint_natural($group_id);
+ if ($q->shared_with_group) {
+ $sth_update_ngm->execute($group_id, $q->id);
+ }
+ else {
+ $sth_insert_ngm->execute($q->id, $group_id);
+ }
+
+ # If we're sharing our query with a group we can bless, we
+ # have the ability to add link to our search to the footer of
+ # direct group members automatically.
+ if ($user->can_bless($group_id) && $cgi->param('force_' . $q->id)) {
+ my $group = new Bugzilla::Group($group_id);
+ my $members = $group->members_non_inherited;
+ foreach my $member (@$members) {
+ next if $member->id == $user->id;
+ $sth_insert_nl->execute($q->id, $member->id) if !$q->link_in_footer($member);
}
+ }
}
+ else {
+ # They have unshared that query.
+ if ($q->shared_with_group) {
+ $sth_delete_ngm->execute($q->id);
+ }
+
+ # Don't remove namedqueries_link_in_footer entries for users
+ # subscribing to the shared query. The idea is that they will
+ # probably want to be subscribers again should the sharing
+ # user choose to share the query again.
+ }
+ }
- $user->flush_queries_cache;
+ $user->flush_queries_cache;
- # Update profiles.mybugslink.
- my $showmybugslink = defined($cgi->param("showmybugslink")) ? 1 : 0;
- $dbh->do("UPDATE profiles SET mybugslink = ? WHERE userid = ?",
- undef, ($showmybugslink, $user->id));
- $user->{'showmybugslink'} = $showmybugslink;
- Bugzilla->memcached->clear({ table => 'profiles', id => $user->id });
+ # Update profiles.mybugslink.
+ my $showmybugslink = defined($cgi->param("showmybugslink")) ? 1 : 0;
+ $dbh->do("UPDATE profiles SET mybugslink = ? WHERE userid = ?",
+ undef, ($showmybugslink, $user->id));
+ $user->{'showmybugslink'} = $showmybugslink;
+ Bugzilla->memcached->clear({table => 'profiles', id => $user->id});
}
sub DoApiKey {
- my $user = Bugzilla->user;
+ my $user = Bugzilla->user;
- my $api_keys = Bugzilla::User::APIKey->match({ user_id => $user->id });
- $vars->{api_keys} = $api_keys;
- $vars->{any_revoked} = grep { $_->revoked } @$api_keys;
+ my $api_keys = Bugzilla::User::APIKey->match({user_id => $user->id});
+ $vars->{api_keys} = $api_keys;
+ $vars->{any_revoked} = grep { $_->revoked } @$api_keys;
}
sub SaveApiKey {
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
-
- # Do it in a transaction.
- $dbh->bz_start_transaction;
-
- # Update any existing keys
- my $api_keys = Bugzilla::User::APIKey->match({ user_id => $user->id });
- foreach my $api_key (@$api_keys) {
- my $description = $cgi->param('description_' . $api_key->id);
- my $revoked = $cgi->param('revoked_' . $api_key->id);
-
- if ($description ne $api_key->description
- || $revoked != $api_key->revoked)
- {
- $api_key->set_all({
- description => $description,
- revoked => $revoked,
- });
- $api_key->update();
- }
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ # Do it in a transaction.
+ $dbh->bz_start_transaction;
+
+ # Update any existing keys
+ my $api_keys = Bugzilla::User::APIKey->match({user_id => $user->id});
+ foreach my $api_key (@$api_keys) {
+ my $description = $cgi->param('description_' . $api_key->id);
+ my $revoked = $cgi->param('revoked_' . $api_key->id);
+
+ if ($description ne $api_key->description || $revoked != $api_key->revoked) {
+ $api_key->set_all({description => $description, revoked => $revoked,});
+ $api_key->update();
}
+ }
- # Create a new API key if requested.
- if ($cgi->param('new_key')) {
- $vars->{new_key} = Bugzilla::User::APIKey->create({
- user_id => $user->id,
- description => scalar $cgi->param('new_description'),
- });
-
- # As a security precaution, we always sent out an e-mail when
- # an API key is created
- my $template = Bugzilla->template_inner($user->setting('lang'));
- my $message;
- $template->process('email/new-api-key.txt.tmpl', $vars, \$message)
- || ThrowTemplateError($template->error());
-
- MessageToMTA($message);
- }
+ # Create a new API key if requested.
+ if ($cgi->param('new_key')) {
+ $vars->{new_key} = Bugzilla::User::APIKey->create(
+ {user_id => $user->id, description => scalar $cgi->param('new_description'),});
+
+ # As a security precaution, we always sent out an e-mail when
+ # an API key is created
+ my $template = Bugzilla->template_inner($user->setting('lang'));
+ my $message;
+ $template->process('email/new-api-key.txt.tmpl', $vars, \$message)
+ || ThrowTemplateError($template->error());
- $dbh->bz_commit_transaction;
+ MessageToMTA($message);
+ }
+
+ $dbh->bz_commit_transaction;
}
###############################################################################
@@ -576,9 +602,10 @@ $cgi->delete('GoAheadAndLogIn');
my $user = Bugzilla->login(LOGIN_OPTIONAL);
if (!$user->id) {
- # Use credentials given in the form if login cookies are not available.
- $cgi->param('Bugzilla_login', $cgi->param('old_login'));
- $cgi->param('Bugzilla_password', $cgi->param('old_password'));
+
+ # Use credentials given in the form if login cookies are not available.
+ $cgi->param('Bugzilla_login', $cgi->param('old_login'));
+ $cgi->param('Bugzilla_password', $cgi->param('old_password'));
}
Bugzilla->login(LOGIN_REQUIRED);
@@ -598,52 +625,55 @@ check_token_data($token, 'edit_user_prefs') if $save_changes;
# Do any saving, and then display the current tab.
SWITCH: for ($current_tab_name) {
- # Extensions must set it to 1 to confirm the tab is valid.
- my $handled = 0;
- Bugzilla::Hook::process('user_preferences',
- { 'vars' => $vars,
- save_changes => $save_changes,
- current_tab => $current_tab_name,
- handled => \$handled });
- last SWITCH if $handled;
-
- /^account$/ && do {
- SaveAccount() if $save_changes;
- DoAccount();
- last SWITCH;
- };
- /^settings$/ && do {
- SaveSettings() if $save_changes;
- DoSettings();
- last SWITCH;
- };
- /^email$/ && do {
- SaveEmail() if $save_changes;
- DoEmail();
- last SWITCH;
- };
- /^permissions$/ && do {
- DoPermissions();
- last SWITCH;
- };
- /^saved-searches$/ && do {
- SaveSavedSearches() if $save_changes;
- DoSavedSearches();
- last SWITCH;
- };
- /^apikey$/ && do {
- SaveApiKey() if $save_changes;
- DoApiKey();
- last SWITCH;
- };
-
- ThrowUserError("unknown_tab",
- { current_tab_name => $current_tab_name });
+ # Extensions must set it to 1 to confirm the tab is valid.
+ my $handled = 0;
+ Bugzilla::Hook::process(
+ 'user_preferences',
+ {
+ 'vars' => $vars,
+ save_changes => $save_changes,
+ current_tab => $current_tab_name,
+ handled => \$handled
+ }
+ );
+ last SWITCH if $handled;
+
+ /^account$/ && do {
+ SaveAccount() if $save_changes;
+ DoAccount();
+ last SWITCH;
+ };
+ /^settings$/ && do {
+ SaveSettings() if $save_changes;
+ DoSettings();
+ last SWITCH;
+ };
+ /^email$/ && do {
+ SaveEmail() if $save_changes;
+ DoEmail();
+ last SWITCH;
+ };
+ /^permissions$/ && do {
+ DoPermissions();
+ last SWITCH;
+ };
+ /^saved-searches$/ && do {
+ SaveSavedSearches() if $save_changes;
+ DoSavedSearches();
+ last SWITCH;
+ };
+ /^apikey$/ && do {
+ SaveApiKey() if $save_changes;
+ DoApiKey();
+ last SWITCH;
+ };
+
+ ThrowUserError("unknown_tab", {current_tab_name => $current_tab_name});
}
delete_token($token) if $save_changes;
if ($current_tab_name ne 'permissions') {
- $vars->{'token'} = issue_session_token('edit_user_prefs');
+ $vars->{'token'} = issue_session_token('edit_user_prefs');
}
# Generate and return the UI (HTML page) from the appropriate template.
diff --git a/votes.cgi b/votes.cgi
index ae692a941..83f36dc6b 100755
--- a/votes.cgi
+++ b/votes.cgi
@@ -18,22 +18,22 @@ use lib qw(. lib);
use Bugzilla;
use Bugzilla::Error;
-my $is_enabled = grep { $_->NAME eq 'Voting' } @{ Bugzilla->extensions };
-$is_enabled || ThrowUserError('extension_disabled', { name => 'Voting' });
+my $is_enabled = grep { $_->NAME eq 'Voting' } @{Bugzilla->extensions};
+$is_enabled || ThrowUserError('extension_disabled', {name => 'Voting'});
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $action = $cgi->param('action') || 'show_user';
if ($action eq "show_bug") {
- $cgi->delete('action');
- $cgi->param('id', 'voting/bug.html');
-}
+ $cgi->delete('action');
+ $cgi->param('id', 'voting/bug.html');
+}
elsif ($action eq "show_user" or $action eq 'vote') {
- $cgi->delete('action') unless $action eq 'vote';
- $cgi->param('id', 'voting/user.html');
+ $cgi->delete('action') unless $action eq 'vote';
+ $cgi->param('id', 'voting/user.html');
}
else {
- ThrowUserError('unknown_action', {action => $action});
+ ThrowUserError('unknown_action', {action => $action});
}
print $cgi->redirect('page.cgi?' . $cgi->query_string);
diff --git a/whine.pl b/whine.pl
index 39c9aeed2..691df886f 100755
--- a/whine.pl
+++ b/whine.pl
@@ -37,38 +37,35 @@ my @seen_schedules = ();
# These statement handles should live outside of their functions in order to
# allow the database to keep their SQL compiled.
-my $sth_run_queries =
- $dbh->prepare("SELECT " .
- "query_name, title, onemailperbug " .
- "FROM whine_queries " .
- "WHERE eventid=? " .
- "ORDER BY sortkey");
-my $sth_get_query =
- $dbh->prepare("SELECT query FROM namedqueries " .
- "WHERE userid = ? AND name = ?");
+my $sth_run_queries
+ = $dbh->prepare("SELECT "
+ . "query_name, title, onemailperbug "
+ . "FROM whine_queries "
+ . "WHERE eventid=? "
+ . "ORDER BY sortkey");
+my $sth_get_query = $dbh->prepare(
+ "SELECT query FROM namedqueries " . "WHERE userid = ? AND name = ?");
# get the event that's scheduled with the lowest run_next value
-my $sth_next_scheduled_event = $dbh->prepare(
- "SELECT " .
- " whine_schedules.eventid, " .
- " whine_events.owner_userid, " .
- " whine_events.subject, " .
- " whine_events.body, " .
- " whine_events.mailifnobugs " .
- "FROM whine_schedules " .
- "LEFT JOIN whine_events " .
- " ON whine_events.id = whine_schedules.eventid " .
- "WHERE run_next <= NOW() " .
- "ORDER BY run_next " .
- $dbh->sql_limit(1)
-);
+my $sth_next_scheduled_event
+ = $dbh->prepare("SELECT "
+ . " whine_schedules.eventid, "
+ . " whine_events.owner_userid, "
+ . " whine_events.subject, "
+ . " whine_events.body, "
+ . " whine_events.mailifnobugs "
+ . "FROM whine_schedules "
+ . "LEFT JOIN whine_events "
+ . " ON whine_events.id = whine_schedules.eventid "
+ . "WHERE run_next <= NOW() "
+ . "ORDER BY run_next "
+ . $dbh->sql_limit(1));
# get all pending schedules matching an eventid
-my $sth_schedules_by_event = $dbh->prepare(
- "SELECT id, mailto_type, mailto " .
- "FROM whine_schedules " .
- "WHERE eventid=? AND run_next <= NOW()"
-);
+my $sth_schedules_by_event
+ = $dbh->prepare("SELECT id, mailto_type, mailto "
+ . "FROM whine_schedules "
+ . "WHERE eventid=? AND run_next <= NOW()");
################################################################################
@@ -88,20 +85,25 @@ my $sth_schedules_by_event = $dbh->prepare(
my $fromaddress = Bugzilla->params->{'mailfrom'};
# get the current date and time
-my ($now_sec, $now_minute, $now_hour, $now_day, $now_month, $now_year,
- $now_weekday) = localtime;
+my (
+ $now_sec, $now_minute, $now_hour, $now_day,
+ $now_month, $now_year, $now_weekday
+) = localtime;
+
# Convert year to two digits
$now_year = sprintf("%02d", $now_year % 100);
+
# Convert the month to January being "1" instead of January being "0".
$now_month++;
my @daysinmonth = qw(0 31 28 31 30 31 30 31 31 30 31 30 31);
+
# Alter February in case of a leap year. This simple way to do it only
# applies if you won't be looking at February of next year, which whining
# doesn't need to do.
-if (($now_year % 4 == 0) &&
- (($now_year % 100 != 0) || ($now_year % 400 == 0))) {
- $daysinmonth[2] = 29;
+if (($now_year % 4 == 0) && (($now_year % 100 != 0) || ($now_year % 400 == 0)))
+{
+ $daysinmonth[2] = 29;
}
# run_day can contain either a calendar day (1, 2, 3...), a day of the week
@@ -113,67 +115,79 @@ if (($now_year % 4 == 0) &&
#
# We go over each uninitialized schedule record and use its settings to
# determine what the next time it runs should be
-my $sched_h = $dbh->prepare("SELECT id, run_day, run_time " .
- "FROM whine_schedules " .
- "WHERE run_next IS NULL" );
+my $sched_h
+ = $dbh->prepare("SELECT id, run_day, run_time "
+ . "FROM whine_schedules "
+ . "WHERE run_next IS NULL");
$sched_h->execute();
while (my ($schedule_id, $day, $time) = $sched_h->fetchrow_array) {
- # fill in some defaults in case they're blank
- $day ||= '0';
- $time ||= '0';
-
- # If this schedule is supposed to run today, we see if it's supposed to be
- # run at a particular hour. If so, we set it for that hour, and if not,
- # it runs at an interval over the course of a day, which means we should
- # set it to run immediately.
- if (&check_today($day)) {
- # Values that are not entirely numeric are intervals, like "30min"
- if ($time !~ /^\d+$/) {
- # set it to now
- $sth = $dbh->prepare( "UPDATE whine_schedules " .
- "SET run_next=NOW() " .
- "WHERE id=?");
- $sth->execute($schedule_id);
- }
- # A time greater than now means it still has to run today
- elsif ($time >= $now_hour) {
- # set it to today + number of hours
- $sth = $dbh->prepare(
- "UPDATE whine_schedules " .
- "SET run_next = " .
- $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'HOUR') .
- " WHERE id = ?");
- $sth->execute($time, $schedule_id);
- }
- # the target time is less than the current time
- else { # set it for the next applicable day
- $day = &get_next_date($day);
- my $run_next = $dbh->sql_date_math('('
- . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY')
- . ')', '+', '?', 'HOUR');
- $sth = $dbh->prepare("UPDATE whine_schedules " .
- "SET run_next = $run_next
- WHERE id = ?");
- $sth->execute($day, $time, $schedule_id);
- }
+ # fill in some defaults in case they're blank
+ $day ||= '0';
+ $time ||= '0';
+
+ # If this schedule is supposed to run today, we see if it's supposed to be
+ # run at a particular hour. If so, we set it for that hour, and if not,
+ # it runs at an interval over the course of a day, which means we should
+ # set it to run immediately.
+ if (&check_today($day)) {
+
+ # Values that are not entirely numeric are intervals, like "30min"
+ if ($time !~ /^\d+$/) {
+
+ # set it to now
+ $sth = $dbh->prepare(
+ "UPDATE whine_schedules " . "SET run_next=NOW() " . "WHERE id=?");
+ $sth->execute($schedule_id);
}
- # If the schedule is not supposed to run today, we set it to run on the
- # appropriate date and time
- else {
- my $target_date = &get_next_date($day);
- # If configured for a particular time, set it to that, otherwise
- # midnight
- my $target_time = ($time =~ /^\d+$/) ? $time : 0;
-
- my $run_next = $dbh->sql_date_math('('
- . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY')
- . ')', '+', '?', 'HOUR');
- $sth = $dbh->prepare("UPDATE whine_schedules " .
- "SET run_next = $run_next
- WHERE id = ?");
- $sth->execute($target_date, $target_time, $schedule_id);
+
+ # A time greater than now means it still has to run today
+ elsif ($time >= $now_hour) {
+
+ # set it to today + number of hours
+ $sth
+ = $dbh->prepare("UPDATE whine_schedules "
+ . "SET run_next = "
+ . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'HOUR')
+ . " WHERE id = ?");
+ $sth->execute($time, $schedule_id);
}
+
+ # the target time is less than the current time
+ else { # set it for the next applicable day
+ $day = &get_next_date($day);
+ my $run_next
+ = $dbh->sql_date_math(
+ '(' . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY') . ')',
+ '+', '?', 'HOUR');
+ $sth = $dbh->prepare(
+ "UPDATE whine_schedules " . "SET run_next = $run_next
+ WHERE id = ?"
+ );
+ $sth->execute($day, $time, $schedule_id);
+ }
+
+ }
+
+ # If the schedule is not supposed to run today, we set it to run on the
+ # appropriate date and time
+ else {
+ my $target_date = &get_next_date($day);
+
+ # If configured for a particular time, set it to that, otherwise
+ # midnight
+ my $target_time = ($time =~ /^\d+$/) ? $time : 0;
+
+ my $run_next
+ = $dbh->sql_date_math(
+ '(' . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY') . ')',
+ '+', '?', 'HOUR');
+ $sth = $dbh->prepare(
+ "UPDATE whine_schedules " . "SET run_next = $run_next
+ WHERE id = ?"
+ );
+ $sth->execute($target_date, $target_time, $schedule_id);
+ }
}
$sched_h->finish();
@@ -187,7 +201,7 @@ $sched_h->finish();
# 5. Return an event hashref
#
# The event hashref consists of:
-# eventid - ID of the event
+# eventid - ID of the event
# author - user object for the event's creator
# users - array of user objects for recipients
# subject - Subject line for the email
@@ -195,91 +209,85 @@ $sched_h->finish();
# mailifnobugs - send message even if there are no query or query results
sub get_next_event {
- my $event = {};
-
- # Loop until there's something to return
- until (scalar keys %{$event}) {
-
- $dbh->bz_start_transaction();
-
- # Get the event ID for the first pending schedule
- $sth_next_scheduled_event->execute;
- my $fetched = $sth_next_scheduled_event->fetch;
- $sth_next_scheduled_event->finish;
- return undef unless $fetched;
- my ($eventid, $owner_id, $subject, $body, $mailifnobugs) = @{$fetched};
-
- my $owner = Bugzilla::User->new($owner_id);
-
- my $whineatothers = $owner->in_group('bz_canusewhineatothers');
-
- my %user_objects; # Used for keeping track of who has been added
-
- # Get all schedules that match that event ID and are pending
- $sth_schedules_by_event->execute($eventid);
-
- # Add the users from those schedules to the list
- while (my $row = $sth_schedules_by_event->fetch) {
- my ($sid, $mailto_type, $mailto) = @{$row};
-
- # Only bother doing any work if this user has whine permission
- if ($owner->in_group('bz_canusewhines')) {
-
- if ($mailto_type == MAILTO_USER) {
- if (not defined $user_objects{$mailto}) {
- if ($mailto == $owner_id) {
- $user_objects{$mailto} = $owner;
- }
- elsif ($whineatothers) {
- $user_objects{$mailto} = Bugzilla::User->new($mailto);
- }
- }
- }
- elsif ($mailto_type == MAILTO_GROUP) {
- my $sth = $dbh->prepare("SELECT name FROM groups " .
- "WHERE id=?");
- $sth->execute($mailto);
- my $groupname = $sth->fetch->[0];
- my $group_id = Bugzilla::Group::ValidateGroupName(
- $groupname, $owner);
- if ($group_id) {
- my $glist = join(',',
- @{Bugzilla::Group->flatten_group_membership(
- $group_id)});
- $sth = $dbh->prepare("SELECT user_id FROM " .
- "user_group_map " .
- "WHERE group_id IN ($glist)");
- $sth->execute();
- for my $row (@{$sth->fetchall_arrayref}) {
- if (not defined $user_objects{$row->[0]}) {
- $user_objects{$row->[0]} =
- Bugzilla::User->new($row->[0]);
- }
- }
- }
- }
+ my $event = {};
- }
+ # Loop until there's something to return
+ until (scalar keys %{$event}) {
- reset_timer($sid);
- }
+ $dbh->bz_start_transaction();
+
+ # Get the event ID for the first pending schedule
+ $sth_next_scheduled_event->execute;
+ my $fetched = $sth_next_scheduled_event->fetch;
+ $sth_next_scheduled_event->finish;
+ return undef unless $fetched;
+ my ($eventid, $owner_id, $subject, $body, $mailifnobugs) = @{$fetched};
+
+ my $owner = Bugzilla::User->new($owner_id);
+
+ my $whineatothers = $owner->in_group('bz_canusewhineatothers');
- $dbh->bz_commit_transaction();
-
- # Only set $event if the user is allowed to do whining
- if ($owner->in_group('bz_canusewhines')) {
- my @users = values %user_objects;
- $event = {
- 'eventid' => $eventid,
- 'author' => $owner,
- 'mailto' => \@users,
- 'subject' => $subject,
- 'body' => $body,
- 'mailifnobugs' => $mailifnobugs,
- };
+ my %user_objects; # Used for keeping track of who has been added
+
+ # Get all schedules that match that event ID and are pending
+ $sth_schedules_by_event->execute($eventid);
+
+ # Add the users from those schedules to the list
+ while (my $row = $sth_schedules_by_event->fetch) {
+ my ($sid, $mailto_type, $mailto) = @{$row};
+
+ # Only bother doing any work if this user has whine permission
+ if ($owner->in_group('bz_canusewhines')) {
+
+ if ($mailto_type == MAILTO_USER) {
+ if (not defined $user_objects{$mailto}) {
+ if ($mailto == $owner_id) {
+ $user_objects{$mailto} = $owner;
+ }
+ elsif ($whineatothers) {
+ $user_objects{$mailto} = Bugzilla::User->new($mailto);
+ }
+ }
}
+ elsif ($mailto_type == MAILTO_GROUP) {
+ my $sth = $dbh->prepare("SELECT name FROM groups " . "WHERE id=?");
+ $sth->execute($mailto);
+ my $groupname = $sth->fetch->[0];
+ my $group_id = Bugzilla::Group::ValidateGroupName($groupname, $owner);
+ if ($group_id) {
+ my $glist = join(',', @{Bugzilla::Group->flatten_group_membership($group_id)});
+ $sth = $dbh->prepare(
+ "SELECT user_id FROM " . "user_group_map " . "WHERE group_id IN ($glist)");
+ $sth->execute();
+ for my $row (@{$sth->fetchall_arrayref}) {
+ if (not defined $user_objects{$row->[0]}) {
+ $user_objects{$row->[0]} = Bugzilla::User->new($row->[0]);
+ }
+ }
+ }
+ }
+
+ }
+
+ reset_timer($sid);
}
- return $event;
+
+ $dbh->bz_commit_transaction();
+
+ # Only set $event if the user is allowed to do whining
+ if ($owner->in_group('bz_canusewhines')) {
+ my @users = values %user_objects;
+ $event = {
+ 'eventid' => $eventid,
+ 'author' => $owner,
+ 'mailto' => \@users,
+ 'subject' => $subject,
+ 'body' => $body,
+ 'mailifnobugs' => $mailifnobugs,
+ };
+ }
+ }
+ return $event;
}
# Run the queries for each event
@@ -293,38 +301,38 @@ sub get_next_event {
# mailifnobugs (send message even if there are no query or query results)
while (my $event = get_next_event) {
- my $eventid = $event->{'eventid'};
-
- # We loop for each target user because some of the queries will be using
- # subjective pronouns
- $dbh = Bugzilla->switch_to_shadow_db();
- for my $target (@{$event->{'mailto'}}) {
- my $args = {
- 'subject' => $event->{'subject'},
- 'body' => $event->{'body'},
- 'eventid' => $event->{'eventid'},
- 'author' => $event->{'author'},
- 'recipient' => $target,
- 'from' => $fromaddress,
- };
-
- # run the queries for this schedule
- my $queries = run_queries($args);
-
- # If mailifnobugs is false, make sure there is something to output
- if (!$event->{'mailifnobugs'}) {
- my $there_are_bugs = 0;
- for my $query (@{$queries}) {
- $there_are_bugs = 1 if scalar @{$query->{'bugs'}};
- }
- next unless $there_are_bugs;
- }
+ my $eventid = $event->{'eventid'};
+
+ # We loop for each target user because some of the queries will be using
+ # subjective pronouns
+ $dbh = Bugzilla->switch_to_shadow_db();
+ for my $target (@{$event->{'mailto'}}) {
+ my $args = {
+ 'subject' => $event->{'subject'},
+ 'body' => $event->{'body'},
+ 'eventid' => $event->{'eventid'},
+ 'author' => $event->{'author'},
+ 'recipient' => $target,
+ 'from' => $fromaddress,
+ };
+
+ # run the queries for this schedule
+ my $queries = run_queries($args);
+
+ # If mailifnobugs is false, make sure there is something to output
+ if (!$event->{'mailifnobugs'}) {
+ my $there_are_bugs = 0;
+ for my $query (@{$queries}) {
+ $there_are_bugs = 1 if scalar @{$query->{'bugs'}};
+ }
+ next unless $there_are_bugs;
+ }
- $args->{'queries'} = $queries;
+ $args->{'queries'} = $queries;
- mail($args);
- }
- $dbh = Bugzilla->switch_to_main_db();
+ mail($args);
+ }
+ $dbh = Bugzilla->switch_to_main_db();
}
################################################################################
@@ -347,111 +355,114 @@ while (my $event = get_next_event) {
# - recipient user object for the recipient
# - author user object of the person who created the whine event
sub mail {
- my $args = shift;
- # Don't send mail to someone whose bugmail notification is disabled.
- return if $args->{recipient}->email_disabled;
-
- $args->{to_user} = $args->{recipient};
- MessageToMTA(generate_email(
- $args,
- {
- header => 'whine/header.txt.tmpl',
- text => 'whine/mail.txt.tmpl',
- html => 'whine/mail.html.tmpl',
- }
- ));
+ my $args = shift;
+
+ # Don't send mail to someone whose bugmail notification is disabled.
+ return if $args->{recipient}->email_disabled;
+
+ $args->{to_user} = $args->{recipient};
+ MessageToMTA(generate_email(
+ $args,
+ {
+ header => 'whine/header.txt.tmpl',
+ text => 'whine/mail.txt.tmpl',
+ html => 'whine/mail.html.tmpl',
+ }
+ ));
}
# run_queries runs all of the queries associated with a schedule ID, adding
# the results to $args or mailing off the template if a query wants individual
# messages for each bug
sub run_queries {
- my $args = shift;
-
- my $return_queries = [];
-
- $sth_run_queries->execute($args->{'eventid'});
- my @queries = ();
- for (@{$sth_run_queries->fetchall_arrayref}) {
- push(@queries,
- {
- 'name' => $_->[0],
- 'title' => $_->[1],
- 'onemailperbug' => $_->[2],
- 'columnlist' => [],
- 'bugs' => [],
- }
- );
+ my $args = shift;
+
+ my $return_queries = [];
+
+ $sth_run_queries->execute($args->{'eventid'});
+ my @queries = ();
+ for (@{$sth_run_queries->fetchall_arrayref}) {
+ push(
+ @queries,
+ {
+ 'name' => $_->[0],
+ 'title' => $_->[1],
+ 'onemailperbug' => $_->[2],
+ 'columnlist' => [],
+ 'bugs' => [],
+ }
+ );
+ }
+
+ foreach my $thisquery (@queries) {
+ next unless $thisquery->{'name'}; # named query is blank
+
+ my $savedquery = get_query($thisquery->{'name'}, $args->{'author'});
+ next unless $savedquery; # silently ignore missing queries
+
+ # Execute the saved query
+ my @searchfields = ('bug_id', DEFAULT_COLUMN_LIST);
+
+ # A new Bugzilla::CGI object needs to be created to allow
+ # Bugzilla::Search to execute a saved query. It's exceedingly weird,
+ # but that's how it works.
+ my $searchparams = new Bugzilla::CGI($savedquery);
+
+ # Use the columnlist for the saved query, if it exists, and make
+ # sure bug_id is always in the list.
+ if (my $columnlist = $searchparams->param('columnlist')) {
+ @searchfields = split(/[\s,]+/, $columnlist);
+ unshift(@searchfields, 'bug_id') unless grep { $_ eq 'bug_id' } @searchfields;
+ }
+ push @{$thisquery->{'columnlist'}}, @searchfields;
+
+ my @orderstrings = split(/,\s*/, $searchparams->param('order') || '');
+ my $search = new Bugzilla::Search(
+ 'fields' => \@searchfields,
+ 'params' => scalar $searchparams->Vars,
+ 'user' => $args->{'recipient'}, # the search runs as the recipient
+ 'order' => \@orderstrings
+ );
+
+ # If a query fails for whatever reason, it shouldn't kill the script.
+ my $data = eval { $search->data };
+ if ($@) {
+ print STDERR get_text('whine_query_failed',
+ {query_name => $thisquery->{'name'}, author => $args->{'author'}, reason => $@})
+ . "\n";
+ next;
}
- foreach my $thisquery (@queries) {
- next unless $thisquery->{'name'}; # named query is blank
-
- my $savedquery = get_query($thisquery->{'name'}, $args->{'author'});
- next unless $savedquery; # silently ignore missing queries
-
- # Execute the saved query
- my @searchfields = ('bug_id', DEFAULT_COLUMN_LIST);
-
- # A new Bugzilla::CGI object needs to be created to allow
- # Bugzilla::Search to execute a saved query. It's exceedingly weird,
- # but that's how it works.
- my $searchparams = new Bugzilla::CGI($savedquery);
-
- # Use the columnlist for the saved query, if it exists, and make
- # sure bug_id is always in the list.
- if (my $columnlist = $searchparams->param('columnlist')) {
- @searchfields = split(/[\s,]+/, $columnlist);
- unshift(@searchfields, 'bug_id') unless grep { $_ eq 'bug_id' } @searchfields;
- }
- push @{$thisquery->{'columnlist'}}, @searchfields;
-
- my @orderstrings = split(/,\s*/, $searchparams->param('order') || '');
- my $search = new Bugzilla::Search(
- 'fields' => \@searchfields,
- 'params' => scalar $searchparams->Vars,
- 'user' => $args->{'recipient'}, # the search runs as the recipient
- 'order' => \@orderstrings
- );
- # If a query fails for whatever reason, it shouldn't kill the script.
- my $data = eval { $search->data };
- if ($@) {
- print STDERR get_text('whine_query_failed', { query_name => $thisquery->{'name'},
- author => $args->{'author'},
- reason => $@ }) . "\n";
- next;
- }
-
- foreach my $row (@$data) {
- my $bug = {};
- for my $field (@searchfields) {
- my $fieldname = $field;
- $fieldname =~ s/^bugs\.//; # No need for bugs.whatever
- $bug->{$fieldname} = shift @$row;
- }
-
- if ($thisquery->{'onemailperbug'}) {
- $args->{'queries'} = [
- {
- 'name' => $thisquery->{'name'},
- 'title' => $thisquery->{'title'},
- 'columnlist' => $thisquery->{'columnlist'},
- 'bugs' => [ $bug ],
- },
- ];
- mail($args);
- delete $args->{'queries'};
- }
- else { # It belongs in one message with any other lists
- push @{$thisquery->{'bugs'}}, $bug;
- }
- }
- if (!$thisquery->{'onemailperbug'} && @{$thisquery->{'bugs'}}) {
- push @{$return_queries}, $thisquery;
- }
+ foreach my $row (@$data) {
+ my $bug = {};
+ for my $field (@searchfields) {
+ my $fieldname = $field;
+ $fieldname =~ s/^bugs\.//; # No need for bugs.whatever
+ $bug->{$fieldname} = shift @$row;
+ }
+
+ if ($thisquery->{'onemailperbug'}) {
+ $args->{'queries'} = [
+ {
+ 'name' => $thisquery->{'name'},
+ 'title' => $thisquery->{'title'},
+ 'columnlist' => $thisquery->{'columnlist'},
+ 'bugs' => [$bug],
+ },
+ ];
+ mail($args);
+ delete $args->{'queries'};
+ }
+ else { # It belongs in one message with any other lists
+ push @{$thisquery->{'bugs'}}, $bug;
+ }
+ }
+ if (!$thisquery->{'onemailperbug'} && @{$thisquery->{'bugs'}}) {
+ push @{$return_queries}, $thisquery;
}
+ }
- return $return_queries;
+ return $return_queries;
}
# get_query gets the namedquery. It's similar to LookupNamedQuery (in
@@ -459,12 +470,12 @@ sub run_queries {
# individual named queries might go away without the whine_queries that point
# to them being removed.
sub get_query {
- my ($name, $user) = @_;
- my $qname = $name;
- $sth_get_query->execute($user->id, $qname);
- my $fetched = $sth_get_query->fetch;
- $sth_get_query->finish;
- return $fetched ? $fetched->[0] : '';
+ my ($name, $user) = @_;
+ my $qname = $name;
+ $sth_get_query->execute($user->id, $qname);
+ my $fetched = $sth_get_query->fetch;
+ $sth_get_query->finish;
+ return $fetched ? $fetched->[0] : '';
}
# check_today gets a run day from the schedule and sees if it matches today
@@ -476,25 +487,23 @@ sub get_query {
# - 'MF' for every weekday
sub check_today {
- my $run_day = shift;
-
- if (($run_day eq 'MF')
- && ($now_weekday > 0)
- && ($now_weekday < 6)) {
- return 1;
- }
- elsif (
- length($run_day) == 3 &&
- index("SunMonTueWedThuFriSat", $run_day)/3 == $now_weekday) {
- return 1;
- }
- elsif (($run_day eq 'All')
- || (($run_day eq 'last') &&
- ($now_day == $daysinmonth[$now_month] ))
- || ($run_day eq $now_day)) {
- return 1;
- }
- return 0;
+ my $run_day = shift;
+
+ if (($run_day eq 'MF') && ($now_weekday > 0) && ($now_weekday < 6)) {
+ return 1;
+ }
+ elsif (length($run_day) == 3
+ && index("SunMonTueWedThuFriSat", $run_day) / 3 == $now_weekday)
+ {
+ return 1;
+ }
+ elsif (($run_day eq 'All')
+ || (($run_day eq 'last') && ($now_day == $daysinmonth[$now_month]))
+ || ($run_day eq $now_day))
+ {
+ return 1;
+ }
+ return 0;
}
# reset_timer sets the next time a whine is supposed to run, assuming it just
@@ -503,95 +512,101 @@ sub check_today {
# reset_timer does not lock the whine_schedules table. Anything that calls it
# should do that itself.
sub reset_timer {
- my $schedule_id = shift;
-
- # Schedules may not be executed more than once for each invocation of
- # whine.pl -- there are legitimate circumstances that can cause this, like
- # a set of whines that take a very long time to execute, so it's done
- # quietly.
- if (grep($_ == $schedule_id, @seen_schedules)) {
- null_schedule($schedule_id);
- return;
+ my $schedule_id = shift;
+
+ # Schedules may not be executed more than once for each invocation of
+ # whine.pl -- there are legitimate circumstances that can cause this, like
+ # a set of whines that take a very long time to execute, so it's done
+ # quietly.
+ if (grep($_ == $schedule_id, @seen_schedules)) {
+ null_schedule($schedule_id);
+ return;
+ }
+ push @seen_schedules, $schedule_id;
+
+ $sth = $dbh->prepare(
+ "SELECT run_day, run_time FROM whine_schedules " . "WHERE id=?");
+ $sth->execute($schedule_id);
+ my ($run_day, $run_time) = $sth->fetchrow_array;
+
+ # It may happen that the run_time field is NULL or blank due to
+ # a bug in editwhines.cgi when this field was initially 0.
+ $run_time ||= 0;
+
+ my $run_today = 0;
+ my $minute_offset = 0;
+
+ # If the schedule is to run today, and it runs many times per day,
+ # it shall be set to run immediately.
+ $run_today = &check_today($run_day);
+ if (($run_today) && ($run_time !~ /^\d+$/)) {
+
+ # The default of 60 catches any bad value
+ my $minute_interval = 60;
+ if ($run_time =~ /^(\d+)min$/i) {
+ $minute_interval = $1;
}
- push @seen_schedules, $schedule_id;
-
- $sth = $dbh->prepare( "SELECT run_day, run_time FROM whine_schedules " .
- "WHERE id=?" );
- $sth->execute($schedule_id);
- my ($run_day, $run_time) = $sth->fetchrow_array;
-
- # It may happen that the run_time field is NULL or blank due to
- # a bug in editwhines.cgi when this field was initially 0.
- $run_time ||= 0;
-
- my $run_today = 0;
- my $minute_offset = 0;
-
- # If the schedule is to run today, and it runs many times per day,
- # it shall be set to run immediately.
- $run_today = &check_today($run_day);
- if (($run_today) && ($run_time !~ /^\d+$/)) {
- # The default of 60 catches any bad value
- my $minute_interval = 60;
- if ($run_time =~ /^(\d+)min$/i) {
- $minute_interval = $1;
- }
- # set the minute offset to the next interval point
- $minute_offset = $minute_interval - ($now_minute % $minute_interval);
- }
- elsif (($run_today) && ($run_time > $now_hour)) {
- # timed event for later today
- # (This should only happen if, for example, an 11pm scheduled event
- # didn't happen until after midnight)
- $minute_offset = (60 * ($run_time - $now_hour)) - $now_minute;
- }
- else {
- # it's not something that runs later today.
- $minute_offset = 0;
-
- # Set the target time if it's a specific hour
- my $target_time = ($run_time =~ /^\d+$/) ? $run_time : 0;
-
- my $nextdate = &get_next_date($run_day);
- my $run_next = $dbh->sql_date_math('('
- . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY')
- . ')', '+', '?', 'HOUR');
- $sth = $dbh->prepare("UPDATE whine_schedules " .
- "SET run_next = $run_next
- WHERE id = ?");
- $sth->execute($nextdate, $target_time, $schedule_id);
- return;
- }
-
- if ($minute_offset > 0) {
- # Scheduling is done in terms of whole minutes.
-
- my $next_run = $dbh->selectrow_array(
- 'SELECT ' . $dbh->sql_date_math('NOW()', '+', '?', 'MINUTE'),
- undef, $minute_offset);
- $next_run = format_time($next_run, "%Y-%m-%d %R");
-
- $sth = $dbh->prepare("UPDATE whine_schedules " .
- "SET run_next = ? WHERE id = ?");
- $sth->execute($next_run, $schedule_id);
- } else {
- # The minute offset is zero or less, which is not supposed to happen.
- # complain to STDERR
- null_schedule($schedule_id);
- print STDERR "Error: bad minute_offset for schedule ID $schedule_id\n";
- }
+ # set the minute offset to the next interval point
+ $minute_offset = $minute_interval - ($now_minute % $minute_interval);
+ }
+ elsif (($run_today) && ($run_time > $now_hour)) {
+
+ # timed event for later today
+ # (This should only happen if, for example, an 11pm scheduled event
+ # didn't happen until after midnight)
+ $minute_offset = (60 * ($run_time - $now_hour)) - $now_minute;
+ }
+ else {
+ # it's not something that runs later today.
+ $minute_offset = 0;
+
+ # Set the target time if it's a specific hour
+ my $target_time = ($run_time =~ /^\d+$/) ? $run_time : 0;
+
+ my $nextdate = &get_next_date($run_day);
+ my $run_next
+ = $dbh->sql_date_math(
+ '(' . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY') . ')',
+ '+', '?', 'HOUR');
+ $sth = $dbh->prepare(
+ "UPDATE whine_schedules " . "SET run_next = $run_next
+ WHERE id = ?"
+ );
+ $sth->execute($nextdate, $target_time, $schedule_id);
+ return;
+ }
+
+ if ($minute_offset > 0) {
+
+ # Scheduling is done in terms of whole minutes.
+
+ my $next_run
+ = $dbh->selectrow_array(
+ 'SELECT ' . $dbh->sql_date_math('NOW()', '+', '?', 'MINUTE'),
+ undef, $minute_offset);
+ $next_run = format_time($next_run, "%Y-%m-%d %R");
+
+ $sth
+ = $dbh->prepare("UPDATE whine_schedules " . "SET run_next = ? WHERE id = ?");
+ $sth->execute($next_run, $schedule_id);
+ }
+ else {
+ # The minute offset is zero or less, which is not supposed to happen.
+ # complain to STDERR
+ null_schedule($schedule_id);
+ print STDERR "Error: bad minute_offset for schedule ID $schedule_id\n";
+ }
}
# null_schedule is used to safeguard against infinite loops. Schedules with
# run_next set to NULL will not be available to get_next_event until they are
# rescheduled, which only happens when whine.pl starts.
sub null_schedule {
- my $schedule_id = shift;
- $sth = $dbh->prepare("UPDATE whine_schedules " .
- "SET run_next = NULL " .
- "WHERE id=?");
- $sth->execute($schedule_id);
+ my $schedule_id = shift;
+ $sth = $dbh->prepare(
+ "UPDATE whine_schedules " . "SET run_next = NULL " . "WHERE id=?");
+ $sth->execute($schedule_id);
}
# get_next_date determines the difference in days between now and the next
@@ -600,56 +615,58 @@ sub null_schedule {
# It takes a run_day argument (see check_today, above, for an explanation),
# and returns an integer, representing a number of days.
sub get_next_date {
- my $day = shift;
+ my $day = shift;
+
+ my $add_days = 0;
- my $add_days = 0;
+ if ($day eq 'All') {
+ $add_days = 1;
+ }
+ elsif ($day eq 'last') {
- if ($day eq 'All') {
- $add_days = 1;
+ # next_date should contain the last day of this month, or next month
+ # if it's today
+ if ($daysinmonth[$now_month] == $now_day) {
+ my $month = $now_month + 1;
+ $month = 1 if $month > 12;
+ $add_days = $daysinmonth[$month] + 1;
}
- elsif ($day eq 'last') {
- # next_date should contain the last day of this month, or next month
- # if it's today
- if ($daysinmonth[$now_month] == $now_day) {
- my $month = $now_month + 1;
- $month = 1 if $month > 12;
- $add_days = $daysinmonth[$month] + 1;
- }
- else {
- $add_days = $daysinmonth[$now_month] - $now_day;
- }
+ else {
+ $add_days = $daysinmonth[$now_month] - $now_day;
}
- elsif ($day eq 'MF') { # any day Monday through Friday
- if ($now_weekday < 5) { # Sun-Thurs
- $add_days = 1;
- }
- elsif ($now_weekday == 5) { # Friday
- $add_days = 3;
- }
- else { # it's 6, Saturday
- $add_days = 2;
- }
+ }
+ elsif ($day eq 'MF') { # any day Monday through Friday
+ if ($now_weekday < 5) { # Sun-Thurs
+ $add_days = 1;
+ }
+ elsif ($now_weekday == 5) { # Friday
+ $add_days = 3;
+ }
+ else { # it's 6, Saturday
+ $add_days = 2;
}
- elsif ($day !~ /^\d+$/) { # A specific day of the week
+ }
+ elsif ($day !~ /^\d+$/) { # A specific day of the week
# The default is used if there is a bad value in the database, in
# which case we mark it to a less-popular day (Sunday)
- my $day_num = 0;
+ my $day_num = 0;
- if (length($day) == 3) {
- $day_num = (index("SunMonTueWedThuFriSat", $day)/3) or 0;
- }
+ if (length($day) == 3) {
+ $day_num = (index("SunMonTueWedThuFriSat", $day) / 3) or 0;
+ }
- $add_days = $day_num - $now_weekday;
- if ($add_days <= 0) { # it's next week
- $add_days += 7;
- }
+ $add_days = $day_num - $now_weekday;
+ if ($add_days <= 0) { # it's next week
+ $add_days += 7;
}
- else { # it's a number, so we set it for that calendar day
- $add_days = $day - $now_day;
- # If it's already beyond that day this month, set it to the next one
- if ($add_days <= 0) {
- $add_days += $daysinmonth[$now_month];
- }
+ }
+ else { # it's a number, so we set it for that calendar day
+ $add_days = $day - $now_day;
+
+ # If it's already beyond that day this month, set it to the next one
+ if ($add_days <= 0) {
+ $add_days += $daysinmonth[$now_month];
}
- return $add_days;
+ }
+ return $add_days;
}
diff --git a/whineatnews.pl b/whineatnews.pl
index 4812b4cfe..07a74a387 100755
--- a/whineatnews.pl
+++ b/whineatnews.pl
@@ -7,8 +7,8 @@
# defined by the Mozilla Public License, v. 2.0.
-# This is a script suitable for running once a day from a cron job. It
-# looks at all the bugs, and sends whiny mail to anyone who has a bug
+# This is a script suitable for running once a day from a cron job. It
+# looks at all the bugs, and sends whiny mail to anyone who has a bug
# assigned to them that has status CONFIRMED, NEW, or REOPENED that has not
# been touched for more than the number of days specified in the whinedays
# param. (We have NEW and REOPENED in there to keep compatibility with old
@@ -28,58 +28,59 @@ use Bugzilla::User;
# Whining is disabled if whinedays is zero
exit unless Bugzilla->params->{'whinedays'} >= 1;
-my $dbh = Bugzilla->dbh;
+my $dbh = Bugzilla->dbh;
my $query = q{SELECT bug_id, short_desc, login_name
FROM bugs
INNER JOIN profiles
ON userid = assigned_to
WHERE bug_status IN (?,?,?)
AND disable_mail = 0
- AND } . $dbh->sql_to_days('NOW()') . " - " .
- $dbh->sql_to_days('delta_ts') . " > " .
- Bugzilla->params->{'whinedays'} .
- " ORDER BY bug_id";
+ AND }
+ . $dbh->sql_to_days('NOW()') . " - "
+ . $dbh->sql_to_days('delta_ts') . " > "
+ . Bugzilla->params->{'whinedays'}
+ . " ORDER BY bug_id";
my %bugs;
my %desc;
-my $slt_bugs = $dbh->selectall_arrayref($query, undef, 'CONFIRMED', 'NEW',
- 'REOPENED');
+my $slt_bugs
+ = $dbh->selectall_arrayref($query, undef, 'CONFIRMED', 'NEW', 'REOPENED');
foreach my $bug (@$slt_bugs) {
- my ($id, $desc, $email) = @$bug;
- if (!defined $bugs{$email}) {
- $bugs{$email} = [];
- }
- if (!defined $desc{$email}) {
- $desc{$email} = [];
- }
- push @{$bugs{$email}}, $id;
- push @{$desc{$email}}, $desc;
+ my ($id, $desc, $email) = @$bug;
+ if (!defined $bugs{$email}) {
+ $bugs{$email} = [];
+ }
+ if (!defined $desc{$email}) {
+ $desc{$email} = [];
+ }
+ push @{$bugs{$email}}, $id;
+ push @{$desc{$email}}, $desc;
}
foreach my $email (sort (keys %bugs)) {
- my $user = new Bugzilla::User({name => $email});
- next if $user->email_disabled;
+ my $user = new Bugzilla::User({name => $email});
+ next if $user->email_disabled;
- my $vars = {'email' => $email};
+ my $vars = {'email' => $email};
- my @bugs = ();
- foreach my $i (@{$bugs{$email}}) {
- my $bug = {};
- $bug->{'summary'} = shift(@{$desc{$email}});
- $bug->{'id'} = $i;
- push @bugs, $bug;
- }
- $vars->{'bugs'} = \@bugs;
+ my @bugs = ();
+ foreach my $i (@{$bugs{$email}}) {
+ my $bug = {};
+ $bug->{'summary'} = shift(@{$desc{$email}});
+ $bug->{'id'} = $i;
+ push @bugs, $bug;
+ }
+ $vars->{'bugs'} = \@bugs;
- my $msg;
- my $template = Bugzilla->template_inner($user->setting('lang'));
- $template->process("email/whine.txt.tmpl", $vars, \$msg)
- or die($template->error());
+ my $msg;
+ my $template = Bugzilla->template_inner($user->setting('lang'));
+ $template->process("email/whine.txt.tmpl", $vars, \$msg)
+ or die($template->error());
- MessageToMTA($msg);
+ MessageToMTA($msg);
- say "$email " . join(" ", @{$bugs{$email}});
+ say "$email " . join(" ", @{$bugs{$email}});
}
diff --git a/xmlrpc.cgi b/xmlrpc.cgi
index 893bfba5d..c0931cb41 100755
--- a/xmlrpc.cgi
+++ b/xmlrpc.cgi
@@ -16,10 +16,11 @@ use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::WebService::Constants;
+
BEGIN {
- if (!Bugzilla->feature('xmlrpc')) {
- ThrowUserError('feature_disabled', { feature => 'xmlrpc' });
- }
+ if (!Bugzilla->feature('xmlrpc')) {
+ ThrowUserError('feature_disabled', {feature => 'xmlrpc'});
+ }
}
use Bugzilla::WebService::Server::XMLRPC;
@@ -28,6 +29,7 @@ Bugzilla->usage_mode(USAGE_MODE_XMLRPC);
# Fix the error code that SOAP::Lite uses for Perl errors.
local $SOAP::Constants::FAULT_SERVER;
$SOAP::Constants::FAULT_SERVER = ERROR_UNKNOWN_FATAL;
+
# The line above is used, this one is ignored, but SOAP::Lite
# might start using this constant (the correct one) for XML-RPC someday.
local $XMLRPC::Constants::FAULT_SERVER;
@@ -35,8 +37,8 @@ $XMLRPC::Constants::FAULT_SERVER = ERROR_UNKNOWN_FATAL;
local @INC = (bz_locations()->{extensionsdir}, @INC);
my $server = new Bugzilla::WebService::Server::XMLRPC;
-# We use a sub for on_action because that gets us the info about what
-# class is being called. Note that this is a hack--this is technically
+
+# We use a sub for on_action because that gets us the info about what
+# class is being called. Note that this is a hack--this is technically
# for setting SOAPAction, which isn't used by XML-RPC.
-$server->on_action(sub { $server->handle_login(WS_DISPATCH, @_) })
- ->handle();
+$server->on_action(sub { $server->handle_login(WS_DISPATCH, @_) })->handle();
diff --git a/xt/lib/Bugzilla/Test/Search.pm b/xt/lib/Bugzilla/Test/Search.pm
index ca3bba5cf..31ba8bea3 100644
--- a/xt/lib/Bugzilla/Test/Search.pm
+++ b/xt/lib/Bugzilla/Test/Search.pm
@@ -59,8 +59,8 @@ use Scalar::Util qw(blessed);
###############
sub new {
- my ($class, $options) = @_;
- return bless { options => $options }, $class;
+ my ($class, $options) = @_;
+ return bless {options => $options}, $class;
}
#############
@@ -68,198 +68,210 @@ sub new {
#############
sub options { return $_[0]->{options} }
-sub option { return $_[0]->{options}->{$_[1]} }
+sub option { return $_[0]->{options}->{$_[1]} }
sub num_tests {
- my ($self) = @_;
- my @top_operators = $self->top_level_operators;
- my @all_operators = $self->all_operators;
- my $top_operator_tests = $self->_total_operator_tests(\@top_operators);
- my $all_operator_tests = $self->_total_operator_tests(\@all_operators);
-
- my @fields = $self->all_fields;
-
- # Basically, we run TESTS_PER_RUN tests for each field/operator combination.
- my $top_combinations = $top_operator_tests * scalar(@fields);
- my $all_combinations = $all_operator_tests * scalar(@fields);
- # But we also have ORs, for which we run combinations^2 tests.
- my $join_tests = $self->option('long')
- ? ($top_combinations * $all_combinations) : 0;
- # And AND tests, which means we run 2x $join_tests;
- $join_tests = $join_tests * 2;
- # Also, because of NOT tests and Normal tests, we run 3x $top_combinations.
- my $basic_tests = $top_combinations * 3;
- my $operator_field_tests = ($basic_tests + $join_tests) * TESTS_PER_RUN;
-
- # Then we test each field/operator combination for SQL injection.
- my @injection_values = INJECTION_TESTS;
- my $sql_injection_tests = scalar(@fields) * scalar(@top_operators)
- * scalar(@injection_values) * NUM_SEARCH_TESTS;
-
- # This @{ [] } thing is the only reasonable way to get a count out of a
- # constant array.
- my $special_tests = scalar(@{ [SPECIAL_PARAM_TESTS, CUSTOM_SEARCH_TESTS] })
- * TESTS_PER_RUN;
-
- return $operator_field_tests + $sql_injection_tests + $special_tests;
+ my ($self) = @_;
+ my @top_operators = $self->top_level_operators;
+ my @all_operators = $self->all_operators;
+ my $top_operator_tests = $self->_total_operator_tests(\@top_operators);
+ my $all_operator_tests = $self->_total_operator_tests(\@all_operators);
+
+ my @fields = $self->all_fields;
+
+ # Basically, we run TESTS_PER_RUN tests for each field/operator combination.
+ my $top_combinations = $top_operator_tests * scalar(@fields);
+ my $all_combinations = $all_operator_tests * scalar(@fields);
+
+ # But we also have ORs, for which we run combinations^2 tests.
+ my $join_tests
+ = $self->option('long') ? ($top_combinations * $all_combinations) : 0;
+
+ # And AND tests, which means we run 2x $join_tests;
+ $join_tests = $join_tests * 2;
+
+ # Also, because of NOT tests and Normal tests, we run 3x $top_combinations.
+ my $basic_tests = $top_combinations * 3;
+ my $operator_field_tests = ($basic_tests + $join_tests) * TESTS_PER_RUN;
+
+ # Then we test each field/operator combination for SQL injection.
+ my @injection_values = INJECTION_TESTS;
+ my $sql_injection_tests
+ = scalar(@fields)
+ * scalar(@top_operators)
+ * scalar(@injection_values)
+ * NUM_SEARCH_TESTS;
+
+ # This @{ [] } thing is the only reasonable way to get a count out of a
+ # constant array.
+ my $special_tests
+ = scalar(@{[SPECIAL_PARAM_TESTS, CUSTOM_SEARCH_TESTS]}) * TESTS_PER_RUN;
+
+ return $operator_field_tests + $sql_injection_tests + $special_tests;
}
sub _total_operator_tests {
- my ($self, $operators) = @_;
-
- # Some operators have more than one test. Find those ones and add
- # them to the total operator tests
- my $extra_operator_tests;
- foreach my $operator (@$operators) {
- my $tests = TESTS->{$operator};
- next if !$tests;
- my $extra_num = scalar(@$tests) - 1;
- $extra_operator_tests += $extra_num;
- }
- return scalar(@$operators) + $extra_operator_tests;
-
+ my ($self, $operators) = @_;
+
+ # Some operators have more than one test. Find those ones and add
+ # them to the total operator tests
+ my $extra_operator_tests;
+ foreach my $operator (@$operators) {
+ my $tests = TESTS->{$operator};
+ next if !$tests;
+ my $extra_num = scalar(@$tests) - 1;
+ $extra_operator_tests += $extra_num;
+ }
+ return scalar(@$operators) + $extra_operator_tests;
+
}
sub all_operators {
- my ($self) = @_;
- if (not $self->{all_operators}) {
-
- my @operators;
- if (my $limit_operators = $self->option('operators')) {
- @operators = split(',', $limit_operators);
- }
- else {
- @operators = sort (keys %{ Bugzilla::Search::OPERATORS() });
- }
- # "substr" is just a backwards-compatibility operator, same as "substring".
- @operators = grep { $_ ne 'substr' } @operators;
- $self->{all_operators} = \@operators;
+ my ($self) = @_;
+ if (not $self->{all_operators}) {
+
+ my @operators;
+ if (my $limit_operators = $self->option('operators')) {
+ @operators = split(',', $limit_operators);
}
- return @{ $self->{all_operators} };
+ else {
+ @operators = sort (keys %{Bugzilla::Search::OPERATORS()});
+ }
+
+ # "substr" is just a backwards-compatibility operator, same as "substring".
+ @operators = grep { $_ ne 'substr' } @operators;
+ $self->{all_operators} = \@operators;
+ }
+ return @{$self->{all_operators}};
}
sub all_fields {
- my $self = shift;
- if (not $self->{all_fields}) {
- $self->_create_custom_fields();
- my @fields = @{ Bugzilla->fields };
- @fields = sort { $a->name cmp $b->name } @fields;
- $self->{all_fields} = \@fields;
- }
- return @{ $self->{all_fields} };
+ my $self = shift;
+ if (not $self->{all_fields}) {
+ $self->_create_custom_fields();
+ my @fields = @{Bugzilla->fields};
+ @fields = sort { $a->name cmp $b->name } @fields;
+ $self->{all_fields} = \@fields;
+ }
+ return @{$self->{all_fields}};
}
sub top_level_operators {
- my ($self) = @_;
- if (!$self->{top_level_operators}) {
- my @operators;
- my $limit_top = $self->option('top-operators');
- if ($limit_top) {
- @operators = split(',', $limit_top);
- }
- else {
- @operators = $self->all_operators;
- }
- $self->{top_level_operators} = \@operators;
+ my ($self) = @_;
+ if (!$self->{top_level_operators}) {
+ my @operators;
+ my $limit_top = $self->option('top-operators');
+ if ($limit_top) {
+ @operators = split(',', $limit_top);
+ }
+ else {
+ @operators = $self->all_operators;
}
- return @{ $self->{top_level_operators} };
+ $self->{top_level_operators} = \@operators;
+ }
+ return @{$self->{top_level_operators}};
}
sub text_fields {
- my ($self) = @_;
- my @text_fields = grep { $_->type == FIELD_TYPE_TEXTAREA
- or $_->type == FIELD_TYPE_FREETEXT } $self->all_fields;
- @text_fields = map { $_->name } @text_fields;
- push(@text_fields, qw(short_desc status_whiteboard bug_file_loc see_also));
- return @text_fields;
+ my ($self) = @_;
+ my @text_fields
+ = grep { $_->type == FIELD_TYPE_TEXTAREA or $_->type == FIELD_TYPE_FREETEXT }
+ $self->all_fields;
+ @text_fields = map { $_->name } @text_fields;
+ push(@text_fields, qw(short_desc status_whiteboard bug_file_loc see_also));
+ return @text_fields;
}
sub bugs {
- my $self = shift;
- $self->{bugs} ||= [map { $self->_create_one_bug($_) } (1..NUM_BUGS)];
- return @{ $self->{bugs} };
+ my $self = shift;
+ $self->{bugs} ||= [map { $self->_create_one_bug($_) } (1 .. NUM_BUGS)];
+ return @{$self->{bugs}};
}
# Get a numbered bug.
sub bug {
- my ($self, $number) = @_;
- return ($self->bugs)[$number - 1];
+ my ($self, $number) = @_;
+ return ($self->bugs)[$number - 1];
}
sub admin {
- my $self = shift;
- if (!$self->{admin_user}) {
- my $admin = create_user("admin");
- Bugzilla::Install::make_admin($admin);
- $self->{admin_user} = $admin;
- }
- # We send back a fresh object every time, to make sure that group
- # memberships are always up-to-date.
- return new Bugzilla::User($self->{admin_user}->id);
+ my $self = shift;
+ if (!$self->{admin_user}) {
+ my $admin = create_user("admin");
+ Bugzilla::Install::make_admin($admin);
+ $self->{admin_user} = $admin;
+ }
+
+ # We send back a fresh object every time, to make sure that group
+ # memberships are always up-to-date.
+ return new Bugzilla::User($self->{admin_user}->id);
}
sub nobody {
- my $self = shift;
- $self->{nobody} ||= Bugzilla::Group->create({ name => "nobody-" . random(),
- description => "Nobody", isbuggroup => 1 });
- return $self->{nobody};
+ my $self = shift;
+ $self->{nobody} ||= Bugzilla::Group->create(
+ {name => "nobody-" . random(), description => "Nobody", isbuggroup => 1});
+ return $self->{nobody};
}
+
sub everybody {
- my ($self) = @_;
- $self->{everybody} ||= create_group('To The Limit');
- return $self->{everybody};
+ my ($self) = @_;
+ $self->{everybody} ||= create_group('To The Limit');
+ return $self->{everybody};
}
sub bug_create_value {
- my ($self, $number, $field) = @_;
- $field = $field->name if blessed($field);
- if ($number == 6 and $field ne 'alias') {
- $number = 1;
- }
- my $extra_values = $self->_extra_bug_create_values->{$number};
- if (exists $extra_values->{$field}) {
- return $extra_values->{$field};
- }
- return $self->_bug_create_values->{$number}->{$field};
+ my ($self, $number, $field) = @_;
+ $field = $field->name if blessed($field);
+ if ($number == 6 and $field ne 'alias') {
+ $number = 1;
+ }
+ my $extra_values = $self->_extra_bug_create_values->{$number};
+ if (exists $extra_values->{$field}) {
+ return $extra_values->{$field};
+ }
+ return $self->_bug_create_values->{$number}->{$field};
}
+
sub bug_update_value {
- my ($self, $number, $field) = @_;
- $field = $field->name if blessed($field);
- if ($number == 6 and $field ne 'alias') {
- $number = 1;
- }
- return $self->_bug_update_values->{$number}->{$field};
+ my ($self, $number, $field) = @_;
+ $field = $field->name if blessed($field);
+ if ($number == 6 and $field ne 'alias') {
+ $number = 1;
+ }
+ return $self->_bug_update_values->{$number}->{$field};
}
# Values used to create the bugs.
sub _bug_create_values {
- my $self = shift;
- return $self->{bug_create_values} if $self->{bug_create_values};
- my %values;
- foreach my $number (1..NUM_BUGS) {
- $values{$number} = $self->_create_field_values($number, 'for create');
- }
- $self->{bug_create_values} = \%values;
- return $self->{bug_create_values};
+ my $self = shift;
+ return $self->{bug_create_values} if $self->{bug_create_values};
+ my %values;
+ foreach my $number (1 .. NUM_BUGS) {
+ $values{$number} = $self->_create_field_values($number, 'for create');
+ }
+ $self->{bug_create_values} = \%values;
+ return $self->{bug_create_values};
}
+
# Values as they existed on the bug, at creation time. Used by the
# changedfrom tests.
sub _extra_bug_create_values {
- my $self = shift;
- $self->{extra_bug_create_values} ||= { map { $_ => {} } (1..NUM_BUGS) };
- return $self->{extra_bug_create_values};
+ my $self = shift;
+ $self->{extra_bug_create_values} ||= {map { $_ => {} } (1 .. NUM_BUGS)};
+ return $self->{extra_bug_create_values};
}
# Values used to update the bugs after they are created.
sub _bug_update_values {
- my $self = shift;
- return $self->{bug_update_values} if $self->{bug_update_values};
- my %values;
- foreach my $number (1..NUM_BUGS) {
- $values{$number} = $self->_create_field_values($number);
- }
- $self->{bug_update_values} = \%values;
- return $self->{bug_update_values};
+ my $self = shift;
+ return $self->{bug_update_values} if $self->{bug_update_values};
+ my %values;
+ foreach my $number (1 .. NUM_BUGS) {
+ $values{$number} = $self->_create_field_values($number);
+ }
+ $self->{bug_update_values} = \%values;
+ return $self->{bug_update_values};
}
##############################
@@ -267,8 +279,8 @@ sub _bug_update_values {
##############################
sub random {
- $_[0] ||= FIELD_SIZE;
- generate_random_password(@_);
+ $_[0] ||= FIELD_SIZE;
+ generate_random_password(@_);
}
# We need to use a custom timestamp for each create() and update(),
@@ -276,53 +288,52 @@ sub random {
# for the entire transaction, and we need each created bug to have
# its own creation_ts and delta_ts.
sub timestamp {
- my ($day, $second) = @_;
- return DateTime->new(
- year => 2037,
- month => 1,
- day => $day,
- hour => 12,
- minute => $second,
- second => 0,
- # We make it floating because the timezone doesn't matter for our uses,
- # and we want totally consistent behavior across all possible machines.
- time_zone => 'floating',
- );
+ my ($day, $second) = @_;
+ return DateTime->new(
+ year => 2037,
+ month => 1,
+ day => $day,
+ hour => 12,
+ minute => $second,
+ second => 0,
+
+ # We make it floating because the timezone doesn't matter for our uses,
+ # and we want totally consistent behavior across all possible machines.
+ time_zone => 'floating',
+ );
}
sub create_keyword {
- my ($number) = @_;
- return Bugzilla::Keyword->create({
- name => "$number-keyword-" . random(),
- description => "Keyword $number" });
+ my ($number) = @_;
+ return Bugzilla::Keyword->create(
+ {name => "$number-keyword-" . random(), description => "Keyword $number"});
}
sub create_user {
- my ($prefix) = @_;
- my $user_name = $prefix . '-' . random(15) . "@" . random(12)
- . "." . random(3);
- my $user_realname = $prefix . '-' . random();
- my $user = Bugzilla::User->create({
- login_name => $user_name,
- realname => $user_realname,
- cryptpassword => '*',
- });
- return $user;
+ my ($prefix) = @_;
+ my $user_name = $prefix . '-' . random(15) . "@" . random(12) . "." . random(3);
+ my $user_realname = $prefix . '-' . random();
+ my $user = Bugzilla::User->create(
+ {login_name => $user_name, realname => $user_realname, cryptpassword => '*',});
+ return $user;
}
sub create_group {
- my ($prefix) = @_;
- return Bugzilla::Group->create({
- name => "$prefix-group-" . random(), description => "Everybody $prefix",
- userregexp => '.*', isbuggroup => 1 });
+ my ($prefix) = @_;
+ return Bugzilla::Group->create({
+ name => "$prefix-group-" . random(),
+ description => "Everybody $prefix",
+ userregexp => '.*',
+ isbuggroup => 1
+ });
}
sub create_legal_value {
- my ($field, $number) = @_;
- my $type = Bugzilla::Field::Choice->type($field);
- my $field_name = $field->name;
- return $type->create({ value => "$number-$field_name-" . random(),
- is_open => 0 });
+ my ($field, $number) = @_;
+ my $type = Bugzilla::Field::Choice->type($field);
+ my $field_name = $field->name;
+ return $type->create(
+ {value => "$number-$field_name-" . random(), is_open => 0});
}
#########################
@@ -330,22 +341,22 @@ sub create_legal_value {
#########################
sub _create_custom_fields {
- my ($self) = @_;
- return if !$self->option('add-custom-fields');
-
- while (my ($type, $name) = each %{ CUSTOM_FIELDS() }) {
- my $exists = new Bugzilla::Field({ name => $name });
- next if $exists;
- Bugzilla::Field->create({
- name => $name,
- type => $type,
- description => "Search Test Field $name",
- enter_bug => 1,
- custom => 1,
- buglist => 1,
- is_mandatory => 0,
- });
- }
+ my ($self) = @_;
+ return if !$self->option('add-custom-fields');
+
+ while (my ($type, $name) = each %{CUSTOM_FIELDS()}) {
+ my $exists = new Bugzilla::Field({name => $name});
+ next if $exists;
+ Bugzilla::Field->create({
+ name => $name,
+ type => $type,
+ description => "Search Test Field $name",
+ enter_bug => 1,
+ custom => 1,
+ buglist => 1,
+ is_mandatory => 0,
+ });
+ }
}
########################
@@ -353,221 +364,232 @@ sub _create_custom_fields {
########################
sub _create_field_values {
- my ($self, $number, $for_create) = @_;
- my $dbh = Bugzilla->dbh;
-
- Bugzilla->set_user($self->admin);
-
- my @selects = grep { $_->is_select } $self->all_fields;
- my %values;
- foreach my $field (@selects) {
- next if $field->is_abnormal;
- $values{$field->name} = create_legal_value($field, $number)->name;
- }
-
- my $group = create_group($number);
- $values{groups} = [$group->name];
-
- $values{'keywords'} = create_keyword($number)->name;
-
- foreach my $field (qw(assigned_to qa_contact reporter cc)) {
- $values{$field} = create_user("$number-$field")->login;
- }
-
- my $classification = Bugzilla::Classification->create(
- { name => "$number-classification-" . random() });
- $classification = $classification->name;
-
- my $version = "$number-version-" . random();
- my $milestone = "$number-tm-" . random(15);
- my $product = Bugzilla::Product->create({
- name => "$number-product-" . random(),
- description => 'Created by t/search.t',
- defaultmilestone => $milestone,
- classification => $classification,
- version => $version,
- allows_unconfirmed => 1,
- });
- foreach my $item ($group, $self->nobody) {
- $product->set_group_controls($item,
- { membercontrol => CONTROLMAPSHOWN,
- othercontrol => CONTROLMAPNA });
- }
- # $product->update() is called lower down.
- my $component = Bugzilla::Component->create({
- product => $product, name => "$number-component-" . random(),
- initialowner => create_user("$number-defaultowner")->login,
- initialqacontact => create_user("$number-defaultqa")->login,
- initial_cc => [create_user("$number-initcc")->login],
- description => "Component $number" });
-
- $values{'product'} = $product->name;
- $values{'component'} = $component->name;
- $values{'target_milestone'} = $milestone;
- $values{'version'} = $version;
-
- foreach my $field ($self->text_fields) {
- # We don't add a - after $field for the text fields, because
- # if we do, fulltext searching for short_desc pulls out
- # "short_desc" as a word and matches it in every bug.
- my $value = "$number-$field" . random();
- if ($field eq 'bug_file_loc' or $field eq 'see_also') {
- $value = "http://$value-" . random(3)
- . "/show_bug.cgi?id=$number";
- }
- $values{$field} = $value;
- }
- $values{'tag'} = ["$number-tag-" . random()];
-
- my @date_fields = grep { $_->type == FIELD_TYPE_DATETIME } $self->all_fields;
- foreach my $field (@date_fields) {
- # We use 03 as the month because that differs from our creation_ts,
- # delta_ts, and deadline. (It's nice to have recognizable values
- # for each field when debugging.)
- my $second = $for_create ? $number : $number + 1;
- $values{$field->name} = "2037-03-0$number 12:34:0$second";
+ my ($self, $number, $for_create) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ Bugzilla->set_user($self->admin);
+
+ my @selects = grep { $_->is_select } $self->all_fields;
+ my %values;
+ foreach my $field (@selects) {
+ next if $field->is_abnormal;
+ $values{$field->name} = create_legal_value($field, $number)->name;
+ }
+
+ my $group = create_group($number);
+ $values{groups} = [$group->name];
+
+ $values{'keywords'} = create_keyword($number)->name;
+
+ foreach my $field (qw(assigned_to qa_contact reporter cc)) {
+ $values{$field} = create_user("$number-$field")->login;
+ }
+
+ my $classification = Bugzilla::Classification->create(
+ {name => "$number-classification-" . random()});
+ $classification = $classification->name;
+
+ my $version = "$number-version-" . random();
+ my $milestone = "$number-tm-" . random(15);
+ my $product = Bugzilla::Product->create({
+ name => "$number-product-" . random(),
+ description => 'Created by t/search.t',
+ defaultmilestone => $milestone,
+ classification => $classification,
+ version => $version,
+ allows_unconfirmed => 1,
+ });
+ foreach my $item ($group, $self->nobody) {
+ $product->set_group_controls($item,
+ {membercontrol => CONTROLMAPSHOWN, othercontrol => CONTROLMAPNA});
+ }
+
+ # $product->update() is called lower down.
+ my $component = Bugzilla::Component->create({
+ product => $product,
+ name => "$number-component-" . random(),
+ initialowner => create_user("$number-defaultowner")->login,
+ initialqacontact => create_user("$number-defaultqa")->login,
+ initial_cc => [create_user("$number-initcc")->login],
+ description => "Component $number"
+ });
+
+ $values{'product'} = $product->name;
+ $values{'component'} = $component->name;
+ $values{'target_milestone'} = $milestone;
+ $values{'version'} = $version;
+
+ foreach my $field ($self->text_fields) {
+
+ # We don't add a - after $field for the text fields, because
+ # if we do, fulltext searching for short_desc pulls out
+ # "short_desc" as a word and matches it in every bug.
+ my $value = "$number-$field" . random();
+ if ($field eq 'bug_file_loc' or $field eq 'see_also') {
+ $value = "http://$value-" . random(3) . "/show_bug.cgi?id=$number";
}
-
- $values{alias} = "$number-alias-" . random(12);
-
- # Prefixing the original comment with "description" makes the
- # lesserthan and greaterthan tests behave predictably.
- my $comm_prefix = $for_create ? "description-" : '';
- $values{comment} = "$comm_prefix$number-comment-" . random()
- . ' ' . random();
-
- my @flags;
- my $setter = create_user("$number-setters.login_name");
- my $requestee = create_user("$number-requestees.login_name");
- $values{set_flags} = _create_flags($number, $setter, $requestee);
-
- my $month = $for_create ? "12" : "02";
- $values{'deadline'} = "2037-$month-0$number";
- my $estimate_times = $for_create ? 10 : 1;
- $values{estimated_time} = $estimate_times * $number;
-
- $values{attachment} = _get_attach_values($number, $for_create);
-
- # Some things only happen on the first bug.
- if ($number == 1) {
- # We use 6 as the prefix for the extra values, because bug 6's values
- # don't otherwise get used (since bug 6 is created as a clone of
- # bug 1). This also makes sure that our greaterthan/lessthan
- # tests work properly.
- my $extra_group = create_group(6);
- $product->set_group_controls($extra_group,
- { membercontrol => CONTROLMAPSHOWN,
- othercontrol => CONTROLMAPNA });
- $values{groups} = [$values{groups}->[0], $extra_group->name];
- my $extra_keyword = create_keyword(6);
- $values{keywords} = [$values{keywords}, $extra_keyword->name];
- my $extra_cc = create_user("6-cc");
- $values{cc} = [$values{cc}, $extra_cc->login];
- my @multi_selects = grep { $_->type == FIELD_TYPE_MULTI_SELECT }
- $self->all_fields;
- foreach my $field (@multi_selects) {
- my $new_value = create_legal_value($field, 6);
- my $name = $field->name;
- $values{$name} = [$values{$name}, $new_value->name];
- }
- push(@{ $values{'tag'} }, "6-tag-" . random());
+ $values{$field} = $value;
+ }
+ $values{'tag'} = ["$number-tag-" . random()];
+
+ my @date_fields = grep { $_->type == FIELD_TYPE_DATETIME } $self->all_fields;
+ foreach my $field (@date_fields) {
+
+ # We use 03 as the month because that differs from our creation_ts,
+ # delta_ts, and deadline. (It's nice to have recognizable values
+ # for each field when debugging.)
+ my $second = $for_create ? $number : $number + 1;
+ $values{$field->name} = "2037-03-0$number 12:34:0$second";
+ }
+
+ $values{alias} = "$number-alias-" . random(12);
+
+ # Prefixing the original comment with "description" makes the
+ # lesserthan and greaterthan tests behave predictably.
+ my $comm_prefix = $for_create ? "description-" : '';
+ $values{comment} = "$comm_prefix$number-comment-" . random() . ' ' . random();
+
+ my @flags;
+ my $setter = create_user("$number-setters.login_name");
+ my $requestee = create_user("$number-requestees.login_name");
+ $values{set_flags} = _create_flags($number, $setter, $requestee);
+
+ my $month = $for_create ? "12" : "02";
+ $values{'deadline'} = "2037-$month-0$number";
+ my $estimate_times = $for_create ? 10 : 1;
+ $values{estimated_time} = $estimate_times * $number;
+
+ $values{attachment} = _get_attach_values($number, $for_create);
+
+ # Some things only happen on the first bug.
+ if ($number == 1) {
+
+ # We use 6 as the prefix for the extra values, because bug 6's values
+ # don't otherwise get used (since bug 6 is created as a clone of
+ # bug 1). This also makes sure that our greaterthan/lessthan
+ # tests work properly.
+ my $extra_group = create_group(6);
+ $product->set_group_controls($extra_group,
+ {membercontrol => CONTROLMAPSHOWN, othercontrol => CONTROLMAPNA});
+ $values{groups} = [$values{groups}->[0], $extra_group->name];
+ my $extra_keyword = create_keyword(6);
+ $values{keywords} = [$values{keywords}, $extra_keyword->name];
+ my $extra_cc = create_user("6-cc");
+ $values{cc} = [$values{cc}, $extra_cc->login];
+ my @multi_selects
+ = grep { $_->type == FIELD_TYPE_MULTI_SELECT } $self->all_fields;
+
+ foreach my $field (@multi_selects) {
+ my $new_value = create_legal_value($field, 6);
+ my $name = $field->name;
+ $values{$name} = [$values{$name}, $new_value->name];
}
-
- # On bug 5, any field that *can* be left empty, *is* left empty.
- if ($number == 5) {
- my @set_fields = grep { $_->type == FIELD_TYPE_SINGLE_SELECT }
- $self->all_fields;
- @set_fields = map { $_->name } @set_fields;
- push(@set_fields, qw(short_desc version reporter));
- foreach my $key (keys %values) {
- delete $values{$key} unless grep { $_ eq $key } @set_fields;
- }
+ push(@{$values{'tag'}}, "6-tag-" . random());
+ }
+
+ # On bug 5, any field that *can* be left empty, *is* left empty.
+ if ($number == 5) {
+ my @set_fields
+ = grep { $_->type == FIELD_TYPE_SINGLE_SELECT } $self->all_fields;
+ @set_fields = map { $_->name } @set_fields;
+ push(@set_fields, qw(short_desc version reporter));
+ foreach my $key (keys %values) {
+ delete $values{$key} unless grep { $_ eq $key } @set_fields;
}
+ }
- $product->update();
+ $product->update();
- return \%values;
+ return \%values;
}
# Flags
sub _create_flags {
- my ($number, $setter, $requestee) = @_;
+ my ($number, $setter, $requestee) = @_;
- my $flagtypes = _create_flagtypes($number);
+ my $flagtypes = _create_flagtypes($number);
- my %flags;
- foreach my $type (qw(a b)) {
- $flags{$type} = _get_flag_values(@_, $flagtypes->{$type});
- }
- return \%flags;
+ my %flags;
+ foreach my $type (qw(a b)) {
+ $flags{$type} = _get_flag_values(@_, $flagtypes->{$type});
+ }
+ return \%flags;
}
sub _create_flagtypes {
- my ($number) = @_;
- my $dbh = Bugzilla->dbh;
- my $name = "$number-flag-" . random();
- my $desc = "FlagType $number";
-
- my %flagtypes;
- foreach my $target (qw(a b)) {
- $dbh->do("INSERT INTO flagtypes
+ my ($number) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $name = "$number-flag-" . random();
+ my $desc = "FlagType $number";
+
+ my %flagtypes;
+ foreach my $target (qw(a b)) {
+ $dbh->do(
+ "INSERT INTO flagtypes
(name, description, target_type, is_requestable,
is_requesteeble, is_multiplicable, cc_list)
- VALUES (?,?,?,1,1,1,'')",
- undef, $name, $desc, $target);
- my $id = $dbh->bz_last_key('flagtypes', 'id');
- $dbh->do('INSERT INTO flaginclusions (type_id) VALUES (?)',
- undef, $id);
- my $flagtype = new Bugzilla::FlagType($id);
- $flagtypes{$target} = $flagtype;
- }
- return \%flagtypes;
+ VALUES (?,?,?,1,1,1,'')", undef, $name, $desc, $target
+ );
+ my $id = $dbh->bz_last_key('flagtypes', 'id');
+ $dbh->do('INSERT INTO flaginclusions (type_id) VALUES (?)', undef, $id);
+ my $flagtype = new Bugzilla::FlagType($id);
+ $flagtypes{$target} = $flagtype;
+ }
+ return \%flagtypes;
}
sub _get_flag_values {
- my ($number, $setter, $requestee, $flagtype) = @_;
-
- my @set_flags;
- if ($number <= 2) {
- foreach my $value (qw(? - + ?)) {
- my $flag = { type_id => $flagtype->id, status => $value,
- setter => $setter, flagtype => $flagtype };
- push(@set_flags, $flag);
- }
- $set_flags[0]->{requestee} = $requestee->login;
+ my ($number, $setter, $requestee, $flagtype) = @_;
+
+ my @set_flags;
+ if ($number <= 2) {
+ foreach my $value (qw(? - + ?)) {
+ my $flag = {
+ type_id => $flagtype->id,
+ status => $value,
+ setter => $setter,
+ flagtype => $flagtype
+ };
+ push(@set_flags, $flag);
}
- else {
- @set_flags = ({ type_id => $flagtype->id, status => '+',
- setter => $setter, flagtype => $flagtype });
- }
- return \@set_flags;
+ $set_flags[0]->{requestee} = $requestee->login;
+ }
+ else {
+ @set_flags = ({
+ type_id => $flagtype->id,
+ status => '+',
+ setter => $setter,
+ flagtype => $flagtype
+ });
+ }
+ return \@set_flags;
}
# Attachments
sub _get_attach_values {
- my ($number, $for_create) = @_;
-
- my $boolean = $number == 1 ? 1 : 0;
- if ($for_create) {
- $boolean = !$boolean ? 1 : 0;
- }
- my $ispatch = $for_create ? 'ispatch' : 'is_patch';
- my $isobsolete = $for_create ? 'isobsolete' : 'is_obsolete';
- my $isprivate = $for_create ? 'isprivate' : 'is_private';
- my $mimetype = $for_create ? 'mimetype' : 'content_type';
-
- my %values = (
- description => "$number-attach_desc-" . random(),
- filename => "$number-filename-" . random(),
- $ispatch => $boolean,
- $isobsolete => $boolean,
- $isprivate => $boolean,
- $mimetype => "text/x-$number-" . random(),
- );
- if ($for_create) {
- $values{data} = "$number-data-" . random() . random();
- }
- return \%values;
+ my ($number, $for_create) = @_;
+
+ my $boolean = $number == 1 ? 1 : 0;
+ if ($for_create) {
+ $boolean = !$boolean ? 1 : 0;
+ }
+ my $ispatch = $for_create ? 'ispatch' : 'is_patch';
+ my $isobsolete = $for_create ? 'isobsolete' : 'is_obsolete';
+ my $isprivate = $for_create ? 'isprivate' : 'is_private';
+ my $mimetype = $for_create ? 'mimetype' : 'content_type';
+
+ my %values = (
+ description => "$number-attach_desc-" . random(),
+ filename => "$number-filename-" . random(),
+ $ispatch => $boolean,
+ $isobsolete => $boolean,
+ $isprivate => $boolean,
+ $mimetype => "text/x-$number-" . random(),
+ );
+ if ($for_create) {
+ $values{data} = "$number-data-" . random() . random();
+ }
+ return \%values;
}
################
@@ -575,194 +597,205 @@ sub _get_attach_values {
################
sub _create_one_bug {
- my ($self, $number) = @_;
- my $dbh = Bugzilla->dbh;
-
- # We need bug 6 to have a unique alias that is not a clone of bug 1's,
- # so we get the alias separately from the other parameters.
- my $alias = $self->bug_create_value($number, 'alias');
- my $update_alias = $self->bug_update_value($number, 'alias');
-
- # Otherwise, make bug 6 a clone of bug 1.
- my $real_number = $number;
- $number = 1 if $number == 6;
-
- my $reporter = $self->bug_create_value($number, 'reporter');
- Bugzilla->set_user(Bugzilla::User->check($reporter));
-
- # We create the bug with one set of values, and then we change it
- # to have different values.
- my %params = %{ $self->_bug_create_values->{$number} };
- $params{alias} = $alias;
-
- # There are some things in bug_create_values that shouldn't go into
- # create().
- delete @params{qw(attachment set_flags tag)};
-
- my ($status, $resolution, $see_also) =
- delete @params{qw(bug_status resolution see_also)};
- # All the bugs are created with everconfirmed = 0.
- $params{bug_status} = 'UNCONFIRMED';
- my $bug = Bugzilla::Bug->create(\%params);
-
- # These are necessary for the changedfrom tests.
- my $extra_values = $self->_extra_bug_create_values->{$number};
- foreach my $field (qw(comments remaining_time percentage_complete
- keyword_objects everconfirmed dependson blocked
- groups_in classification actual_time))
- {
- $extra_values->{$field} = $bug->$field;
+ my ($self, $number) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # We need bug 6 to have a unique alias that is not a clone of bug 1's,
+ # so we get the alias separately from the other parameters.
+ my $alias = $self->bug_create_value($number, 'alias');
+ my $update_alias = $self->bug_update_value($number, 'alias');
+
+ # Otherwise, make bug 6 a clone of bug 1.
+ my $real_number = $number;
+ $number = 1 if $number == 6;
+
+ my $reporter = $self->bug_create_value($number, 'reporter');
+ Bugzilla->set_user(Bugzilla::User->check($reporter));
+
+ # We create the bug with one set of values, and then we change it
+ # to have different values.
+ my %params = %{$self->_bug_create_values->{$number}};
+ $params{alias} = $alias;
+
+ # There are some things in bug_create_values that shouldn't go into
+ # create().
+ delete @params{qw(attachment set_flags tag)};
+
+ my ($status, $resolution, $see_also)
+ = delete @params{qw(bug_status resolution see_also)};
+
+ # All the bugs are created with everconfirmed = 0.
+ $params{bug_status} = 'UNCONFIRMED';
+ my $bug = Bugzilla::Bug->create(\%params);
+
+ # These are necessary for the changedfrom tests.
+ my $extra_values = $self->_extra_bug_create_values->{$number};
+ foreach my $field (qw(comments remaining_time percentage_complete
+ keyword_objects everconfirmed dependson blocked
+ groups_in classification actual_time))
+ {
+ $extra_values->{$field} = $bug->$field;
+ }
+ $extra_values->{reporter_accessible} = $number == 1 ? 0 : 1;
+ $extra_values->{cclist_accessible} = $number == 1 ? 0 : 1;
+
+ if ($number == 5) {
+
+ # Bypass Bugzilla::Bug--we don't want any changes in bugs_activity
+ # for bug 5.
+ $dbh->do(
+ 'UPDATE bugs SET qa_contact = NULL, reporter_accessible = 0,
+ cclist_accessible = 0 WHERE bug_id = ?', undef,
+ $bug->id
+ );
+ $dbh->do('DELETE FROM cc WHERE bug_id = ?', undef, $bug->id);
+ my $ts = '1970-01-01 00:00:00';
+ $dbh->do(
+ 'UPDATE bugs SET creation_ts = ?, delta_ts = ?
+ WHERE bug_id = ?', undef, $ts, $ts, $bug->id
+ );
+ $dbh->do('UPDATE longdescs SET bug_when = ? WHERE bug_id = ?',
+ undef, $ts, $bug->id);
+ $bug->{creation_ts} = $ts;
+ $extra_values->{see_also} = [];
+ }
+ else {
+ # Manually set the creation_ts so that each bug has a different one.
+ #
+ # Also, manually update the resolution and bug_status, because
+ # we want to see both of them change in bugs_activity, so we
+ # have to start with values for both (and as of the time when I'm
+ # writing this test, Bug->create doesn't support setting resolution).
+ #
+ # Same for see_also.
+ my $timestamp = timestamp($number, $number - 1);
+ my $creation_ts = $timestamp->ymd . ' ' . $timestamp->hms;
+ $bug->{creation_ts} = $creation_ts;
+ $dbh->do('UPDATE longdescs SET bug_when = ? WHERE bug_id = ?',
+ undef, $creation_ts, $bug->id);
+ $dbh->do(
+ 'UPDATE bugs SET creation_ts = ?, bug_status = ?,
+ resolution = ? WHERE bug_id = ?', undef, $creation_ts, $status,
+ $resolution, $bug->id
+ );
+ $dbh->do('INSERT INTO bug_see_also (bug_id, value, class) VALUES (?,?,?)',
+ undef, $bug->id, $see_also, 'Bugzilla::BugUrl::Bugzilla');
+ $extra_values->{see_also} = $bug->see_also;
+
+ # All the tags must be created as the admin user, so that the
+ # admin user can find them, later.
+ my $original_user = Bugzilla->user;
+ Bugzilla->set_user($self->admin);
+ my $tags = $self->bug_create_value($number, 'tag');
+ $bug->add_tag($_) foreach @$tags;
+ $extra_values->{tags} = $tags;
+ Bugzilla->set_user($original_user);
+
+ if ($number == 1) {
+
+ # Bug 1 needs to start off with reporter_accessible and
+ # cclist_accessible being 0, so that when we change them to 1,
+ # that change shows up in bugs_activity.
+ $dbh->do(
+ 'UPDATE bugs SET reporter_accessible = 0,
+ cclist_accessible = 0 WHERE bug_id = ?', undef, $bug->id
+ );
+
+ # Bug 1 gets three comments, so that longdescs.count matches it
+ # uniquely. The third comment is added in the middle, so that the
+ # last comment contains all of the important data, like work_time.
+ $bug->add_comment("1-comment-" . random(100));
}
- $extra_values->{reporter_accessible} = $number == 1 ? 0 : 1;
- $extra_values->{cclist_accessible} = $number == 1 ? 0 : 1;
-
- if ($number == 5) {
- # Bypass Bugzilla::Bug--we don't want any changes in bugs_activity
- # for bug 5.
- $dbh->do('UPDATE bugs SET qa_contact = NULL, reporter_accessible = 0,
- cclist_accessible = 0 WHERE bug_id = ?',
- undef, $bug->id);
- $dbh->do('DELETE FROM cc WHERE bug_id = ?', undef, $bug->id);
- my $ts = '1970-01-01 00:00:00';
- $dbh->do('UPDATE bugs SET creation_ts = ?, delta_ts = ?
- WHERE bug_id = ?', undef, $ts, $ts, $bug->id);
- $dbh->do('UPDATE longdescs SET bug_when = ? WHERE bug_id = ?',
- undef, $ts, $bug->id);
- $bug->{creation_ts} = $ts;
- $extra_values->{see_also} = [];
+
+ my %update_params = %{$self->_bug_update_values->{$number}};
+ my %reverse_map = reverse %{Bugzilla::Bug->FIELD_MAP};
+ foreach my $db_name (keys %reverse_map) {
+ next if $db_name eq 'comment';
+ next if $db_name eq 'status_whiteboard';
+ if (exists $update_params{$db_name}) {
+ my $update_name = $reverse_map{$db_name};
+ $update_params{$update_name} = delete $update_params{$db_name};
+ }
}
- else {
- # Manually set the creation_ts so that each bug has a different one.
- #
- # Also, manually update the resolution and bug_status, because
- # we want to see both of them change in bugs_activity, so we
- # have to start with values for both (and as of the time when I'm
- # writing this test, Bug->create doesn't support setting resolution).
- #
- # Same for see_also.
- my $timestamp = timestamp($number, $number - 1);
- my $creation_ts = $timestamp->ymd . ' ' . $timestamp->hms;
- $bug->{creation_ts} = $creation_ts;
- $dbh->do('UPDATE longdescs SET bug_when = ? WHERE bug_id = ?',
- undef, $creation_ts, $bug->id);
- $dbh->do('UPDATE bugs SET creation_ts = ?, bug_status = ?,
- resolution = ? WHERE bug_id = ?',
- undef, $creation_ts, $status, $resolution, $bug->id);
- $dbh->do('INSERT INTO bug_see_also (bug_id, value, class) VALUES (?,?,?)',
- undef, $bug->id, $see_also, 'Bugzilla::BugUrl::Bugzilla');
- $extra_values->{see_also} = $bug->see_also;
-
- # All the tags must be created as the admin user, so that the
- # admin user can find them, later.
- my $original_user = Bugzilla->user;
- Bugzilla->set_user($self->admin);
- my $tags = $self->bug_create_value($number, 'tag');
- $bug->add_tag($_) foreach @$tags;
- $extra_values->{tags} = $tags;
- Bugzilla->set_user($original_user);
-
- if ($number == 1) {
- # Bug 1 needs to start off with reporter_accessible and
- # cclist_accessible being 0, so that when we change them to 1,
- # that change shows up in bugs_activity.
- $dbh->do('UPDATE bugs SET reporter_accessible = 0,
- cclist_accessible = 0 WHERE bug_id = ?',
- undef, $bug->id);
- # Bug 1 gets three comments, so that longdescs.count matches it
- # uniquely. The third comment is added in the middle, so that the
- # last comment contains all of the important data, like work_time.
- $bug->add_comment("1-comment-" . random(100));
- }
-
- my %update_params = %{ $self->_bug_update_values->{$number} };
- my %reverse_map = reverse %{ Bugzilla::Bug->FIELD_MAP };
- foreach my $db_name (keys %reverse_map) {
- next if $db_name eq 'comment';
- next if $db_name eq 'status_whiteboard';
- if (exists $update_params{$db_name}) {
- my $update_name = $reverse_map{$db_name};
- $update_params{$update_name} = delete $update_params{$db_name};
- }
- }
-
- my ($new_status, $new_res) =
- delete @update_params{qw(status resolution)};
- # Bypass the status workflow.
- $bug->{bug_status} = $new_status;
- $bug->{resolution} = $new_res;
- $bug->{everconfirmed} = 1 if $number == 1;
-
- # add/remove/set fields.
- $update_params{keywords} = { set => $update_params{keywords} };
- $update_params{groups} = { add => $update_params{groups},
- remove => $bug->groups_in };
- my @cc_remove = map { $_->login } @{ $bug->cc_users };
- my $cc_new = $update_params{cc};
- my @cc_add = ref($cc_new) ? @$cc_new : ($cc_new);
- # We make the admin an explicit CC on bug 1 (but not on bug 6), so
- # that we can test the %user% pronoun properly.
- if ($real_number == 1) {
- push(@cc_add, $self->admin->login);
- }
- $update_params{cc} = { add => \@cc_add, remove => \@cc_remove };
- my $see_also_remove = $bug->see_also;
- my $see_also_add = [$update_params{see_also}];
- $update_params{see_also} = { add => $see_also_add,
- remove => $see_also_remove };
- $update_params{comment} = { body => $update_params{comment} };
- $update_params{work_time} = $number;
- # Setting work_time kills the remaining_time, so we need to
- # preserve that. We add 8 because that produces an integer
- # percentage_complete for bug 1, which is necessary for
- # accurate "equals"-type searching.
- $update_params{remaining_time} = $number + 8;
- $update_params{reporter_accessible} = $number == 1 ? 1 : 0;
- $update_params{cclist_accessible} = $number == 1 ? 1 : 0;
- $update_params{alias} = $update_alias;
-
- $bug->set_all(\%update_params);
- my $flags = $self->bug_create_value($number, 'set_flags')->{b};
- $bug->set_flags([], $flags);
- $timestamp->set(second => $number);
- $bug->update($timestamp->ymd . ' ' . $timestamp->hms);
- $extra_values->{flags} = $bug->flags;
-
- # It's not generally safe to do update() multiple times on
- # the same Bug object.
- $bug = new Bugzilla::Bug($bug->id);
- my $update_flags = $self->bug_update_value($number, 'set_flags')->{b};
- $_->{status} = 'X' foreach @{ $bug->flags };
- $bug->set_flags($bug->flags, $update_flags);
- if ($number == 1) {
- my $comment_id = $bug->comments->[-1]->id;
- $bug->set_comment_is_private({ $comment_id => 1 });
- }
- $bug->update($bug->delta_ts);
-
- my $attach_create = $self->bug_create_value($number, 'attachment');
- my $attachment = Bugzilla::Attachment->create({
- bug => $bug,
- creation_ts => $creation_ts,
- %$attach_create });
- # Store for the changedfrom tests.
- $extra_values->{attachments} =
- [new Bugzilla::Attachment($attachment->id)];
-
- my $attach_update = $self->bug_update_value($number, 'attachment');
- $attachment->set_all($attach_update);
- # In order to keep the mimetype on the ispatch attachment,
- # we need to bypass the validator.
- $attachment->{mimetype} = $attach_update->{content_type};
- my $attach_flags = $self->bug_update_value($number, 'set_flags')->{a};
- $attachment->set_flags([], $attach_flags);
- $attachment->update($bug->delta_ts);
+
+ my ($new_status, $new_res) = delete @update_params{qw(status resolution)};
+
+ # Bypass the status workflow.
+ $bug->{bug_status} = $new_status;
+ $bug->{resolution} = $new_res;
+ $bug->{everconfirmed} = 1 if $number == 1;
+
+ # add/remove/set fields.
+ $update_params{keywords} = {set => $update_params{keywords}};
+ $update_params{groups}
+ = {add => $update_params{groups}, remove => $bug->groups_in};
+ my @cc_remove = map { $_->login } @{$bug->cc_users};
+ my $cc_new = $update_params{cc};
+ my @cc_add = ref($cc_new) ? @$cc_new : ($cc_new);
+
+ # We make the admin an explicit CC on bug 1 (but not on bug 6), so
+ # that we can test the %user% pronoun properly.
+ if ($real_number == 1) {
+ push(@cc_add, $self->admin->login);
+ }
+ $update_params{cc} = {add => \@cc_add, remove => \@cc_remove};
+ my $see_also_remove = $bug->see_also;
+ my $see_also_add = [$update_params{see_also}];
+ $update_params{see_also} = {add => $see_also_add, remove => $see_also_remove};
+ $update_params{comment} = {body => $update_params{comment}};
+ $update_params{work_time} = $number;
+
+ # Setting work_time kills the remaining_time, so we need to
+ # preserve that. We add 8 because that produces an integer
+ # percentage_complete for bug 1, which is necessary for
+ # accurate "equals"-type searching.
+ $update_params{remaining_time} = $number + 8;
+ $update_params{reporter_accessible} = $number == 1 ? 1 : 0;
+ $update_params{cclist_accessible} = $number == 1 ? 1 : 0;
+ $update_params{alias} = $update_alias;
+
+ $bug->set_all(\%update_params);
+ my $flags = $self->bug_create_value($number, 'set_flags')->{b};
+ $bug->set_flags([], $flags);
+ $timestamp->set(second => $number);
+ $bug->update($timestamp->ymd . ' ' . $timestamp->hms);
+ $extra_values->{flags} = $bug->flags;
+
+ # It's not generally safe to do update() multiple times on
+ # the same Bug object.
+ $bug = new Bugzilla::Bug($bug->id);
+ my $update_flags = $self->bug_update_value($number, 'set_flags')->{b};
+ $_->{status} = 'X' foreach @{$bug->flags};
+ $bug->set_flags($bug->flags, $update_flags);
+ if ($number == 1) {
+ my $comment_id = $bug->comments->[-1]->id;
+ $bug->set_comment_is_private({$comment_id => 1});
}
-
- # Values for changedfrom.
- $extra_values->{creation_ts} = $bug->creation_ts;
- $extra_values->{delta_ts} = $bug->creation_ts;
-
- return new Bugzilla::Bug($bug->id);
+ $bug->update($bug->delta_ts);
+
+ my $attach_create = $self->bug_create_value($number, 'attachment');
+ my $attachment = Bugzilla::Attachment->create(
+ {bug => $bug, creation_ts => $creation_ts, %$attach_create});
+
+ # Store for the changedfrom tests.
+ $extra_values->{attachments} = [new Bugzilla::Attachment($attachment->id)];
+
+ my $attach_update = $self->bug_update_value($number, 'attachment');
+ $attachment->set_all($attach_update);
+
+ # In order to keep the mimetype on the ispatch attachment,
+ # we need to bypass the validator.
+ $attachment->{mimetype} = $attach_update->{content_type};
+ my $attach_flags = $self->bug_update_value($number, 'set_flags')->{a};
+ $attachment->set_flags([], $attach_flags);
+ $attachment->update($bug->delta_ts);
+ }
+
+ # Values for changedfrom.
+ $extra_values->{creation_ts} = $bug->creation_ts;
+ $extra_values->{delta_ts} = $bug->creation_ts;
+
+ return new Bugzilla::Bug($bug->id);
}
###################################
@@ -778,44 +811,45 @@ sub _create_one_bug {
# field, which we store more efficiently, in an array, and then we re-populate
# the Test_Results in Test::Builder at the end of the test.
sub clean_test_history {
- my ($self) = @_;
- return if !$self->option('long');
- my $builder = Test::More->builder;
- my $current_test = $builder->current_test;
-
- # I don't use details() because I don't want to copy the array.
- my $results = $builder->{Test_Results};
- my $check_test = $current_test - 1;
- while (my $result = $results->[$check_test]) {
- last if !$result;
- $self->test_success($check_test, $result->{ok});
- $check_test--;
- }
-
- # Truncate the test history array, but retain the current test number.
- $builder->{Test_Results} = [];
- $builder->{Curr_Test} = $current_test;
+ my ($self) = @_;
+ return if !$self->option('long');
+ my $builder = Test::More->builder;
+ my $current_test = $builder->current_test;
+
+ # I don't use details() because I don't want to copy the array.
+ my $results = $builder->{Test_Results};
+ my $check_test = $current_test - 1;
+ while (my $result = $results->[$check_test]) {
+ last if !$result;
+ $self->test_success($check_test, $result->{ok});
+ $check_test--;
+ }
+
+ # Truncate the test history array, but retain the current test number.
+ $builder->{Test_Results} = [];
+ $builder->{Curr_Test} = $current_test;
}
sub test_success {
- my ($self, $index, $status) = @_;
- $self->{test_success}->[$index] = $status;
- return $self->{test_success};
+ my ($self, $index, $status) = @_;
+ $self->{test_success}->[$index] = $status;
+ return $self->{test_success};
}
sub repopulate_test_results {
- my ($self) = @_;
- return if !$self->option('long');
- $self->clean_test_history();
- # We create only two hashes, for memory efficiency.
- my %ok = ( ok => 1 );
- my %not_ok = ( ok => 0 );
- my @results;
- foreach my $success (@{ $self->{test_success} }) {
- push(@results, $success ? \%ok : \%not_ok);
- }
- my $builder = Test::More->builder;
- $builder->{Test_Results} = \@results;
+ my ($self) = @_;
+ return if !$self->option('long');
+ $self->clean_test_history();
+
+ # We create only two hashes, for memory efficiency.
+ my %ok = (ok => 1);
+ my %not_ok = (ok => 0);
+ my @results;
+ foreach my $success (@{$self->{test_success}}) {
+ push(@results, $success ? \%ok : \%not_ok);
+ }
+ my $builder = Test::More->builder;
+ $builder->{Test_Results} = \@results;
}
##########
@@ -828,13 +862,13 @@ sub repopulate_test_results {
# have to re-run the value-translation code every time (which can be pretty
# slow).
sub value_translation_cache {
- my ($self, $field_test, $value) = @_;
- return if !$self->option('long');
- my $test_name = $field_test->name;
- if (@_ == 3) {
- $self->{value_translation_cache}->{$test_name} = $value;
- }
- return $self->{value_translation_cache}->{$test_name};
+ my ($self, $field_test, $value) = @_;
+ return if !$self->option('long');
+ my $test_name = $field_test->name;
+ if (@_ == 3) {
+ $self->{value_translation_cache}->{$test_name} = $value;
+ }
+ return $self->{value_translation_cache}->{$test_name};
}
# When doing AND/OR tests, the value for transformed_value_was_equal
@@ -842,13 +876,13 @@ sub value_translation_cache {
# if we pull our values from the value_translation_cache. So we need
# to also cache the values for transformed_value_was_equal.
sub was_equal_cache {
- my ($self, $field_test, $number, $value) = @_;
- return if !$self->option('long');
- my $test_name = $field_test->name;
- if (@_ == 4) {
- $self->{tvwe_cache}->{$test_name}->{$number} = $value;
- }
- return $self->{tvwe_cache}->{$test_name}->{$number};
+ my ($self, $field_test, $number, $value) = @_;
+ return if !$self->option('long');
+ my $test_name = $field_test->name;
+ if (@_ == 4) {
+ $self->{tvwe_cache}->{$test_name}->{$number} = $value;
+ }
+ return $self->{tvwe_cache}->{$test_name}->{$number};
}
#############
@@ -856,132 +890,135 @@ sub was_equal_cache {
#############
sub run {
- my ($self) = @_;
- my $dbh = Bugzilla->dbh;
-
- # We want backtraces on any "die" message or any warning.
- # Otherwise it's hard to trace errors inside of Bugzilla::Search from
- # reading automated test run results.
- local $SIG{__WARN__} = \&Carp::cluck;
- local $SIG{__DIE__} = \&Carp::confess;
-
- $dbh->bz_start_transaction();
-
- # Some parameters need to be set in order for the tests to function
- # properly.
- my $everybody = $self->everybody;
- my $params = Bugzilla->params;
- local $params->{'useclassification'} = 1;
- local $params->{'useqacontact'} = 1;
- local $params->{'usetargetmilestone'} = 1;
- local $params->{'mail_delivery_method'} = 'None';
- local $params->{'timetrackinggroup'} = $everybody->name;
- local $params->{'insidergroup'} = $everybody->name;
-
- $self->_setup_bugs();
-
- # Even though _setup_bugs set us as an admin, we want to be sure at
- # this point that we have an admin with refreshed group memberships.
- Bugzilla->set_user($self->admin);
- foreach my $test (CUSTOM_SEARCH_TESTS) {
- my $custom_test = new Bugzilla::Test::Search::CustomTest($test, $self);
- $custom_test->run();
- }
- foreach my $test (SPECIAL_PARAM_TESTS) {
- my $operator_test =
- new Bugzilla::Test::Search::OperatorTest($test->{operator}, $self);
- my $field = Bugzilla::Field->check($test->{field});
- my $special_test = new Bugzilla::Test::Search::FieldTestNormal(
- $operator_test, $field, $test);
- $special_test->run();
- }
- foreach my $operator ($self->top_level_operators) {
- my $operator_test =
- new Bugzilla::Test::Search::OperatorTest($operator, $self);
- $operator_test->run();
- }
-
- # Rollbacks won't get rid of bugs_fulltext entries, so we do that ourselves.
- my @bug_ids = map { $_->id } $self->bugs;
- my $bug_id_string = join(',', @bug_ids);
- $dbh->do("DELETE FROM bugs_fulltext WHERE bug_id IN ($bug_id_string)");
- $dbh->bz_rollback_transaction();
- $self->repopulate_test_results();
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # We want backtraces on any "die" message or any warning.
+ # Otherwise it's hard to trace errors inside of Bugzilla::Search from
+ # reading automated test run results.
+ local $SIG{__WARN__} = \&Carp::cluck;
+ local $SIG{__DIE__} = \&Carp::confess;
+
+ $dbh->bz_start_transaction();
+
+ # Some parameters need to be set in order for the tests to function
+ # properly.
+ my $everybody = $self->everybody;
+ my $params = Bugzilla->params;
+ local $params->{'useclassification'} = 1;
+ local $params->{'useqacontact'} = 1;
+ local $params->{'usetargetmilestone'} = 1;
+ local $params->{'mail_delivery_method'} = 'None';
+ local $params->{'timetrackinggroup'} = $everybody->name;
+ local $params->{'insidergroup'} = $everybody->name;
+
+ $self->_setup_bugs();
+
+ # Even though _setup_bugs set us as an admin, we want to be sure at
+ # this point that we have an admin with refreshed group memberships.
+ Bugzilla->set_user($self->admin);
+ foreach my $test (CUSTOM_SEARCH_TESTS) {
+ my $custom_test = new Bugzilla::Test::Search::CustomTest($test, $self);
+ $custom_test->run();
+ }
+ foreach my $test (SPECIAL_PARAM_TESTS) {
+ my $operator_test
+ = new Bugzilla::Test::Search::OperatorTest($test->{operator}, $self);
+ my $field = Bugzilla::Field->check($test->{field});
+ my $special_test
+ = new Bugzilla::Test::Search::FieldTestNormal($operator_test, $field, $test);
+ $special_test->run();
+ }
+ foreach my $operator ($self->top_level_operators) {
+ my $operator_test = new Bugzilla::Test::Search::OperatorTest($operator, $self);
+ $operator_test->run();
+ }
+
+ # Rollbacks won't get rid of bugs_fulltext entries, so we do that ourselves.
+ my @bug_ids = map { $_->id } $self->bugs;
+ my $bug_id_string = join(',', @bug_ids);
+ $dbh->do("DELETE FROM bugs_fulltext WHERE bug_id IN ($bug_id_string)");
+ $dbh->bz_rollback_transaction();
+ $self->repopulate_test_results();
}
# This makes a few changes to the bugs after they're created--changes
# that can only be done after all the bugs have been created.
sub _setup_bugs {
- my ($self) = @_;
- $self->_setup_dependencies();
- $self->_set_bug_id_fields();
- $self->_protect_bug_6();
+ my ($self) = @_;
+ $self->_setup_dependencies();
+ $self->_set_bug_id_fields();
+ $self->_protect_bug_6();
}
+
sub _setup_dependencies {
- my ($self) = @_;
- my $dbh = Bugzilla->dbh;
-
- # Set up depedency relationships between the bugs.
- # Bug 1 + 6 depend on bug 2 and block bug 3.
- my $bug2 = $self->bug(2);
- my $bug3 = $self->bug(3);
- foreach my $number (1,6) {
- my $bug = $self->bug($number);
- my @original_delta = ($bug2->delta_ts, $bug3->delta_ts);
- Bugzilla->set_user($bug->reporter);
- $bug->set_dependencies([$bug2->id], [$bug3->id]);
- $bug->update($bug->delta_ts);
- # Setting dependencies changed the delta_ts on bug2 and bug3, so
- # re-set them back to what they were before. However, we leave
- # the correct update times in bugs_activity, so that the changed*
- # searches still work right.
- my $set_delta = $dbh->prepare(
- 'UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
- foreach my $row ([$original_delta[0], $bug2->id],
- [$original_delta[1], $bug3->id])
- {
- $set_delta->execute(@$row);
- }
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # Set up depedency relationships between the bugs.
+ # Bug 1 + 6 depend on bug 2 and block bug 3.
+ my $bug2 = $self->bug(2);
+ my $bug3 = $self->bug(3);
+ foreach my $number (1, 6) {
+ my $bug = $self->bug($number);
+ my @original_delta = ($bug2->delta_ts, $bug3->delta_ts);
+ Bugzilla->set_user($bug->reporter);
+ $bug->set_dependencies([$bug2->id], [$bug3->id]);
+ $bug->update($bug->delta_ts);
+
+ # Setting dependencies changed the delta_ts on bug2 and bug3, so
+ # re-set them back to what they were before. However, we leave
+ # the correct update times in bugs_activity, so that the changed*
+ # searches still work right.
+ my $set_delta = $dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
+ foreach
+ my $row ([$original_delta[0], $bug2->id], [$original_delta[1], $bug3->id])
+ {
+ $set_delta->execute(@$row);
}
+ }
}
sub _set_bug_id_fields {
- my ($self) = @_;
- # BUG_ID fields couldn't be set before, because before we create bug 1,
- # we don't necessarily have any valid bug ids.)
- my @bug_id_fields = grep { $_->type == FIELD_TYPE_BUG_ID }
- $self->all_fields;
- foreach my $number (1..NUM_BUGS) {
- my $bug = $self->bug($number);
- $number = 1 if $number == 6;
- next if $number == 5;
- my $other_bug = $self->bug($number + 1);
- Bugzilla->set_user($bug->reporter);
- foreach my $field (@bug_id_fields) {
- $bug->set_custom_field($field, $other_bug->id);
- $bug->update($bug->delta_ts);
- }
+ my ($self) = @_;
+
+ # BUG_ID fields couldn't be set before, because before we create bug 1,
+ # we don't necessarily have any valid bug ids.)
+ my @bug_id_fields = grep { $_->type == FIELD_TYPE_BUG_ID } $self->all_fields;
+ foreach my $number (1 .. NUM_BUGS) {
+ my $bug = $self->bug($number);
+ $number = 1 if $number == 6;
+ next if $number == 5;
+ my $other_bug = $self->bug($number + 1);
+ Bugzilla->set_user($bug->reporter);
+ foreach my $field (@bug_id_fields) {
+ $bug->set_custom_field($field, $other_bug->id);
+ $bug->update($bug->delta_ts);
}
+ }
}
sub _protect_bug_6 {
- my ($self) = @_;
- my $dbh = Bugzilla->dbh;
-
- Bugzilla->set_user($self->admin);
-
- # Put bug6 in the nobody group.
- my $nobody = $self->nobody;
- # We pull it newly from the DB to be sure it's safe to call update()
- # on.
- my $bug6 = new Bugzilla::Bug($self->bug(6)->id);
- $bug6->add_group($nobody);
- $bug6->update($bug6->delta_ts);
-
- # Remove the admin (and everybody else) from the $nobody group.
- $dbh->do('DELETE FROM group_group_map
- WHERE grantor_id = ? OR member_id = ?', undef,
- $nobody->id, $nobody->id);
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ Bugzilla->set_user($self->admin);
+
+ # Put bug6 in the nobody group.
+ my $nobody = $self->nobody;
+
+ # We pull it newly from the DB to be sure it's safe to call update()
+ # on.
+ my $bug6 = new Bugzilla::Bug($self->bug(6)->id);
+ $bug6->add_group($nobody);
+ $bug6->update($bug6->delta_ts);
+
+ # Remove the admin (and everybody else) from the $nobody group.
+ $dbh->do(
+ 'DELETE FROM group_group_map
+ WHERE grantor_id = ? OR member_id = ?', undef, $nobody->id,
+ $nobody->id
+ );
}
1;
diff --git a/xt/lib/Bugzilla/Test/Search/AndTest.pm b/xt/lib/Bugzilla/Test/Search/AndTest.pm
index f34ba1f3a..6132c0eb6 100644
--- a/xt/lib/Bugzilla/Test/Search/AndTest.pm
+++ b/xt/lib/Bugzilla/Test/Search/AndTest.pm
@@ -22,13 +22,13 @@ use constant type => 'AND';
# In an AND test, bugs ARE supposed to be contained only if they are contained
# by ALL tests.
sub bug_is_contained {
- my ($self, $number) = @_;
- return all { $_->bug_is_contained($number) } $self->field_tests;
+ my ($self, $number) = @_;
+ return all { $_->bug_is_contained($number) } $self->field_tests;
}
sub _bug_will_actually_be_contained {
- my ($self, $number) = @_;
- return all { $_->will_actually_contain_bug($number) } $self->field_tests;
+ my ($self, $number) = @_;
+ return all { $_->will_actually_contain_bug($number) } $self->field_tests;
}
##############################
@@ -36,17 +36,17 @@ sub _bug_will_actually_be_contained {
##############################
sub search_params {
- my ($self) = @_;
- my @all_params = map { $_->search_params } $self->field_tests;
- my %params;
- my $chart = 0;
- foreach my $item (@all_params) {
- $params{"field0-$chart-0"} = $item->{'field0-0-0'};
- $params{"type0-$chart-0"} = $item->{'type0-0-0'};
- $params{"value0-$chart-0"} = $item->{'value0-0-0'};
- $chart++;
- }
- return \%params;
+ my ($self) = @_;
+ my @all_params = map { $_->search_params } $self->field_tests;
+ my %params;
+ my $chart = 0;
+ foreach my $item (@all_params) {
+ $params{"field0-$chart-0"} = $item->{'field0-0-0'};
+ $params{"type0-$chart-0"} = $item->{'type0-0-0'};
+ $params{"value0-$chart-0"} = $item->{'value0-0-0'};
+ $chart++;
+ }
+ return \%params;
}
1;
diff --git a/xt/lib/Bugzilla/Test/Search/Constants.pm b/xt/lib/Bugzilla/Test/Search/Constants.pm
index 5d84ec6ff..84080cfe8 100644
--- a/xt/lib/Bugzilla/Test/Search/Constants.pm
+++ b/xt/lib/Bugzilla/Test/Search/Constants.pm
@@ -17,28 +17,28 @@ use Bugzilla::Constants;
use Bugzilla::Util qw(generate_random_password);
our @EXPORT = qw(
- ATTACHMENT_FIELDS
- BROKEN_NOT
- COLUMN_TRANSLATION
- COMMENT_FIELDS
- CUSTOM_FIELDS
- CUSTOM_SEARCH_TESTS
- FIELD_SIZE
- FIELD_SUBSTR_SIZE
- FLAG_FIELDS
- INJECTION_BROKEN_FIELD
- INJECTION_BROKEN_OPERATOR
- INJECTION_TESTS
- KNOWN_BROKEN
- NUM_BUGS
- NUM_SEARCH_TESTS
- SKIP_FIELDS
- SPECIAL_PARAM_TESTS
- SUBSTR_NO_FIELD_ADD
- SUBSTR_SIZE
- TESTS
- TESTS_PER_RUN
- USER_FIELDS
+ ATTACHMENT_FIELDS
+ BROKEN_NOT
+ COLUMN_TRANSLATION
+ COMMENT_FIELDS
+ CUSTOM_FIELDS
+ CUSTOM_SEARCH_TESTS
+ FIELD_SIZE
+ FIELD_SUBSTR_SIZE
+ FLAG_FIELDS
+ INJECTION_BROKEN_FIELD
+ INJECTION_BROKEN_OPERATOR
+ INJECTION_TESTS
+ KNOWN_BROKEN
+ NUM_BUGS
+ NUM_SEARCH_TESTS
+ SKIP_FIELDS
+ SPECIAL_PARAM_TESTS
+ SUBSTR_NO_FIELD_ADD
+ SUBSTR_SIZE
+ TESTS
+ TESTS_PER_RUN
+ USER_FIELDS
);
# Bug 1 is designed to be found by all the "equals" tests. It has
@@ -63,6 +63,7 @@ use constant NUM_BUGS => 6;
# How many tests there are for each operator/field combination other
# than the "contains" tests.
use constant NUM_SEARCH_TESTS => 3;
+
# This is how many tests get run for each field/operator.
use constant TESTS_PER_RUN => NUM_SEARCH_TESTS + NUM_BUGS;
@@ -74,40 +75,37 @@ use constant FIELD_SIZE => 30;
# These are the custom fields that are created if the BZ_MODIFY_DATABASE_TESTS
# environment variable is set.
use constant CUSTOM_FIELDS => {
- FIELD_TYPE_FREETEXT, 'cf_freetext',
- FIELD_TYPE_SINGLE_SELECT, 'cf_single_select',
- FIELD_TYPE_MULTI_SELECT, 'cf_multi_select',
- FIELD_TYPE_TEXTAREA, 'cf_textarea',
- FIELD_TYPE_DATETIME, 'cf_datetime',
- FIELD_TYPE_BUG_ID, 'cf_bugid',
+ FIELD_TYPE_FREETEXT, 'cf_freetext',
+ FIELD_TYPE_SINGLE_SELECT, 'cf_single_select',
+ FIELD_TYPE_MULTI_SELECT, 'cf_multi_select',
+ FIELD_TYPE_TEXTAREA, 'cf_textarea',
+ FIELD_TYPE_DATETIME, 'cf_datetime',
+ FIELD_TYPE_BUG_ID, 'cf_bugid',
};
# This translates fielddefs names into Search column names.
use constant COLUMN_TRANSLATION => {
- creation_ts => 'opendate',
- delta_ts => 'changeddate',
- work_time => 'actual_time',
+ creation_ts => 'opendate',
+ delta_ts => 'changeddate',
+ work_time => 'actual_time',
};
# Make comment field names to their Bugzilla::Comment accessor.
use constant COMMENT_FIELDS => {
- longdesc => 'body',
- commenter => 'author',
- 'longdescs.isprivate' => 'is_private',
+ longdesc => 'body',
+ commenter => 'author',
+ 'longdescs.isprivate' => 'is_private',
};
# Same as above, for Bugzilla::Attachment.
-use constant ATTACHMENT_FIELDS => {
- mimetype => 'contenttype',
- submitter => 'attacher',
- thedata => 'data',
-};
+use constant ATTACHMENT_FIELDS =>
+ {mimetype => 'contenttype', submitter => 'attacher', thedata => 'data',};
# Same, for Bugzilla::Flag.
use constant FLAG_FIELDS => {
- 'flagtypes.name' => 'name',
- 'setters.login_name' => 'setter',
- 'requestees.login_name' => 'requestee',
+ 'flagtypes.name' => 'name',
+ 'setters.login_name' => 'setter',
+ 'requestees.login_name' => 'requestee',
};
# These are fields that we don't test. Test::More will mark these
@@ -115,40 +113,43 @@ use constant FLAG_FIELDS => {
#
# We don't support days_elapsed or owner_idle_time yet.
use constant SKIP_FIELDS => qw(
- owner_idle_time
- days_elapsed
+ owner_idle_time
+ days_elapsed
);
# All the fields that represent users.
use constant USER_FIELDS => qw(
- assigned_to
- cc
- reporter
- qa_contact
- commenter
- attachments.submitter
- setters.login_name
- requestees.login_name
+ assigned_to
+ cc
+ reporter
+ qa_contact
+ commenter
+ attachments.submitter
+ setters.login_name
+ requestees.login_name
);
# For the "substr"-type searches, how short of a substring should
# we use? The goal is to be shorter than the full string, but
# long enough to still be globally unique.
use constant SUBSTR_SIZE => 20;
+
# However, for some fields, we use a different size.
use constant FIELD_SUBSTR_SIZE => {
- alias => 11,
- # Just the month and day.
- deadline => -5,
- creation_ts => -8,
- delta_ts => -8,
- percentage_complete => 1,
- work_time => 3,
- remaining_time => 3,
- target_milestone => 15,
- longdesc => 25,
- # Just the hour and minute.
- FIELD_TYPE_DATETIME, -5,
+ alias => 11,
+
+ # Just the month and day.
+ deadline => -5,
+ creation_ts => -8,
+ delta_ts => -8,
+ percentage_complete => 1,
+ work_time => 3,
+ remaining_time => 3,
+ target_milestone => 15,
+ longdesc => 25,
+
+ # Just the hour and minute.
+ FIELD_TYPE_DATETIME, -5,
};
# For most fields, we add the length of the name of the field plus
@@ -156,9 +157,9 @@ use constant FIELD_SUBSTR_SIZE => {
# we're going to use. However, for some fields, it doesn't make sense to
# add in their field name this way.
use constant SUBSTR_NO_FIELD_ADD => FIELD_TYPE_DATETIME, qw(
- target_milestone remaining_time percentage_complete work_time
- attachments.mimetype attachments.submitter attachments.filename
- attachments.description flagtypes.name
+ target_milestone remaining_time percentage_complete work_time
+ attachments.mimetype attachments.submitter attachments.filename
+ attachments.description flagtypes.name
);
################
@@ -178,42 +179,42 @@ use constant SUBSTR_NO_FIELD_ADD => FIELD_TYPE_DATETIME, qw(
# lessthaneq. What we're really saying here by marking these broken
# is that there ought to be some way of searching "all ccs" vs "any cc"
# (and same for the other fields).
-use constant GREATERTHAN_BROKEN => (
- cc => { contains => [1] },
-);
+use constant GREATERTHAN_BROKEN => (cc => {contains => [1]},);
# allwords and allwordssubstr have these broken tests in common.
use constant ALLWORDS_BROKEN => (
- # allwordssubstr on cc fields matches against a single cc,
- # instead of matching against all ccs on a bug.
- cc => { contains => [1] },
- # bug 828344 changed how these searches operate to revert back to the 4.0
- # behavour, so these tests need to be updated (bug 849117).
- 'flagtypes.name' => { contains => [1] },
- longdesc => { contains => [1] },
+
+ # allwordssubstr on cc fields matches against a single cc,
+ # instead of matching against all ccs on a bug.
+ cc => {contains => [1]},
+
+ # bug 828344 changed how these searches operate to revert back to the 4.0
+ # behavour, so these tests need to be updated (bug 849117).
+ 'flagtypes.name' => {contains => [1]},
+ longdesc => {contains => [1]},
);
# Fields that don't generally work at all with changed* searches, but
# probably should.
use constant CHANGED_BROKEN => (
- classification => { contains => [1] },
- commenter => { contains => [1] },
- percentage_complete => { contains => [1] },
- 'requestees.login_name' => { contains => [1] },
- 'setters.login_name' => { contains => [1] },
- delta_ts => { contains => [1] },
+ classification => {contains => [1]},
+ commenter => {contains => [1]},
+ percentage_complete => {contains => [1]},
+ 'requestees.login_name' => {contains => [1]},
+ 'setters.login_name' => {contains => [1]},
+ delta_ts => {contains => [1]},
);
# These are additional broken tests that changedfrom and changedto
# have in common.
use constant CHANGED_VALUE_BROKEN => (
- bug_group => { contains => [1] },
- cc => { contains => [1] },
- estimated_time => { contains => [1] },
- 'flagtypes.name' => { contains => [1] },
- keywords => { contains => [1] },
- 'longdescs.count' => { search => 1 },
- FIELD_TYPE_MULTI_SELECT, { contains => [1] },
+ bug_group => {contains => [1]},
+ cc => {contains => [1]},
+ estimated_time => {contains => [1]},
+ 'flagtypes.name' => {contains => [1]},
+ keywords => {contains => [1]},
+ 'longdescs.count' => {search => 1},
+ FIELD_TYPE_MULTI_SELECT, {contains => [1]},
);
@@ -238,110 +239,92 @@ use constant CHANGED_VALUE_BROKEN => (
# while the other fails. In this case, we have a special override for
# "operator-value", which uniquely identifies tests.
use constant KNOWN_BROKEN => {
- greaterthan => { GREATERTHAN_BROKEN },
- greaterthaneq => { GREATERTHAN_BROKEN },
+ greaterthan => {GREATERTHAN_BROKEN},
+ greaterthaneq => {GREATERTHAN_BROKEN},
- 'allwordssubstr-<1>' => { ALLWORDS_BROKEN },
- 'allwords-<1>' => {
- ALLWORDS_BROKEN,
- },
- 'anywords-<1>' => {
- 'flagtypes.name' => { contains => [1,2,3,4,5] },
- },
- 'anywords-<1> <2>' => {
- 'flagtypes.name' => { contains => [3,4,5] },
- },
- 'anywordssubstr-<1> <2>' => {
- 'flagtypes.name' => { contains => [3,4,5] },
- },
+ 'allwordssubstr-<1>' => {ALLWORDS_BROKEN},
+ 'allwords-<1>' => {ALLWORDS_BROKEN,},
+ 'anywords-<1>' => {'flagtypes.name' => {contains => [1, 2, 3, 4, 5]},},
+ 'anywords-<1> <2>' => {'flagtypes.name' => {contains => [3, 4, 5]},},
+ 'anywordssubstr-<1> <2>' => {'flagtypes.name' => {contains => [3, 4, 5]},},
- # setters.login_name and requestees.login name aren't tracked individually
- # in bugs_activity, so can't be searched using this method.
- #
- # percentage_complete isn't tracked in bugs_activity (and it would be
- # really hard to track). However, it adds a 0=0 term instead of using
- # the changed* charts or simply denying them.
- #
- # delta_ts changedbefore/after should probably search for bugs based
- # on their delta_ts.
- #
- # creation_ts changedbefore/after should search for bug creation dates.
- #
- # The commenter field changedbefore/after should search for comment
- # creation dates.
- #
- # classification isn't being tracked properly in bugs_activity, I think.
- #
- # attach_data.thedata should search when attachments were created and
- # who they were created by.
- 'changedbefore' => {
- CHANGED_BROKEN,
- 'attach_data.thedata' => { contains => [1] },
- },
- 'changedafter' => {
- 'attach_data.thedata' => { contains => [2,3,4] },
- classification => { contains => [2,3,4] },
- commenter => { contains => [2,3,4] },
- delta_ts => { contains => [2,3,4] },
- percentage_complete => { contains => [2,3,4] },
- 'requestees.login_name' => { contains => [2,3,4] },
- 'setters.login_name' => { contains => [2,3,4] },
- },
- changedfrom => {
- CHANGED_BROKEN,
- CHANGED_VALUE_BROKEN,
- # All fields should have a way to search for "changing
- # from a blank value" probably.
- blocked => { contains => [3,4,5], no_criteria => 1 },
- dependson => { contains => [2,4,5], no_criteria => 1 },
- work_time => { contains => [1] },
- FIELD_TYPE_BUG_ID, { contains => [5], no_criteria => 1 },
- },
- # changeto doesn't find remaining_time changes (possibly due to us not
- # tracking that data properly).
- #
- # multi-valued fields are stored as comma-separated strings, so you
- # can't do changedfrom/to on them.
- #
- # Perhaps commenter can either tell you who the last commenter was,
- # or if somebody commented at a given time (combined with other
- # charts).
- #
- # longdesc changedto/from doesn't do anything; maybe it should.
- # Same for attach_data.thedata.
- changedto => {
- CHANGED_BROKEN,
- CHANGED_VALUE_BROKEN,
- 'attach_data.thedata' => { contains => [1] },
- longdesc => { contains => [1] },
- remaining_time => { contains => [1] },
- },
- changedby => {
- CHANGED_BROKEN,
- # This should probably search the attacher or anybody who changed
- # anything about an attachment at all.
- 'attach_data.thedata' => { contains => [1] },
- # This should probably search the reporter.
- creation_ts => { contains => [1] },
- },
- notequals => {
- 'flagtypes.name' => { contains => [1, 5] },
- longdesc => { contains => [1] },
- },
- notregexp => {
- 'flagtypes.name' => { contains => [1, 5] },
- longdesc => { contains => [1] },
- },
- notsubstring => {
- 'flagtypes.name' => { contains => [5] },
- longdesc => { contains => [1] },
- },
- nowords => {
- 'flagtypes.name' => { contains => [1, 5] },
- },
- nowordssubstr => {
- 'flagtypes.name' => { contains => [5] },
- },
+ # setters.login_name and requestees.login name aren't tracked individually
+ # in bugs_activity, so can't be searched using this method.
+ #
+ # percentage_complete isn't tracked in bugs_activity (and it would be
+ # really hard to track). However, it adds a 0=0 term instead of using
+ # the changed* charts or simply denying them.
+ #
+ # delta_ts changedbefore/after should probably search for bugs based
+ # on their delta_ts.
+ #
+ # creation_ts changedbefore/after should search for bug creation dates.
+ #
+ # The commenter field changedbefore/after should search for comment
+ # creation dates.
+ #
+ # classification isn't being tracked properly in bugs_activity, I think.
+ #
+ # attach_data.thedata should search when attachments were created and
+ # who they were created by.
+ 'changedbefore' =>
+ {CHANGED_BROKEN, 'attach_data.thedata' => {contains => [1]},},
+ 'changedafter' => {
+ 'attach_data.thedata' => {contains => [2, 3, 4]},
+ classification => {contains => [2, 3, 4]},
+ commenter => {contains => [2, 3, 4]},
+ delta_ts => {contains => [2, 3, 4]},
+ percentage_complete => {contains => [2, 3, 4]},
+ 'requestees.login_name' => {contains => [2, 3, 4]},
+ 'setters.login_name' => {contains => [2, 3, 4]},
+ },
+ changedfrom => {
+ CHANGED_BROKEN, CHANGED_VALUE_BROKEN,
+
+ # All fields should have a way to search for "changing
+ # from a blank value" probably.
+ blocked => {contains => [3, 4, 5], no_criteria => 1},
+ dependson => {contains => [2, 4, 5], no_criteria => 1},
+ work_time => {contains => [1]},
+ FIELD_TYPE_BUG_ID, {contains => [5], no_criteria => 1},
+ },
+
+ # changeto doesn't find remaining_time changes (possibly due to us not
+ # tracking that data properly).
+ #
+ # multi-valued fields are stored as comma-separated strings, so you
+ # can't do changedfrom/to on them.
+ #
+ # Perhaps commenter can either tell you who the last commenter was,
+ # or if somebody commented at a given time (combined with other
+ # charts).
+ #
+ # longdesc changedto/from doesn't do anything; maybe it should.
+ # Same for attach_data.thedata.
+ changedto => {
+ CHANGED_BROKEN, CHANGED_VALUE_BROKEN,
+ 'attach_data.thedata' => {contains => [1]},
+ longdesc => {contains => [1]},
+ remaining_time => {contains => [1]},
+ },
+ changedby => {
+ CHANGED_BROKEN,
+
+ # This should probably search the attacher or anybody who changed
+ # anything about an attachment at all.
+ 'attach_data.thedata' => {contains => [1]},
+
+ # This should probably search the reporter.
+ creation_ts => {contains => [1]},
+ },
+ notequals =>
+ {'flagtypes.name' => {contains => [1, 5]}, longdesc => {contains => [1]},},
+ notregexp =>
+ {'flagtypes.name' => {contains => [1, 5]}, longdesc => {contains => [1]},},
+ notsubstring =>
+ {'flagtypes.name' => {contains => [5]}, longdesc => {contains => [1]},},
+ nowords => {'flagtypes.name' => {contains => [1, 5]},},
+ nowordssubstr => {'flagtypes.name' => {contains => [5]},},
};
###################
@@ -350,135 +333,90 @@ use constant KNOWN_BROKEN => {
# Common BROKEN_NOT values for the changed* fields.
use constant CHANGED_BROKEN_NOT => (
- "attach_data.thedata" => { contains => [1] },
- "classification" => { contains => [1] },
- "commenter" => { contains => [1] },
- "delta_ts" => { contains => [1] },
- percentage_complete => { contains => [1] },
- "requestees.login_name" => { contains => [1] },
- "setters.login_name" => { contains => [1] },
+ "attach_data.thedata" => {contains => [1]},
+ "classification" => {contains => [1]},
+ "commenter" => {contains => [1]},
+ "delta_ts" => {contains => [1]},
+ percentage_complete => {contains => [1]},
+ "requestees.login_name" => {contains => [1]},
+ "setters.login_name" => {contains => [1]},
);
# For changedfrom and changedto.
use constant CHANGED_FROM_TO_BROKEN_NOT => (
- 'longdescs.count' => { search => 1 },
- "bug_group" => { contains => [1] },
- "cc" => { contains => [1] },
- "estimated_time" => { contains => [1] },
- "flagtypes.name" => { contains => [1] },
- "keywords" => { contains => [1] },
- FIELD_TYPE_MULTI_SELECT, { contains => [1] },
+ 'longdescs.count' => {search => 1},
+ "bug_group" => {contains => [1]},
+ "cc" => {contains => [1]},
+ "estimated_time" => {contains => [1]},
+ "flagtypes.name" => {contains => [1]},
+ "keywords" => {contains => [1]},
+ FIELD_TYPE_MULTI_SELECT, {contains => [1]},
);
# These are field/operator combinations that are broken when run under NOT().
use constant BROKEN_NOT => {
- allwords => {
- cc => { contains => [1] },
- 'flagtypes.name' => { contains => [1, 5] },
- longdesc => { contains => [1] },
- },
- 'allwords-<1> <2>' => {
- cc => { },
- },
- allwordssubstr => {
- cc => { contains => [1] },
- 'flagtypes.name' => { contains => [5, 6] },
- longdesc => { contains => [1] },
- },
- 'allwordssubstr-<1>,<2>' => {
- cc => { },
- longdesc => { contains => [1] },
- },
- anyexact => {
- 'flagtypes.name' => { contains => [1, 2, 5] },
- },
- 'anywords-<1>' => {
- 'flagtypes.name' => { contains => [1, 2, 3, 4, 5] },
- },
- 'anywords-<1> <2>' => {
- 'flagtypes.name' => { contains => [3, 4, 5] },
- },
- anywordssubstr => {
- 'flagtypes.name' => { contains => [5] },
- },
- 'anywordssubstr-<1> <2>' => {
- 'flagtypes.name' => { contains => [3,4,5] },
- },
- casesubstring => {
- 'flagtypes.name' => { contains => [5] },
- },
- changedafter => {
- "attach_data.thedata" => { contains => [2, 3, 4] },
- "classification" => { contains => [2, 3, 4] },
- "commenter" => { contains => [2, 3, 4] },
- percentage_complete => { contains => [2, 3, 4] },
- "delta_ts" => { contains => [2, 3, 4] },
- "requestees.login_name" => { contains => [2, 3, 4] },
- "setters.login_name" => { contains => [2, 3, 4] },
- },
- changedbefore => {
- CHANGED_BROKEN_NOT,
- },
- changedby => {
- CHANGED_BROKEN_NOT,
- creation_ts => { contains => [1] },
- work_time => { contains => [1] },
- },
- changedfrom => {
- CHANGED_BROKEN_NOT,
- CHANGED_FROM_TO_BROKEN_NOT,
- 'attach_data.thedata' => { },
- blocked => { contains => [1, 2] },
- dependson => { contains => [1, 3] },
- work_time => { contains => [1] },
- FIELD_TYPE_BUG_ID, { contains => [1 .. 4] },
- },
- changedto => {
- CHANGED_BROKEN_NOT,
- CHANGED_FROM_TO_BROKEN_NOT,
- longdesc => { contains => [1] },
- "remaining_time" => { contains => [1] },
- },
- greaterthan => {
- cc => { contains => [1] },
- 'flagtypes.name' => { contains => [5] },
- },
- greaterthaneq => {
- cc => { contains => [1] },
- 'flagtypes.name' => { contains => [2, 5] },
- },
- equals => {
- 'flagtypes.name' => { contains => [1, 5] },
- },
- notequals => {
- longdesc => { contains => [1] },
- },
- notregexp => {
- longdesc => { contains => [1] },
- },
- notsubstring => {
- longdesc => { contains => [1] },
- },
- 'nowords-<1>' => {
- 'flagtypes.name' => { contains => [5] },
- },
- 'nowordssubstr-<1>' => {
- 'flagtypes.name' => { contains => [5] },
- },
- lessthan => {
- 'flagtypes.name' => { contains => [5] },
- },
- lessthaneq => {
- 'flagtypes.name' => { contains => [1, 5] },
- },
- regexp => {
- 'flagtypes.name' => { contains => [1, 5] },
- longdesc => { contains => [1] },
- },
- substring => {
- 'flagtypes.name' => { contains => [5] },
- longdesc => { contains => [1] },
- },
+ allwords => {
+ cc => {contains => [1]},
+ 'flagtypes.name' => {contains => [1, 5]},
+ longdesc => {contains => [1]},
+ },
+ 'allwords-<1> <2>' => {cc => {},},
+ allwordssubstr => {
+ cc => {contains => [1]},
+ 'flagtypes.name' => {contains => [5, 6]},
+ longdesc => {contains => [1]},
+ },
+ 'allwordssubstr-<1>,<2>' => {cc => {}, longdesc => {contains => [1]},},
+ anyexact => {'flagtypes.name' => {contains => [1, 2, 5]},},
+ 'anywords-<1>' => {'flagtypes.name' => {contains => [1, 2, 3, 4, 5]},},
+ 'anywords-<1> <2>' => {'flagtypes.name' => {contains => [3, 4, 5]},},
+ anywordssubstr => {'flagtypes.name' => {contains => [5]},},
+ 'anywordssubstr-<1> <2>' => {'flagtypes.name' => {contains => [3, 4, 5]},},
+ casesubstring => {'flagtypes.name' => {contains => [5]},},
+ changedafter => {
+ "attach_data.thedata" => {contains => [2, 3, 4]},
+ "classification" => {contains => [2, 3, 4]},
+ "commenter" => {contains => [2, 3, 4]},
+ percentage_complete => {contains => [2, 3, 4]},
+ "delta_ts" => {contains => [2, 3, 4]},
+ "requestees.login_name" => {contains => [2, 3, 4]},
+ "setters.login_name" => {contains => [2, 3, 4]},
+ },
+ changedbefore => {CHANGED_BROKEN_NOT,},
+ changedby => {
+ CHANGED_BROKEN_NOT,
+ creation_ts => {contains => [1]},
+ work_time => {contains => [1]},
+ },
+ changedfrom => {
+ CHANGED_BROKEN_NOT, CHANGED_FROM_TO_BROKEN_NOT,
+ 'attach_data.thedata' => {},
+ blocked => {contains => [1, 2]},
+ dependson => {contains => [1, 3]},
+ work_time => {contains => [1]},
+ FIELD_TYPE_BUG_ID, {contains => [1 .. 4]},
+ },
+ changedto => {
+ CHANGED_BROKEN_NOT, CHANGED_FROM_TO_BROKEN_NOT,
+ longdesc => {contains => [1]},
+ "remaining_time" => {contains => [1]},
+ },
+ greaterthan =>
+ {cc => {contains => [1]}, 'flagtypes.name' => {contains => [5]},},
+ greaterthaneq =>
+ {cc => {contains => [1]}, 'flagtypes.name' => {contains => [2, 5]},},
+ equals => {'flagtypes.name' => {contains => [1, 5]},},
+ notequals => {longdesc => {contains => [1]},},
+ notregexp => {longdesc => {contains => [1]},},
+ notsubstring => {longdesc => {contains => [1]},},
+ 'nowords-<1>' => {'flagtypes.name' => {contains => [5]},},
+ 'nowordssubstr-<1>' => {'flagtypes.name' => {contains => [5]},},
+ lessthan => {'flagtypes.name' => {contains => [5]},},
+ lessthaneq => {'flagtypes.name' => {contains => [1, 5]},},
+ regexp =>
+ {'flagtypes.name' => {contains => [1, 5]}, longdesc => {contains => [1]},},
+ substring =>
+ {'flagtypes.name' => {contains => [5]}, longdesc => {contains => [1]},},
};
#############
@@ -489,113 +427,117 @@ use constant BROKEN_NOT => {
# Regex tests need unique test values for certain fields.
use constant REGEX_OVERRIDE => {
- 'attachments.mimetype' => { value => '^text/x-1-' },
- bug_file_loc => { value => '^http://1-' },
- see_also => { value => '^http://1-' },
- blocked => { value => '^<1>$' },
- dependson => { value => '^<1>$' },
- bug_id => { value => '^<1>$' },
- 'attachments.isobsolete' => { value => '^1'},
- 'attachments.ispatch' => { value => '^1'},
- 'attachments.isprivate' => { value => '^1' },
- cclist_accessible => { value => '^1' },
- reporter_accessible => { value => '^1' },
- everconfirmed => { value => '^1' },
- 'longdescs.count' => { value => '^3' },
- 'longdescs.isprivate' => { value => '^1' },
- creation_ts => { value => '^2037-01-01' },
- delta_ts => { value => '^2037-01-01' },
- deadline => { value => '^2037-02-01' },
- estimated_time => { value => '^1.0' },
- remaining_time => { value => '^9.0' },
- work_time => { value => '^1.0' },
- longdesc => { value => '^1-' },
- percentage_complete => { value => '^10' },
- FIELD_TYPE_BUG_ID, { value => '^<1>$' },
- FIELD_TYPE_DATETIME, { value => '^2037-03-01' }
+ 'attachments.mimetype' => {value => '^text/x-1-'},
+ bug_file_loc => {value => '^http://1-'},
+ see_also => {value => '^http://1-'},
+ blocked => {value => '^<1>$'},
+ dependson => {value => '^<1>$'},
+ bug_id => {value => '^<1>$'},
+ 'attachments.isobsolete' => {value => '^1'},
+ 'attachments.ispatch' => {value => '^1'},
+ 'attachments.isprivate' => {value => '^1'},
+ cclist_accessible => {value => '^1'},
+ reporter_accessible => {value => '^1'},
+ everconfirmed => {value => '^1'},
+ 'longdescs.count' => {value => '^3'},
+ 'longdescs.isprivate' => {value => '^1'},
+ creation_ts => {value => '^2037-01-01'},
+ delta_ts => {value => '^2037-01-01'},
+ deadline => {value => '^2037-02-01'},
+ estimated_time => {value => '^1.0'},
+ remaining_time => {value => '^9.0'},
+ work_time => {value => '^1.0'},
+ longdesc => {value => '^1-'},
+ percentage_complete => {value => '^10'},
+ FIELD_TYPE_BUG_ID, {value => '^<1>$'}, FIELD_TYPE_DATETIME,
+ {value => '^2037-03-01'}
};
# Common overrides between lessthan and lessthaneq.
use constant LESSTHAN_OVERRIDE => (
- alias => { contains => [1,5] },
- estimated_time => { contains => [1,5] },
- qa_contact => { contains => [1,5] },
- resolution => { contains => [1,5] },
- status_whiteboard => { contains => [1,5] },
- FIELD_TYPE_TEXTAREA, { contains => [1,5] },
- FIELD_TYPE_FREETEXT, { contains => [1,5] },
+ alias => {contains => [1, 5]},
+ estimated_time => {contains => [1, 5]},
+ qa_contact => {contains => [1, 5]},
+ resolution => {contains => [1, 5]},
+ status_whiteboard => {contains => [1, 5]},
+ FIELD_TYPE_TEXTAREA, {contains => [1, 5]}, FIELD_TYPE_FREETEXT,
+ {contains => [1, 5]},
);
# The mandatorily-set fields have values higher than <1>,
# so bug 5 shows up.
use constant GREATERTHAN_OVERRIDE => (
- classification => { contains => [2,3,4,5] },
- assigned_to => { contains => [2,3,4,5] },
- bug_id => { contains => [2,3,4,5] },
- bug_group => { contains => [1,2,3,4] },
- bug_severity => { contains => [2,3,4,5] },
- bug_status => { contains => [2,3,4,5] },
- component => { contains => [2,3,4,5] },
- commenter => { contains => [2,3,4,5] },
- # keywords matches if *any* keyword matches
- keywords => { contains => [1,2,3,4] },
- longdesc => { contains => [1,2,3,4] },
- op_sys => { contains => [2,3,4,5] },
- priority => { contains => [2,3,4,5] },
- product => { contains => [2,3,4,5] },
- reporter => { contains => [2,3,4,5] },
- rep_platform => { contains => [2,3,4,5] },
- short_desc => { contains => [2,3,4,5] },
- version => { contains => [2,3,4,5] },
- tag => { contains => [1,2,3,4] },
- target_milestone => { contains => [2,3,4,5] },
- # Bug 2 is the only bug besides 1 that has a Requestee set.
- 'requestees.login_name' => { contains => [2] },
- FIELD_TYPE_SINGLE_SELECT, { contains => [2,3,4,5] },
- # Override SINGLE_SELECT for resolution.
- resolution => { contains => [2,3,4] },
- # MULTI_SELECTs match if *any* value matches
- FIELD_TYPE_MULTI_SELECT, { contains => [1,2,3,4] },
+ classification => {contains => [2, 3, 4, 5]},
+ assigned_to => {contains => [2, 3, 4, 5]},
+ bug_id => {contains => [2, 3, 4, 5]},
+ bug_group => {contains => [1, 2, 3, 4]},
+ bug_severity => {contains => [2, 3, 4, 5]},
+ bug_status => {contains => [2, 3, 4, 5]},
+ component => {contains => [2, 3, 4, 5]},
+ commenter => {contains => [2, 3, 4, 5]},
+
+ # keywords matches if *any* keyword matches
+ keywords => {contains => [1, 2, 3, 4]},
+ longdesc => {contains => [1, 2, 3, 4]},
+ op_sys => {contains => [2, 3, 4, 5]},
+ priority => {contains => [2, 3, 4, 5]},
+ product => {contains => [2, 3, 4, 5]},
+ reporter => {contains => [2, 3, 4, 5]},
+ rep_platform => {contains => [2, 3, 4, 5]},
+ short_desc => {contains => [2, 3, 4, 5]},
+ version => {contains => [2, 3, 4, 5]},
+ tag => {contains => [1, 2, 3, 4]},
+ target_milestone => {contains => [2, 3, 4, 5]},
+
+ # Bug 2 is the only bug besides 1 that has a Requestee set.
+ 'requestees.login_name' => {contains => [2]},
+ FIELD_TYPE_SINGLE_SELECT, {contains => [2, 3, 4, 5]},
+
+ # Override SINGLE_SELECT for resolution.
+ resolution => {contains => [2, 3, 4]},
+
+ # MULTI_SELECTs match if *any* value matches
+ FIELD_TYPE_MULTI_SELECT, {contains => [1, 2, 3, 4]},
);
# For all positive multi-value types.
use constant MULTI_BOOLEAN_OVERRIDE => (
- 'attachments.ispatch' => { value => '1,1', contains => [1] },
- 'attachments.isobsolete' => { value => '1,1', contains => [1] },
- 'attachments.isprivate' => { value => '1,1', contains => [1] },
- cclist_accessible => { value => '1,1', contains => [1] },
- reporter_accessible => { value => '1,1', contains => [1] },
- 'longdescs.isprivate' => { value => '1,1', contains => [1] },
- everconfirmed => { value => '1,1', contains => [1] },
+ 'attachments.ispatch' => {value => '1,1', contains => [1]},
+ 'attachments.isobsolete' => {value => '1,1', contains => [1]},
+ 'attachments.isprivate' => {value => '1,1', contains => [1]},
+ cclist_accessible => {value => '1,1', contains => [1]},
+ reporter_accessible => {value => '1,1', contains => [1]},
+ 'longdescs.isprivate' => {value => '1,1', contains => [1]},
+ everconfirmed => {value => '1,1', contains => [1]},
);
# Same as above, for negative multi-value types.
use constant NEGATIVE_MULTI_BOOLEAN_OVERRIDE => (
- 'attachments.ispatch' => { value => '1,1', contains => [2,3,4,5] },
- 'attachments.isobsolete' => { value => '1,1', contains => [2,3,4,5] },
- 'attachments.isprivate' => { value => '1,1', contains => [2,3,4,5] },
- cclist_accessible => { value => '1,1', contains => [2,3,4,5] },
- reporter_accessible => { value => '1,1', contains => [2,3,4,5] },
- 'longdescs.isprivate' => { value => '1,1', contains => [2,3,4,5] },
- everconfirmed => { value => '1,1', contains => [2,3,4,5] },
+ 'attachments.ispatch' => {value => '1,1', contains => [2, 3, 4, 5]},
+ 'attachments.isobsolete' => {value => '1,1', contains => [2, 3, 4, 5]},
+ 'attachments.isprivate' => {value => '1,1', contains => [2, 3, 4, 5]},
+ cclist_accessible => {value => '1,1', contains => [2, 3, 4, 5]},
+ reporter_accessible => {value => '1,1', contains => [2, 3, 4, 5]},
+ 'longdescs.isprivate' => {value => '1,1', contains => [2, 3, 4, 5]},
+ everconfirmed => {value => '1,1', contains => [2, 3, 4, 5]},
);
# For anyexact and anywordssubstr
use constant ANY_OVERRIDE => (
- 'longdescs.count' => { contains => [1,2,3,4] },
- 'work_time' => { value => '1.0,2.0' },
- dependson => { value => '<1>,<3>', contains => [1,3] },
- MULTI_BOOLEAN_OVERRIDE,
+ 'longdescs.count' => {contains => [1, 2, 3, 4]},
+ 'work_time' => {value => '1.0,2.0'},
+ dependson => {value => '<1>,<3>', contains => [1, 3]},
+ MULTI_BOOLEAN_OVERRIDE,
);
# For all the changed* searches. The ones that have empty contains
# are fields that never change in value, or will never be rationally
# tracked in bugs_activity.
use constant CHANGED_OVERRIDE => (
- 'attachments.submitter' => { contains => [] },
- bug_id => { contains => [] },
- reporter => { contains => [] },
- tag => { contains => [] },
+ 'attachments.submitter' => {contains => []},
+ bug_id => {contains => []},
+ reporter => {contains => []},
+ tag => {contains => []},
);
#########
@@ -637,7 +579,7 @@ use constant CHANGED_OVERRIDE => (
# on Bug 1. If we did an "anywordssubstr" search test, it would
# become a space-separated string of the first few characters
# of each CC's login name on Bug 1.
-#
+#
# <#-id> - The bug id of the numbered bug.
# <#-reporter> - The login name of the numbered bug's reporter.
# <#-delta> - The delta_ts of the numbered bug.
@@ -655,301 +597,330 @@ use constant CHANGED_OVERRIDE => (
# override: This allows you to override "contains" and "values" for
# certain fields.
use constant TESTS => {
- equals => [
- { contains => [1], value => '<1>' },
- ],
- notequals => [
- { contains => [2,3,4,5], value => '<1>' },
- ],
- substring => [
- { contains => [1], value => '<1>',
- override => {
- percentage_complete => { contains => [1,2,3] },
- }
- },
- ],
- casesubstring => [
- { contains => [1], value => '<1>',
- override => {
- percentage_complete => { contains => [1,2,3] },
- }
- },
- { contains => [], value => '<1>', transform => sub { lc($_[0]) },
- extra_name => 'lc', if_equal => { contains => [1] },
- override => {
- percentage_complete => { contains => [1,2,3] },
- }
- },
- ],
- notsubstring => [
- { contains => [2,3,4,5], value => '<1>',
- override => {
- percentage_complete => { contains => [4,5] },
- },
- }
- ],
- regexp => [
- { contains => [1], value => '<1>', escape => 1,
- override => {
- percentage_complete => { value => '^10' },
- }
- },
- { contains => [1], value => '^1-', override => REGEX_OVERRIDE },
- ],
- notregexp => [
- { contains => [2,3,4,5], value => '<1>', escape => 1,
- override => {
- percentage_complete => { value => '^10' },
- }
- },
- { contains => [2,3,4,5], value => '^1-', override => REGEX_OVERRIDE },
- ],
- lessthan => [
- { contains => [1], value => 2,
- override => {
- # A lot of these contain bug 5 because an empty value is validly
- # less than the specified value.
- bug_file_loc => { value => 'http://2-', contains => [1,5] },
- see_also => { value => 'http://2-' },
- 'attachments.mimetype' => { value => 'text/x-2-' },
- blocked => { value => '<4-id>', contains => [1,2] },
- dependson => { value => '<3-id>', contains => [1,3] },
- bug_id => { value => '<2-id>' },
- 'attachments.isprivate' => { value => 1, contains => [2,3,4] },
- 'attachments.isobsolete' => { value => 1, contains => [2,3,4] },
- 'attachments.ispatch' => { value => 1, contains => [2,3,4] },
- cclist_accessible => { value => 1, contains => [2,3,4,5] },
- reporter_accessible => { value => 1, contains => [2,3,4,5] },
- 'longdescs.count' => { value => 3, contains => [2,3,4,5] },
- 'longdescs.isprivate' => { value => 1, contains => [1,2,3,4,5] },
- everconfirmed => { value => 1, contains => [2,3,4,5] },
- creation_ts => { value => '2037-01-02', contains => [1,5] },
- delta_ts => { value => '2037-01-02', contains => [1,5] },
- deadline => { value => '2037-02-02', contains => [1,5] },
- remaining_time => { value => 10, contains => [1,5] },
- percentage_complete => { value => 11, contains => [1,5] },
- longdesc => { value => '2-', contains => [1,5] },
- work_time => { value => 1, contains => [5] },
- FIELD_TYPE_BUG_ID, { value => '<2>', contains => [1,5] },
- FIELD_TYPE_DATETIME, { value => '2037-03-02', contains => [1,5] },
- LESSTHAN_OVERRIDE,
- }
- },
- ],
- lessthaneq => [
- { contains => [1], value => '<1>',
- override => {
- 'attachments.isobsolete' => { value => 0, contains => [2,3,4] },
- 'attachments.ispatch' => { value => 0, contains => [2,3,4] },
- 'attachments.isprivate' => { value => 0, contains => [2,3,4] },
- cclist_accessible => { value => 0, contains => [2,3,4,5] },
- reporter_accessible => { value => 0, contains => [2,3,4,5] },
- 'longdescs.count' => { value => 2, contains => [2,3,4,5] },
- 'longdescs.isprivate' => { value => -1, contains => [] },
- everconfirmed => { value => 0, contains => [2,3,4,5] },
- bug_file_loc => { contains => [1,5] },
- blocked => { contains => [1,2] },
- deadline => { contains => [1,5] },
- dependson => { contains => [1,3] },
- creation_ts => { contains => [1,5] },
- delta_ts => { contains => [1,5] },
- remaining_time => { contains => [1,5] },
- longdesc => { contains => [1,5] },
- percentage_complete => { contains => [1,5] },
- work_time => { value => 1, contains => [1,5] },
- FIELD_TYPE_BUG_ID, { contains => [1,5] },
- FIELD_TYPE_DATETIME, { contains => [1,5] },
- LESSTHAN_OVERRIDE,
- },
- },
- ],
- greaterthan => [
- { contains => [2,3,4], value => '<1>',
- override => {
- dependson => { contains => [3] },
- blocked => { contains => [2] },
- 'attachments.ispatch' => { value => 0, contains => [1] },
- 'attachments.isobsolete' => { value => 0, contains => [1] },
- 'attachments.isprivate' => { value => 0, contains => [1] },
- cclist_accessible => { value => 0, contains => [1] },
- reporter_accessible => { value => 0, contains => [1] },
- 'longdescs.count' => { value => 2, contains => [1] },
- 'longdescs.isprivate' => { value => 0, contains => [1] },
- everconfirmed => { value => 0, contains => [1] },
- 'flagtypes.name' => { value => 2, contains => [2,3,4] },
- GREATERTHAN_OVERRIDE,
- },
- },
- ],
- greaterthaneq => [
- { contains => [2,3,4], value => '<2>',
- override => {
- 'attachments.ispatch' => { value => 1, contains => [1] },
- 'attachments.isobsolete' => { value => 1, contains => [1] },
- 'attachments.isprivate' => { value => 1, contains => [1] },
- cclist_accessible => { value => 1, contains => [1] },
- reporter_accessible => { value => 1, contains => [1] },
- 'longdescs.count' => { value => 3, contains => [1] },
- 'longdescs.isprivate' => { value => 1, contains => [1] },
- everconfirmed => { value => 1, contains => [1] },
- dependson => { value => '<3>', contains => [1,3] },
- blocked => { contains => [1,2] },
- GREATERTHAN_OVERRIDE,
- }
- },
- ],
- matches => [
- { contains => [1], value => '<1>' },
- ],
- notmatches => [
- { contains => [2,3,4,5], value => '<1>' },
- ],
- anyexact => [
- { contains => [1,2], value => '<1>, <2>',
- override => { ANY_OVERRIDE } },
- ],
- anywordssubstr => [
- { contains => [1,2], value => '<1> <2>',
- override => {
- ANY_OVERRIDE,
- percentage_complete => { contains => [1,2,3] },
- }
- },
- ],
- allwordssubstr => [
- { contains => [1], value => '<1>',
- override => {
- MULTI_BOOLEAN_OVERRIDE,
- # We search just the number "1" for percentage_complete,
- # which matches a lot of bugs.
- percentage_complete => { contains => [1,2,3] },
- },
- },
- { contains => [], value => '<1>,<2>',
- override => {
- dependson => { value => '<1-id> <3-id>', contains => [] },
- # bug 3 has the value "21" here, so matches "2,1"
- percentage_complete => { value => '<2>,<3>', contains => [3] },
- # 1 0 matches bug 1, which has both public and private comments.
- 'longdescs.isprivate' => { contains => [1] },
- }
- },
- ],
- nowordssubstr => [
- { contains => [2,3,4,5], value => '<1>',
- override => {
- # longdescs.isprivate translates to "1 0", so no bugs should
- # show up.
- 'longdescs.isprivate' => { contains => [] },
- percentage_complete => { contains => [4,5] },
- work_time => { contains => [2,3,4,5] },
- }
- },
- ],
- anywords => [
- { contains => [1], value => '<1>',
- override => {
- MULTI_BOOLEAN_OVERRIDE,
- }
- },
- { contains => [1,2], value => '<1> <2>',
- override => {
- MULTI_BOOLEAN_OVERRIDE,
- dependson => { value => '<1> <3>', contains => [1,3] },
- 'longdescs.count' => { contains => [1,2,3,4] },
- },
- },
- ],
- allwords => [
- { contains => [1], value => '<1>',
- override => { MULTI_BOOLEAN_OVERRIDE } },
- { contains => [], value => '<1> <2>',
- override => {
- dependson => { contains => [], value => '<2-id> <3-id>' },
- # 1 0 matches bug 1, which has both public and private comments.
- 'longdescs.isprivate' => { contains => [1] },
- }
- },
- ],
- nowords => [
- { contains => [2,3,4,5], value => '<1>',
- override => {
- # longdescs.isprivate translates to "1 0", so no bugs should
- # show up.
- 'longdescs.isprivate' => { contains => [] },
- work_time => { contains => [2,3,4,5] },
- }
- },
- ],
-
- changedbefore => [
- { contains => [1], value => '<1-delta>',
- override => {
- CHANGED_OVERRIDE,
- creation_ts => { contains => [1,5] },
- blocked => { contains => [1,2] },
- dependson => { contains => [1,3] },
- longdesc => { contains => [1,5] },
- 'longdescs.count' => { contains => [1,5] },
- }
- },
- ],
- changedafter => [
- { contains => [2,3,4], value => '<2-delta>',
- override => {
- CHANGED_OVERRIDE,
- creation_ts => { contains => [3,4] },
- # We only change this for one bug, and it doesn't match.
- 'longdescs.isprivate' => { contains => [] },
- # Same for everconfirmed.
- 'everconfirmed' => { contains => [] },
- # For blocked and dependson, they have the delta_ts of bug1
- # in the bugs_activity table, so they won't ever match.
- blocked => { contains => [] },
- dependson => { contains => [] },
- }
- },
- ],
- changedfrom => [
- { contains => [1], value => '<1>',
- override => {
- CHANGED_OVERRIDE,
- # The test never changes an already-set dependency field, but
- # we *can* attempt to test searching against an empty value,
- # which should get us some bugs.
- blocked => { value => '', contains => [1,2] },
- dependson => { value => '', contains => [1,3] },
- FIELD_TYPE_BUG_ID, { value => '', contains => [1,2,3,4] },
- # longdesc changedfrom doesn't make any sense.
- longdesc => { contains => [] },
- # Nor does creation_ts changedfrom.
- creation_ts => { contains => [] },
- 'attach_data.thedata' => { contains => [] },
- bug_id => { value => '<1-id>', contains => [] },
- },
- },
- ],
- changedto => [
- { contains => [1], value => '<1>',
- override => {
- CHANGED_OVERRIDE,
- # I can't imagine any use for creation_ts changedto.
- creation_ts => { contains => [] },
- }
- },
- ],
- changedby => [
- { contains => [1], value => '<1-reporter>',
- override => {
- CHANGED_OVERRIDE,
- blocked => { contains => [1,2] },
- dependson => { contains => [1,3] },
- },
- },
- ],
- # XXX these need tests developed
- isempty => [],
- isnotempty => [],
+ equals => [{contains => [1], value => '<1>'},],
+ notequals => [{contains => [2, 3, 4, 5], value => '<1>'},],
+ substring => [
+ {
+ contains => [1],
+ value => '<1>',
+ override => {percentage_complete => {contains => [1, 2, 3]},}
+ },
+ ],
+ casesubstring => [
+ {
+ contains => [1],
+ value => '<1>',
+ override => {percentage_complete => {contains => [1, 2, 3]},}
+ },
+ {
+ contains => [],
+ value => '<1>',
+ transform => sub { lc($_[0]) },
+ extra_name => 'lc',
+ if_equal => {contains => [1]},
+ override => {percentage_complete => {contains => [1, 2, 3]},}
+ },
+ ],
+ notsubstring => [{
+ contains => [2, 3, 4, 5],
+ value => '<1>',
+ override => {percentage_complete => {contains => [4, 5]},},
+ }],
+ regexp => [
+ {
+ contains => [1],
+ value => '<1>',
+ escape => 1,
+ override => {percentage_complete => {value => '^10'},}
+ },
+ {contains => [1], value => '^1-', override => REGEX_OVERRIDE},
+ ],
+ notregexp => [
+ {
+ contains => [2, 3, 4, 5],
+ value => '<1>',
+ escape => 1,
+ override => {percentage_complete => {value => '^10'},}
+ },
+ {contains => [2, 3, 4, 5], value => '^1-', override => REGEX_OVERRIDE},
+ ],
+ lessthan => [
+ {
+ contains => [1],
+ value => 2,
+ override => {
+
+ # A lot of these contain bug 5 because an empty value is validly
+ # less than the specified value.
+ bug_file_loc => {value => 'http://2-', contains => [1, 5]},
+ see_also => {value => 'http://2-'},
+ 'attachments.mimetype' => {value => 'text/x-2-'},
+ blocked => {value => '<4-id>', contains => [1, 2]},
+ dependson => {value => '<3-id>', contains => [1, 3]},
+ bug_id => {value => '<2-id>'},
+ 'attachments.isprivate' => {value => 1, contains => [2, 3, 4]},
+ 'attachments.isobsolete' => {value => 1, contains => [2, 3, 4]},
+ 'attachments.ispatch' => {value => 1, contains => [2, 3, 4]},
+ cclist_accessible => {value => 1, contains => [2, 3, 4, 5]},
+ reporter_accessible => {value => 1, contains => [2, 3, 4, 5]},
+ 'longdescs.count' => {value => 3, contains => [2, 3, 4, 5]},
+ 'longdescs.isprivate' => {value => 1, contains => [1, 2, 3, 4, 5]},
+ everconfirmed => {value => 1, contains => [2, 3, 4, 5]},
+ creation_ts => {value => '2037-01-02', contains => [1, 5]},
+ delta_ts => {value => '2037-01-02', contains => [1, 5]},
+ deadline => {value => '2037-02-02', contains => [1, 5]},
+ remaining_time => {value => 10, contains => [1, 5]},
+ percentage_complete => {value => 11, contains => [1, 5]},
+ longdesc => {value => '2-', contains => [1, 5]},
+ work_time => {value => 1, contains => [5]},
+ FIELD_TYPE_BUG_ID, {value => '<2>', contains => [1, 5]}, FIELD_TYPE_DATETIME,
+ {value => '2037-03-02', contains => [1, 5]}, LESSTHAN_OVERRIDE,
+ }
+ },
+ ],
+ lessthaneq => [
+ {
+ contains => [1],
+ value => '<1>',
+ override => {
+ 'attachments.isobsolete' => {value => 0, contains => [2, 3, 4]},
+ 'attachments.ispatch' => {value => 0, contains => [2, 3, 4]},
+ 'attachments.isprivate' => {value => 0, contains => [2, 3, 4]},
+ cclist_accessible => {value => 0, contains => [2, 3, 4, 5]},
+ reporter_accessible => {value => 0, contains => [2, 3, 4, 5]},
+ 'longdescs.count' => {value => 2, contains => [2, 3, 4, 5]},
+ 'longdescs.isprivate' => {value => -1, contains => []},
+ everconfirmed => {value => 0, contains => [2, 3, 4, 5]},
+ bug_file_loc => {contains => [1, 5]},
+ blocked => {contains => [1, 2]},
+ deadline => {contains => [1, 5]},
+ dependson => {contains => [1, 3]},
+ creation_ts => {contains => [1, 5]},
+ delta_ts => {contains => [1, 5]},
+ remaining_time => {contains => [1, 5]},
+ longdesc => {contains => [1, 5]},
+ percentage_complete => {contains => [1, 5]},
+ work_time => {value => 1, contains => [1, 5]},
+ FIELD_TYPE_BUG_ID, {contains => [1, 5]}, FIELD_TYPE_DATETIME,
+ {contains => [1, 5]}, LESSTHAN_OVERRIDE,
+ },
+ },
+ ],
+ greaterthan => [
+ {
+ contains => [2, 3, 4],
+ value => '<1>',
+ override => {
+ dependson => {contains => [3]},
+ blocked => {contains => [2]},
+ 'attachments.ispatch' => {value => 0, contains => [1]},
+ 'attachments.isobsolete' => {value => 0, contains => [1]},
+ 'attachments.isprivate' => {value => 0, contains => [1]},
+ cclist_accessible => {value => 0, contains => [1]},
+ reporter_accessible => {value => 0, contains => [1]},
+ 'longdescs.count' => {value => 2, contains => [1]},
+ 'longdescs.isprivate' => {value => 0, contains => [1]},
+ everconfirmed => {value => 0, contains => [1]},
+ 'flagtypes.name' => {value => 2, contains => [2, 3, 4]},
+ GREATERTHAN_OVERRIDE,
+ },
+ },
+ ],
+ greaterthaneq => [
+ {
+ contains => [2, 3, 4],
+ value => '<2>',
+ override => {
+ 'attachments.ispatch' => {value => 1, contains => [1]},
+ 'attachments.isobsolete' => {value => 1, contains => [1]},
+ 'attachments.isprivate' => {value => 1, contains => [1]},
+ cclist_accessible => {value => 1, contains => [1]},
+ reporter_accessible => {value => 1, contains => [1]},
+ 'longdescs.count' => {value => 3, contains => [1]},
+ 'longdescs.isprivate' => {value => 1, contains => [1]},
+ everconfirmed => {value => 1, contains => [1]},
+ dependson => {value => '<3>', contains => [1, 3]},
+ blocked => {contains => [1, 2]},
+ GREATERTHAN_OVERRIDE,
+ }
+ },
+ ],
+ matches => [{contains => [1], value => '<1>'},],
+ notmatches => [{contains => [2, 3, 4, 5], value => '<1>'},],
+ anyexact =>
+ [{contains => [1, 2], value => '<1>, <2>', override => {ANY_OVERRIDE}},],
+ anywordssubstr => [
+ {
+ contains => [1, 2],
+ value => '<1> <2>',
+ override => {ANY_OVERRIDE, percentage_complete => {contains => [1, 2, 3]},}
+ },
+ ],
+ allwordssubstr => [
+ {
+ contains => [1],
+ value => '<1>',
+ override => {
+ MULTI_BOOLEAN_OVERRIDE,
+
+ # We search just the number "1" for percentage_complete,
+ # which matches a lot of bugs.
+ percentage_complete => {contains => [1, 2, 3]},
+ },
+ },
+ {
+ contains => [],
+ value => '<1>,<2>',
+ override => {
+ dependson => {value => '<1-id> <3-id>', contains => []},
+
+ # bug 3 has the value "21" here, so matches "2,1"
+ percentage_complete => {value => '<2>,<3>', contains => [3]},
+
+ # 1 0 matches bug 1, which has both public and private comments.
+ 'longdescs.isprivate' => {contains => [1]},
+ }
+ },
+ ],
+ nowordssubstr => [
+ {
+ contains => [2, 3, 4, 5],
+ value => '<1>',
+ override => {
+
+ # longdescs.isprivate translates to "1 0", so no bugs should
+ # show up.
+ 'longdescs.isprivate' => {contains => []},
+ percentage_complete => {contains => [4, 5]},
+ work_time => {contains => [2, 3, 4, 5]},
+ }
+ },
+ ],
+ anywords => [
+ {contains => [1], value => '<1>', override => {MULTI_BOOLEAN_OVERRIDE,}},
+ {
+ contains => [1, 2],
+ value => '<1> <2>',
+ override => {
+ MULTI_BOOLEAN_OVERRIDE,
+ dependson => {value => '<1> <3>', contains => [1, 3]},
+ 'longdescs.count' => {contains => [1, 2, 3, 4]},
+ },
+ },
+ ],
+ allwords => [
+ {contains => [1], value => '<1>', override => {MULTI_BOOLEAN_OVERRIDE}},
+ {
+ contains => [],
+ value => '<1> <2>',
+ override => {
+ dependson => {contains => [], value => '<2-id> <3-id>'},
+
+ # 1 0 matches bug 1, which has both public and private comments.
+ 'longdescs.isprivate' => {contains => [1]},
+ }
+ },
+ ],
+ nowords => [
+ {
+ contains => [2, 3, 4, 5],
+ value => '<1>',
+ override => {
+
+ # longdescs.isprivate translates to "1 0", so no bugs should
+ # show up.
+ 'longdescs.isprivate' => {contains => []},
+ work_time => {contains => [2, 3, 4, 5]},
+ }
+ },
+ ],
+
+ changedbefore => [
+ {
+ contains => [1],
+ value => '<1-delta>',
+ override => {
+ CHANGED_OVERRIDE,
+ creation_ts => {contains => [1, 5]},
+ blocked => {contains => [1, 2]},
+ dependson => {contains => [1, 3]},
+ longdesc => {contains => [1, 5]},
+ 'longdescs.count' => {contains => [1, 5]},
+ }
+ },
+ ],
+ changedafter => [
+ {
+ contains => [2, 3, 4],
+ value => '<2-delta>',
+ override => {
+ CHANGED_OVERRIDE,
+ creation_ts => {contains => [3, 4]},
+
+ # We only change this for one bug, and it doesn't match.
+ 'longdescs.isprivate' => {contains => []},
+
+ # Same for everconfirmed.
+ 'everconfirmed' => {contains => []},
+
+ # For blocked and dependson, they have the delta_ts of bug1
+ # in the bugs_activity table, so they won't ever match.
+ blocked => {contains => []},
+ dependson => {contains => []},
+ }
+ },
+ ],
+ changedfrom => [
+ {
+ contains => [1],
+ value => '<1>',
+ override => {
+ CHANGED_OVERRIDE,
+
+ # The test never changes an already-set dependency field, but
+ # we *can* attempt to test searching against an empty value,
+ # which should get us some bugs.
+ blocked => {value => '', contains => [1, 2]},
+ dependson => {value => '', contains => [1, 3]},
+ FIELD_TYPE_BUG_ID, {value => '', contains => [1, 2, 3, 4]},
+
+ # longdesc changedfrom doesn't make any sense.
+ longdesc => {contains => []},
+
+ # Nor does creation_ts changedfrom.
+ creation_ts => {contains => []},
+ 'attach_data.thedata' => {contains => []},
+ bug_id => {value => '<1-id>', contains => []},
+ },
+ },
+ ],
+ changedto => [
+ {
+ contains => [1],
+ value => '<1>',
+ override => {
+ CHANGED_OVERRIDE,
+
+ # I can't imagine any use for creation_ts changedto.
+ creation_ts => {contains => []},
+ }
+ },
+ ],
+ changedby => [
+ {
+ contains => [1],
+ value => '<1-reporter>',
+ override => {
+ CHANGED_OVERRIDE,
+ blocked => {contains => [1, 2]},
+ dependson => {contains => [1, 3]},
+ },
+ },
+ ],
+
+ # XXX these need tests developed
+ isempty => [],
+ isnotempty => [],
};
# Fields that do not behave as we expect, for InjectionTest.
@@ -958,59 +929,62 @@ use constant TESTS => {
# operator_ok overrides the "brokenness" of certain operators, so that they
# are always OK for that field/operator combination.
use constant INJECTION_BROKEN_FIELD => {
- # Pg can't run injection tests against integer or date fields. See bug 577557.
- 'attachments.isobsolete' => { db_skip => ['Pg'] },
- 'attachments.ispatch' => { db_skip => ['Pg'] },
- 'attachments.isprivate' => { db_skip => ['Pg'] },
- blocked => { db_skip => ['Pg'] },
- bug_id => { db_skip => ['Pg'] },
- cclist_accessible => { db_skip => ['Pg'] },
- creation_ts => { db_skip => ['Pg'] },
- days_elapsed => { db_skip => ['Pg'] },
- dependson => { db_skip => ['Pg'] },
- deadline => { db_skip => ['Pg'] },
- delta_ts => { db_skip => ['Pg'] },
- estimated_time => { db_skip => ['Pg'] },
- everconfirmed => { db_skip => ['Pg'] },
- 'longdescs.isprivate' => { db_skip => ['Pg'] },
- percentage_complete => { db_skip => ['Pg'] },
- remaining_time => { db_skip => ['Pg'] },
- reporter_accessible => { db_skip => ['Pg'] },
- work_time => { db_skip => ['Pg'] },
- FIELD_TYPE_BUG_ID, { db_skip => ['Pg'] },
- FIELD_TYPE_DATETIME, { db_skip => ['Pg'] },
- owner_idle_time => { search => 1 },
- 'longdescs.count' => {
- search => 1,
- db_skip => ['Pg'],
- operator_ok => [qw(allwords allwordssubstr anywordssubstr casesubstring
- changedbefore changedafter greaterthan greaterthaneq
- lessthan lessthaneq notregexp notsubstring
- nowordssubstr regexp substring anywords
- notequals nowords equals anyexact)],
- },
+
+ # Pg can't run injection tests against integer or date fields. See bug 577557.
+ 'attachments.isobsolete' => {db_skip => ['Pg']},
+ 'attachments.ispatch' => {db_skip => ['Pg']},
+ 'attachments.isprivate' => {db_skip => ['Pg']},
+ blocked => {db_skip => ['Pg']},
+ bug_id => {db_skip => ['Pg']},
+ cclist_accessible => {db_skip => ['Pg']},
+ creation_ts => {db_skip => ['Pg']},
+ days_elapsed => {db_skip => ['Pg']},
+ dependson => {db_skip => ['Pg']},
+ deadline => {db_skip => ['Pg']},
+ delta_ts => {db_skip => ['Pg']},
+ estimated_time => {db_skip => ['Pg']},
+ everconfirmed => {db_skip => ['Pg']},
+ 'longdescs.isprivate' => {db_skip => ['Pg']},
+ percentage_complete => {db_skip => ['Pg']},
+ remaining_time => {db_skip => ['Pg']},
+ reporter_accessible => {db_skip => ['Pg']},
+ work_time => {db_skip => ['Pg']},
+ FIELD_TYPE_BUG_ID,
+ {db_skip => ['Pg']},
+ FIELD_TYPE_DATETIME,
+ {db_skip => ['Pg']},
+ owner_idle_time => {search => 1},
+ 'longdescs.count' => {
+ search => 1,
+ db_skip => ['Pg'],
+ operator_ok => [qw(allwords allwordssubstr anywordssubstr casesubstring
+ changedbefore changedafter greaterthan greaterthaneq
+ lessthan lessthaneq notregexp notsubstring
+ nowordssubstr regexp substring anywords
+ notequals nowords equals anyexact)],
+ },
};
# Operators that do not behave as we expect, for InjectionTest.
# search => 1 means the Bugzilla::Search creation fails, but
# field_ok contains fields that it does actually succeed for.
use constant INJECTION_BROKEN_OPERATOR => {
- changedafter => { search => 1, field_ok => ['creation_ts'] },
- changedbefore => { search => 1, field_ok => ['creation_ts'] },
- changedby => { search => 1 },
- isempty => { search => 1 },
- isnotempty => { search => 1 },
+ changedafter => {search => 1, field_ok => ['creation_ts']},
+ changedbefore => {search => 1, field_ok => ['creation_ts']},
+ changedby => {search => 1},
+ isempty => {search => 1},
+ isnotempty => {search => 1},
};
# Tests run by Bugzilla::Test::Search::InjectionTest.
# We have to make sure the values are all one word or they'll be split
# up by the multi-word tests.
use constant INJECTION_TESTS => (
- { value => ';SEMICOLON_TEST' },
- { value => '--COMMENT_TEST' },
- { value => "'QUOTE_TEST" },
- { value => "';QUOTE_SEMICOLON_TEST" },
- { value => '/*STAR_COMMENT_TEST' }
+ {value => ';SEMICOLON_TEST'},
+ {value => '--COMMENT_TEST'},
+ {value => "'QUOTE_TEST"},
+ {value => "';QUOTE_SEMICOLON_TEST"},
+ {value => '/*STAR_COMMENT_TEST'}
);
#################
@@ -1018,185 +992,257 @@ use constant INJECTION_TESTS => (
#################
use constant SPECIAL_PARAM_TESTS => (
- { field => 'bug_status', operator => 'anyexact', value => '__open__',
- contains => [5] },
- { field => 'bug_status', operator => 'anyexact', value => '__closed__',
- contains => [1,2,3,4] },
- { field => 'bug_status', operator => 'anyexact', value => '__all__',
- contains => [1,2,3,4,5] },
-
- { field => 'resolution', operator => 'anyexact', value => '---',
- contains => [5] },
-
- # email* query parameters.
- { field => 'assigned_to', operator => 'anyexact',
- value => '<1>, <2-reporter>', contains => [1,2],
- extra_params => { emailreporter1 => 1 } },
- { field => 'assigned_to', operator => 'equals',
- value => '<1>', extra_name => 'email2', contains => [],
- extra_params => {
- email2 => generate_random_password(100), emaillongdesc2 => 1,
- },
- },
-
- # standard pronouns
- { field => 'assigned_to', operator => 'equals', value => '%assignee%',
- contains => [1,2,3,4,5] },
- { field => 'reporter', operator => 'equals', value => '%reporter%',
- contains => [1,2,3,4,5] },
- { field => 'qa_contact', operator => 'equals', value => '%qacontact%',
- contains => [1,2,3,4,5] },
- { field => 'cc', operator => 'equals', value => '%user%',
- contains => [1] },
- # group pronouns
- { field => 'reporter', operator => 'equals',
- value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] },
- { field => 'assigned_to', operator => 'equals',
- value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] },
- { field => 'qa_contact', operator => 'equals',
- value => '%group.<1-bug_group>%', contains => [1,2,3,4] },
- { field => 'cc', operator => 'equals',
- value => '%group.<1-bug_group>%', contains => [1,2,3,4] },
- { field => 'commenter', operator => 'equals',
- value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] },
+ {
+ field => 'bug_status',
+ operator => 'anyexact',
+ value => '__open__',
+ contains => [5]
+ },
+ {
+ field => 'bug_status',
+ operator => 'anyexact',
+ value => '__closed__',
+ contains => [1, 2, 3, 4]
+ },
+ {
+ field => 'bug_status',
+ operator => 'anyexact',
+ value => '__all__',
+ contains => [1, 2, 3, 4, 5]
+ },
+
+ {
+ field => 'resolution',
+ operator => 'anyexact',
+ value => '---',
+ contains => [5]
+ },
+
+ # email* query parameters.
+ {
+ field => 'assigned_to',
+ operator => 'anyexact',
+ value => '<1>, <2-reporter>',
+ contains => [1, 2],
+ extra_params => {emailreporter1 => 1}
+ },
+ {
+ field => 'assigned_to',
+ operator => 'equals',
+ value => '<1>',
+ extra_name => 'email2',
+ contains => [],
+ extra_params => {email2 => generate_random_password(100), emaillongdesc2 => 1,},
+ },
+
+ # standard pronouns
+ {
+ field => 'assigned_to',
+ operator => 'equals',
+ value => '%assignee%',
+ contains => [1, 2, 3, 4, 5]
+ },
+ {
+ field => 'reporter',
+ operator => 'equals',
+ value => '%reporter%',
+ contains => [1, 2, 3, 4, 5]
+ },
+ {
+ field => 'qa_contact',
+ operator => 'equals',
+ value => '%qacontact%',
+ contains => [1, 2, 3, 4, 5]
+ },
+ {field => 'cc', operator => 'equals', value => '%user%', contains => [1]},
+
+ # group pronouns
+ {
+ field => 'reporter',
+ operator => 'equals',
+ value => '%group.<1-bug_group>%',
+ contains => [1, 2, 3, 4, 5]
+ },
+ {
+ field => 'assigned_to',
+ operator => 'equals',
+ value => '%group.<1-bug_group>%',
+ contains => [1, 2, 3, 4, 5]
+ },
+ {
+ field => 'qa_contact',
+ operator => 'equals',
+ value => '%group.<1-bug_group>%',
+ contains => [1, 2, 3, 4]
+ },
+ {
+ field => 'cc',
+ operator => 'equals',
+ value => '%group.<1-bug_group>%',
+ contains => [1, 2, 3, 4]
+ },
+ {
+ field => 'commenter',
+ operator => 'equals',
+ value => '%group.<1-bug_group>%',
+ contains => [1, 2, 3, 4, 5]
+ },
);
use constant CUSTOM_SEARCH_TESTS => (
- { name => 'OP without CP', contains => [1],
- params => [
- { f => 'OP' },
- { f => 'bug_id', o => 'equals', v => '<1>' },
- ]
- },
+ {
+ name => 'OP without CP',
+ contains => [1],
+ params => [{f => 'OP'}, {f => 'bug_id', o => 'equals', v => '<1>'},]
+ },
- { name => 'Empty OP/CP pair before criteria', contains => [1],
- params => [
- { f => 'OP' }, { f => 'CP' },
- { f => 'bug_id', o => 'equals', v => '<1>' },
- ]
- },
+ {
+ name => 'Empty OP/CP pair before criteria',
+ contains => [1],
+ params =>
+ [{f => 'OP'}, {f => 'CP'}, {f => 'bug_id', o => 'equals', v => '<1>'},]
+ },
- { name => 'Empty OP/CP pair after criteria', contains => [1],
- params => [
- { f => 'bug_id', o => 'equals', v => '<1>' },
- { f => 'OP' }, { f => 'CP' },
- ]
- },
+ {
+ name => 'Empty OP/CP pair after criteria',
+ contains => [1],
+ params =>
+ [{f => 'bug_id', o => 'equals', v => '<1>'}, {f => 'OP'}, {f => 'CP'},]
+ },
- { name => 'empty OP/CP mid criteria', contains => [1],
- columns => ['assigned_to'],
- params => [
- { f => 'bug_id', o => 'equals', v => '<1>' },
- { f => 'OP' }, { f => 'CP' },
- { f => 'assigned_to', o => 'substr', v => '@' },
- ]
- },
+ {
+ name => 'empty OP/CP mid criteria',
+ contains => [1],
+ columns => ['assigned_to'],
+ params => [
+ {f => 'bug_id', o => 'equals', v => '<1>'},
+ {f => 'OP'},
+ {f => 'CP'},
+ {f => 'assigned_to', o => 'substr', v => '@'},
+ ]
+ },
- { name => 'bug_id = 1 AND assigned_to contains @', contains => [1],
- columns => ['assigned_to'],
- params => [
- { f => 'bug_id', o => 'equals', v => '<1>' },
- { f => 'assigned_to', o => 'substr', v => '@' },
- ]
- },
+ {
+ name => 'bug_id = 1 AND assigned_to contains @',
+ contains => [1],
+ columns => ['assigned_to'],
+ params => [
+ {f => 'bug_id', o => 'equals', v => '<1>'},
+ {f => 'assigned_to', o => 'substr', v => '@'},
+ ]
+ },
- { name => 'NOT(bug_id = 1) AND NOT(assigned_to = 2)',
- contains => [3,4,5],
- columns => ['assigned_to'],
- params => [
- { n => 1, f => 'bug_id', o => 'equals', v => '<1>' },
- { n => 1, f => 'assigned_to', o => 'equals', v => '<2>' },
- ]
- },
+ {
+ name => 'NOT(bug_id = 1) AND NOT(assigned_to = 2)',
+ contains => [3, 4, 5],
+ columns => ['assigned_to'],
+ params => [
+ {n => 1, f => 'bug_id', o => 'equals', v => '<1>'},
+ {n => 1, f => 'assigned_to', o => 'equals', v => '<2>'},
+ ]
+ },
- { name => 'bug_id = 1 OR assigned_to = 2', contains => [1,2],
- columns => ['assigned_to'], top_params => { j_top => 'OR' },
- params => [
- { f => 'bug_id', o => 'equals', v => '<1>' },
- { f => 'assigned_to', o => 'equals', v => '<2>' },
- ]
- },
+ {
+ name => 'bug_id = 1 OR assigned_to = 2',
+ contains => [1, 2],
+ columns => ['assigned_to'],
+ top_params => {j_top => 'OR'},
+ params => [
+ {f => 'bug_id', o => 'equals', v => '<1>'},
+ {f => 'assigned_to', o => 'equals', v => '<2>'},
+ ]
+ },
- { name => 'NOT(bug_id = 1 AND assigned_to = 1)', contains => [2,3,4,5],
- columns => ['assigned_to'],
- params => [
- { f => 'OP', n => 1 },
- { f => 'bug_id', o => 'equals', v => '<1>' },
- { f => 'assigned_to', o => 'equals', v => '<1>' },
- { f => 'CP' },
- ]
- },
+ {
+ name => 'NOT(bug_id = 1 AND assigned_to = 1)',
+ contains => [2, 3, 4, 5],
+ columns => ['assigned_to'],
+ params => [
+ {f => 'OP', n => 1},
+ {f => 'bug_id', o => 'equals', v => '<1>'},
+ {f => 'assigned_to', o => 'equals', v => '<1>'},
+ {f => 'CP'},
+ ]
+ },
- { name => '(bug_id = 1 AND assigned_to contains @) '
- . ' OR (bug_id = 2 AND assigned_to contains @)',
- contains => [1,2], columns => ['assigned_to'],
- top_params => { j_top => 'OR' },
- params => [
- { f => 'OP' },
- { f => 'bug_id', o => 'equals', v => '<1>' },
- { f => 'assigned_to', o => 'substr', v => '@' },
- { f => 'CP' },
- { f => 'OP' },
- { f => 'bug_id', o => 'equals', v => '<2>' },
- { f => 'assigned_to', o => 'substr', v => '@' },
- { f => 'CP' },
- ]
- },
+ {
+ name => '(bug_id = 1 AND assigned_to contains @) '
+ . ' OR (bug_id = 2 AND assigned_to contains @)',
+ contains => [1, 2],
+ columns => ['assigned_to'],
+ top_params => {j_top => 'OR'},
+ params => [
+ {f => 'OP'},
+ {f => 'bug_id', o => 'equals', v => '<1>'},
+ {f => 'assigned_to', o => 'substr', v => '@'},
+ {f => 'CP'},
+ {f => 'OP'},
+ {f => 'bug_id', o => 'equals', v => '<2>'},
+ {f => 'assigned_to', o => 'substr', v => '@'},
+ {f => 'CP'},
+ ]
+ },
- { name => '(bug_id = 1 OR assigned_to = 2) '
- . ' AND (bug_id = 2 OR assigned_to = 1)',
- contains => [1,2], columns => ['assigned_to'],
- params => [
- { f => 'OP', j => 'OR' },
- { f => 'bug_id', o => 'equals', v => '<1>' },
- { f => 'assigned_to', o => 'equals', v => '<2>' },
- { f => 'CP' },
- { f => 'OP', j => 'OR' },
- { f => 'bug_id', o => 'equals', v => '<2>' },
- { f => 'assigned_to', o => 'equals', v => '<1>' },
- { f => 'CP' },
- ]
- },
+ {
+ name => '(bug_id = 1 OR assigned_to = 2) '
+ . ' AND (bug_id = 2 OR assigned_to = 1)',
+ contains => [1, 2],
+ columns => ['assigned_to'],
+ params => [
+ {f => 'OP', j => 'OR'},
+ {f => 'bug_id', o => 'equals', v => '<1>'},
+ {f => 'assigned_to', o => 'equals', v => '<2>'},
+ {f => 'CP'},
+ {f => 'OP', j => 'OR'},
+ {f => 'bug_id', o => 'equals', v => '<2>'},
+ {f => 'assigned_to', o => 'equals', v => '<1>'},
+ {f => 'CP'},
+ ]
+ },
- { name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) '
- . ' AND (bug_id = 2 OR assigned_to = 1) )',
- contains => [1,2,3], columns => ['assigned_to'],
- top_params => { j_top => 'OR' },
- params => [
- { f => 'bug_id', o => 'equals', v => '<3>' },
- { f => 'OP' },
- { f => 'OP', j => 'OR' },
- { f => 'bug_id', o => 'equals', v => '<1>' },
- { f => 'assigned_to', o => 'equals', v => '<2>' },
- { f => 'CP' },
- { f => 'OP', j => 'OR' },
- { f => 'bug_id', o => 'equals', v => '<2>' },
- { f => 'assigned_to', o => 'equals', v => '<1>' },
- { f => 'CP' },
- { f => 'CP' },
- ]
- },
+ {
+ name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) '
+ . ' AND (bug_id = 2 OR assigned_to = 1) )',
+ contains => [1, 2, 3],
+ columns => ['assigned_to'],
+ top_params => {j_top => 'OR'},
+ params => [
+ {f => 'bug_id', o => 'equals', v => '<3>'},
+ {f => 'OP'},
+ {f => 'OP', j => 'OR'},
+ {f => 'bug_id', o => 'equals', v => '<1>'},
+ {f => 'assigned_to', o => 'equals', v => '<2>'},
+ {f => 'CP'},
+ {f => 'OP', j => 'OR'},
+ {f => 'bug_id', o => 'equals', v => '<2>'},
+ {f => 'assigned_to', o => 'equals', v => '<1>'},
+ {f => 'CP'},
+ {f => 'CP'},
+ ]
+ },
- { name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) '
- . ' AND (bug_id = 2 OR assigned_to = 1) ) OR bug_id = 4',
- contains => [1,2,3,4], columns => ['assigned_to'],
- top_params => { j_top => 'OR' },
- params => [
- { f => 'bug_id', o => 'equals', v => '<3>' },
- { f => 'OP' },
- { f => 'OP', j => 'OR' },
- { f => 'bug_id', o => 'equals', v => '<1>' },
- { f => 'assigned_to', o => 'equals', v => '<2>' },
- { f => 'CP' },
- { f => 'OP', j => 'OR' },
- { f => 'bug_id', o => 'equals', v => '<2>' },
- { f => 'assigned_to', o => 'equals', v => '<1>' },
- { f => 'CP' },
- { f => 'CP' },
- { f => 'bug_id', o => 'equals', v => '<4>' },
- ]
- },
+ {
+ name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) '
+ . ' AND (bug_id = 2 OR assigned_to = 1) ) OR bug_id = 4',
+ contains => [1, 2, 3, 4],
+ columns => ['assigned_to'],
+ top_params => {j_top => 'OR'},
+ params => [
+ {f => 'bug_id', o => 'equals', v => '<3>'},
+ {f => 'OP'},
+ {f => 'OP', j => 'OR'},
+ {f => 'bug_id', o => 'equals', v => '<1>'},
+ {f => 'assigned_to', o => 'equals', v => '<2>'},
+ {f => 'CP'},
+ {f => 'OP', j => 'OR'},
+ {f => 'bug_id', o => 'equals', v => '<2>'},
+ {f => 'assigned_to', o => 'equals', v => '<1>'},
+ {f => 'CP'},
+ {f => 'CP'},
+ {f => 'bug_id', o => 'equals', v => '<4>'},
+ ]
+ },
);
diff --git a/xt/lib/Bugzilla/Test/Search/CustomTest.pm b/xt/lib/Bugzilla/Test/Search/CustomTest.pm
index 132e5ac40..7455d4828 100644
--- a/xt/lib/Bugzilla/Test/Search/CustomTest.pm
+++ b/xt/lib/Bugzilla/Test/Search/CustomTest.pm
@@ -24,7 +24,7 @@ use Storable qw(dclone);
sub new {
my ($class, $test, $search_test) = @_;
- bless { raw_test => dclone($test), search_test => $search_test }, $class;
+ bless {raw_test => dclone($test), search_test => $search_test}, $class;
}
#############
@@ -32,31 +32,33 @@ sub new {
#############
sub search_test { return $_[0]->{search_test} }
-sub name { return 'Custom: ' . $_[0]->test->{name} }
-sub test { return $_[0]->{raw_test} }
+sub name { return 'Custom: ' . $_[0]->test->{name} }
+sub test { return $_[0]->{raw_test} }
sub operator_test { die "unimplemented" }
-sub field_object { die "unimplemented" }
-sub main_value { die "unimplenmented" }
-sub test_value { die "unimplemented" }
+sub field_object { die "unimplemented" }
+sub main_value { die "unimplenmented" }
+sub test_value { die "unimplemented" }
+
# Custom tests don't use transforms.
-sub transformed_value_was_equal { 0 }
+sub transformed_value_was_equal {0}
+
sub debug_value {
- my ($self) = @_;
- my $string = '';
- my $params = $self->search_params;
- foreach my $param (keys %$params) {
- $string .= $param . "=" . $params->{$param} . '&';
- }
- chop($string);
- return $string;
+ my ($self) = @_;
+ my $string = '';
+ my $params = $self->search_params;
+ foreach my $param (keys %$params) {
+ $string .= $param . "=" . $params->{$param} . '&';
+ }
+ chop($string);
+ return $string;
}
# The tests we know are broken for this operator/field combination.
sub _known_broken { return {} }
-sub contains_known_broken { return undef }
-sub search_known_broken { return undef }
-sub field_not_yet_implemented { return undef }
+sub contains_known_broken { return undef }
+sub search_known_broken { return undef }
+sub field_not_yet_implemented { return undef }
sub invalid_field_operator_combination { return undef }
#########################################
@@ -66,36 +68,35 @@ sub invalid_field_operator_combination { return undef }
# Converts the f, o, v rows into f0, o0, v0, etc. and translates
# the values appropriately.
sub search_params {
- my ($self) = @_;
-
- my %params = %{ $self->test->{top_params} || {} };
- my $counter = 0;
- foreach my $row (@{ $self->test->{params} }) {
- $row->{v} = $self->translate_value($row) if exists $row->{v};
- foreach my $key (keys %$row) {
- $params{"${key}$counter"} = $row->{$key};
- }
- $counter++;
+ my ($self) = @_;
+
+ my %params = %{$self->test->{top_params} || {}};
+ my $counter = 0;
+ foreach my $row (@{$self->test->{params}}) {
+ $row->{v} = $self->translate_value($row) if exists $row->{v};
+ foreach my $key (keys %$row) {
+ $params{"${key}$counter"} = $row->{$key};
}
+ $counter++;
+ }
- return \%params;
+ return \%params;
}
sub translate_value {
- my ($self, $row) = @_;
- my $as_test = { field => $row->{f}, operator => $row->{o},
- value => $row->{v} };
- my $operator_test = new Bugzilla::Test::Search::OperatorTest($row->{o},
- $self->search_test);
- my $field = Bugzilla::Field->check($row->{f});
- my $field_test = new Bugzilla::Test::Search::FieldTest($operator_test,
- $field, $as_test);
- return $field_test->translated_value;
+ my ($self, $row) = @_;
+ my $as_test = {field => $row->{f}, operator => $row->{o}, value => $row->{v}};
+ my $operator_test
+ = new Bugzilla::Test::Search::OperatorTest($row->{o}, $self->search_test);
+ my $field = Bugzilla::Field->check($row->{f});
+ my $field_test
+ = new Bugzilla::Test::Search::FieldTest($operator_test, $field, $as_test);
+ return $field_test->translated_value;
}
sub search_columns {
- my ($self) = @_;
- return ['bug_id', @{ $self->test->{columns} || [] }];
+ my ($self) = @_;
+ return ['bug_id', @{$self->test->{columns} || []}];
}
1;
diff --git a/xt/lib/Bugzilla/Test/Search/FieldTest.pm b/xt/lib/Bugzilla/Test/Search/FieldTest.pm
index 5e86d92e2..2ccaab735 100644
--- a/xt/lib/Bugzilla/Test/Search/FieldTest.pm
+++ b/xt/lib/Bugzilla/Test/Search/FieldTest.pm
@@ -26,10 +26,12 @@ use Test::Exception;
###############
sub new {
- my ($class, $operator_test, $field, $test) = @_;
- return bless { operator_test => $operator_test,
- field_object => $field,
- raw_test => $test }, $class;
+ my ($class, $operator_test, $field, $test) = @_;
+ return bless {
+ operator_test => $operator_test,
+ field_object => $field,
+ raw_test => $test
+ }, $class;
}
#############
@@ -40,144 +42,156 @@ sub num_tests { return TESTS_PER_RUN }
# The Bugzilla::Test::Search::OperatorTest that this is a child of.
sub operator_test { return $_[0]->{operator_test} }
+
# The Bugzilla::Field being tested.
sub field_object { return $_[0]->{field_object} }
+
# The name of the field being tested, which we need much more often
# than we need the object.
sub field {
- my ($self) = @_;
- $self->{field_name} ||= $self->field_object->name;
- return $self->{field_name};
+ my ($self) = @_;
+ $self->{field_name} ||= $self->field_object->name;
+ return $self->{field_name};
}
+
# The Bugzilla::Test::Search object that this is a child of.
sub search_test { return $_[0]->operator_test->search_test }
+
# The operator being tested
sub operator { return $_[0]->operator_test->operator }
+
# The bugs currently being tested by Bugzilla::Test::Search.
sub bugs { return $_[0]->search_test->bugs }
+
sub bug {
- my $self = shift;
- return $self->search_test->bug(@_);
+ my $self = shift;
+ return $self->search_test->bug(@_);
}
+
sub number {
- my ($self, $id) = @_;
- foreach my $number (1..NUM_BUGS) {
- return $number if $self->search_test->bug($number)->id == $id;
- }
- return 0;
+ my ($self, $id) = @_;
+ foreach my $number (1 .. NUM_BUGS) {
+ return $number if $self->search_test->bug($number)->id == $id;
+ }
+ return 0;
}
# The name displayed for this test by Test::More. Used in test descriptions.
sub name {
- my ($self) = @_;
- my $field = $self->field;
- my $operator = $self->operator;
- my $value = $self->main_value;
-
- my $name = "$field-$operator-$value";
- if (my $extra_name = $self->test->{extra_name}) {
- $name .= "-$extra_name";
- }
- return $name;
+ my ($self) = @_;
+ my $field = $self->field;
+ my $operator = $self->operator;
+ my $value = $self->main_value;
+
+ my $name = "$field-$operator-$value";
+ if (my $extra_name = $self->test->{extra_name}) {
+ $name .= "-$extra_name";
+ }
+ return $name;
}
# The appropriate value from the TESTS constant for this test, taking
# into account overrides.
sub test {
- my $self = shift;
- return $self->{test} if $self->{test};
-
- my %test = %{ $self->{raw_test} };
-
- # We have field name overrides...
- my $override = $test{override}->{$self->field};
- # And also field type overrides.
- if (!$override) {
- $override = $test{override}->{$self->field_object->type} || {};
- }
-
- foreach my $key (%$override) {
- $test{$key} = $override->{$key};
- }
-
- $self->{test} = \%test;
- return $self->{test};
+ my $self = shift;
+ return $self->{test} if $self->{test};
+
+ my %test = %{$self->{raw_test}};
+
+ # We have field name overrides...
+ my $override = $test{override}->{$self->field};
+
+ # And also field type overrides.
+ if (!$override) {
+ $override = $test{override}->{$self->field_object->type} || {};
+ }
+
+ foreach my $key (%$override) {
+ $test{$key} = $override->{$key};
+ }
+
+ $self->{test} = \%test;
+ return $self->{test};
}
# All the values for all the bugs for this field.
sub _field_values {
- my ($self) = @_;
- return $self->{field_values} if $self->{field_values};
-
- my %field_values;
- foreach my $number (1..NUM_BUGS) {
- $field_values{$number} = $self->_field_values_for_bug($number);
- }
- $self->{field_values} = \%field_values;
- return $self->{field_values};
+ my ($self) = @_;
+ return $self->{field_values} if $self->{field_values};
+
+ my %field_values;
+ foreach my $number (1 .. NUM_BUGS) {
+ $field_values{$number} = $self->_field_values_for_bug($number);
+ }
+ $self->{field_values} = \%field_values;
+ return $self->{field_values};
}
+
# The values for this field for the numbered bug.
sub bug_values {
- my ($self, $number) = @_;
- return @{ $self->_field_values->{$number} };
+ my ($self, $number) = @_;
+ return @{$self->_field_values->{$number}};
}
# The untranslated, non-overriden value--used in the name of the test
# and other places.
sub main_value { return $_[0]->{raw_test}->{value} }
+
# The untranslated test value, taking into account overrides.
-sub test_value { return $_[0]->test->{value} };
+sub test_value { return $_[0]->test->{value} }
+
# The value translated appropriately for passing to Bugzilla::Search.
sub translated_value {
- my $self = shift;
- if (!exists $self->{translated_value}) {
- my $value = $self->search_test->value_translation_cache($self);
- if (!defined $value) {
- $value = $self->_translate_value();
- $self->search_test->value_translation_cache($self, $value);
- }
- $self->{translated_value} = $value;
+ my $self = shift;
+ if (!exists $self->{translated_value}) {
+ my $value = $self->search_test->value_translation_cache($self);
+ if (!defined $value) {
+ $value = $self->_translate_value();
+ $self->search_test->value_translation_cache($self, $value);
}
- return $self->{translated_value};
+ $self->{translated_value} = $value;
+ }
+ return $self->{translated_value};
}
+
# Used in failure diagnostic messages.
sub debug_fail {
- my ($self, $number, $results, $sql) = @_;
- my @expected = @{ $self->test->{contains} };
- my @results = sort
- map { $self->number($_) }
- map { $_->[0] }
- @$results;
- return
- " Value: '" . $self->translated_value . "'\n" .
- "Expected: [" . join(',', @expected) . "]\n" .
- " Results: [" . join(',', @results) . "]\n" .
- trim($sql) . "\n";
+ my ($self, $number, $results, $sql) = @_;
+ my @expected = @{$self->test->{contains}};
+ my @results = sort map { $self->number($_) } map { $_->[0] } @$results;
+ return
+ " Value: '"
+ . $self->translated_value . "'\n"
+ . "Expected: ["
+ . join(',', @expected) . "]\n"
+ . " Results: ["
+ . join(',', @results) . "]\n"
+ . trim($sql) . "\n";
}
# True for a bug if we ran the "transform" function on it and the
# result was equal to its first value.
sub transformed_value_was_equal {
- my ($self, $number, $value) = @_;
- if (@_ > 2) {
- $self->{transformed_value_was_equal}->{$number} = $value;
- $self->search_test->was_equal_cache($self, $number, $value);
- }
- my $cached = $self->search_test->was_equal_cache($self, $number);
- return $cached if defined $cached;
- return $self->{transformed_value_was_equal}->{$number};
+ my ($self, $number, $value) = @_;
+ if (@_ > 2) {
+ $self->{transformed_value_was_equal}->{$number} = $value;
+ $self->search_test->was_equal_cache($self, $number, $value);
+ }
+ my $cached = $self->search_test->was_equal_cache($self, $number);
+ return $cached if defined $cached;
+ return $self->{transformed_value_was_equal}->{$number};
}
# True if this test is supposed to contain the numbered bug.
sub bug_is_contained {
- my ($self, $number) = @_;
- my $contains = $self->test->{contains};
- if ($self->transformed_value_was_equal($number)
- and !$self->test->{override}->{$self->field}->{contains})
- {
- $contains = $self->test->{if_equal}->{contains};
- }
- return grep($_ == $number, @$contains) ? 1 : 0;
+ my ($self, $number) = @_;
+ my $contains = $self->test->{contains};
+ if ($self->transformed_value_was_equal($number)
+ and !$self->test->{override}->{$self->field}->{contains})
+ {
+ $contains = $self->test->{if_equal}->{contains};
+ }
+ return grep($_ == $number, @$contains) ? 1 : 0;
}
###################################################
@@ -186,112 +200,114 @@ sub bug_is_contained {
# The tests we know are broken for this operator/field combination.
sub _known_broken {
- my ($self, $constant, $skip_pg_check) = @_;
-
- $constant ||= KNOWN_BROKEN;
- my $field = $self->field;
- my $type = $self->field_object->type;
- my $operator = $self->operator;
- my $value = $self->main_value;
- my $value_name = "$operator-$value";
- if (my $extra_name = $self->test->{extra_name}) {
- $value_name .= "-$extra_name";
- }
-
- my $value_broken = $constant->{$value_name}->{$field};
- $value_broken ||= $constant->{$value_name}->{$type};
- return $value_broken if $value_broken;
- my $operator_broken = $constant->{$operator}->{$field};
- $operator_broken ||= $constant->{$operator}->{$type};
- return $operator_broken if $operator_broken;
- return {};
+ my ($self, $constant, $skip_pg_check) = @_;
+
+ $constant ||= KNOWN_BROKEN;
+ my $field = $self->field;
+ my $type = $self->field_object->type;
+ my $operator = $self->operator;
+ my $value = $self->main_value;
+ my $value_name = "$operator-$value";
+ if (my $extra_name = $self->test->{extra_name}) {
+ $value_name .= "-$extra_name";
+ }
+
+ my $value_broken = $constant->{$value_name}->{$field};
+ $value_broken ||= $constant->{$value_name}->{$type};
+ return $value_broken if $value_broken;
+ my $operator_broken = $constant->{$operator}->{$field};
+ $operator_broken ||= $constant->{$operator}->{$type};
+ return $operator_broken if $operator_broken;
+ return {};
}
# True if the "contains" search for the numbered bug is broken.
# That is, either the result is supposed to contain it and doesn't,
# or the result is not supposed to contain it and does.
sub contains_known_broken {
- my ($self, $number) = @_;
- my $field = $self->field;
- my $operator = $self->operator;
+ my ($self, $number) = @_;
+ my $field = $self->field;
+ my $operator = $self->operator;
- my $contains_broken = $self->_known_broken->{contains} || [];
- if (grep($_ == $number, @$contains_broken)) {
- return "$field $operator contains $number is known to be broken";
- }
- return undef;
+ my $contains_broken = $self->_known_broken->{contains} || [];
+ if (grep($_ == $number, @$contains_broken)) {
+ return "$field $operator contains $number is known to be broken";
+ }
+ return undef;
}
# Used by subclasses. Checks both bug_is_contained and contains_known_broken
# to tell you whether or not the bug will *actually* be found by the test.
sub will_actually_contain_bug {
- my ($self, $number) = @_;
- my $is_contained = $self->bug_is_contained($number) ? 1 : 0;
- my $is_broken = $self->contains_known_broken($number) ? 1 : 0;
+ my ($self, $number) = @_;
+ my $is_contained = $self->bug_is_contained($number) ? 1 : 0;
+ my $is_broken = $self->contains_known_broken($number) ? 1 : 0;
+
+ # If the test is supposed to contain the bug and *isn't* broken,
+ # then the test will contain the bug.
+ return 1 if ($is_contained and !$is_broken);
- # If the test is supposed to contain the bug and *isn't* broken,
- # then the test will contain the bug.
- return 1 if ($is_contained and !$is_broken);
- # If this test is *not* supposed to contain the bug, but that test is
- # broken, then this test *will* contain the bug.
- return 1 if (!$is_contained and $is_broken);
+ # If this test is *not* supposed to contain the bug, but that test is
+ # broken, then this test *will* contain the bug.
+ return 1 if (!$is_contained and $is_broken);
- return 0;
+ return 0;
}
# Returns a string if creating a Bugzilla::Search object throws an error,
# with this field/operator/value combination.
sub search_known_broken {
- my ($self) = @_;
- my $field = $self->field;
- my $operator = $self->operator;
- if ($self->_known_broken->{search}) {
- return "Bugzilla::Search for $field $operator is known to be broken";
- }
- return undef;
+ my ($self) = @_;
+ my $field = $self->field;
+ my $operator = $self->operator;
+ if ($self->_known_broken->{search}) {
+ return "Bugzilla::Search for $field $operator is known to be broken";
+ }
+ return undef;
}
-
+
# Returns a string if we haven't yet implemented the tests for this field,
# but we plan to in the future.
sub field_not_yet_implemented {
- my ($self) = @_;
- my $skip_this_field = grep { $_ eq $self->field } SKIP_FIELDS;
- if ($skip_this_field) {
- my $field = $self->field;
- return "$field testing not yet implemented";
- }
- return undef;
+ my ($self) = @_;
+ my $skip_this_field = grep { $_ eq $self->field } SKIP_FIELDS;
+ if ($skip_this_field) {
+ my $field = $self->field;
+ return "$field testing not yet implemented";
+ }
+ return undef;
}
# Returns a message if this field/operator combination can't ever be run.
# At no time in the future will this field/operator combination ever work.
sub invalid_field_operator_combination {
- my ($self) = @_;
- my $field = $self->field;
- my $operator = $self->operator;
-
- if ($field eq 'content' && $operator !~ /matches/) {
- return "content field does not support $operator";
- }
- elsif ($operator =~ /matches/ && $field ne 'content') {
- return "matches operator does not support fields other than content";
- }
- return undef;
+ my ($self) = @_;
+ my $field = $self->field;
+ my $operator = $self->operator;
+
+ if ($field eq 'content' && $operator !~ /matches/) {
+ return "content field does not support $operator";
+ }
+ elsif ($operator =~ /matches/ && $field ne 'content') {
+ return "matches operator does not support fields other than content";
+ }
+ return undef;
}
# True if this field is broken in an OR combination.
sub join_broken {
- my ($self, $or_broken_map) = @_;
- my $or_broken = $or_broken_map->{$self->field . '-' . $self->operator};
- if (!$or_broken) {
- # See if this is a comment field, and in that case, if there's
- # a generic entry for all comment fields.
- my $is_comment_field = COMMENT_FIELDS->{$self->field};
- if ($is_comment_field) {
- $or_broken = $or_broken_map->{'longdescs.-' . $self->operator};
- }
+ my ($self, $or_broken_map) = @_;
+ my $or_broken = $or_broken_map->{$self->field . '-' . $self->operator};
+ if (!$or_broken) {
+
+ # See if this is a comment field, and in that case, if there's
+ # a generic entry for all comment fields.
+ my $is_comment_field = COMMENT_FIELDS->{$self->field};
+ if ($is_comment_field) {
+ $or_broken = $or_broken_map->{'longdescs.-' . $self->operator};
}
- return $or_broken;
+ }
+ return $or_broken;
}
#########################################
@@ -300,28 +316,28 @@ sub join_broken {
# The data that will get passed to Bugzilla::Search as its arguments.
sub search_params {
- my ($self) = @_;
- return $self->{search_params} if $self->{search_params};
+ my ($self) = @_;
+ return $self->{search_params} if $self->{search_params};
+
+ my %params = (
+ "field0-0-0" => $self->field,
+ "type0-0-0" => $self->operator,
+ "value0-0-0" => $self->translated_value,
+ );
- my %params = (
- "field0-0-0" => $self->field,
- "type0-0-0" => $self->operator,
- "value0-0-0" => $self->translated_value,
- );
-
- $self->{search_params} = \%params;
- return $self->{search_params};
+ $self->{search_params} = \%params;
+ return $self->{search_params};
}
sub search_columns {
- my ($self) = @_;
- my $field = $self->field;
- my @search_fields = qw(bug_id);
- if ($self->field_object->buglist) {
- my $col_name = COLUMN_TRANSLATION->{$field} || $field;
- push(@search_fields, $col_name);
- }
- return \@search_fields;
+ my ($self) = @_;
+ my $field = $self->field;
+ my @search_fields = qw(bug_id);
+ if ($self->field_object->buglist) {
+ my $col_name = COLUMN_TRANSLATION->{$field} || $field;
+ push(@search_fields, $col_name);
+ }
+ return \@search_fields;
}
@@ -330,103 +346,107 @@ sub search_columns {
################
sub _field_values_for_bug {
- my ($self, $number) = @_;
- my $field = $self->field;
-
- my @values;
-
- if ($field =~ /^attach.+\.(.+)$/ ) {
- my $attach_field = $1;
- $attach_field = ATTACHMENT_FIELDS->{$attach_field} || $attach_field;
- @values = $self->_values_for($number, 'attachments', $attach_field);
- }
- elsif (my $flag_field = FLAG_FIELDS->{$field}) {
- @values = $self->_values_for($number, 'flags', $flag_field);
- }
- elsif (my $translation = COMMENT_FIELDS->{$field}) {
- @values = $self->_values_for($number, 'comments', $translation);
- # We want the last value to come first, so that single-value
- # searches use the last comment.
- @values = reverse @values;
- }
- elsif ($field eq 'longdescs.count') {
- @values = scalar(@{ $self->bug($number)->comments });
- }
- elsif ($field eq 'work_time') {
- @values = $self->_values_for($number, 'actual_time');
- }
- elsif ($field eq 'bug_group') {
- @values = $self->_values_for($number, 'groups_in', 'name');
- }
- elsif ($field eq 'keywords') {
- @values = $self->_values_for($number, 'keyword_objects', 'name');
- }
- elsif ($field eq 'content') {
- @values = $self->_values_for($number, 'short_desc');
- }
- elsif ($field eq 'see_also') {
- @values = $self->_values_for($number, 'see_also', 'name');
- }
- elsif ($field eq 'tag') {
- @values = $self->_values_for($number, 'tags');
- }
- # Bugzilla::Bug truncates creation_ts, but we need the full value
- # from the database. This has no special value for changedfrom,
- # because it never changes.
- elsif ($field eq 'creation_ts') {
- my $bug = $self->bug($number);
- my $creation_ts = Bugzilla->dbh->selectrow_array(
- 'SELECT creation_ts FROM bugs WHERE bug_id = ?',
- undef, $bug->id);
- @values = ($creation_ts);
- }
- else {
- @values = $self->_values_for($number, $field);
- }
-
- # We convert user objects to their login name, here, all in one
- # block for simplicity.
- if (grep { $_ eq $field } USER_FIELDS) {
- # requestees.login_name is empty for most bugs (but checking
- # blessed(undef) handles that.
- # Values that come from %original_values aren't User objects.
- @values = map { blessed($_) ? $_->login : $_ } @values;
- @values = grep { defined $_ } @values;
- }
-
- return \@values;
+ my ($self, $number) = @_;
+ my $field = $self->field;
+
+ my @values;
+
+ if ($field =~ /^attach.+\.(.+)$/) {
+ my $attach_field = $1;
+ $attach_field = ATTACHMENT_FIELDS->{$attach_field} || $attach_field;
+ @values = $self->_values_for($number, 'attachments', $attach_field);
+ }
+ elsif (my $flag_field = FLAG_FIELDS->{$field}) {
+ @values = $self->_values_for($number, 'flags', $flag_field);
+ }
+ elsif (my $translation = COMMENT_FIELDS->{$field}) {
+ @values = $self->_values_for($number, 'comments', $translation);
+
+ # We want the last value to come first, so that single-value
+ # searches use the last comment.
+ @values = reverse @values;
+ }
+ elsif ($field eq 'longdescs.count') {
+ @values = scalar(@{$self->bug($number)->comments});
+ }
+ elsif ($field eq 'work_time') {
+ @values = $self->_values_for($number, 'actual_time');
+ }
+ elsif ($field eq 'bug_group') {
+ @values = $self->_values_for($number, 'groups_in', 'name');
+ }
+ elsif ($field eq 'keywords') {
+ @values = $self->_values_for($number, 'keyword_objects', 'name');
+ }
+ elsif ($field eq 'content') {
+ @values = $self->_values_for($number, 'short_desc');
+ }
+ elsif ($field eq 'see_also') {
+ @values = $self->_values_for($number, 'see_also', 'name');
+ }
+ elsif ($field eq 'tag') {
+ @values = $self->_values_for($number, 'tags');
+ }
+
+ # Bugzilla::Bug truncates creation_ts, but we need the full value
+ # from the database. This has no special value for changedfrom,
+ # because it never changes.
+ elsif ($field eq 'creation_ts') {
+ my $bug = $self->bug($number);
+ my $creation_ts
+ = Bugzilla->dbh->selectrow_array(
+ 'SELECT creation_ts FROM bugs WHERE bug_id = ?',
+ undef, $bug->id);
+ @values = ($creation_ts);
+ }
+ else {
+ @values = $self->_values_for($number, $field);
+ }
+
+ # We convert user objects to their login name, here, all in one
+ # block for simplicity.
+ if (grep { $_ eq $field } USER_FIELDS) {
+
+ # requestees.login_name is empty for most bugs (but checking
+ # blessed(undef) handles that.
+ # Values that come from %original_values aren't User objects.
+ @values = map { blessed($_) ? $_->login : $_ } @values;
+ @values = grep { defined $_ } @values;
+ }
+
+ return \@values;
}
sub _values_for {
- my ($self, $number, $bug_field, $item_field) = @_;
+ my ($self, $number, $bug_field, $item_field) = @_;
- my $item;
- if ($self->operator eq 'changedfrom') {
- $item = $self->search_test->bug_create_value($number, $bug_field);
- }
- else {
- my $bug = $self->bug($number);
- $item = $bug->$bug_field;
- }
+ my $item;
+ if ($self->operator eq 'changedfrom') {
+ $item = $self->search_test->bug_create_value($number, $bug_field);
+ }
+ else {
+ my $bug = $self->bug($number);
+ $item = $bug->$bug_field;
+ }
- if ($item_field) {
- if ($bug_field eq 'flags' and $item_field eq 'name') {
- return (map { $_->name . $_->status } @$item);
- }
- return (map { $self->_get_item($_, $item_field) } @$item);
+ if ($item_field) {
+ if ($bug_field eq 'flags' and $item_field eq 'name') {
+ return (map { $_->name . $_->status } @$item);
}
+ return (map { $self->_get_item($_, $item_field) } @$item);
+ }
- return @$item if ref($item) eq 'ARRAY';
- return $item if defined $item;
- return ();
+ return @$item if ref($item) eq 'ARRAY';
+ return $item if defined $item;
+ return ();
}
sub _get_item {
- my ($self, $from, $field) = @_;
- if (blessed($from)) {
- return $from->$field;
- }
- return $from->{$field};
+ my ($self, $from, $field) = @_;
+ if (blessed($from)) {
+ return $from->$field;
+ }
+ return $from->{$field};
}
#####################
@@ -439,83 +459,85 @@ sub _get_item {
# and then we insert it as required into the "value" from TESTS. (For example,
# <1> becomes the value for the field from bug 1.)
sub _translate_value {
- my $self = shift;
- my $value = $self->test_value;
- foreach my $number (1..NUM_BUGS) {
- $value = $self->_translate_value_for_bug($number, $value);
- }
- # Sanity check to make sure that none of the <> stuff was left in.
- if ($value =~ /<\d/) {
- die $self->name . ": value untranslated: $value\n";
- }
- return $value;
+ my $self = shift;
+ my $value = $self->test_value;
+ foreach my $number (1 .. NUM_BUGS) {
+ $value = $self->_translate_value_for_bug($number, $value);
+ }
+
+ # Sanity check to make sure that none of the <> stuff was left in.
+ if ($value =~ /<\d/) {
+ die $self->name . ": value untranslated: $value\n";
+ }
+ return $value;
}
sub _translate_value_for_bug {
- my ($self, $number, $value) = @_;
-
- my $bug = $self->bug($number);
-
- my $bug_id = $bug->id;
- $value =~ s/<$number-id>/$bug_id/g;
- my $bug_delta = $bug->delta_ts;
- $value =~ s/<$number-delta>/$bug_delta/g;
- my $reporter = $bug->reporter->login;
- $value =~ s/<$number-reporter>/$reporter/g;
- if ($value =~ /<$number-bug_group>/) {
- my @bug_groups = map { $_->name } @{ $bug->groups_in };
- @bug_groups = grep { $_ =~ /^\d+-group-/ } @bug_groups;
- my $group = $bug_groups[0];
- $value =~ s/<$number-bug_group>/$group/g;
- }
-
- my @bug_values = $self->bug_values($number);
- return $value if !@bug_values;
-
- if ($self->operator =~ /substr/) {
- @bug_values = map { $self->_substr_value($_) } @bug_values;
- }
-
- my $string_value = $bug_values[0];
- if ($self->operator =~ /word/) {
- $string_value = join(' ', @bug_values);
- }
- if (my $func = $self->test->{transform}) {
- my $transformed = $func->(@bug_values);
- my $is_equal = $transformed eq $bug_values[0] ? 1 : 0;
- $self->transformed_value_was_equal($number, $is_equal);
- $string_value = $transformed;
- }
-
- if ($self->test->{escape}) {
- $string_value = quotemeta($string_value);
- }
- $value =~ s/<$number>/$string_value/g;
-
- return $value;
+ my ($self, $number, $value) = @_;
+
+ my $bug = $self->bug($number);
+
+ my $bug_id = $bug->id;
+ $value =~ s/<$number-id>/$bug_id/g;
+ my $bug_delta = $bug->delta_ts;
+ $value =~ s/<$number-delta>/$bug_delta/g;
+ my $reporter = $bug->reporter->login;
+ $value =~ s/<$number-reporter>/$reporter/g;
+ if ($value =~ /<$number-bug_group>/) {
+ my @bug_groups = map { $_->name } @{$bug->groups_in};
+ @bug_groups = grep { $_ =~ /^\d+-group-/ } @bug_groups;
+ my $group = $bug_groups[0];
+ $value =~ s/<$number-bug_group>/$group/g;
+ }
+
+ my @bug_values = $self->bug_values($number);
+ return $value if !@bug_values;
+
+ if ($self->operator =~ /substr/) {
+ @bug_values = map { $self->_substr_value($_) } @bug_values;
+ }
+
+ my $string_value = $bug_values[0];
+ if ($self->operator =~ /word/) {
+ $string_value = join(' ', @bug_values);
+ }
+ if (my $func = $self->test->{transform}) {
+ my $transformed = $func->(@bug_values);
+ my $is_equal = $transformed eq $bug_values[0] ? 1 : 0;
+ $self->transformed_value_was_equal($number, $is_equal);
+ $string_value = $transformed;
+ }
+
+ if ($self->test->{escape}) {
+ $string_value = quotemeta($string_value);
+ }
+ $value =~ s/<$number>/$string_value/g;
+
+ return $value;
}
sub _substr_value {
- my ($self, $value) = @_;
- my $field = $self->field;
- my $type = $self->field_object->type;
- my $substr_size = SUBSTR_SIZE;
- if (exists FIELD_SUBSTR_SIZE->{$field}) {
- $substr_size = FIELD_SUBSTR_SIZE->{$field};
- }
- elsif (exists FIELD_SUBSTR_SIZE->{$type}) {
- $substr_size = FIELD_SUBSTR_SIZE->{$type};
- }
- if ($substr_size > 0) {
- # The field name is included in every field value, and if it's
- # long, it might take up the whole substring, and we don't want that.
- if (!grep { $_ eq $field or $_ eq $type } SUBSTR_NO_FIELD_ADD) {
- $substr_size += length($field);
- }
- my $string = substr($value, 0, $substr_size);
- return $string;
- }
- return substr($value, $substr_size);
+ my ($self, $value) = @_;
+ my $field = $self->field;
+ my $type = $self->field_object->type;
+ my $substr_size = SUBSTR_SIZE;
+ if (exists FIELD_SUBSTR_SIZE->{$field}) {
+ $substr_size = FIELD_SUBSTR_SIZE->{$field};
+ }
+ elsif (exists FIELD_SUBSTR_SIZE->{$type}) {
+ $substr_size = FIELD_SUBSTR_SIZE->{$type};
+ }
+ if ($substr_size > 0) {
+
+ # The field name is included in every field value, and if it's
+ # long, it might take up the whole substring, and we don't want that.
+ if (!grep { $_ eq $field or $_ eq $type } SUBSTR_NO_FIELD_ADD) {
+ $substr_size += length($field);
+ }
+ my $string = substr($value, 0, $substr_size);
+ return $string;
+ }
+ return substr($value, $substr_size);
}
#####################
@@ -523,95 +545,93 @@ sub _substr_value {
#####################
sub run {
- my ($self) = @_;
-
- my $invalid_combination = $self->invalid_field_operator_combination;
- my $field_not_implemented = $self->field_not_yet_implemented;
-
- SKIP: {
- skip($invalid_combination, $self->num_tests) if $invalid_combination;
- TODO: {
- todo_skip ($field_not_implemented, $self->num_tests) if $field_not_implemented;
- $self->do_tests();
- }
+ my ($self) = @_;
+
+ my $invalid_combination = $self->invalid_field_operator_combination;
+ my $field_not_implemented = $self->field_not_yet_implemented;
+
+SKIP: {
+ skip($invalid_combination, $self->num_tests) if $invalid_combination;
+ TODO: {
+ todo_skip($field_not_implemented, $self->num_tests) if $field_not_implemented;
+ $self->do_tests();
}
+ }
}
sub do_tests {
- my ($self) = @_;
- my $name = $self->name;
+ my ($self) = @_;
+ my $name = $self->name;
- my $search_broken = $self->search_known_broken;
-
- my $search = $self->_test_search_object_creation();
+ my $search_broken = $self->search_known_broken;
- my $sql;
- TODO: {
- local $TODO = $search_broken if $search_broken;
- lives_ok { $sql = $search->_sql } "$name: generate SQL";
- }
-
- my $results;
- SKIP: {
- skip "Can't run SQL without any SQL", 1 if !defined $sql;
- $results = $self->_test_sql($search);
- }
+ my $search = $self->_test_search_object_creation();
+
+ my $sql;
+TODO: {
+ local $TODO = $search_broken if $search_broken;
+ lives_ok { $sql = $search->_sql } "$name: generate SQL";
+ }
- $self->_test_content($results, $sql);
+ my $results;
+SKIP: {
+ skip "Can't run SQL without any SQL", 1 if !defined $sql;
+ $results = $self->_test_sql($search);
+ }
+
+ $self->_test_content($results, $sql);
}
sub _test_search_object_creation {
- my ($self) = @_;
- my $name = $self->name;
- my @args = (fields => $self->search_columns, params => $self->search_params);
- my $search;
- lives_ok { $search = new Bugzilla::Search(@args) }
- "$name: create search object";
- return $search;
+ my ($self) = @_;
+ my $name = $self->name;
+ my @args = (fields => $self->search_columns, params => $self->search_params);
+ my $search;
+ lives_ok { $search = new Bugzilla::Search(@args) }
+ "$name: create search object";
+ return $search;
}
sub _test_sql {
- my ($self, $search) = @_;
- my $name = $self->name;
- my $results;
- lives_ok { $results = $search->data } "$name: Run SQL Query"
- or diag($search->_sql);
- return $results;
+ my ($self, $search) = @_;
+ my $name = $self->name;
+ my $results;
+ lives_ok { $results = $search->data } "$name: Run SQL Query"
+ or diag($search->_sql);
+ return $results;
}
sub _test_content {
- my ($self, $results, $sql) = @_;
+ my ($self, $results, $sql) = @_;
- SKIP: {
- skip "Without results we can't test them", NUM_BUGS if !$results;
- foreach my $number (1..NUM_BUGS) {
- $self->_test_content_for_bug($number, $results, $sql);
- }
+SKIP: {
+ skip "Without results we can't test them", NUM_BUGS if !$results;
+ foreach my $number (1 .. NUM_BUGS) {
+ $self->_test_content_for_bug($number, $results, $sql);
}
+ }
}
sub _test_content_for_bug {
- my ($self, $number, $results, $sql) = @_;
- my $name = $self->name;
-
- my $contains_known_broken = $self->contains_known_broken($number);
-
- my %result_ids = map { $_->[0] => 1 } @$results;
- my $bug_id = $self->bug($number)->id;
-
- TODO: {
- local $TODO = $contains_known_broken if $contains_known_broken;
- if ($self->bug_is_contained($number)) {
- ok($result_ids{$bug_id},
- "$name: contains bug $number ($bug_id)")
- or diag $self->debug_fail($number, $results, $sql);
- }
- else {
- ok(!$result_ids{$bug_id},
- "$name: does not contain bug $number ($bug_id)")
- or diag $self->debug_fail($number, $results, $sql);
- }
+ my ($self, $number, $results, $sql) = @_;
+ my $name = $self->name;
+
+ my $contains_known_broken = $self->contains_known_broken($number);
+
+ my %result_ids = map { $_->[0] => 1 } @$results;
+ my $bug_id = $self->bug($number)->id;
+
+TODO: {
+ local $TODO = $contains_known_broken if $contains_known_broken;
+ if ($self->bug_is_contained($number)) {
+ ok($result_ids{$bug_id}, "$name: contains bug $number ($bug_id)")
+ or diag $self->debug_fail($number, $results, $sql);
+ }
+ else {
+ ok(!$result_ids{$bug_id}, "$name: does not contain bug $number ($bug_id)")
+ or diag $self->debug_fail($number, $results, $sql);
}
+ }
}
1;
diff --git a/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm b/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm
index 888e7eb13..101c09053 100644
--- a/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm
+++ b/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm
@@ -15,9 +15,9 @@ use parent qw(Bugzilla::Test::Search::FieldTest);
use Scalar::Util qw(blessed);
use constant CH_OPERATOR => {
- changedafter => 'chfieldfrom',
- changedbefore => 'chfieldto',
- changedto => 'chfieldvalue',
+ changedafter => 'chfieldfrom',
+ changedbefore => 'chfieldto',
+ changedto => 'chfieldvalue',
};
use constant EMAIL_FIELDS => qw(assigned_to qa_contact cc reporter commenter);
@@ -27,78 +27,79 @@ use constant EMAIL_FIELDS => qw(assigned_to qa_contact cc reporter commenter);
# sometimes (like in Bugzilla::Test::Search's direct code) we just want
# to create a FieldTestNormal.
sub new {
- my $class = shift;
- my ($first_arg) = @_;
- if (blessed $first_arg
- and $first_arg->isa('Bugzilla::Test::Search::FieldTest'))
- {
- my $self = { %$first_arg };
- return bless $self, $class;
- }
- return $class->SUPER::new(@_);
+ my $class = shift;
+ my ($first_arg) = @_;
+ if (blessed $first_arg and $first_arg->isa('Bugzilla::Test::Search::FieldTest'))
+ {
+ my $self = {%$first_arg};
+ return bless $self, $class;
+ }
+ return $class->SUPER::new(@_);
}
sub name {
- my $self = shift;
- my $name = $self->SUPER::name(@_);
- return "$name (Normal Params)";
+ my $self = shift;
+ my $name = $self->SUPER::name(@_);
+ return "$name (Normal Params)";
}
sub search_columns {
- my $self = shift;
- my $field = $self->field;
- # For the assigned_to, qa_contact, and reporter fields, have the
- # "Normal Params" test check that the _realname columns work
- # all by themselves.
- if (grep($_ eq $field, EMAIL_FIELDS) && $self->field_object->buglist) {
- return ['bug_id', "${field}_realname"]
- }
- return $self->SUPER::search_columns(@_);
+ my $self = shift;
+ my $field = $self->field;
+
+ # For the assigned_to, qa_contact, and reporter fields, have the
+ # "Normal Params" test check that the _realname columns work
+ # all by themselves.
+ if (grep($_ eq $field, EMAIL_FIELDS) && $self->field_object->buglist) {
+ return ['bug_id', "${field}_realname"];
+ }
+ return $self->SUPER::search_columns(@_);
}
sub search_params {
- my ($self) = @_;
- my $field = $self->field;
- my $operator = $self->operator;
- my $value = $self->translated_value;
- if ($operator eq 'anyexact') {
- $value = [split ',', $value];
- }
-
- if (my $ch_param = CH_OPERATOR->{$operator}) {
- if ($field eq 'creation_ts') {
- $field = '[Bug creation]';
- }
- return { chfield => $field, $ch_param => $value };
- }
-
- if ($field eq 'delta_ts' and $operator eq 'greaterthaneq') {
- return { chfieldfrom => $value };
- }
- if ($field eq 'delta_ts' and $operator eq 'lessthaneq') {
- return { chfieldto => $value };
- }
-
- if ($field eq 'deadline' and $operator eq 'greaterthaneq') {
- return { deadlinefrom => $value };
- }
- if ($field eq 'deadline' and $operator eq 'lessthaneq') {
- return { deadlineto => $value };
- }
-
- if (grep { $_ eq $field } EMAIL_FIELDS) {
- $field = 'longdesc' if $field eq 'commenter';
- return {
- email1 => $value,
- "email${field}1" => 1,
- emailtype1 => $operator,
- # Used to do extra tests on special sorts of email* combinations.
- %{ $self->test->{extra_params} || {} },
- };
+ my ($self) = @_;
+ my $field = $self->field;
+ my $operator = $self->operator;
+ my $value = $self->translated_value;
+ if ($operator eq 'anyexact') {
+ $value = [split ',', $value];
+ }
+
+ if (my $ch_param = CH_OPERATOR->{$operator}) {
+ if ($field eq 'creation_ts') {
+ $field = '[Bug creation]';
}
+ return {chfield => $field, $ch_param => $value};
+ }
+
+ if ($field eq 'delta_ts' and $operator eq 'greaterthaneq') {
+ return {chfieldfrom => $value};
+ }
+ if ($field eq 'delta_ts' and $operator eq 'lessthaneq') {
+ return {chfieldto => $value};
+ }
+
+ if ($field eq 'deadline' and $operator eq 'greaterthaneq') {
+ return {deadlinefrom => $value};
+ }
+ if ($field eq 'deadline' and $operator eq 'lessthaneq') {
+ return {deadlineto => $value};
+ }
+
+ if (grep { $_ eq $field } EMAIL_FIELDS) {
+ $field = 'longdesc' if $field eq 'commenter';
+ return {
+ email1 => $value,
+ "email${field}1" => 1,
+ emailtype1 => $operator,
+
+ # Used to do extra tests on special sorts of email* combinations.
+ %{$self->test->{extra_params} || {}},
+ };
+ }
- $field =~ s/\./_/g;
- return { $field => $value, "${field}_type" => $operator };
+ $field =~ s/\./_/g;
+ return {$field => $value, "${field}_type" => $operator};
}
1;
diff --git a/xt/lib/Bugzilla/Test/Search/InjectionTest.pm b/xt/lib/Bugzilla/Test/Search/InjectionTest.pm
index 90eaabc78..f34acc6bb 100644
--- a/xt/lib/Bugzilla/Test/Search/InjectionTest.pm
+++ b/xt/lib/Bugzilla/Test/Search/InjectionTest.pm
@@ -18,60 +18,62 @@ use Test::Exception;
sub num_tests { return NUM_SEARCH_TESTS }
sub _known_broken {
- my ($self) = @_;
- my $operator_broken = INJECTION_BROKEN_OPERATOR->{$self->operator};
- # We don't want to auto-vivify $operator_broken and thus make it true.
- my @field_ok = $operator_broken ? @{ $operator_broken->{field_ok} || [] }
- : ();
- $operator_broken = undef if grep { $_ eq $self->field } @field_ok;
-
- my $field_broken = INJECTION_BROKEN_FIELD->{$self->field}
- || INJECTION_BROKEN_FIELD->{$self->field_object->type};
- # We don't want to auto-vivify $field_broken and thus make it true.
- my @operator_ok = $field_broken ? @{ $field_broken->{operator_ok} || [] }
- : ();
- $field_broken = undef if grep { $_ eq $self->operator } @operator_ok;
-
- return $operator_broken || $field_broken || {};
+ my ($self) = @_;
+ my $operator_broken = INJECTION_BROKEN_OPERATOR->{$self->operator};
+
+ # We don't want to auto-vivify $operator_broken and thus make it true.
+ my @field_ok = $operator_broken ? @{$operator_broken->{field_ok} || []} : ();
+ $operator_broken = undef if grep { $_ eq $self->field } @field_ok;
+
+ my $field_broken = INJECTION_BROKEN_FIELD->{$self->field}
+ || INJECTION_BROKEN_FIELD->{$self->field_object->type};
+
+ # We don't want to auto-vivify $field_broken and thus make it true.
+ my @operator_ok = $field_broken ? @{$field_broken->{operator_ok} || []} : ();
+ $field_broken = undef if grep { $_ eq $self->operator } @operator_ok;
+
+ return $operator_broken || $field_broken || {};
}
sub sql_error_ok { return $_[0]->_known_broken->{sql_error} }
# Injection tests only skip fields on certain dbs.
sub field_not_yet_implemented {
- my ($self) = @_;
- # We use the constant directly because we don't want operator_ok
- # or field_ok to stop us.
- my $broken = INJECTION_BROKEN_FIELD->{$self->field}
- || INJECTION_BROKEN_FIELD->{$self->field_object->type};
- my $skip_for_dbs = $broken->{db_skip};
- return undef if !$skip_for_dbs;
- my $dbh = Bugzilla->dbh;
- if (my ($skip) = grep { $dbh->isa("Bugzilla::DB::$_") } @$skip_for_dbs) {
- my $field = $self->field;
- return "$field injection testing is not supported with $skip";
- }
- return undef;
+ my ($self) = @_;
+
+ # We use the constant directly because we don't want operator_ok
+ # or field_ok to stop us.
+ my $broken = INJECTION_BROKEN_FIELD->{$self->field}
+ || INJECTION_BROKEN_FIELD->{$self->field_object->type};
+ my $skip_for_dbs = $broken->{db_skip};
+ return undef if !$skip_for_dbs;
+ my $dbh = Bugzilla->dbh;
+ if (my ($skip) = grep { $dbh->isa("Bugzilla::DB::$_") } @$skip_for_dbs) {
+ my $field = $self->field;
+ return "$field injection testing is not supported with $skip";
+ }
+ return undef;
}
+
# Injection tests don't do translation.
sub translated_value { $_[0]->test_value }
sub name { return "injection-" . $_[0]->SUPER::name; }
# Injection tests don't check content.
-sub _test_content {}
+sub _test_content { }
sub _test_sql {
- my $self = shift;
- my ($sql) = @_;
- my $dbh = Bugzilla->dbh;
- my $name = $self->name;
- if (my $error_ok = $self->sql_error_ok) {
- throws_ok { $dbh->selectall_arrayref($sql) } $error_ok,
- "$name: SQL query dies, as we expect";
- return;
- }
- return $self->SUPER::_test_sql(@_);
+ my $self = shift;
+ my ($sql) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $name = $self->name;
+ if (my $error_ok = $self->sql_error_ok) {
+ throws_ok { $dbh->selectall_arrayref($sql) } $error_ok,
+ "$name: SQL query dies, as we expect";
+ return;
+ }
+ return $self->SUPER::_test_sql(@_);
}
1;
diff --git a/xt/lib/Bugzilla/Test/Search/NotTest.pm b/xt/lib/Bugzilla/Test/Search/NotTest.pm
index 190b8567b..ea0ecc5b2 100644
--- a/xt/lib/Bugzilla/Test/Search/NotTest.pm
+++ b/xt/lib/Bugzilla/Test/Search/NotTest.pm
@@ -20,9 +20,9 @@ use Bugzilla::Test::Search::Constants;
# We just clone a FieldTest because that's the best for performance,
# overall--that way we don't have to translate the value again.
sub new {
- my ($class, $field_test) = @_;
- my $self = { %$field_test };
- return bless $self, $class;
+ my ($class, $field_test) = @_;
+ my $self = {%$field_test};
+ return bless $self, $class;
}
#############
@@ -30,32 +30,33 @@ sub new {
#############
sub name {
- my ($self) = @_;
- return "NOT(" . $self->SUPER::name . ")";
+ my ($self) = @_;
+ return "NOT(" . $self->SUPER::name . ")";
}
# True if this test is supposed to contain the numbered bug. Reversed for
# NOT tests.
sub bug_is_contained {
- my $self = shift;
- my ($number) = @_;
- # No search ever returns bug 6, because it's protected by security groups
- # that the searcher isn't a member of.
- return 0 if $number == 6;
- return $self->SUPER::bug_is_contained(@_) ? 0 : 1;
+ my $self = shift;
+ my ($number) = @_;
+
+ # No search ever returns bug 6, because it's protected by security groups
+ # that the searcher isn't a member of.
+ return 0 if $number == 6;
+ return $self->SUPER::bug_is_contained(@_) ? 0 : 1;
}
# NOT tests have their own constant for tracking broken-ness.
sub _known_broken {
- my ($self) = @_;
- return $self->SUPER::_known_broken(BROKEN_NOT, 'skip pg check');
+ my ($self) = @_;
+ return $self->SUPER::_known_broken(BROKEN_NOT, 'skip pg check');
}
sub search_params {
- my ($self) = @_;
- my %params = %{ $self->SUPER::search_params() };
- $params{negate0} = 1;
- return \%params;
+ my ($self) = @_;
+ my %params = %{$self->SUPER::search_params()};
+ $params{negate0} = 1;
+ return \%params;
}
1;
diff --git a/xt/lib/Bugzilla/Test/Search/OperatorTest.pm b/xt/lib/Bugzilla/Test/Search/OperatorTest.pm
index 5ab502dfc..57d058ad7 100644
--- a/xt/lib/Bugzilla/Test/Search/OperatorTest.pm
+++ b/xt/lib/Bugzilla/Test/Search/OperatorTest.pm
@@ -24,10 +24,10 @@ use Bugzilla::Test::Search::NotTest;
###############
sub new {
- my ($invocant, $operator, $search_test) = @_;
- $search_test ||= $invocant->search_test;
- my $class = ref($invocant) || $invocant;
- return bless { search_test => $search_test, operator => $operator }, $class;
+ my ($invocant, $operator, $search_test) = @_;
+ $search_test ||= $invocant->search_test;
+ my $class = ref($invocant) || $invocant;
+ return bless {search_test => $search_test, operator => $operator}, $class;
}
#############
@@ -36,68 +36,69 @@ sub new {
# The Bugzilla::Test::Search object that this is a child of.
sub search_test { return $_[0]->{search_test} }
+
# The operator being tested
sub operator { return $_[0]->{operator} }
+
# The tests that we're going to run on this operator.
-sub tests { return @{ TESTS->{$_[0]->operator } } }
+sub tests { return @{TESTS->{$_[0]->operator}} }
+
# The fields we're going to test for this operator.
sub test_fields { return $_[0]->search_test->all_fields }
sub run {
- my ($self) = @_;
-
- foreach my $field ($self->test_fields) {
- foreach my $test ($self->tests) {
- my $field_test =
- new Bugzilla::Test::Search::FieldTest($self, $field, $test);
- $field_test->run();
- my $normal_test =
- new Bugzilla::Test::Search::FieldTestNormal($field_test);
- $normal_test->run();
- my $not_test = new Bugzilla::Test::Search::NotTest($field_test);
- $not_test->run();
-
- next if !$self->search_test->option('long');
-
- # Run the OR tests. This tests every other operator (including
- # this operator itself) in combination with every other field,
- # in an OR with this operator and field.
- foreach my $other_operator ($self->search_test->all_operators) {
- $self->run_join_tests($field_test, $other_operator);
- }
- }
- foreach my $test (INJECTION_TESTS) {
- my $injection_test =
- new Bugzilla::Test::Search::InjectionTest($self, $field, $test);
- $injection_test->run();
- }
+ my ($self) = @_;
+
+ foreach my $field ($self->test_fields) {
+ foreach my $test ($self->tests) {
+ my $field_test = new Bugzilla::Test::Search::FieldTest($self, $field, $test);
+ $field_test->run();
+ my $normal_test = new Bugzilla::Test::Search::FieldTestNormal($field_test);
+ $normal_test->run();
+ my $not_test = new Bugzilla::Test::Search::NotTest($field_test);
+ $not_test->run();
+
+ next if !$self->search_test->option('long');
+
+ # Run the OR tests. This tests every other operator (including
+ # this operator itself) in combination with every other field,
+ # in an OR with this operator and field.
+ foreach my $other_operator ($self->search_test->all_operators) {
+ $self->run_join_tests($field_test, $other_operator);
+ }
}
+ foreach my $test (INJECTION_TESTS) {
+ my $injection_test
+ = new Bugzilla::Test::Search::InjectionTest($self, $field, $test);
+ $injection_test->run();
+ }
+ }
}
sub run_join_tests {
- my ($self, $field_test, $other_operator) = @_;
-
- my $other_operator_test = $self->new($other_operator);
- foreach my $other_test ($other_operator_test->tests) {
- foreach my $other_field ($self->test_fields) {
- $self->_run_one_join_test($field_test, $other_operator_test,
- $other_field, $other_test);
- $self->search_test->clean_test_history();
- }
+ my ($self, $field_test, $other_operator) = @_;
+
+ my $other_operator_test = $self->new($other_operator);
+ foreach my $other_test ($other_operator_test->tests) {
+ foreach my $other_field ($self->test_fields) {
+ $self->_run_one_join_test($field_test, $other_operator_test, $other_field,
+ $other_test);
+ $self->search_test->clean_test_history();
}
+ }
}
sub _run_one_join_test {
- my ($self, $field_test, $other_operator_test, $other_field, $other_test) = @_;
- my $other_field_test =
- new Bugzilla::Test::Search::FieldTest($other_operator_test,
- $other_field, $other_test);
- my $or_test = new Bugzilla::Test::Search::OrTest($field_test,
- $other_field_test);
- $or_test->run();
- my $and_test = new Bugzilla::Test::Search::AndTest($field_test,
- $other_field_test);
- $and_test->run();
+ my ($self, $field_test, $other_operator_test, $other_field, $other_test) = @_;
+ my $other_field_test
+ = new Bugzilla::Test::Search::FieldTest($other_operator_test, $other_field,
+ $other_test);
+ my $or_test
+ = new Bugzilla::Test::Search::OrTest($field_test, $other_field_test);
+ $or_test->run();
+ my $and_test
+ = new Bugzilla::Test::Search::AndTest($field_test, $other_field_test);
+ $and_test->run();
}
1;
diff --git a/xt/lib/Bugzilla/Test/Search/OrTest.pm b/xt/lib/Bugzilla/Test/Search/OrTest.pm
index 1b948f38d..ebb16089d 100644
--- a/xt/lib/Bugzilla/Test/Search/OrTest.pm
+++ b/xt/lib/Bugzilla/Test/Search/OrTest.pm
@@ -20,36 +20,36 @@ use constant type => 'OR';
###############
sub new {
- my $class = shift;
- my $self = { field_tests => [@_] };
- return bless $self, $class;
+ my $class = shift;
+ my $self = {field_tests => [@_]};
+ return bless $self, $class;
}
#############
# Accessors #
#############
-sub field_tests { return @{ $_[0]->{field_tests} } }
+sub field_tests { return @{$_[0]->{field_tests}} }
sub search_test { ($_[0]->field_tests)[0]->search_test }
sub name {
- my ($self) = @_;
- my @names = map { $_->name } $self->field_tests;
- return join('-' . $self->type . '-', @names);
+ my ($self) = @_;
+ my @names = map { $_->name } $self->field_tests;
+ return join('-' . $self->type . '-', @names);
}
# In an OR test, bugs ARE supposed to be contained if they are contained
# by ANY test.
sub bug_is_contained {
- my ($self, $number) = @_;
- return any { $_->bug_is_contained($number) } $self->field_tests;
+ my ($self, $number) = @_;
+ return any { $_->bug_is_contained($number) } $self->field_tests;
}
# Needed only for failure messages
sub debug_value {
- my ($self) = @_;
- my @values = map { $_->field . ' ' . $_->debug_value } $self->field_tests;
- return join(' ' . $self->type . ' ', @values);
+ my ($self) = @_;
+ my @values = map { $_->field . ' ' . $_->debug_value } $self->field_tests;
+ return join(' ' . $self->type . ' ', @values);
}
########################
@@ -57,61 +57,66 @@ sub debug_value {
########################
sub field_not_yet_implemented {
- my ($self) = @_;
- return $self->_join_messages('field_not_yet_implemented');
+ my ($self) = @_;
+ return $self->_join_messages('field_not_yet_implemented');
}
+
sub invalid_field_operator_combination {
- my ($self) = @_;
- return $self->_join_messages('invalid_field_operator_combination');
+ my ($self) = @_;
+ return $self->_join_messages('invalid_field_operator_combination');
}
+
sub search_known_broken {
- my ($self) = @_;
- return $self->_join_messages('search_known_broken');
+ my ($self) = @_;
+ return $self->_join_messages('search_known_broken');
}
sub _join_messages {
- my ($self, $message_method) = @_;
- my @messages = map { $_->$message_method } $self->field_tests;
- @messages = grep { $_ } @messages;
- return join(' AND ', @messages);
+ my ($self, $message_method) = @_;
+ my @messages = map { $_->$message_method } $self->field_tests;
+ @messages = grep {$_} @messages;
+ return join(' AND ', @messages);
}
sub _bug_will_actually_be_contained {
- my ($self, $number) = @_;
-
- foreach my $test ($self->field_tests) {
- # Some tests are broken in such a way that they actually
- # generate no criteria in the SQL. In this case, the only way
- # the test contains the bug is if *another* test contains it.
- next if $test->_known_broken->{no_criteria};
- return 1 if $test->will_actually_contain_bug($number);
- }
- return 0;
+ my ($self, $number) = @_;
+
+ foreach my $test ($self->field_tests) {
+
+ # Some tests are broken in such a way that they actually
+ # generate no criteria in the SQL. In this case, the only way
+ # the test contains the bug is if *another* test contains it.
+ next if $test->_known_broken->{no_criteria};
+ return 1 if $test->will_actually_contain_bug($number);
+ }
+ return 0;
}
sub contains_known_broken {
- my ($self, $number) = @_;
-
- if ( ( $self->bug_is_contained($number)
- and !$self->_bug_will_actually_be_contained($number) )
- or ( !$self->bug_is_contained($number)
- and $self->_bug_will_actually_be_contained($number) ) )
- {
- my @messages = map { $_->contains_known_broken($number) }
- $self->field_tests;
- @messages = grep { $_ } @messages;
- # Sometimes, with things that break because of no_criteria, there won't
- # be anything in @messages even though we need to print out a message.
- if (!@messages) {
- my @no_criteria = grep { $_->_known_broken->{no_criteria} }
- $self->field_tests;
- @messages = map { "No criteria generated by " . $_->name }
- @no_criteria;
- }
- die "broken test with no message" if !@messages;
- return join(' AND ', @messages);
+ my ($self, $number) = @_;
+
+ if (
+ (
+ $self->bug_is_contained($number)
+ and !$self->_bug_will_actually_be_contained($number)
+ )
+ or ( !$self->bug_is_contained($number)
+ and $self->_bug_will_actually_be_contained($number))
+ )
+ {
+ my @messages = map { $_->contains_known_broken($number) } $self->field_tests;
+ @messages = grep {$_} @messages;
+
+ # Sometimes, with things that break because of no_criteria, there won't
+ # be anything in @messages even though we need to print out a message.
+ if (!@messages) {
+ my @no_criteria = grep { $_->_known_broken->{no_criteria} } $self->field_tests;
+ @messages = map { "No criteria generated by " . $_->name } @no_criteria;
}
- return undef;
+ die "broken test with no message" if !@messages;
+ return join(' AND ', @messages);
+ }
+ return undef;
}
##############################
@@ -119,23 +124,23 @@ sub contains_known_broken {
##############################
sub search_columns {
- my ($self) = @_;
- my @columns = map { @{ $_->search_columns } } $self->field_tests;
- return [uniq @columns];
+ my ($self) = @_;
+ my @columns = map { @{$_->search_columns} } $self->field_tests;
+ return [uniq @columns];
}
sub search_params {
- my ($self) = @_;
- my @all_params = map { $_->search_params } $self->field_tests;
- my %params;
- my $chart = 0;
- foreach my $item (@all_params) {
- $params{"field0-0-$chart"} = $item->{'field0-0-0'};
- $params{"type0-0-$chart"} = $item->{'type0-0-0'};
- $params{"value0-0-$chart"} = $item->{'value0-0-0'};
- $chart++;
- }
- return \%params;
+ my ($self) = @_;
+ my @all_params = map { $_->search_params } $self->field_tests;
+ my %params;
+ my $chart = 0;
+ foreach my $item (@all_params) {
+ $params{"field0-0-$chart"} = $item->{'field0-0-0'};
+ $params{"type0-0-$chart"} = $item->{'type0-0-0'};
+ $params{"value0-0-$chart"} = $item->{'value0-0-0'};
+ $chart++;
+ }
+ return \%params;
}
1;
diff --git a/xt/search.t b/xt/search.t
index 81f2f3338..5c7097ac1 100644
--- a/xt/search.t
+++ b/xt/search.t
@@ -22,7 +22,8 @@ use Test::More;
my %switches;
GetOptions(\%switches, 'operators=s', 'top-operators=s', 'long',
- 'add-custom-fields', 'help|h') || die $@;
+ 'add-custom-fields', 'help|h')
+ || die $@;
pod2usage(verbose => 1) if $switches{'help'};